Overview
Hooks are optional functions that run automatically at specific points during agent execution. This reference documents all available hooks, their signatures, parameters, and usage patterns.
All hooks should use the defineHook utility for strict typing and better developer experience.
Using defineHook
import { defineHook } from '@standardagents/builder' ;
export default defineHook ( 'filter_messages' , async ( state , rows ) => {
// TypeScript knows exactly what state and rows are!
return rows ;
} ) ;
Available Hooks
filter_messages
Filters or modifies SQL row data before it’s transformed to chat completion format.
defineHook ( 'filter_messages' , async ( state , rows ) => {
return rows ;
})
Array of SQL rows from messages table
The filtered/modified row array
MessageRow Structure
interface MessageRow {
id : string ;
role : 'system' | 'user' | 'assistant' | 'tool' ;
content : string | null ;
name : string | null ;
tool_calls : string | null ; // JSON string of tool calls
tool_call_id : string | null ; // For role='tool' messages
log_id : string | null ; // Reference to logs table
created_at : number ; // Microseconds timestamp
request_sent_at : number | null ;
response_completed_at : number | null ;
status : 'pending' | 'completed' | 'failed' | null ;
silent : number | null ; // 1 if hidden, 0/null otherwise
tool_status : 'success' | 'error' | null ;
}
When It Runs
Timing : Before SQL rows are transformed to chat completion format
Frequency : Every turn that loads message history
Context : After rows fetched from SQLite, before transformation
Examples
import { defineHook } from '@standardagents/builder' ;
export default defineHook ( 'filter_messages' , async ( state , rows ) => {
// Only keep messages from last 24 hours
const dayAgo = ( Date . now () - 24 * 60 * 60 * 1000 ) * 1000 ; // microseconds
return rows . filter ( row => row . created_at >= dayAgo );
} ) ;
import { defineHook } from '@standardagents/builder' ;
export default defineHook ( 'filter_messages' , async ( state , rows ) => {
if ( state . agentConfig . title === 'Production Agent' ) {
// Remove all error messages for cleaner context
return rows . filter ( row => row . tool_status !== 'error' );
}
return rows ;
} ) ;
prefilter_llm_history
Modifies the message history before it’s sent to the LLM.
defineHook ( 'prefilter_llm_history' , async ( state , messages ) => {
return messages ;
})
Array of messages about to be sent to LLM
The filtered/modified message array
Message Structure
interface Message {
role : 'system' | 'user' | 'assistant' | 'tool' ;
content : string | null ;
tool_calls ?: string | null ;
tool_call_id ?: string | null ;
name ?: string | null ;
}
When It Runs
Timing : Immediately before sending messages to LLM
Frequency : Every turn that involves an LLM request
Context : After message history assembled but before API call
Examples
import { defineHook } from '@standardagents/builder' ;
export default defineHook ( 'prefilter_llm_history' , async ( state , messages ) => {
// Keep only last 20 messages
return messages . slice ( - 20 );
} ) ;
import { defineHook } from '@standardagents/builder' ;
export default defineHook ( 'prefilter_llm_history' , async ( state , messages ) => {
// Inject current turn count into system prompt
const systemMsg = messages . find ( m => m . role === 'system' );
if ( systemMsg && systemMsg . content ) {
systemMsg . content += ` \n\n Current turn: ${ state . turnCount } /25` ;
}
return messages ;
} ) ;
post_process_message
Modifies assistant messages after LLM response but before storage.
This hook is defined but not yet invoked in FlowEngine. It will be activated in a future update.
defineHook ( 'post_process_message' , async ( state , message ) => {
return message ;
})
The assistant’s message from LLM
Examples
before_create_message
Modifies any message before it’s inserted into the database.
defineHook ( 'before_create_message' , async ( state , message ) => {
return message ;
})
Message about to be created
CreateMessage Structure
interface CreateMessage {
id : string ;
role : 'system' | 'user' | 'assistant' | 'tool' ;
content : string | null ;
tool_calls ?: string | null ;
tool_call_id ?: string | null ;
name ?: string | null ;
created_at : number ;
status ?: 'pending' | 'completed' | 'failed' ;
silent ?: boolean ;
}
When It Runs
Timing : Immediately before INSERT INTO messages
Frequency : Every time a message is created
Scope : ALL message types (user, assistant, tool, system)
Examples
import { defineHook } from '@standardagents/builder' ;
export default defineHook ( 'before_create_message' , async ( state , message ) => {
// Tag messages with agent name
if ( message . role === 'assistant' ) {
message . name = state . agentConfig . title ;
}
return message ;
} ) ;
import { defineHook } from '@standardagents/builder' ;
export default defineHook ( 'before_create_message' , async ( state , message ) => {
// Add prefix based on role
if ( message . content && message . role === 'user' ) {
message . content = `[User] ${ message . content } ` ;
}
return message ;
} ) ;
Conditional Transformation
after_create_message
Runs after a message is successfully inserted into the database.
defineHook ( 'after_create_message' , async ( state , message ) => {
// No return value
})
The message that was just created
When It Runs
Timing : Immediately after successful INSERT INTO messages
Frequency : Every time a message is created
Scope : ALL message types
Examples
import { defineHook } from '@standardagents/builder' ;
export default defineHook ( 'after_create_message' , async ( state , message ) => {
// Send to analytics service
if ( message . role === 'assistant' && message . content ) {
try {
await fetch ( 'https://analytics.example.com/events' , {
method: 'POST' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify ({
event: 'message_created' ,
thread_id: state . threadId ,
agent_id: state . agentConfig . id ,
message_id: message . id ,
content_length: message . content . length ,
}),
});
} catch ( error ) {
console . error ( 'Analytics failed:' , error );
}
}
} ) ;
import { defineHook } from '@standardagents/builder' ;
export default defineHook ( 'after_create_message' , async ( state , message ) => {
// Notify webhook on user messages
if ( message . role === 'user' ) {
await fetch ( 'https://webhook.example.com/new-message' , {
method: 'POST' ,
body: JSON . stringify ({
threadId: state . threadId ,
content: message . content ,
}),
});
}
} ) ;
before_update_message
Modifies message updates before they’re applied to the database.
defineHook ( 'before_update_message' , async ( state , messageId , updates ) => {
return updates ;
})
ID of message being updated
The modified updates object
MessageUpdates Structure
interface MessageUpdates {
content ?: string | null ;
tool_calls ?: string | null ;
log_id ?: string | null ;
response_completed_at ?: number | null ;
status ?: 'pending' | 'completed' | 'failed' ;
}
Updates object only contains fields being changed, not the full message.
When It Runs
Timing : Immediately before UPDATE messages SET …
Frequency : Every time a message is updated
Common Triggers : Status changes, content streaming completion, adding tool_calls
Examples
import { defineHook } from '@standardagents/builder' ;
export default defineHook ( 'before_update_message' , async ( state , messageId , updates ) => {
// Ensure completed messages have timestamp
if ( updates . status === 'completed' && ! updates . response_completed_at ) {
updates . response_completed_at = Date . now () * 1000 ; // microseconds
}
return updates ;
} ) ;
import { defineHook } from '@standardagents/builder' ;
export default defineHook ( 'before_update_message' , async ( state , messageId , updates ) => {
// Clean up content on updates
if ( updates . content && typeof updates . content === 'string' ) {
updates . content = updates . content . trim ();
}
return updates ;
} ) ;
after_update_message
Runs after a message is successfully updated in the database.
defineHook ( 'after_update_message' , async ( state , messageId , updates ) => {
// No return value
})
ID of message that was updated
When It Runs
Timing : Immediately after successful UPDATE messages
Frequency : Every time a message is updated
Scope : All message update operations
Examples
import { defineHook } from '@standardagents/builder' ;
export default defineHook ( 'after_update_message' , async ( state , messageId , updates ) => {
// Log status transitions
if ( updates . status ) {
console . log ( `[Status Change] Message ${ messageId } : ${ updates . status } ` );
// Send to monitoring
await fetch ( 'https://monitor.example.com/status' , {
method: 'POST' ,
body: JSON . stringify ({
messageId ,
status: updates . status ,
threadId: state . threadId ,
}),
});
}
} ) ;
Intercepts successful tool executions before results are stored in the database.
defineHook ( 'after_tool_call_success' , async ( state , call , result ) => {
return result ; // or null to remove from history
})
The tool call that was executed
The successful result from the tool
Returns
Promise<ToolResult | null>
Modified result, or null to remove the tool call from history
interface ToolCall {
id : string ;
type : "function" ;
function : {
name : string ;
arguments : string ; // JSON string
};
}
interface ToolResult {
status : "success" ;
result ?: string ;
}
When It Runs
Timing : Immediately after successful tool execution, before storage
Frequency : Every time a tool executes successfully
Scope : All tool types (native, prompt, agent)
Returning null
When the hook returns null:
The tool call message is removed from message history
The tool result message is not stored
You can inject custom messages using injectMessage()
Examples
Convert Tool Call to User Message
import { defineHook } from "@standardagents/builder" ;
export default defineHook ( 'after_tool_call_success' , async ( state , call , result ) => {
// Remove sensitive data from results
if ( call . function . name === "lookup_user" && result . result ) {
const data = JSON . parse ( result . result );
delete data . ssn ;
delete data . password ;
result . result = JSON . stringify ( data );
}
return result ;
} ) ;
Intercepts failed tool executions before errors are stored in the database.
defineHook ( 'after_tool_call_failure' , async ( state , call , result ) => {
return result ; // or null to remove from history
})
The tool call that failed
The error result from the tool
Returns
Promise<ToolResult | null>
Modified error result, or null to remove the tool call from history
interface ToolResult {
status : "error" ;
error ?: string ;
stack ?: string ;
}
When It Runs
Timing : Immediately after tool failure, before storage
Frequency : Every time a tool fails or throws an error
Scope : All tool types (native, prompt, agent)
Examples
import { defineHook , injectMessage } from "@standardagents/builder" ;
export default defineHook ( 'after_tool_call_failure' , async ( state , call , result ) => {
// Silently suppress "not found" errors
if ( result . error ?. includes ( "not found" )) {
// Inject a user message instead
await injectMessage ( state , {
role: "user" ,
content: "The requested item was not found." ,
});
// Remove the failed tool call
return null ;
}
return result ; // Pass through other errors
} ) ;
import { defineHook } from "@standardagents/builder" ;
export default defineHook ( 'after_tool_call_failure' , async ( state , call , result ) => {
// Add context to error messages
if ( result . error ) {
result . error = `[ ${ call . function . name } failed] ${ result . error } ` ;
}
return result ;
} ) ;
import { defineHook } from "@standardagents/builder" ;
export default defineHook ( 'after_tool_call_failure' , async ( state , call , result ) => {
// Notify external system of critical errors
if ( call . function . name === "payment_processing" ) {
try {
await fetch ( "https://alerts.example.com/error" , {
method: "POST" ,
body: JSON . stringify ({
tool: call . function . name ,
error: result . error ,
threadId: state . threadId ,
}),
});
} catch ( err ) {
console . error ( "Failed to send error notification:" , err );
}
}
return result ;
} ) ;
Common Types
FlowState
Full execution context available to all hooks:
interface FlowState {
// Identity
threadId : string ;
flowId : string ;
// Configuration
agentConfig : Agent ;
currentSide : 'a' | 'b' ;
// Execution State
turnCount : number ;
stopped : boolean ;
stoppedBy ?: 'a' | 'b' ;
// Message Context
messageHistory : Message [];
// Tool Execution
sequence : {
queue : ToolCall [];
isHandling : boolean ;
};
// Streaming & Telemetry
stream : StreamManager ;
emitMessage ?: ( msg : any ) => void ;
emitLog ?: ( log : any ) => void ;
// Runtime Context
env : Env ;
storage : DurableObjectStorage ;
context : Record < string , any >;
// Retry Tracking
retryCount : number ;
retryReason ?: string ;
// Abort Control
abortController ?: AbortController ;
}
Agent
Agent configuration from D1:
interface Agent {
id : string ;
title : string ;
type : 'dual_ai' | 'ai_human' ;
// Side A Configuration
side_a_system_prompt : string ;
side_a_stop_option : 'returns_content' | 'tool' ;
side_a_stop_tool ?: string ;
// Side B Configuration (null for ai_human)
side_b_system_prompt ?: string ;
side_b_stop_option ?: 'returns_content' | 'tool' ;
side_b_stop_tool ?: string ;
// Additional fields...
max_session_turns ?: number ;
expose_as_tool ?: boolean ;
tool_description ?: string ;
}
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
Example Error Flow
// Your hook throws an error
export default defineHook ( 'before_create_message' , async ( state , message ) => {
throw new Error ( 'Something went wrong!' );
} ) ;
// Framework behavior:
// 1. Catches error
// 2. Logs: [Hooks] ✗ Error running before_create_message hook: Something went wrong!
// 3. Returns original message (unmodified)
// 4. Continues execution normally
Best Practices for Error Handling
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
}
} ) ;
export default defineHook ( 'before_update_message' , async ( state , messageId , updates ) => {
// Check if field exists before modifying
if ( updates . status && updates . status === 'completed' ) {
updates . response_completed_at = Date . now () * 1000 ;
}
return updates ;
} ) ;
File Structure
agents/
└── hooks/
├── filter_messages.ts
├── prefilter_llm_history.ts
├── before_create_message.ts
├── after_create_message.ts
├── before_update_message.ts
├── after_update_message.ts
├── after_tool_call_success.ts
└── after_tool_call_failure.ts
Requirements:
File name must match hook name
One hook per file
Default export required
Use defineHook for type safety