Processor Types
Processors fit into four slot families. Each family runs at a different point in the agent loop and has a different interface.
Family 1 — Message Modifiers
Run before the LLM call. Receive the assembled message list, return a modified list. Used for prompt assembly, context injection, compression, redaction.
interface MessageModifier {
modify(messages: Message[], ctx: Context): Promise<Message[]>;
}
Built-ins (see Built-in Processors):
CoreSystemPromptModifier— injects the core system promptEnvironmentContextModifier— adds cwd, git branch, timeIdeContextModifier— adds open file, selection, cursorDirectoryContextModifier— adds directory listingAtFileProcessorModifier— resolves@filereferences to contentsArgumentProcessorModifier— substitutes{arg}-style placeholdersMemoryImportModifier— pulls in@memory:referencesChatHistoryMessageModifier— manages the history windowChatCompressionModifier— compresses older turns when over budgetConversationCompactorModifier— aggressive compactionShellProcessorModifier— handles$(cmd)shell substitutionLoopDetectionModifier— detects and breaks repetitive loopsChatRecordingModifier— records final messages for replay
Family 2 — Tool Modifiers
Run around tool calls. Can inject available tools, rewrite arguments, or validate calls against extra rules.
interface ToolInjectionModifier {
inject(availableTools: Tool[], ctx: Context): Promise<Tool[]>;
}
interface ToolParameterModifier {
modify(call: ToolCall, ctx: Context): Promise<ToolCall>;
}
interface ToolValidationModifier {
validate(call: ToolCall, ctx: Context): Promise<ValidationResult>;
}
Built-ins:
ToolInjectionModifier— contextually expands the tool allowlistToolParameterModifier— rewrites arguments before executionToolValidationModifier— checks calls against custom rules
Family 3 — LLM Step Processors
Wrap the LLM call itself. Run before, after, or both.
interface LLMAgentStep {
before?(request: LLMRequest, ctx: Context): Promise<LLMRequest>;
after?(response: LLMResponse, ctx: Context): Promise<LLMResponse>;
}
Used for: streaming transforms, response validation, model fallback, usage logging.
Family 4 — Response Modifiers
Run after the LLM responds, before the agent acts on it. Used for redaction, normalization, safety checks.
interface ResponseModifier {
modify(response: LLMResponse, ctx: Context): Promise<LLMResponse>;
}
Ordering within a family
Within a family, processors run in a declared order. The framework picks defaults for built-ins; custom processors are inserted at a declared position:
processors:
message_modifiers:
- CoreSystemPromptModifier # position 1
- DirectoryContextModifier # position 2
- { name: MyRedactor, after: AtFileProcessorModifier } # explicit
- ChatCompressionModifier # position 3
Order matters: a redactor that runs before AtFileProcessorModifier won't see resolved file contents.
Reading vs modifying
Some processors observe without modifying:
class MetricsModifier implements MessageModifier {
async modify(messages, ctx) {
ctx.metrics.incr("messages.assembled", messages.length);
return messages; // unchanged
}
}
These are fine — the framework runs them for side effects. But if you're only observing, consider using a hook instead — hooks are cheaper and don't sit on the critical path.
Control flow
A processor can return more than just the modified value. For message modifiers:
return { messages: newMessages, halt: true, reason: "budget exceeded" };
Returning halt: true stops the pipeline. Useful for emergency stops (budget exceeded, guardrail failure).
For tool modifiers:
return { call, deny: true, reason: "path outside workspace" };
Denying a tool call surfaces a structured denial to the agent.