Presets¶
Permission required: presets
Full CRUD access to the user's generation presets and their prompt blocks. Use this for extensions that manage Loom presets, inspect prompt assembly structure, or batch-edit blocks and categories.
Shape¶
Presets are stored as one record with several JSON fields:
parametersstores sampler/custom-body settings and other provider parameters.prompt_orderstores the ordered prompt block list.promptsstores prompt behavior, completion settings, and advanced prompt settings.metadatastores Loom metadata such as description, source, model profiles, default status, and prompt variable values.
Prompt categories are not separate records. A category is a structural prompt block where marker === 'category'. Its children are the following non-category prompt blocks until the next category block. Use spindle.presets.categories.list() when you want this grouping precomputed by the host.
Usage¶
// List presets (paginated)
const { data, total } = await spindle.presets.list({ limit: 20, offset: 0 })
// Get a single preset
const preset = await spindle.presets.get('preset-id')
if (preset) {
spindle.log.info(`Found preset: ${preset.name}`)
}
// Create a minimal Loom-style preset
const newPreset = await spindle.presets.create({
name: 'My Extension Preset',
provider: 'loom',
engine: 'classic',
parameters: {},
prompt_order: [],
prompts: {},
metadata: { description: 'Created by my extension' },
})
// Update the preset metadata
const updated = await spindle.presets.update(newPreset.id, {
metadata: {
...newPreset.metadata,
description: 'Updated description',
},
})
// Delete the preset
const deleted = await spindle.presets.delete(newPreset.id)
Methods¶
| Method | Returns | Description |
|---|---|---|
list(options?) |
Promise<{ data: UserPresetDTO[], total: number }> |
List presets. Options: { limit?, offset? }. Defaults: limit 50, max 200. |
get(presetId) |
Promise<UserPresetDTO \| null> |
Get a preset by ID. Returns null if not found. |
create(input) |
Promise<UserPresetDTO> |
Create a new preset. name and provider are required. |
update(presetId, input) |
Promise<UserPresetDTO> |
Update a preset. All fields are optional. |
delete(presetId) |
Promise<boolean> |
Delete a preset. Returns true if deleted. |
UserPresetDTO¶
{
id: string
name: string
provider: string
engine: string
parameters: Record<string, unknown>
prompt_order: PromptBlockDTO[]
prompts: Record<string, unknown>
metadata: Record<string, unknown>
created_at: number // unix epoch seconds
updated_at: number
}
UserPresetCreateDTO¶
| Field | Type | Required | Description |
|---|---|---|---|
name |
string |
Yes | Preset name |
provider |
string |
Yes | Preset provider, usually loom for native Lumiverse presets |
engine |
string |
No | Engine identifier. Defaults to classic |
parameters |
Record<string, unknown> |
No | Provider parameters and Loom sampler/custom-body settings |
prompt_order |
PromptBlockDTO[] |
No | Ordered prompt blocks, including structural category markers |
prompts |
Record<string, unknown> |
No | Prompt behavior, completion settings, and advanced settings |
metadata |
Record<string, unknown> |
No | Preset metadata and extension-specific data |
UserPresetUpdateDTO¶
Same fields as UserPresetCreateDTO, but all are optional, including name and provider.
Prompt variable cleanup
When prompt_order or metadata is updated, Lumiverse prunes stale metadata.promptVariables entries that no longer correspond to a variable definition on a block. This matches the built-in preset editor behavior.
Prompt Blocks¶
Prompt blocks are managed through spindle.presets.blocks. Block operations update the parent preset's prompt_order and trigger the normal preset update flow.
Block Usage¶
// List blocks in order
const blocks = await spindle.presets.blocks.list('preset-id')
// Get a single block
const block = await spindle.presets.blocks.get('preset-id', 'block-id')
// Append a new system block
const newBlock = await spindle.presets.blocks.create('preset-id', {
name: 'Style Guide',
content: 'Write with concise, vivid prose.',
role: 'system',
position: 'pre_history',
enabled: true,
})
// Insert a category marker at the start of the preset
const category = await spindle.presets.blocks.create(
'preset-id',
{
name: 'Tone',
marker: 'category',
categoryMode: 'radio',
content: '',
},
{ index: 0 },
)
// Update a block
const updatedBlock = await spindle.presets.blocks.update('preset-id', newBlock.id, {
enabled: false,
})
// Delete a block
const blockDeleted = await spindle.presets.blocks.delete('preset-id', newBlock.id)
Block Methods¶
| Method | Returns | Description |
|---|---|---|
list(presetId) |
Promise<PromptBlockDTO[]> |
Return the preset's ordered prompt blocks. |
get(presetId, blockId) |
Promise<PromptBlockDTO \| null> |
Get a block by ID. Returns null if not found. |
create(presetId, input, options?) |
Promise<PromptBlockDTO> |
Create a prompt block. options.index inserts at a specific zero-based position; omitted appends. |
update(presetId, blockId, input) |
Promise<PromptBlockDTO> |
Update a block. All fields except id are optional. |
delete(presetId, blockId) |
Promise<boolean> |
Delete a block. Returns true if deleted. |
PromptBlockDTO¶
{
id: string
name: string
content: string
role: 'system' | 'user' | 'assistant' | 'user_append' | 'assistant_append'
enabled: boolean
position: 'pre_history' | 'post_history' | 'in_history'
depth: number
marker: string | null
isLocked: boolean
color: string | null
injectionTrigger: string[]
group: string | null
categoryMode?: 'radio' | 'checkbox' | null
variables?: PromptVariableDefDTO[]
}
PromptBlockCreateDTO / PromptBlockUpdateDTO¶
PromptBlockCreateDTO accepts any subset of PromptBlockDTO. Missing fields are defaulted by the host. PromptBlockUpdateDTO accepts any subset except id; the existing block ID is preserved.
Common fields:
| Field | Type | Description |
|---|---|---|
name |
string |
Human-readable block label |
content |
string |
Prompt text for normal blocks; usually empty for marker blocks |
role |
'system' \| 'user' \| 'assistant' \| 'user_append' \| 'assistant_append' |
Message role or append injection tag |
enabled |
boolean |
Whether the block participates in prompt assembly |
position |
'pre_history' \| 'post_history' \| 'in_history' |
Where the block injects relative to chat history |
depth |
number |
Depth when position is in_history |
marker |
string \| null |
Structural marker. Use 'category' for category headers |
categoryMode |
'radio' \| 'checkbox' \| null |
Category selection mode; meaningful only on category marker blocks |
variables |
PromptVariableDefDTO[] |
Prompt variable definitions for this block |
Categories¶
Use spindle.presets.categories.list() to get category grouping without reimplementing Lumiverse's grouping rules.
const groups = await spindle.presets.categories.list('preset-id')
for (const group of groups) {
const label = group.categoryBlock?.name ?? 'Uncategorized'
spindle.log.info(`${label}: ${group.children.length} blocks`)
}
Category Methods¶
| Method | Returns | Description |
|---|---|---|
list(presetId) |
Promise<PromptBlockCategoryGroupDTO[]> |
Return category groups derived from the preset's ordered blocks. |
PromptBlockCategoryGroupDTO¶
{
categoryBlock: PromptBlockDTO | null
children: PromptBlockDTO[]
}
The first group can have categoryBlock: null when normal blocks appear before the first category marker.
User Scoping¶
For user-scoped extensions, the user context is inferred automatically. For operator-scoped extensions, pass userId as the final argument or inside the options object where supported.
// Operator-scoped extension targeting a specific user
const { data } = await spindle.presets.list({ userId: 'user-id' })
const block = await spindle.presets.blocks.create(
'preset-id',
{ name: 'Operator Note', content: '...' },
{ userId: 'user-id' },
)
Best Practices¶
- Treat
parameters,prompts, andmetadataas owned by the preset editor unless you intentionally manage those fields. - Namespace extension-specific metadata under your extension identifier to avoid collisions.
- Prefer block CRUD for localized prompt edits instead of rewriting the entire
prompt_orderarray. - Use
categories.list()for UI or analytics; create/update/delete category headers throughblocks.*. - Check
spindle.permissions.has('presets')before showing preset-management UI.