docs: group execution hooks and document all hook contexts (#6548)
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.
This commit is contained in:
Lucas Gomide
2026-07-15 09:17:52 -03:00
committed by GitHub
parent 0e5d0ecfb9
commit da9902da4f
6 changed files with 703 additions and 791 deletions

View File

@@ -64,7 +64,7 @@ from crewai.hooks import on, InterceptionPoint
@on(InterceptionPoint.POST_TOOL_CALL, agents=["researcher"], tools=["web_search"])
def log_search_results(ctx):
print(f"search returned: {str(ctx.payload)[:80]}")
print(f"search returned: {(ctx.tool_result or '')[:80]}")
```
Applied to a method inside a `@CrewBase` class, `@on` registers a
@@ -84,30 +84,36 @@ class MyProjCrew:
## Interception point catalog
`payload` is the value a hook may mutate or replace at each point.
Each family has a detailed guide covering its context schema, payload
semantics, and examples.
### Execution boundaries
### [Execution boundaries](/edge/en/learn/execution-boundary-hooks)
| Point | When | `payload` |
|-------|------|-----------|
| Point | When | `ctx.payload` |
|-------|------|---------------|
| `EXECUTION_START` | A crew or flow is about to begin | inputs `dict` |
| `INPUT` | Resolved inputs for the execution | inputs `dict` |
| `OUTPUT` | Final result is ready | the output object |
| `EXECUTION_END` | A crew or flow has finished | the output object |
### Model & tool boundaries
### [Model boundaries](/edge/en/learn/llm-hooks) & [tool boundaries](/edge/en/learn/tool-hooks)
| Point | When | `payload` |
|-------|------|-----------|
| Point | When | Hook receives |
|-------|------|---------------|
| `PRE_MODEL_CALL` | Before an LLM call | `LLMCallHookContext` |
| `POST_MODEL_CALL` | After an LLM call | response |
| `POST_MODEL_CALL` | After an LLM call | `LLMCallHookContext` (with `response` set) |
| `PRE_TOOL_CALL` | Before a tool runs | `ToolCallHookContext` |
| `POST_TOOL_CALL` | After a tool runs | tool result |
| `POST_TOOL_CALL` | After a tool runs | `ToolCallHookContext` (with results set) |
### Step points
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.
| Point | When | `payload` |
|-------|------|-----------|
### [Step points](/edge/en/learn/step-hooks)
| Point | When | `ctx.payload` |
|-------|------|---------------|
| `PRE_STEP` | Before a task or flow-method step | step input |
| `POST_STEP` | After a task or flow-method step | step output |
@@ -147,12 +153,12 @@ def enforce_policy(ctx):
@on(InterceptionPoint.PRE_TOOL_CALL)
def block_dangerous_tools(ctx):
dangerous = {"delete_file", "drop_table", "system_shutdown"}
if ctx.payload.tool_name in dangerous:
raise HookAborted(reason=f"{ctx.payload.tool_name} is blocked", source="safety-policy")
if ctx.tool_name in dangerous:
raise HookAborted(reason=f"{ctx.tool_name} is blocked", source="safety-policy")
@on(InterceptionPoint.PRE_MODEL_CALL)
def iteration_limit(ctx):
if ctx.payload.iterations > 15:
if ctx.iterations > 15:
raise HookAborted(reason="maximum iterations exceeded", source="loop-guard")
```
@@ -161,8 +167,8 @@ def iteration_limit(ctx):
```python
@on(InterceptionPoint.PRE_TOOL_CALL, tools=["send_email", "make_payment"])
def require_approval(ctx):
response = ctx.payload.request_human_input(
prompt=f"Approve {ctx.payload.tool_name}?",
response = ctx.request_human_input(
prompt=f"Approve {ctx.tool_name}?",
default_message="Type 'yes' to approve:",
)
if response.lower() != "yes":
@@ -171,8 +177,8 @@ def require_approval(ctx):
### Sanitizing outputs
A non-`None` return value replaces the payload, so transformations are plain
return statements:
A non-`None` return value replaces the interceptable value, so transformations
are plain return statements:
```python
import re
@@ -182,7 +188,7 @@ def redact_keys(ctx):
return re.sub(
r'(api[_-]?key)["\']?\s*[:=]\s*["\']?[\w-]+',
r"\1: [REDACTED]",
ctx.payload,
ctx.response,
flags=re.IGNORECASE,
)
```
@@ -254,10 +260,11 @@ Differences from `@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 rich point-specific contexts — `LLMCallHookContext` (with full
executor access) and `ToolCallHookContext` — the same objects `@on` exposes
as `ctx.payload` at the `PRE_MODEL_CALL` / `PRE_TOOL_CALL` points.
- Crew-scoping uses the `*_crew` decorator variants inside `@CrewBase` classes.
- They receive the same rich contexts — `LLMCallHookContext` (with full
executor access) and `ToolCallHookContext` — that `@on` hooks receive at the
model/tool points.
- Crew-scoping works the same way: apply the decorator to a method inside a
`@CrewBase` class.
- They support the same `agents=` / `tools=` filters.
You might still prefer them for existing codebases that already use
@@ -265,10 +272,10 @@ You might still prefer them for existing codebases that already use
For the detailed guides — context attributes, patterns, and management APIs
(`register_*` / `unregister_*` / `clear_*`) — see:
- [LLM Call Hooks →](/learn/llm-hooks)
- [Tool Call Hooks →](/learn/tool-hooks)
- [LLM Call Hooks →](/edge/en/learn/llm-hooks)
- [Tool Call Hooks →](/edge/en/learn/tool-hooks)
## Related documentation
- [Before and After Kickoff Hooks →](/learn/before-and-after-kickoff-hooks)
- [Human-in-the-Loop →](/learn/human-in-the-loop)
- [Before and After Kickoff Hooks →](/edge/en/learn/before-and-after-kickoff-hooks)
- [Human-in-the-Loop →](/edge/en/learn/human-in-the-loop)