@on decorator: one registration API and one
contract cover every interception point in the framework.
The point-specific decorators (
@before_llm_call, @after_tool_call, …) keep
working unchanged — they are adapters over the same engine. See
Point-specific decorators (legacy) at the
end of this page.The contract
Every hook is a synchronous callable that receives a single typed context:Registering hooks
Use@on for global hooks. It accepts agents= / tools= filters to scope a
hook to specific agent roles or tool names:
@CrewBase class, @on registers a
crew-scoped hook, active only while that crew runs:
Interception point catalog
Each family has a detailed guide covering its context schema, payload semantics, and examples.Execution boundaries
Model boundaries & tool boundaries
At these four points the hook receives the rich legacy context directly as
its argument — there is no separate
ctx.payload. Mutate ctx.messages /
ctx.tool_input in place, and return a string from a post hook to replace the
response / tool result.
Step points
PRE_STEP / POST_STEP carry ctx.kind ("task" or "flow_method") and
ctx.step_name.
Aborting an operation
HookAborted carries a reason and an optional source. The source defaults
to the aborting hook when omitted, which is useful for telemetry and failure
messages:
Composition, ordering, and fail-open
- Multiple hooks on the same point run in registration order, global hooks first, then execution-scoped hooks. Legacy hooks registered for the same point participate in the same chain.
- The (possibly mutated) payload flows from one hook to the next.
HookAbortedpropagates by design and stops the chain.- Any other exception raised by a hook is swallowed (fail-open) so a single buggy hook can’t crash a run.
- When no hook is registered for a point, dispatch is a single dict lookup (no-op fast path), so unused points cost effectively nothing.
Common patterns
Safety guardrails
Human-in-the-loop approval
Sanitizing outputs
A non-None return value replaces the interceptable value, so transformations
are plain return statements:
Observing steps
Telemetry
Whenever a point actually dispatches to at least one hook, CrewAI emits aHookDispatchedEvent on the event bus with the point, the outcome
(proceeded / modified / aborted), the hook count, the duration, and — for
aborts — the reason and source. The no-op fast path emits nothing.
Managing hooks in tests
Global hooks persist for the lifetime of the process. Reset them between tests:Best practices
- Keep hooks focused — one clear responsibility per hook; register several small hooks rather than one that does everything.
- Keep hooks fast — hooks run on every dispatch of their point; avoid heavy computation and lazy-import heavy dependencies.
- Prefer scoping — use
agents=/tools=filters and crew-scoped registration instead of unconditional global hooks. - Abort loudly — raise
HookAbortedwith a meaningfulreasonandsource; that context surfaces in error messages and telemetry. Remember that any other exception is swallowed (fail-open), so don’t rely on raisingValueErrorto stop a run.
Point-specific decorators (legacy)
Before@on, LLM and tool calls were hooked with dedicated decorator pairs.
These keep working unchanged — they are adapters over the same dispatcher, so
they compose with @on hooks in the same registration-order chain:
@on:
- They cover only the four model/tool points — no execution boundaries, no steps.
- Blocking is
return False, with no abort reason or source attached. - They receive the same rich contexts —
LLMCallHookContext(with full executor access) andToolCallHookContext— that@onhooks receive at the model/tool points. - Crew-scoping works the same way: apply the decorator to a method inside a
@CrewBaseclass. - They support the same
agents=/tools=filters.
return False semantics, or when you want the point-specific typed signatures.
For the detailed guides — context attributes, patterns, and management APIs
(register_* / unregister_* / clear_*) — see:
