World Books¶
Permission required: world_books
Full CRUD access to the user's world books and their entries. Use this for extensions that manage, analyze, or batch-edit World Info / lorebook data.
Usage¶
// List world books (paginated)
const { data, total } = await spindle.world_books.list({ limit: 20, offset: 0 })
// Get a single world book
const book = await spindle.world_books.get('world-book-id')
if (book) {
spindle.log.info(`Found: ${book.name} (${book.description})`)
}
// Create a world book
const newBook = await spindle.world_books.create({
name: 'My Lorebook',
description: 'Character knowledge base',
})
// Update a world book
const updated = await spindle.world_books.update(newBook.id, {
description: 'Updated description',
})
// Delete a world book (cascades all entries)
const deleted = await spindle.world_books.delete(newBook.id)
Methods¶
| Method | Returns | Description |
|---|---|---|
list(options?) |
Promise<{ data: WorldBookDTO[], total: number }> |
List world books. Options: { limit?, offset? }. Defaults: limit 50, max 200. |
get(worldBookId) |
Promise<WorldBookDTO \| null> |
Get a world book by ID. Returns null if not found. |
create(input) |
Promise<WorldBookDTO> |
Create a new world book. name is required. |
update(worldBookId, input) |
Promise<WorldBookDTO> |
Update a world book. All fields are optional. |
delete(worldBookId) |
Promise<boolean> |
Delete a world book and all its entries. Returns true if deleted. |
WorldBookDTO¶
{
id: string
name: string
description: string
metadata: Record<string, unknown>
created_at: number // unix epoch seconds
updated_at: number
}
WorldBookCreateDTO¶
| Field | Type | Required | Description |
|---|---|---|---|
name |
string |
Yes | World book name |
description |
string |
No | Description |
metadata |
Record<string, unknown> |
No | Arbitrary metadata |
WorldBookUpdateDTO¶
Same fields as WorldBookCreateDTO, but all are optional (including name).
Entries¶
World book entries are managed via spindle.world_books.entries. Each entry belongs to a world book and contains keywords, content, and activation settings used by the World Info system during prompt assembly.
Entry Usage¶
// List entries in a world book (paginated)
const { data, total } = await spindle.world_books.entries.list('world-book-id', {
limit: 50, offset: 0,
})
// Get a single entry
const entry = await spindle.world_books.entries.get('entry-id')
if (entry) {
spindle.log.info(`Entry: ${entry.comment} — keys: ${entry.key.join(', ')}`)
}
// Create an entry
const newEntry = await spindle.world_books.entries.create('world-book-id', {
key: ['dragon', 'dragons'],
keysecondary: ['fire', 'scales'],
content: 'Dragons are ancient creatures that breathe fire.',
comment: 'Dragon lore',
position: 0,
selective: true,
})
// Update an entry
const updatedEntry = await spindle.world_books.entries.update(newEntry.id, {
content: 'Dragons are ancient, intelligent creatures that breathe fire.',
disabled: false,
})
// Delete an entry
const entryDeleted = await spindle.world_books.entries.delete(newEntry.id)
Entry Methods¶
| Method | Returns | Description |
|---|---|---|
list(worldBookId, options?) |
Promise<{ data: WorldBookEntryDTO[], total: number }> |
List entries in a world book. Options: { limit?, offset? }. Defaults: limit 50, max 200. |
get(entryId) |
Promise<WorldBookEntryDTO \| null> |
Get an entry by ID. Returns null if not found. |
create(worldBookId, input) |
Promise<WorldBookEntryDTO> |
Create a new entry in a world book. All fields are optional. |
update(entryId, input) |
Promise<WorldBookEntryDTO> |
Update an entry. All fields are optional. |
delete(entryId) |
Promise<boolean> |
Delete an entry. Returns true if deleted. |
WorldBookEntryDTO¶
{
id: string
world_book_id: string
uid: string
key: string[] // primary activation keywords
keysecondary: string[] // secondary keywords (for selective mode)
content: string // the injected text
comment: string // human-readable label
position: number // injection position (0=before, 1=after, 4=depth, etc.)
depth: number // depth for position-based injection
role: string | null // "system", "user", or "assistant"
order_value: number // sort order within position bucket
selective: boolean // require secondary keys to match too
constant: boolean // always active (skip keyword check)
disabled: boolean // entry is disabled
group_name: string // mutual exclusion group
group_override: boolean // override group competition
group_weight: number // weight for group selection
probability: number // activation probability (0-100)
scan_depth: number | null // how many messages to scan for keywords
case_sensitive: boolean // case-sensitive keyword matching
match_whole_words: boolean // match whole words only
automation_id: string | null // automation identifier
use_regex: boolean // treat keys as regex patterns
prevent_recursion: boolean // prevent recursive activation
exclude_recursion: boolean // exclude from recursion scanning
delay_until_recursion: boolean
priority: number // activation priority
sticky: number // stay active for N turns after match
cooldown: number // cooldown turns after deactivation
delay: number // delay N turns before first activation
selective_logic: number // 0=AND, 1=NOT, 2=OR for secondary keys
use_probability: boolean // whether probability field is active
vectorized: boolean // entry has vector embeddings
extensions: Record<string, unknown>
created_at: number // unix epoch seconds
updated_at: number
}
WorldBookEntryCreateDTO / WorldBookEntryUpdateDTO¶
All fields are optional. Common fields you'll typically set:
| Field | Type | Description |
|---|---|---|
key |
string[] |
Primary activation keywords |
keysecondary |
string[] |
Secondary keywords (used with selective) |
content |
string |
The text to inject into the prompt |
comment |
string |
Human-readable label/name |
position |
number |
Injection position (0=WI Before, 1=WI After, 4=at depth) |
depth |
number |
Depth for position-based injection (default 4) |
selective |
boolean |
Require secondary keys to also match |
constant |
boolean |
Always active regardless of keywords |
disabled |
boolean |
Disable this entry |
order_value |
number |
Sort order within position bucket (default 100) |
priority |
number |
Activation priority (default 10) |
See WorldBookEntryDTO above for the full list of supported fields.
World Info Activation¶
Query which world info entries would activate for a given chat — without running a full generation. This runs the complete activation pipeline: keyword matching, selective logic, probability rolls, sticky/cooldown/delay state, group competition, budget enforcement, and vector-based activation (if embeddings are configured).
spindle.world_books.getActivated(chatId, userId?)¶
const activated = await spindle.world_books.getActivated('chat-id')
spindle.log.info(`${activated.length} entries activated`)
for (const entry of activated) {
const source = entry.source === 'vector'
? `vector (score: ${entry.score?.toFixed(4)})`
: 'keyword'
spindle.log.info(`[${source}] ${entry.comment} — keys: ${entry.keys.join(', ')}`)
}
This is useful for:
- Debugging — see exactly which WI entries fire for the current conversation state
- Analytics — track which lorebook entries are used most often
- Dynamic content — react to activated entries in interceptors or context handlers
- Validation — verify that entry keywords are triggering as expected
Parameters¶
| Parameter | Type | Description |
|---|---|---|
chatId |
string |
Required. The chat to evaluate activation against. |
userId |
string |
For operator-scoped extensions only. |
Returns¶
Promise<ActivatedWorldInfoEntryDTO[]> — an array of activated entries from all sources (character world book, persona world book, global world books).
ActivatedWorldInfoEntryDTO¶
| Field | Type | Description |
|---|---|---|
id |
string |
The world book entry ID. Can be used with spindle.world_books.entries.get() to fetch the full entry. |
comment |
string |
The entry's human-readable label. |
keys |
string[] |
The entry's primary activation keywords. |
source |
"keyword" \| "vector" |
How the entry was activated — via keyword matching or vector similarity search. |
score |
number |
Optional. For vector-activated entries, the similarity score (lower = more similar in cosine distance). Not present for keyword-activated entries. |
What gets scanned
The activation pipeline evaluates entries from all world books attached to the current context:
- The character's attached world book (from
character.extensions.world_book_id) - The active persona's attached world book (from
persona.attached_world_book_id) - All global world books (from the
globalWorldBookssetting)
Entries are scanned against the chat's message history using the same logic as prompt assembly.
Note
For user-scoped extensions, the user context is inferred automatically. For operator-scoped extensions, the user ID is resolved from the extension context. World books are always scoped to a single user.