Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion agent-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,20 @@
"items": {
"$ref": "#/definitions/HookDefinition"
}
},
"stop": {
"type": "array",
"description": "Hooks that run when the model finishes responding and is about to hand control back to the user. Can perform post-response validation or logging.",
"items": {
"$ref": "#/definitions/HookDefinition"
}
},
"notification": {
"type": "array",
"description": "Hooks that run when the agent sends a notification (error, warning) to the user. Can send external notifications or log events.",
"items": {
"$ref": "#/definitions/HookDefinition"
}
}
},
"additionalProperties": false
Expand Down Expand Up @@ -1559,4 +1573,4 @@
"additionalProperties": false
}
}
}
}
2 changes: 2 additions & 0 deletions docs/configuration/agents/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ agents:
session_start: [list]
session_end: [list]
on_user_input: [list]
stop: [list]
notification: [list]
structured_output: # Optional: constrain output format
name: string
schema: object
Expand Down
171 changes: 138 additions & 33 deletions docs/configuration/hooks/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,24 @@ Hooks allow you to execute shell commands or scripts at key points in an agent's
- Block dangerous operations based on custom rules
- Set up the environment when a session starts
- Clean up resources when a session ends
- Log or validate model responses before returning to the user
- Send external notifications on agent errors or warnings

</div>

## Hook Types

There are five hook event types:
There are seven hook event types:

| Event | When it fires | Can block? |
| --------------- | ---------------------------------------- | ---------- |
| `pre_tool_use` | Before a tool call executes | Yes |
| `post_tool_use` | After a tool completes successfully | No |
| `session_start` | When a session begins or resumes | No |
| `session_end` | When a session terminates | No |
| `on_user_input` | When the agent is waiting for user input | No |
| Event | When it fires | Can block? |
| ---------------- | ------------------------------------------------------ | ---------- |
| `pre_tool_use` | Before a tool call executes | Yes |
| `post_tool_use` | After a tool completes successfully | No |
| `session_start` | When a session begins or resumes | No |
| `session_end` | When a session terminates | No |
| `on_user_input` | When the agent is waiting for user input | No |
| `stop` | When the model finishes responding | No |
| `notification` | When the agent emits a notification (error or warning) | No |

## Configuration

Expand Down Expand Up @@ -74,6 +78,16 @@ agents:
on_user_input:
- type: command
command: "./scripts/notify.sh"

# Run when the model finishes responding
stop:
- type: command
command: "./scripts/log-response.sh"

