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 (ThreadState) 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.
Defining Hooks
Hooks are defined using defineHook with a unique identifier:
import { defineHook } from '@standardagents/spec' ;
export default defineHook ({
hook: 'filter_messages' ,
id: 'remove_failed_tools' ,
execute : async ( state , messages ) => {
return messages . filter ( m => m . role !== 'tool' || m . tool_status !== 'error' );
} ,
}) ;
Each hook requires three properties:
Property Type Description hookHookNameThe hook type (e.g., 'filter_messages') idstringUnique identifier for this hook (snake_case) executeFunctionThe hook implementation, typed based on hook
Hook ID Requirements
IDs must be unique across all hooks in the project
IDs must be snake_case (lowercase letters, numbers, underscores, starting with a letter)
IDs should be descriptive of what the hook does
Benefits of defineHook:
Strict typing : Parameters automatically typed based on hook type
IntelliSense support : Full autocomplete in your editor
Type checking : Catch errors at compile time
Hook Scoping
Hooks must be explicitly declared on prompts or agents to execute. Undeclared hooks do not run.
Prompt-Level Hooks
import { definePrompt } from '@standardagents/spec' ;
export default definePrompt ({
name: 'customer_support' ,
// ... other config ...
hooks: [ 'remove_failed_tools' , 'log_tool_calls' ] ,
}) ;
Prompt hooks execute when that prompt is the active execution context.
Agent-Level Hooks
import { defineAgent } from '@standardagents/spec' ;
export default defineAgent ({
name: 'support_agent' ,
// ... other config ...
hooks: [ 'inject_context' , 'sanitize_output' ] ,
}) ;
Agent hooks are a fallback — they execute when the active prompt has no hooks defined.
Resolution Priority
If the current prompt declares hooks, only those hooks run
If the prompt has no hooks but the agent does, agent hooks run
If neither declares hooks, no hooks execute
This means hooks in agents/hooks/ that aren’t referenced by any prompt or agent will never run. Always add hook IDs to the relevant prompt or agent.
Multiple Hooks of Same Type
You can define multiple hooks of the same hook type with different IDs:
// agents/hooks/limit_messages.ts
export default defineHook ({
hook: 'filter_messages' ,
id: 'limit_to_20_messages' ,
execute : async ( state , messages ) => messages . slice ( - 20 ) ,
}) ;
// agents/hooks/remove_system.ts
export default defineHook ({
hook: 'filter_messages' ,
id: 'remove_system_messages' ,
execute : async ( state , messages ) => messages . filter ( m => m . role !== 'system' ) ,
}) ;
When both are included in a prompt’s hooks array, they execute in the order listed:
hooks : [ 'limit_to_20_messages' , 'remove_system_messages' ]
Hook Types
These hooks receive data, modify it, and return the modified version:
import { defineHook } from '@standardagents/spec' ;
export default defineHook ({
hook: 'filter_messages' ,
id: 'keep_completed_only' ,
execute : async ( state , rows ) => {
return rows . filter ( row => row . status === 'completed' );
} ,
}) ;
Available transformation hooks:
filter_messages - Filter message 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
before_store_tool_result - Modify tool result before storage
after_tool_call_success - Modify or remove successful tool results
after_tool_call_failure - Modify or remove failed tool results
Event Hooks
These hooks run after an event occurs and don’t return anything:
import { defineHook } from '@standardagents/spec' ;
export default defineHook ({
hook: 'after_create_message' ,
id: 'log_message_creation' ,
execute : async ( state , message ) => {
console . log ( 'Message created:' , message . id );
} ,
}) ;
Available event hooks:
after_create_message - After message inserted
after_update_message - After message updated
Common Use Cases
Message Filtering
Filter out unwanted messages before they’re sent to the LLM:
import { defineHook } from '@standardagents/spec' ;
export default defineHook ({
hook: 'filter_messages' ,
id: 'remove_failed_tool_messages' ,
execute : async ( state , rows ) => {
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/spec' ;
export default defineHook ({
hook: 'after_create_message' ,
id: 'send_analytics' ,
execute : 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 } from '@standardagents/spec' ;
import { injectMessage } from '@standardagents/builder' ;
export default defineHook ({
hook: 'after_tool_call_success' ,
id: 'convert_user_input_tool' ,
execute : async ( state , call , result ) => {
if ( call . function . name === 'get_user_input' ) {
await injectMessage ( state , {
role: 'user' ,
content: result . result || '' ,
});
return null ;
}
return result ;
} ,
}) ;
Message Enrichment
Add context to messages before storage:
import { defineHook } from '@standardagents/spec' ;
export default defineHook ({
hook: 'before_create_message' ,
id: 'tag_assistant_messages' ,
execute : async ( state , message ) => {
if ( message . role === 'assistant' ) {
message . name = state . agentConfig . title ;
}
return message ;
} ,
}) ;
Clean sensitive data from tool results before storage:
import { defineHook } from '@standardagents/spec' ;
export default defineHook ({
hook: 'before_store_tool_result' ,
id: 'sanitize_pii' ,
execute : async ( state , toolCall , toolResult ) => {
if ( toolResult . result ) {
toolResult . result = toolResult . result . replace ( / \b \d {3} - \d {2} - \d {4} \b / g , '***-**-****' );
}
return toolResult ;
} ,
}) ;
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 before_store_tool_resultTransform Before storing tool result Sanitize results, add metadata 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/
├── limit_messages.ts
├── remove_failed_tools.ts
├── log_analytics.ts
└── sanitize_pii.ts
Requirements:
File names can be anything (hook is identified by its id)
Default export required
Use defineHook for type safety
Multiple hooks of the same type are supported (each with a unique id)
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 ({
hook: 'filter_messages' ,
id: 'buggy_filter' ,
execute : 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 ({
hook: 'filter_messages' ,
id: 'quick_filter' ,
execute : async ( state , rows ) => {
return rows . filter ( row => row . status === 'completed' );
} ,
}) ;
// Avoid - slow operation
export default defineHook ({
hook: 'filter_messages' ,
id: 'slow_filter' ,
execute : async ( state , rows ) => {
for ( const row of rows ) {
await slowExternalAPI ( row );
}
return rows ;
} ,
}) ;
Wrap risky operations in try-catch: export default defineHook ({
hook: 'after_create_message' ,
id: 'safe_analytics' ,
execute : 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 );
}
} ,
}) ;
Scope Hooks Intentionally
Only add hooks to prompts/agents that need them: // Prompt that needs message filtering
definePrompt ({
name: 'support_prompt' ,
hooks: [ 'limit_to_20_messages' , 'remove_failed_tools' ],
// ...
});
// Prompt that doesn't need hooks — leave hooks undefined
definePrompt ({
name: 'simple_responder' ,
// No hooks property — no hooks run
// ...
});
ThreadState Context
All hooks receive a ThreadState object containing execution context:
interface ThreadState {
// Identity (readonly)
readonly threadId : string ;
readonly agentId : string ;
readonly userId : string | null ;
readonly createdAt : number ;
// Execution State (null at rest)
execution : ExecutionState | null ;
// Context Storage
context : Record < string , unknown >;
// Methods
getMessages ( options ? ) : Promise < MessagesResult >;
injectMessage ( input ) : Promise < Message >;
queueTool ( toolName , args ) : void ;
emit ( event , data ) : void ;
// ... and more
}
Use ThreadState to:
Check which agent is executing (state.agentId)
Access execution state (state.execution?.stepCount)
Get/inject messages
Emit events to frontend
Access the file system
Next Steps
Hooks API Reference Complete hooks specification
ThreadState Learn about ThreadState interface
Examples See hooks in action