Overview
Four interception points cover the boundaries:
For a crew, the output payload is a
CrewOutput. For a flow, it is the final
flow-method result.
Hook Signature
return None), mutate in
place, replace by returning, or abort by raising
HookAborted. An abort at any
boundary propagates out of kickoff() with its reason.
Context Schema
Each point receives a typed context. All contexts share the base fields:ctx.inputs aliases the original inputs dict, so in-place edits through
either name are equivalent. If an earlier hook replaced the payload by
returning a new dict, only ctx.payload is rebound — always read and write
ctx.payload when hooks might chain.Crew Runs vs. Flow Runs
Boundary hooks fire on both runtimes, and crew execution internally rides on a flow runtime. During acrew.kickoff(), a global boundary hook therefore fires
for the crew boundary (ctx.crew set, ctx.flow None) and for the
internal flow (ctx.flow set, ctx.crew None). Discriminate by runtime:
Common Use Cases
Policy Check at Start
Input Rewriting
Output Sanitization
OUTPUT runs before EXECUTION_END, and both see the (possibly replaced)
payload from earlier hooks; the final rewritten value is what kickoff()
returns.
Observing Failures
EXECUTION_END fires exactly once per execution, on success and on failure
alike. When the run raises — a task error, a flow-method exception, or a
HookAborted from an earlier point — the hook receives status="failed" with
the exception in ctx.error, and the original exception still propagates out
of kickoff() unchanged:
EXECUTION_END does not fire when EXECUTION_START never
dispatched (an abort at start counts as the execution never beginning), and
raising HookAborted from a failure-path EXECUTION_END dispatch is ignored —
there is nothing left to abort, and the original error wins.
Ordering
For a crew run the boundary order is:HookDispatchedEvent) is emitted per dispatch.
