Hooks
[TK]
1. Hook Definition
A hook is an interception point in the agent execution lifecycle. Hooks enable logging, validation, transformation, and side effects without modifying core agent logic.
1.1 Available Hooks
The specification defines the following hooks:
| Hook | Trigger Point | Purpose |
|---|---|---|
filter_messages | Before LLM context assembly | Filter/transform message history |
prefilter_llm_history | After context assembly | Final adjustments before LLM request |
before_create_message | Before message insert | Transform message before storage |
after_create_message | After message insert | Side effects after storage |
before_update_message | Before message update | Transform update data |
after_update_message | After message update | Side effects after update |
before_store_tool_result | Before tool result storage | Transform tool results |
after_tool_call_success | After successful tool call | Post-process success results |
after_tool_call_failure | After failed tool call | Handle/recover from errors |
1.2 Hook Registration
Hooks are identified by their name and MUST implement the signature defined in Section 7:
- Hook names MUST match one of the defined hook types in Section 1.1
- Each hook implementation MUST be created using
defineHook() - Multiple implementations of the same hook MAY exist (composition order is implementation-defined)
- Hook implementations SHOULD be idempotent where possible
- Implementations SHOULD enforce timeouts on hook execution
- Hooks SHOULD NOT perform blocking operations that could timeout execution
- Hooks MUST NOT expose sensitive data in logs or error messages
2. Hook Context
2.1 ThreadState Parameter
All hooks receive a ThreadState instance as their first parameter. See the Threads specification for the complete interface.
Key properties available during hook execution:
| Property | Type | Description |
|---|---|---|
threadId | string | Unique thread identifier |
agentId | string | Agent that owns this thread |
userId | string | null | Associated user |
execution | ExecutionState | Always present in hooks |
2.2 Execution State
Since hooks run during agent execution, state.execution is always available:
| Property | Type | Description |
|---|---|---|
flowId | string | Current execution flow identifier |
currentSide | 'a' | 'b' | Current execution side |
stepCount | number | Current step count (LLM cycles) |
stopped | boolean | Whether execution has stopped |
abortSignal | AbortSignal | Cancellation signal |
2.3 Using ThreadState in Hooks
defineHook('filter_messages', async (state, messages) => {
// Access thread identity
console.log(`Thread: ${state.threadId}`);
// Access execution state
console.log(`Step: ${state.execution.stepCount}`);
// Load resources
const agent = await state.loadAgent(state.agentId);
return messages;
});
3. Message Filtering Hooks
3.1 filter_messages
Called before messages are assembled into LLM context. Receives raw message data from storage.
Signature:
(state: ThreadState, messages: HookMessage[]) => Promise<HookMessage[]>
Use cases:
- Limit conversation history to N most recent messages
- Filter out system-only messages
- Remove messages matching certain patterns
- Inject synthetic messages
Example:
defineHook('filter_messages', async (state, messages) => {
// Only include last 20 messages
return messages.slice(-20);
});
3.2 prefilter_llm_history
Called after messages are transformed into LLM chat format, before the request is sent.
Signature:
(state: ThreadState, messages: LLMMessage[]) => Promise<LLMMessage[]>
Use cases:
- Add dynamic instructions to context
- Modify message content before LLM sees it
- Inject reminders or constraints
Hooks modifying message content SHOULD validate inputs to prevent injection attacks.
Example:
defineHook('prefilter_llm_history', async (state, messages) => {
// Add reminder to keep responses concise
const last = messages[messages.length - 1];
if (last?.role === 'user' && typeof last.content === 'string') {
last.content += '\n\n(Remember to be concise)';
}
return messages;
});
4. Message Lifecycle Hooks
4.1 before_create_message
Called before a message is inserted into storage. Return modified data to transform the message.
Signature:
(state: ThreadState, message: Record<string, unknown>) => Promise<Record<string, unknown>>
Behavior:
- Implementations MUST call this hook before inserting any message
- The returned object MUST be used for insertion
- Failures SHOULD prevent message creation
4.2 after_create_message
Called after a message is successfully inserted. Cannot modify the message.
Signature:
(state: ThreadState, message: Record<string, unknown>) => Promise<void>
Behavior:
- Implementations MUST call this hook after successful insertion
- Failures SHOULD NOT affect the created message
- Use for logging, analytics, or triggering external systems
4.3 before_update_message
Called before a message update is applied. Return modified update data.
Signature:
(state: ThreadState, messageId: string, updates: Record<string, unknown>) => Promise<Record<string, unknown>>
Behavior:
- Implementations MUST call this hook before updating
- The returned object MUST be used for the update
- Failures SHOULD prevent the update
4.4 after_update_message
Called after a message is successfully updated.
Signature:
(state: ThreadState, message: HookMessage) => Promise<void>
Behavior:
- Implementations MUST call this hook after successful update
- Failures SHOULD NOT affect the updated message
5. Tool Execution Hooks
5.1 before_store_tool_result
Called before a tool result is stored in the database.
Signature:
(state: ThreadState, toolCall: Record<string, unknown>, toolResult: Record<string, unknown>) => Promise<Record<string, unknown>>
Use cases:
- Sanitize sensitive data from results
- Add metadata to tool results
- Transform result format
5.2 after_tool_call_success
Called after a tool executes successfully. Can return modified result or null for original.
Signature:
(state: ThreadState, toolCall: HookToolCall, toolResult: HookToolResult) => Promise<HookToolResult | null>
Behavior:
- If hook returns
null, the original result is used - If hook returns a result, it replaces the original
- Use for logging, metrics, or result post-processing
5.3 after_tool_call_failure
Called after a tool execution fails. Can return modified error or null for original.
Signature:
(state: ThreadState, toolCall: HookToolCall, toolResult: HookToolResult) => Promise<HookToolResult | null>
Use cases:
- Error logging and alerting
- Error recovery attempts
- Error message transformation
Hooks accessing external services MUST handle failures gracefully.
6. Type Definitions
6.1 HookMessage
interface HookMessage {
id: string;
role: 'system' | 'user' | 'assistant' | 'tool';
content: string | null;
name?: string | null;
tool_calls?: string | null;
tool_call_id?: string | null;
created_at: number;
parent_id?: string | null;
depth?: number;
}
6.2 HookToolCall
interface HookToolCall {
id: string;
type: 'function';
function: {
name: string;
arguments: string;
};
}
6.3 HookToolResult
interface HookToolResult {
status: 'success' | 'error';
result?: string;
error?: string;
stack?: string;
}
6.4 LLMMessage
interface LLMMessage {
role: string;
content: string | null;
tool_calls?: unknown;
tool_call_id?: string;
name?: string;
}
7. TypeScript Reference
/**
* Hook signatures for all available hooks.
* All hooks receive ThreadState as their first parameter.
* See the Threads specification for the ThreadState interface.
*/
interface HookSignatures<
Message = HookMessage,
ToolCall = HookToolCall,
ToolResult = HookToolResult,
> {
filter_messages: (state: ThreadState, messages: Message[]) => Promise<Message[]>;
prefilter_llm_history: (
state: ThreadState,
messages: LLMMessage[]
) => Promise<LLMMessage[]>;
before_create_message: (
state: ThreadState,
message: Record<string, unknown>
) => Promise<Record<string, unknown>>;
after_create_message: (
state: ThreadState,
message: Record<string, unknown>
) => Promise<void>;
before_update_message: (
state: ThreadState,
messageId: string,
updates: Record<string, unknown>
) => Promise<Record<string, unknown>>;
after_update_message: (state: ThreadState, message: Message) => Promise<void>;
before_store_tool_result: (
state: ThreadState,
toolCall: Record<string, unknown>,
toolResult: Record<string, unknown>
) => Promise<Record<string, unknown>>;
after_tool_call_success: (
state: ThreadState,
toolCall: ToolCall,
toolResult: ToolResult
) => Promise<ToolResult | null>;
after_tool_call_failure: (
state: ThreadState,
toolCall: ToolCall,
toolResult: ToolResult
) => Promise<ToolResult | null>;
}
/**
* Valid hook names.
*/
type HookName = keyof HookSignatures;
/**
* Define a hook with strict typing.
*/
function defineHook<K extends keyof HookSignatures>(
hookName: K,
implementation: HookSignatures[K]
): HookSignatures[K];
8. Examples
8.1 Message Context Limiting
import { defineHook } from '@standardagents/spec';
export default defineHook('filter_messages', async (state, messages) => {
// Keep only the most recent 50 messages
return messages.slice(-50);
});
8.2 Logging All Tool Calls
import { defineHook } from '@standardagents/spec';
export default defineHook('after_tool_call_success', async (state, toolCall, result) => {
console.log({
event: 'tool_success',
threadId: state.threadId,
step: state.execution.stepCount,
tool: toolCall.function.name,
args: toolCall.function.arguments,
result: result.result,
timestamp: Date.now(),
});
return null; // Use original result
});
8.3 Error Alerting
import { defineHook } from '@standardagents/spec';
export default defineHook('after_tool_call_failure', async (state, toolCall, result) => {
// Send alert for critical tool failures
if (toolCall.function.name === 'payment_process') {
await sendAlert({
level: 'critical',
message: `Payment tool failed: ${result.error}`,
context: { threadId: state.threadId },
});
}
return null; // Use original error
});
8.4 Adding Metadata to Messages
import { defineHook } from '@standardagents/spec';
export default defineHook('before_create_message', async (state, message) => {
return {
...message,
metadata: JSON.stringify({
flow_id: state.execution.flowId,
step: state.execution.stepCount,
side: state.execution.currentSide,
}),
};
});
8.5 Sanitizing Sensitive Data
import { defineHook } from '@standardagents/spec';
export default defineHook('before_store_tool_result', async (state, toolCall, result) => {
// Redact credit card numbers from results
const sanitized = { ...result };
if (typeof sanitized.result === 'string') {
sanitized.result = sanitized.result.replace(
/\b\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}\b/g,
'****-****-****-****'
);
}
return sanitized;
});