World Info Interceptor¶
Permission required: generation
World info interceptors run before world info activation. They receive the candidate entries plus the current chat state, and return a list of entry IDs to disable for this turn and/or per-entry content overrides.
spindle.registerWorldInfoInterceptor(async (ctx) => {
const disabled: string[] = []
for (const entry of ctx.entries) {
if (entry.constant && ctx.chatTurn < 5) disabled.push(entry.id)
}
return { disabled }
}, 100)
Use this when an entry's stored fields can't express the activation rule (turn-based gates, sticky flags, external state lookups, content rewrites driven by retrieval).
Parameters¶
| Param | Type | Description |
|---|---|---|
handler |
(ctx: WorldInfoInterceptorCtx) => Promise<WorldInfoInterceptorResult \| void> |
Returns disable + content-override decisions, or void to pass through |
priority |
number |
Optional. Lower values run first. Default:100 |
One interceptor per extension; a second registration replaces the first.
Context Object¶
interface WorldInfoInterceptorCtx {
chatId: string
characterId: string
userId?: string
entries: WorldInfoInterceptorEntry[]
messages: WorldInfoInterceptorMessage[]
chatTurn: number
chatMetadata: Record<string, unknown>
}
interface WorldInfoInterceptorEntry {
id: string
world_book_id: string
comment: string
disabled: boolean
constant: boolean
extensions: Record<string, unknown>
key: string[]
keysecondary: string[]
position: number
depth: number
priority: number
probability: number
use_probability: boolean
content: string
automation_id: string | null
selective: boolean
selective_logic: number
match_whole_words: boolean
case_sensitive: boolean
use_regex: boolean
prevent_recursion: boolean
exclude_recursion: boolean
delay_until_recursion: boolean
scan_depth: number | null
order_value: number
}
interface WorldInfoInterceptorMessage {
id: string
role: "system" | "user" | "assistant"
content: string
is_user: boolean
is_greeting: boolean
greeting_index?: number
swipe_id: number
index_in_chat: number
}
entries reflects the current state of the chain: each interceptor sees the previous one's mutations. disabled: true on an incoming entry can mean either "stored as disabled" or "an earlier interceptor disabled it"; use extensions to mark your own provenance if you need to tell them apart.
chatMetadata is the chat-level metadata blob. Persist cross-turn state here via spindle.chats.update; the snapshot is read-only.
Return Value¶
interface WorldInfoInterceptorResult {
disabled?: string[]
enabled?: string[]
forced?: string[]
mutated?: { id: string; content?: string }[]
}
Return void (or omit all arrays) for a no-op pass-through. The four axes are independent:
| Field | Effect |
|---|---|
disabled |
Forces disabled: true. Wins against any enabled/forced vote anywhere in the chain. |
enabled |
Un-flips a stored disabled: true. No effect on entries already enabled or on entries any handler voted to disable. |
forced |
Sets constant: true for this turn (activates regardless of key match). No effect if any handler voted to disable. Independent of enabled. To force a stored-disabled entry, vote both enabled and forced. |
mutated |
Replaces content for this turn only; the stored entry is unchanged. Applies regardless of activation state. |
Mutating an entry that another interceptor disabled is allowed but inert.
Composition Order¶
Multiple interceptors run in priority order (lower first), with registration order as the tie-breaker. Each interceptor receives the previous one's mutations applied to the entry list. There is no cap on chain depth.
Vote-off precedence is the chain's invariant: once any handler votes disabled for an entry, no later handler's enabled or forced can revive it. This makes per-handler reasoning order-independent on the disable axis.
Content overrides accumulate last-write-wins: if two handlers set content for the same entry, the higher-priority handler (later in the chain) wins.
Permission Scope¶
registerWorldInfoInterceptor requires the generation permission — the same gate that covers registerInterceptor and registerContextHandler. No additional permission is needed for content overrides.
Timeout¶
Each interceptor runs inside a 10-second wall-clock budget. On timeout or thrown error: the chain logs the failure and forwards the previous entry list to the next handler. World info activation itself never aborts.
Users notice the wait
The interceptor fires before activation, which fires before prompt assembly, which fires before the LLM call. Slow handlers add visible latency before the first streamed token.
World Info Interceptor vs Context Handler vs Interceptor¶
| Hook | When it fires | What it changes |
|---|---|---|
| World Info Interceptor | Before world info activation | Per-entry disable + content overrides |
| Context Handler | Before prompt assembly | The generation context |
| Interceptor | After assembly, before LLM call | The outgoing message array |