mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-07-22 15:25:09 +00:00
Some checks failed
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Check Documentation Broken Links / Check broken links (push) Has been cancelled
Vulnerability Scan / pip-audit (push) Has been cancelled
Build uv cache / build-cache (3.10) (push) Has been cancelled
Build uv cache / build-cache (3.11) (push) Has been cancelled
Build uv cache / build-cache (3.12) (push) Has been cancelled
Build uv cache / build-cache (3.13) (push) Has been cancelled
Nightly Canary Release / Check for new commits (push) Has been cancelled
Nightly Canary Release / Build nightly packages (push) Has been cancelled
Nightly Canary Release / Publish nightly to PyPI (push) Has been cancelled
Mark stale issues and pull requests / stale (push) Has been cancelled
* docs: group execution hooks and document all hook contexts The hooks pages sat flat in the learn nav and only documented the LLM and tool call contexts, with examples built on the legacy decorators. Groups them under a collapsible "Execution Hooks" section, adds `step-hooks` and `execution-boundary-hooks` pages covering every hook context from source, reworks the LLM and tool pages to lead with `@on` while keeping the decorators, and links the orphaned `before-and-after-kickoff-hooks` page into the group. * docs: prefix hook cross-links with /en so they resolve in edge The new edge-only hooks pages linked each other with versionless paths like `/learn/step-hooks`, which mintlify resolves against the frozen default version where those pages do not exist, breaking the CI link check. Uses the `/en/learn/...` form the other edge pages already use. * docs: use /edge prefix for links to edge-only hooks pages The `/en/learn/...` form still resolves against the default frozen version, where `step-hooks` and `execution-boundary-hooks` do not exist yet, so the link checker kept failing. Links now use the explicit `/edge/en/learn/...` form, matching how `consuming-streams.mdx` linked to edge-only streaming pages before they were frozen.
143 lines
4.4 KiB
Plaintext
143 lines
4.4 KiB
Plaintext
---
|
|
title: Step Hooks
|
|
description: Intercept task and flow-method steps with PRE_STEP and POST_STEP hooks in CrewAI
|
|
mode: "wide"
|
|
---
|
|
|
|
Step hooks intercept each unit of work inside an execution: every crew **task**
|
|
and every **flow method**. Use them to inspect or rewrite what goes into a
|
|
step, transform what comes out, or trace step-by-step progress — without
|
|
touching the level of individual LLM or tool calls.
|
|
|
|
## Overview
|
|
|
|
Two interception points cover steps:
|
|
|
|
| Point | When | `ctx.payload` |
|
|
|-------|------|---------------|
|
|
| `PRE_STEP` | Before a task or flow method runs | step input (see below) |
|
|
| `POST_STEP` | After a task or flow method runs | step output (see below) |
|
|
|
|
What the payload holds depends on `ctx.kind`:
|
|
|
|
| `ctx.kind` | `PRE_STEP` payload | `POST_STEP` payload |
|
|
|------------|--------------------|---------------------|
|
|
| `"task"` | The context string passed to the agent | The `TaskOutput` object |
|
|
| `"flow_method"` | The method's parameters as a `dict` | The method's return value |
|
|
|
|
For flow methods, positional arguments appear in the params dict under `_0`,
|
|
`_1`, ... keys and keyword arguments under their own names; edits and
|
|
replacements are mapped back onto the actual call.
|
|
|
|
## Hook Signature
|
|
|
|
```python
|
|
from crewai.hooks import on, HookAborted, InterceptionPoint
|
|
|
|
@on(InterceptionPoint.PRE_STEP)
|
|
def step_hook(ctx) -> Any | None:
|
|
# Mutate ctx.payload in place, or
|
|
# return a non-None value to replace it, or
|
|
# raise HookAborted(reason, source) to stop the step
|
|
return None
|
|
```
|
|
|
|
## Context Schema
|
|
|
|
Both points receive a `StepContext`:
|
|
|
|
```python
|
|
class StepContext(InterceptionContext):
|
|
payload: Any # Step input (pre) or step output (post)
|
|
kind: str | None # "task" or "flow_method"
|
|
step_name: str | None # Task name/description, or flow method name
|
|
output: Any # POST_STEP only: same object as payload
|
|
agent: Any # Task steps: the executing agent (else None)
|
|
agent_role: str | None # Task steps: the agent's role (else None)
|
|
task: Any # Task steps: the Task instance (else None)
|
|
crew: Any # None for step points
|
|
flow: Any # Flow-method steps: the Flow instance (else None)
|
|
```
|
|
|
|
For task steps, `step_name` is the task's `name` (falling back to its
|
|
description). For flow-method steps, it is the method name.
|
|
|
|
## Common Use Cases
|
|
|
|
### Step Tracing
|
|
|
|
```python
|
|
@on(InterceptionPoint.POST_STEP)
|
|
def trace_steps(ctx):
|
|
print(f"{ctx.kind} '{ctx.step_name}' finished")
|
|
```
|
|
|
|
### Rewriting Task Context
|
|
|
|
```python
|
|
@on(InterceptionPoint.PRE_STEP)
|
|
def inject_disclaimer(ctx):
|
|
if ctx.kind != "task":
|
|
return None
|
|
return f"{ctx.payload}\n\nNote: treat all figures as estimates."
|
|
```
|
|
|
|
### Transforming Task Output
|
|
|
|
```python
|
|
@on(InterceptionPoint.POST_STEP)
|
|
def normalize_output(ctx):
|
|
if ctx.kind != "task":
|
|
return None
|
|
ctx.payload.raw = ctx.payload.raw.strip()
|
|
```
|
|
|
|
<Note>
|
|
`POST_STEP` runs before the task's output is stored, so rewrites propagate
|
|
everywhere the output is used: downstream task context, callbacks, the final
|
|
crew output, and the task's `output_file` on disk.
|
|
</Note>
|
|
|
|
### Guarding Flow Methods
|
|
|
|
```python
|
|
@on(InterceptionPoint.PRE_STEP)
|
|
def guard_publish(ctx):
|
|
if ctx.kind == "flow_method" and ctx.step_name == "publish":
|
|
if not ctx.flow.state.get("reviewed"):
|
|
raise HookAborted(reason="publish requires review", source="review-gate")
|
|
```
|
|
|
|
### Filtering by Agent
|
|
|
|
Step hooks support the same `agents=` filter as the other points (matched
|
|
against the executing agent's role on task steps):
|
|
|
|
```python
|
|
@on(InterceptionPoint.POST_STEP, agents=["Researcher"])
|
|
def log_research_steps(ctx):
|
|
print(f"research step done: {ctx.step_name}")
|
|
```
|
|
|
|
## Aborting a Step
|
|
|
|
Raising `HookAborted` in `PRE_STEP` stops the step before any agent or method
|
|
work happens, and the abort propagates out of the execution with its reason —
|
|
it is not swallowed. Any other exception raised by a step hook is swallowed
|
|
(fail-open), like at every other point.
|
|
|
|
## Managing Hooks in Tests
|
|
|
|
```python
|
|
from crewai.hooks import clear_all_hooks
|
|
|
|
clear_all_hooks() # Clears every point, including steps
|
|
```
|
|
|
|
## Related Documentation
|
|
|
|
- [Execution Hooks Overview →](/edge/en/learn/execution-hooks)
|
|
- [Execution Boundary Hooks →](/edge/en/learn/execution-boundary-hooks)
|
|
- [LLM Call Hooks →](/edge/en/learn/llm-hooks)
|
|
- [Tool Call Hooks →](/edge/en/learn/tool-hooks)
|