# Run on agent errors and warnings
notification:
- type: command
command: "./scripts/alert.sh"
```

## Matcher Patterns
Expand Down Expand Up @@ -107,22 +121,29 @@ Hooks receive JSON input via stdin with context about the event:

### Input Fields by Event Type

| Field | pre_tool_use | post_tool_use | session_start | session_end | on_user_input |
| ----------------- | ------------ | ------------- | ------------- | ----------- | ------------- |
| `session_id` | ✓ | ✓ | ✓ | ✓ | ✓ |
| `cwd` | ✓ | ✓ | ✓ | ✓ | ✓ |
| `hook_event_name` | ✓ | ✓ | ✓ | ✓ | ✓ |
| `tool_name` | ✓ | ✓ | | | |
| `tool_use_id` | ✓ | ✓ | | | |
| `tool_input` | ✓ | ✓ | | | |
| `tool_response` | | ✓ | | | |
| `source` | | | ✓ | | |
| `reason` | | | | ✓ | |
| Field | pre_tool_use | post_tool_use | session_start | session_end | on_user_input | stop | notification |
| ---------------------- | ------------ | ------------- | ------------- | ----------- | ------------- | ---- | ------------ |
| `session_id` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| `cwd` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| `hook_event_name` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| `tool_name` | ✓ | ✓ | | | | | |
| `tool_use_id` | ✓ | ✓ | | | | | |
| `tool_input` | ✓ | ✓ | | | | | |
| `tool_response` | | ✓ | | | | | |
| `source` | | | ✓ | | | | |
| `reason` | | | | ✓ | | | |
| `stop_response` | | | | | | ✓ | |
| `notification_level` | | | | | | | ✓ |
| `notification_message` | | | | | | | ✓ |

The `source` field for `session_start` can be: `startup`, `resume`, `clear`, or `compact`.

The `reason` field for `session_end` can be: `clear`, `logout`, `prompt_input_exit`, or `other`.

The `stop_response` field contains the model's final text response.

The `notification_level` field can be: `error` or `warning`.

## Hook Output

Hooks communicate back via JSON output to stdout:
Expand Down Expand Up @@ -165,6 +186,10 @@ The `hook_specific_output` for `pre_tool_use` supports:
| `permission_decision_reason` | string | Explanation for the decision |
| `updated_input` | object | Modified tool input (replaces original) |

### Plain Text Output

For `session_start`, `post_tool_use`, and `stop` hooks, plain text written to stdout (i.e., output that is not valid JSON) is captured as additional context for the agent.

## Exit Codes

Hook exit codes have special meaning:
Expand All @@ -175,7 +200,37 @@ Hook exit codes have special meaning:
| `2` | Blocking error — stop the operation |
| Other | Error — logged but execution continues |

## Example: Validation Script
## Timeout

Hooks have a default timeout of 60 seconds. You can customize this per hook:

```yaml
hooks:
pre_tool_use:
- matcher: "*"
hooks:
- type: command
command: "./slow-validation.sh"
timeout: 120 # 2 minutes
```

<div class="callout callout-warning">
<div class="callout-title">⚠️ Performance
</div>
<p>Hooks run synchronously and can slow down agent execution. Keep hook scripts fast and efficient. Consider using <code>suppress_output: true</code> for logging hooks to reduce noise.</p>

</div>

<div class="callout callout-info">
<div class="callout-title">ℹ️ Session End and Cancellation
</div>
<p><code>session_end</code> hooks are designed to run even when the session is interrupted (e.g., Ctrl+C). They are still subject to their configured timeout.</p>

</div>

## Examples

### Validation Script

A simple pre-tool-use hook that blocks dangerous shell commands:

Expand All @@ -201,7 +256,7 @@ echo '{"decision": "allow"}'
exit 0
```

## Example: Audit Logging
### Audit Logging

A post-tool-use hook that logs all tool calls:

Expand All @@ -222,24 +277,74 @@ echo '{"continue": true}'
exit 0
```

## Timeout
### Session Lifecycle

Hooks have a default timeout of 60 seconds. You can customize this per hook:
Session start and end hooks for environment setup and cleanup:

```yaml
hooks:
pre_tool_use:
- matcher: "*"
hooks:
- type: command
command: "./slow-validation.sh"
timeout: 120 # 2 minutes
session_start:
- type: command
timeout: 10
command: |
INPUT=$(cat)
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // "unknown"')
echo "Session $SESSION_ID started at $(date)" >> /tmp/agent-session.log
echo '{"hook_specific_output":{"additional_context":"Session initialized."}}'

