Hook System
The hook system provides lifecycle callbacks at key points in the agent loop. Hooks are registered on the HookBus and can observe, modify, or short-circuit agent behavior.
Hook lifecycle
Available hooks
beforeAgentStart
interface BeforeAgentStartContext {
prompt: string
systemPrompt: string
messages: AgentMessage[]
}
interface BeforeAgentStartResult {
messages?: AgentMessage[]
systemPrompt?: string
}Fired before the agent starts processing. Use to:
- Modify the system prompt dynamically
- Prepend or append messages
- Inject context from external sources
beforeToolCall
interface BeforeToolCallContext {
toolCall: ToolCall
args: Record<string, unknown>
iteration: number
}
interface BeforeToolCallResult {
content?: string // Short-circuit with content
isError?: boolean // Treat as error
args?: Record<string, unknown> // Rewrite arguments
}Fired before a tool executes. Use to:
- Log tool invocations
- Validate or rewrite arguments
- Short-circuit execution (return
{ content }or{ isError })
afterToolCall
interface AfterToolCallContext {
toolCall: ToolCall
args: Record<string, unknown>
result: string
isError: boolean
iteration: number
}
interface AfterToolCallResult {
content?: string // Override result
isError?: boolean // Mark as error
terminate?: boolean // Stop the loop
}Fired after tool completion. Use to:
- Log results
- Handle or transform errors
- Terminate the loop on specific conditions
prepareNextTurn
interface PrepareNextTurnContext {
messages: Message[]
iteration: number
hadToolCalls: boolean
}
interface PrepareNextTurnResult {
messages: Message[] // Return modified messages
}Fired before preparing the next turn. Use to:
- Transform messages before sending to the provider
- Inject context or instructions
- Remove or modify conversation history
transformContext
interface TransformContext {
messages: AgentMessage[]
iteration: number
signal?: AbortSignal
taskState?: ExplicitTaskState // Live structured task state
}
interface TransformContextResult {
messages: AgentMessage[]
}Fired during context assembly. Use to:
- Modify messages based on task state
- Abort transformation via signal
- Apply task-aware context shaping
beforeProviderRequest
interface BeforeProviderRequestContext {
model: string
sessionId: string
iteration: number
streamOptions: AgentHarnessStreamOptions
}
interface BeforeProviderRequestResult {
headers?: Record<string, string | undefined> // Header patch
timeoutMs?: number // Timeout override
maxRetries?: number // Retry override
cacheRetention?: string // Cache hint
metadata?: Record<string, unknown> // Additional headers
transport?: string // Provider metadata
}Fired before sending a request to the LLM provider. Use to:
- Add custom headers
- Override timeout or retry settings
- Inject provider metadata
beforeProviderPayload
interface BeforeProviderPayloadContext {
model: string
payload: Record<string, unknown>
}
interface BeforeProviderPayloadResult {
payload: Record<string, unknown>
}Fired before sending the request payload. Use to:
- Modify the payload structure
- Add or remove fields
- Inject provider-specific options
afterProviderResponse
interface AfterProviderResponseContext {
model: string
content: string
toolCallCount: number
stopReason: StopReason
usageTokens?: number
iteration: number
}Fired after receiving a response from the LLM provider. Use to:
- Log responses
- Track token usage
- Trigger alerts on specific patterns
shouldStopAfterTurn
interface ShouldStopAfterTurnContext {
messages: Message[]
iteration: number
hadToolCalls: boolean
}Fired after each turn. Use to:
- Implement custom termination conditions
- Stop the loop based on message content
getSteeringMessages
interface GetSteeringMessagesContext {
messages: Message[]
iteration: number
}Fired when building steering messages. Use to:
- Inject steering context
- Modify steering behavior
getFollowUpMessages
interface GetFollowUpMessagesContext {
messages: Message[]
iteration: number
assistantText: string
stopReason?: StopReason
}Fired when building follow-up messages. Use to:
- Inject follow-up context
- Modify follow-up behavior
beforeCompact
interface BeforeCompactContext {
messages: Message[]
tokensBefore: number
reason: "manual" | "auto"
}
interface BeforeCompactResult {
cancel?: boolean // Skip compaction
summary?: string // Pre-built summary
}Fired before compaction. Use to:
- Skip compaction entirely
- Provide a pre-built summary
Writing a hook
// plugins/logging.ts
export default {
name: 'logging',
hooks: {
beforeToolCall({ toolCall, args, iteration }) {
console.log(`[log] ${toolCall.name}(${JSON.stringify(args)})`)
},
afterToolCall({ toolCall, result, isError }) {
if (isError) {
console.error(`[log] ${toolCall.name} failed: ${result}`)
}
},
},
}Hook execution order
Hooks execute in registration order within each event type. Each hook can return a result that modifies behavior (e.g., short-circuiting tool execution, overriding results).