Hooks let you register middleware callbacks that fire at key points in the agent execution lifecycle. Register once with WithHooks — the SDK calls your functions automatically across Run, RunAsync, and Stream.
Every hook can inspect , mutate , or abort the operation it wraps. Return an error from any hook to abort the run immediately.
Register hooks
a , err := agent . NewAgent (
agent . WithLLMClient ( llmClient ),
agent . WithHooks ( "guardrails" , agent . AgentHooks {
BeforeLLM : [] agent . BeforeLLMHook {
func ( ctx context . Context , in agent . BeforeLLMHookInput ) ( agent . BeforeLLMHookOutput , error ) {
// inspect or modify in.Request before the LLM call
return agent . BeforeLLMHookOutput { Request : in . Request }, nil
},
},
AfterLLM : [] agent . AfterLLMHook {
func ( ctx context . Context , in agent . AfterLLMHookInput ) ( agent . AfterLLMHookOutput , error ) {
// inspect or modify in.Response after the LLM call
return agent . AfterLLMHookOutput { Response : in . Response }, nil
},
},
}),
)
Call WithHooks multiple times to register independent groups . Each group runs under its own name in RunMeta.HooksGroup:
a , err := agent . NewAgent (
agent . WithHooks ( "pii-scrubber" , piiHooks ),
agent . WithHooks ( "cost-tracker" , costHooks ),
agent . WithHooks ( "audit-logger" , auditHooks ),
// ...
)
Hook group names participate in the Temporal agent config fingerprint. The agent and worker processes must register the same hook group names — implementations are not hashed, only the names.
Hook points
Hook Fires What you can mutate BeforeLLMHookBefore each LLM request Request (messages, system prompt, tools, params) AfterLLMHookAfter each LLM response Response (content, tool calls) BeforeToolHookBefore native/MCP tool executes Args AfterToolHookAfter native/MCP tool executes Content, Err BeforeRetrieveHookBefore each retriever search Query AfterRetrieveHookAfter each retriever search Documents BeforeMemoryLoadHookBefore memory recall Query, Limit, MinScore, Kinds AfterMemoryLoadHookAfter memory recall, before prompt injection PromptContext BeforeMemoryStoreHookBefore each memory record is persisted Record, ID AfterMemoryStoreHookAfter memory is persisted Error/abort only
Every hook receives RunMeta with RunID, Iteration, and HooksGroup for correlation.
Use cases
Guardrails and safety
Block unsafe inputs before they reach the LLM and filter toxic outputs before they reach the user:
agent . WithHooks ( "safety" , agent . AgentHooks {
BeforeLLM : [] agent . BeforeLLMHook {
func ( ctx context . Context , in agent . BeforeLLMHookInput ) ( agent . BeforeLLMHookOutput , error ) {
if containsInjection ( in . Request . Messages ) {
return agent . BeforeLLMHookOutput {}, errors . New ( "prompt injection detected" )
}
return agent . BeforeLLMHookOutput { Request : in . Request }, nil
},
},
AfterLLM : [] agent . AfterLLMHook {
func ( ctx context . Context , in agent . AfterLLMHookInput ) ( agent . AfterLLMHookOutput , error ) {
if isToxic ( in . Response . Content ) {
return agent . AfterLLMHookOutput {}, errors . New ( "unsafe output blocked" )
}
return agent . AfterLLMHookOutput { Response : in . Response }, nil
},
},
})
Hooks: BeforeLLM, AfterLLM
PII and data privacy
Scrub sensitive data before it leaves your system or gets persisted:
agent . WithHooks ( "pii" , agent . AgentHooks {
BeforeLLM : [] agent . BeforeLLMHook {
func ( ctx context . Context , in agent . BeforeLLMHookInput ) ( agent . BeforeLLMHookOutput , error ) {
req := in . Request
req . SystemMessage = redactPII ( req . SystemMessage )
return agent . BeforeLLMHookOutput { Request : req }, nil
},
},
BeforeMemoryStore : [] agent . BeforeMemoryStoreHook {
func ( ctx context . Context , in agent . BeforeMemoryStoreHookInput ) ( agent . BeforeMemoryStoreHookOutput , error ) {
rec := in . Record
rec . Text = redactPII ( rec . Text )
return agent . BeforeMemoryStoreHookOutput { Record : rec }, nil
},
},
})
Hooks: BeforeLLM, AfterLLM, BeforeTool, AfterTool, BeforeRetrieve, AfterRetrieve, BeforeMemoryStore, AfterMemoryLoad
Token budget and cost tracking
Enforce a per-run or per-user token budget across LLM calls:
agent . WithHooks ( "cost" , agent . AgentHooks {
AfterLLM : [] agent . AfterLLMHook {
func ( ctx context . Context , in agent . AfterLLMHookInput ) ( agent . AfterLLMHookOutput , error ) {
if in . Response . Usage != nil {
trackTokens ( in . RunMeta . RunID , in . Response . Usage . TotalTokens )
if budgetExceeded ( in . RunMeta . RunID ) {
return agent . AfterLLMHookOutput {}, errors . New ( "token budget exceeded" )
}
}
return agent . AfterLLMHookOutput { Response : in . Response }, nil
},
},
})
Hooks: BeforeLLM (cache read / short-circuit), AfterLLM (token tracking, cache write, model fallback)
Tenant isolation for memory
Enforce that each tenant only reads and writes their own memories:
agent . WithHooks ( "tenant-isolation" , agent . AgentHooks {
BeforeMemoryLoad : [] agent . BeforeMemoryLoadHook {
func ( ctx context . Context , in agent . BeforeMemoryLoadHookInput ) ( agent . BeforeMemoryLoadHookOutput , error ) {
if in . Scope . TenantID == "" {
return agent . BeforeMemoryLoadHookOutput {}, errors . New ( "tenant ID required for memory access" )
}
return agent . BeforeMemoryLoadHookOutput {
Query : in . Query , Limit : in . Limit ,
MinScore : in . MinScore , Kinds : in . Kinds ,
}, nil
},
},
BeforeMemoryStore : [] agent . BeforeMemoryStoreHook {
func ( ctx context . Context , in agent . BeforeMemoryStoreHookInput ) ( agent . BeforeMemoryStoreHookOutput , error ) {
if in . Scope . TenantID == "" {
return agent . BeforeMemoryStoreHookOutput {}, errors . New ( "tenant ID required for memory store" )
}
return agent . BeforeMemoryStoreHookOutput { Record : in . Record , ID : in . ID }, nil
},
},
})
Hooks: BeforeMemoryLoad, BeforeMemoryStore
Audit logging
Log every operation for observability without touching business logic:
agent . WithHooks ( "audit" , agent . AgentHooks {
AfterLLM : [] agent . AfterLLMHook {
func ( ctx context . Context , in agent . AfterLLMHookInput ) ( agent . AfterLLMHookOutput , error ) {
log . Printf ( "run= %s iter= %d tokens= %d " ,
in . RunMeta . RunID , in . RunMeta . Iteration , in . Response . Usage . TotalTokens )
return agent . AfterLLMHookOutput { Response : in . Response }, nil
},
},
AfterTool : [] agent . AfterToolHook {
func ( ctx context . Context , in agent . AfterToolHookInput ) ( agent . AfterToolHookOutput , error ) {
log . Printf ( "run= %s tool= %s err= %v " , in . RunMeta . RunID , in . Call . Name , in . Err )
return agent . AfterToolHookOutput { Content : in . Content , Err : in . Err }, nil
},
},
AfterMemoryStore : [] agent . AfterMemoryStoreHook {
func ( ctx context . Context , in agent . AfterMemoryStoreHookInput ) ( agent . AfterMemoryStoreHookOutput , error ) {
log . Printf ( "run= %s memory stored id= %s " , in . RunMeta . RunID , in . ID )
return agent . AfterMemoryStoreHookOutput {}, nil
},
},
})
Hooks: BeforeLLM, AfterLLM, BeforeTool, AfterTool, BeforeRetrieve, AfterRetrieve, AfterMemoryLoad, AfterMemoryStore
Retrieval query rewriting and re-ranking
Modify the query before search and filter or re-rank results after:
agent . WithHooks ( "retrieval" , agent . AgentHooks {
BeforeRetrieve : [] agent . BeforeRetrieveHook {
func ( ctx context . Context , in agent . BeforeRetrieveHookInput ) ( agent . BeforeRetrieveHookOutput , error ) {
return agent . BeforeRetrieveHookOutput { Query : expandQuery ( in . Query )}, nil
},
},
AfterRetrieve : [] agent . AfterRetrieveHook {
func ( ctx context . Context , in agent . AfterRetrieveHookInput ) ( agent . AfterRetrieveHookOutput , error ) {
return agent . AfterRetrieveHookOutput { Documents : rerank ( in . Documents )}, nil
},
},
})
Hooks: BeforeRetrieve (query rewriting), AfterRetrieve (filtering, re-ranking)
Use case reference
Goal Hooks to use Prompt injection / input guardrails BeforeLLMOutput content filtering AfterLLMPII scrubbing from prompts / responses BeforeLLM, AfterLLMPII scrubbing from tool args / results BeforeTool, AfterToolPII scrubbing from retrieval BeforeRetrieve, AfterRetrievePII scrubbing from memory BeforeMemoryStore, AfterMemoryLoadToken tracking and budget enforcement AfterLLMLLM response caching BeforeLLM (read), AfterLLM (write)Model fallback on error AfterLLMTool rate limiting BeforeToolTool input validation / authorization BeforeToolLog all operations BeforeLLM, AfterLLM, BeforeTool, AfterTool, BeforeRetrieve, AfterRetrieve, AfterMemoryLoad, AfterMemoryStoreRetrieval query rewriting BeforeRetrieveDocument re-ranking / filtering AfterRetrieveMemory query rewriting BeforeMemoryLoadFilter injected memory context AfterMemoryLoadMemory tenant isolation BeforeMemoryLoad, BeforeMemoryStoreControl what gets persisted BeforeMemoryStoreAudit memory reads / writes AfterMemoryLoad, AfterMemoryStore
Example
Hooks PII scrubbing, retrieval filtering, memory tenant isolation — every hook point registered
Memory BeforeMemoryLoad and BeforeMemoryStore hooks
Retrieval BeforeRetrieve and AfterRetrieve hooks
Observability OTLP traces and metrics alongside hooks
Configuration WithHooks reference