Run automatic checks before or after agent actions — block bad output, require approval, or trigger custom validation logic.
GoClaw's hook system lets you attach quality gates to agent lifecycle events. A hook is a check that runs at a specific event (e.g. after a subagent completes a delegation). If the check fails, GoClaw can block the result and optionally retry with feedback.
Two evaluator types are available:
| Type | How it validates |
|---|---|
command |
Runs a shell command — exit 0 = pass, non-zero = fail |
agent |
Delegates to a reviewer agent — APPROVED = pass, REJECTED: ... = fail |
{
"event": "delegation.completed",
"type": "command",
"command": "./scripts/check-output.sh",
"block_on_failure": true,
"max_retries": 2,
"timeout_seconds": 60
}| Field | Type | Description |
|---|---|---|
event |
string | Lifecycle event that triggers this hook (e.g. "delegation.completed") |
type |
string | "command" or "agent" |
command |
string | Shell command to run (type=command only) |
agent |
string | Reviewer agent key (type=agent only) |
block_on_failure |
bool | If true, a failing hook stops execution; if false, failure is logged but continues |
max_retries |
int | How many times to retry after a blocking failure (0 = no retry) |
timeout_seconds |
int | Per-hook timeout (default 60s) |
flowchart TD
EVENT["Lifecycle event fires\ne.g. delegation.completed"] --> ENGINE["Hook Engine\nmatches event to configs"]
ENGINE --> EVAL1["Evaluator 1\n(command or agent)"]
ENGINE --> EVAL2["Evaluator 2\n(command or agent)"]
EVAL1 -->|pass| NEXT["Continue"]
EVAL1 -->|fail + block| BLOCK["Block result\n(optionally retry with feedback)"]
EVAL1 -->|fail + non-block| LOG["Log warning\nContinue anyway"]
EVAL2 -->|pass| NEXT
The engine evaluates hooks in order. The first blocking failure stops evaluation and returns the failure result. Non-blocking failures are logged but do not interrupt the flow. If all hooks pass (or none match the event), execution continues normally.
The command evaluator runs a shell command via sh -c. The content being validated is passed via stdin. Exit 0 means the hook passed; any other exit code means it failed. The stderr output becomes the feedback shown to the agent on retry.
Environment variables available inside the command:
| Variable | Value |
|---|---|
HOOK_EVENT |
The event name |
HOOK_SOURCE_AGENT |
Key of the agent that produced the output |
HOOK_TARGET_AGENT |
Key of the delegated-to agent |
HOOK_TASK |
The original task string |
HOOK_USER_ID |
User ID who triggered the request |
Example — basic content length check:
#!/bin/sh
# check-output.sh: fail if output is too short
content=$(cat)
length=${#content}
if [ "$length" -lt 100 ]; then
echo "Output is too short ($length chars). Provide a more complete response." >&2
exit 1
fi
exit 0Hook config:
{
"event": "delegation.completed",
"type": "command",
"command": "./scripts/check-output.sh",
"block_on_failure": true,
"max_retries": 1,
"timeout_seconds": 10
}The agent evaluator delegates to a reviewer agent. GoClaw sends a structured prompt with the original task, source/target agent keys, and the output to review. The reviewer must reply with exactly:
APPROVED(optionally followed by comments) — hook passesREJECTED: <specific feedback>— hook fails; the feedback is used as the retry prompt
The evaluation runs with hooks skipped (WithSkipHooks) to prevent infinite recursion.
Example — code review gate:
{
"event": "delegation.completed",
"type": "agent",
"agent": "code-reviewer",
"block_on_failure": true,
"max_retries": 2,
"timeout_seconds": 120
}The code-reviewer agent receives a prompt like:
[Quality Gate Evaluation]
You are reviewing the output of a delegated task for quality.
Original task: Write a Go function to parse JSON...
Source agent: orchestrator
Target agent: backend-dev
Output to evaluate:
<agent output here>
Respond with EXACTLY one of:
- "APPROVED" if the output meets quality standards
- "REJECTED: <specific feedback>" with actionable improvement suggestions
Content filtering — block replies containing prohibited content using a command hook that greps for banned patterns.
Length/format validation — reject outputs that are too short, missing required sections, or have wrong formatting.
Approval workflows — use an agent hook wired to a strict reviewer agent that checks correctness before a result is accepted.
Security scanning — run a script that checks generated code or shell commands for dangerous patterns before execution.
Non-blocking audit — set block_on_failure: false to log all outputs to an audit system without blocking the flow.
Two-hook setup: format check then agent review:
{
"hooks": [
{
"event": "delegation.completed",
"type": "command",
"command": "python3 ./scripts/validate-format.py",
"block_on_failure": true,
"max_retries": 0,
"timeout_seconds": 15
},
{
"event": "delegation.completed",
"type": "agent",
"agent": "quality-reviewer",
"block_on_failure": true,
"max_retries": 2,
"timeout_seconds": 90
}
]
}Non-blocking audit logger:
{
"event": "delegation.completed",
"type": "command",
"command": "curl -s -X POST https://audit.internal/log -d @-",
"block_on_failure": false,
"timeout_seconds": 5
}| Issue | Cause | Fix |
|---|---|---|
hooks: unknown hook type, skipping |
Typo in type field |
Use "command" or "agent" exactly |
| Command always passes even with exit 1 | Wrapper script swallows exit code | Ensure script doesn't have ` |
| Agent evaluator hangs | Reviewer agent slow or stuck | Set timeout_seconds to a reasonable value |
| Infinite retry loop | max_retries too high + agent can't fix it |
Cap max_retries at 2–3; add escape conditions |
| Hooks fire on reviewer agent itself | Recursion | GoClaw injects WithSkipHooks for agent evaluator calls automatically |
| Non-blocking hook blocks anyway | block_on_failure: true set accidentally |
Check config; set to false for observe-only hooks |
- Extended Thinking — deeper reasoning before producing output
- Exec Approval — human-in-the-loop approval for shell commands