Skip to main content
Execution boundary hooks intercept the outermost edges of a run — before any work starts, when inputs are resolved, when the final result is ready, and when the execution finishes. They fire for both crews and flows and are the right place for run-level policy checks, input rewriting, and output sanitization.

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

Boundary hooks follow the standard contract: proceed (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:
The per-point contexts add a named alias for the payload:
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 a crew.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

Rewritten inputs flow into task interpolation, so the run behaves as if it was kicked off with the modified dict.

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:
Two caveats: 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:
Hooks at the same point run in registration order, global hooks first, then crew-scoped hooks. Telemetry (HookDispatchedEvent) is emitted per dispatch.

Managing Hooks in Tests