Overview
LLM hooks are executed at two interception points:
Write them with the
@on decorator. The
legacy @before_llm_call / @after_llm_call decorators
keep working unchanged — both styles register on the same engine and run in one
ordered chain.
Hook Signature
LLMCallHookContext directly as the hook argument (there is no separate
ctx.payload): mutate ctx.messages in place before the call, and return a
string to replace the response after it.
Blocking a call raises ValueError("LLM call blocked by before_llm_call hook")
inside the executor; the HookAborted reason and source are recorded in
telemetry.
LLM Hook Context
TheLLMCallHookContext object provides comprehensive access to execution state:
request_human_input(prompt, default_message), which
pauses live console updates and collects input from the terminal — useful for
approval gates.
Modifying Messages
Important: Always modify messages in-place:Registration Methods
1. Global Hooks
Apply to all LLM calls across all crews. Use theagents= filter to scope a
hook to specific agent roles:
2. Crew-Scoped Hooks
Apply the same decorator to a method inside a@CrewBase class to scope the
hook to that crew only:
Common Use Cases
1. Iteration Limiting
2. Human Approval Gate
3. Adding System Context
4. Response Sanitization
5. Debug Logging
Hook Management
register_before_llm_call_hook,
unregister_before_llm_call_hook, clear_before_llm_call_hooks,
clear_all_llm_call_hooks, get_before_llm_call_hooks, and their after_
counterparts) operates on the same underlying registries, so either API can
manage hooks registered by the other.
Legacy Decorators
The original per-point decorators keep working unchanged and run in the same registration-order chain as@on hooks:
@on:
- Blocking is
return Falsefrom a before hook — equivalent to raisingHookAborted, but without a custom reason or source for telemetry. - Signatures are point-specific: before hooks return
bool | None, after hooks returnstr | None. The context object is the sameLLMCallHookContext. - Filters and crew-scoping work the same way:
@before_llm_call(agents=[...]), and applying the decorator to a@CrewBasemethod scopes it to that crew.
@on for new code; keep the legacy style where it is already in use —
there is no behavioral penalty.
Best Practices
- Keep hooks focused and fast — they run on every LLM call
- Modify in-place — always mutate
ctx.messages, never replace the list - Use type hints — annotate with
LLMCallHookContextfor IDE support - Abort loudly — raise
HookAbortedwith a meaningful reason and source; any other exception is swallowed (fail-open) - Clear hooks in tests — call
clear_all_hooks()between test runs
Troubleshooting
Hook Not Executing
- Verify the hook is registered before crew execution
- Check whether an earlier hook aborted (subsequent hooks don’t run)
Message Modifications Not Persisting
- Use in-place modifications:
ctx.messages.append(...) - Don’t replace the list:
ctx.messages = []
Response Modifications Not Working
- Return the modified string from a
POST_MODEL_CALLhook - Returning
Nonekeeps the original response
