What are Hooks?
Hooks are optional functions that run automatically at specific points during agent execution. They allow you to modify data, perform side effects, and integrate with external systems without modifying the framework code.
How Hooks Work
Hooks are functions that receive execution context (FlowState) and data, then either:
Transform data and return modified version (transformation hooks)
Perform side effects without returning anything (event hooks)
All hooks are wrapped in error handling - if a hook throws an error, it’s logged but execution continues with original data.
Hook Types
These hooks receive data, modify it, and return the modified version:
import { defineHook } from '@standardagents/builder' ;
export default defineHook ( 'filter_messages' , async ( state , rows ) => {
// Modify rows
return rows . filter ( row => row . status === 'completed' );
} ) ;
Available transformation hooks:
filter_messages - Filter SQL rows before transformation
prefilter_llm_history - Modify messages before sending to LLM
before_create_message - Modify message before database insert
before_update_message - Modify updates before applying
Event Hooks
These hooks run after an event occurs and don’t return anything:
import { defineHook } from '@standardagents/builder' ;
export default defineHook ( 'after_create_message' , async ( state , message ) => {
// Perform side effects (logging, analytics, etc.)
console . log ( 'Message created:' , message . id );
} ) ;
Available event hooks:
after_create_message - After message inserted
after_update_message - After message updated
after_tool_call_success - After successful tool execution
after_tool_call_failure - After failed tool execution
Common Use Cases
Message Filtering
Filter out unwanted messages before they’re sent to the LLM:
import { defineHook } from '@standardagents/builder' ;
export default defineHook ( 'filter_messages' , async ( state , rows ) => {
// Remove failed tool messages
return rows . filter ( row => {
if ( row . role === 'tool' && row . tool_status === 'error' ) {
return false ;
}
return true ;
});
} ) ;
External Logging
Send events to external analytics services:
import { defineHook } from '@standardagents/builder' ;
export default defineHook ( 'after_create_message' , async ( state , message ) => {
if ( message . role === 'assistant' ) {
await fetch ( 'https://analytics.example.com/events' , {
method: 'POST' ,
body: JSON . stringify ({
event: 'message_created' ,
thread_id: state . threadId ,
message_id: message . id ,
}),
});
}
} ) ;
Convert tool calls to user messages:
import { defineHook , injectMessage } from '@standardagents/builder' ;
export default defineHook ( 'after_tool_call_success' , async ( state , call , result ) => {
if ( call . function . name === 'get_user_input' ) {
// Inject a user message instead
await injectMessage ( state , {
role: 'user' ,
content: result . result || '' ,
});
// Return null to remove the tool call from history
return null ;
}
return result ;
} ) ;
Message Enrichment
Add context to messages before storage:
import { defineHook } from '@standardagents/builder' ;
export default defineHook ( 'before_create_message' , async ( state , message ) => {
if ( message . role === 'assistant' ) {
message . name = state . agentConfig . title ;
}
return message ;
} ) ;
Using defineHook
All hooks should use the defineHook utility for strict typing:
import { defineHook } from '@standardagents/builder' ;
export default defineHook ( 'filter_messages' , async ( state , rows ) => {
// TypeScript knows exactly what state and rows are!
return rows ;
} ) ;
Benefits:
Strict typing : Parameters automatically typed based on hook name
IntelliSense support : Full autocomplete in your editor
Type checking : Catch errors at compile time
Better documentation : Type hints show exactly what each parameter is
Available Hooks Quick Reference
Hook Type When It Runs Use Case filter_messagesTransform Before SQL → Message conversion Filter/modify message rows prefilter_llm_historyTransform Before sending to LLM Limit context, add dynamic data before_create_messageTransform Before INSERT Add metadata, modify content before_update_messageTransform Before UPDATE Validate changes, add timestamps after_create_messageEvent After INSERT External logging, webhooks after_update_messageEvent After UPDATE Track status changes after_tool_call_successTransform After successful tool Modify results, convert to messages after_tool_call_failureTransform After failed tool Enhance errors, suppress failures
File Organization
Hooks are auto-discovered from the agents/hooks/ directory:
agents/
└── hooks/
├── filter_messages.ts
├── after_create_message.ts
└── after_tool_call_success.ts
Requirements:
File name must match hook name: filter_messages.ts
Default export required
Use defineHook for type safety
Error Handling
All hooks are wrapped in error handling that:
Catches exceptions without breaking execution
Logs errors with [Hooks] ✗ prefix
Returns original data as fallback
// Your hook throws an error
export default defineHook ( 'filter_messages' , async ( state , rows ) => {
throw new Error ( 'Something went wrong!' );
} ) ;
// Framework behavior:
// 1. Catches error
// 2. Logs: [Hooks] ✗ Error running filter_messages hook: Something went wrong!
// 3. Returns original rows (unmodified)
// 4. Continues execution normally
Always wrap risky operations (API calls, etc.) in try-catch blocks to handle errors gracefully.
Best Practices
Hooks run in the critical execution path. Keep them fast (< 100ms ideal): // Good - quick operation
export default defineHook ( 'filter_messages' , async ( state , rows ) => {
return rows . filter ( row => row . status === 'completed' );
} ) ;
// Avoid - slow operation
export default defineHook ( 'filter_messages' , async ( state , rows ) => {
// Don't do expensive API calls here
for ( const row of rows ) {
await slowExternalAPI ( row );
}
return rows ;
} ) ;
Wrap risky operations in try-catch: export default defineHook ( 'after_create_message' , async ( state , message ) => {
try {
await fetch ( 'https://api.example.com/log' , {
method: 'POST' ,
body: JSON . stringify ( message ),
});
} catch ( error ) {
console . error ( '[Hook] External API failed:' , error );
// Don't throw - let hook complete successfully
}
} ) ;
FlowState Context
All hooks receive a FlowState object containing execution context:
interface FlowState {
// Identity
threadId : string ;
flowId : string ;
// Configuration
agentConfig : Agent ;
currentSide : 'a' | 'b' ;
// Execution State
turnCount : number ;
stopped : boolean ;
// Message Context
messageHistory : Message [];
// Storage & Environment
storage : DurableObjectStorage ;
env : Env ;
// ... and more
}
Use FlowState to:
Check which agent is executing
Access turn count
Query storage
Access environment bindings
Emit events to frontend
Next Steps