session_end:
- type: command
timeout: 10
command: |
INPUT=$(cat)
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // "unknown"')
REASON=$(echo "$INPUT" | jq -r '.reason // "unknown"')
echo "Session $SESSION_ID ended ($REASON) at $(date)" >> /tmp/agent-session.log
```

<div class="callout callout-warning">
<div class="callout-title">⚠️ Performance
</div>
<p>Hooks run synchronously and can slow down agent execution. Keep hook scripts fast and efficient. Consider using <code>suppress_output: true</code> for logging hooks to reduce noise.</p>
### Response Logging with Stop Hook

Log every model response for analytics or compliance:

```yaml
hooks:
stop:
- type: command
timeout: 10
command: |
INPUT=$(cat)
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // "unknown"')
RESPONSE_LENGTH=$(echo "$INPUT" | jq -r '.stop_response // ""' | wc -c | tr -d ' ')
echo "[$(date)] Session $SESSION_ID - Response: $RESPONSE_LENGTH chars" >> /tmp/agent-responses.log
```

The `stop` hook is useful for:

- **Response quality checks** — validate that responses meet criteria before returning
- **Analytics** — track response lengths, patterns, or content
- **Compliance logging** — record all agent outputs for audit

### Error Notifications

Send alerts when the agent encounters errors:

```yaml
hooks:
notification:
- type: command
timeout: 10
command: |
INPUT=$(cat)
LEVEL=$(echo "$INPUT" | jq -r '.notification_level // "unknown"')
MESSAGE=$(echo "$INPUT" | jq -r '.notification_message // "no message"')
echo "[$(date)] [$LEVEL] $MESSAGE" >> /tmp/agent-notifications.log
```

The `notification` hook fires when:

- The model returns an error (all models failed)
- A degenerate tool call loop is detected
- The maximum iteration limit is reached

</div>

Expand Down
40 changes: 40 additions & 0 deletions examples/hooks_notification.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/usr/bin/env docker agent run
#
# Notification Hook Example
#
# This example demonstrates the notification hook, which fires whenever
# the agent sends a notification to the user — such as errors, warnings,
# or when the agent pauses for user input (max iterations reached).
#
# The hook receives JSON on stdin with:
# - notification_level: "error" or "warning"
# - notification_message: the notification content
#
# Use cases:
# - Send Slack/Teams notifications when errors occur
# - Log all agent notifications for audit trails
# - Send desktop notifications (e.g., via osascript on macOS)
#
# Try it:
# - Run the agent and trigger an error condition to see the notification
# - Check /tmp/agent-notifications.log for logged notifications
#

agents:
root:
model: openai/gpt-4o
description: An agent with notification hooks
instruction: |
You are a helpful assistant.
toolsets:
- type: shell

hooks:
notification:
- type: command
timeout: 10
command: |
INPUT=$(cat)
LEVEL=$(echo "$INPUT" | jq -r '.notification_level // "unknown"')
MESSAGE=$(echo "$INPUT" | jq -r '.notification_message // "no message"')
echo "[$(date)] [$LEVEL] $MESSAGE" >> /tmp/agent-notifications.log
42 changes: 42 additions & 0 deletions examples/hooks_session_lifecycle.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#!/usr/bin/env docker agent run
#
# Session Lifecycle Hooks Example
#
# This example demonstrates session_start and session_end hooks.
# These hooks run when the agent session begins and ends, allowing
# you to set up the environment, load context, or perform cleanup.
#
# Try these scenarios:
# - Start the agent and see the session start message
# - Ask a question, then exit to see the session end message
# - Check /tmp/agent-session.log for the session log
#

agents:
root:
model: openai/gpt-4o
description: An agent with session lifecycle hooks
instruction: |
You are a helpful assistant. When the user asks what happened at startup,
tell them about the session hooks that ran.
toolsets:
- type: shell

hooks:
session_start:
- type: command
timeout: 10
command: |
INPUT=$(cat)
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // "unknown"')
echo "🚀 Session $SESSION_ID started at $(date)" >> /tmp/agent-session.log
echo '{"hook_specific_output":{"additional_context":"Session initialized. Log file: /tmp/agent-session.log"}}'

session_end:
- type: command
timeout: 10
command: |
INPUT=$(cat)
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // "unknown"')
REASON=$(echo "$INPUT" | jq -r '.reason // "unknown"')
echo "👋 Session $SESSION_ID ended (reason: $REASON) at $(date)" >> /tmp/agent-session.log
36 changes: 36 additions & 0 deletions examples/hooks_stop.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#!/usr/bin/env docker agent run
#
# Stop Hook Example
#
# This example demonstrates the stop hook, which fires whenever the model
# finishes its response and is about to return control to the user.
#
# The hook receives the model's final response content via stdin as JSON
# (in the "stop_response" field), enabling use cases like:
# - Response quality validation
# - Logging and analytics
# - Sending notifications when the agent replies
#
# Try these scenarios:
# - Ask the agent a question and see the stop hook log the response length
# - Check /tmp/agent-responses.log for a log of all responses
#

agents:
root:
model: openai/gpt-4o
description: An agent with a stop hook that logs responses
instruction: |
You are a helpful assistant. Answer questions concisely.
toolsets:
- type: shell

hooks:
stop:
- type: command
timeout: 10
command: |
INPUT=$(cat)
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // "unknown"')
RESPONSE_LENGTH=$(echo "$INPUT" | jq -r '.stop_response // ""' | wc -c | tr -d ' ')
echo "[$(date)] Session $SESSION_ID - Response length: $RESPONSE_LENGTH chars" >> /tmp/agent-responses.log
Loading
Loading