Skip to content

Commit a5e4548

Browse files
authored
Merge pull request #2136 from dgageot/board/what-kinfd-of-hook-claude-code-has-and-w-466f6107
Add stop and notification hooks, wire up session lifecycle hooks
2 parents 4dd843d + f298d98 commit a5e4548

13 files changed

Lines changed: 602 additions & 41 deletions

File tree

agent-schema.json

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,20 @@
411411
"items": {
412412
"$ref": "#/definitions/HookDefinition"
413413
}
414+
},
415+
"stop": {
416+
"type": "array",
417+
"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.",
418+
"items": {
419+
"$ref": "#/definitions/HookDefinition"
420+
}
421+
},
422+
"notification": {
423+
"type": "array",
424+
"description": "Hooks that run when the agent sends a notification (error, warning) to the user. Can send external notifications or log events.",
425+
"items": {
426+
"$ref": "#/definitions/HookDefinition"
427+
}
414428
}
415429
},
416430
"additionalProperties": false
@@ -1559,4 +1573,4 @@
15591573
"additionalProperties": false
15601574
}
15611575
}
1562-
}
1576+
}

docs/configuration/agents/index.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ agents:
4242
session_start: [list]
4343
session_end: [list]
4444
on_user_input: [list]
45+
stop: [list]
46+
notification: [list]
4547
structured_output: # Optional: constrain output format
4648
name: string
4749
schema: object

docs/configuration/hooks/index.md

Lines changed: 138 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -21,20 +21,24 @@ Hooks allow you to execute shell commands or scripts at key points in an agent's
2121
- Block dangerous operations based on custom rules
2222
- Set up the environment when a session starts
2323
- Clean up resources when a session ends
24+
- Log or validate model responses before returning to the user
25+
- Send external notifications on agent errors or warnings
2426

2527
</div>
2628

2729
## Hook Types
2830

29-
There are five hook event types:
31+
There are seven hook event types:
3032

31-
| Event | When it fires | Can block? |
32-
| --------------- | ---------------------------------------- | ---------- |
33-
| `pre_tool_use` | Before a tool call executes | Yes |
34-
| `post_tool_use` | After a tool completes successfully | No |
35-
| `session_start` | When a session begins or resumes | No |
36-
| `session_end` | When a session terminates | No |
37-
| `on_user_input` | When the agent is waiting for user input | No |
33+
| Event | When it fires | Can block? |
34+
| ---------------- | ------------------------------------------------------ | ---------- |
35+
| `pre_tool_use` | Before a tool call executes | Yes |
36+
| `post_tool_use` | After a tool completes successfully | No |
37+
| `session_start` | When a session begins or resumes | No |
38+
| `session_end` | When a session terminates | No |
39+
| `on_user_input` | When the agent is waiting for user input | No |
40+
| `stop` | When the model finishes responding | No |
41+
| `notification` | When the agent emits a notification (error or warning) | No |
3842

3943
## Configuration
4044

@@ -74,6 +78,16 @@ agents:
7478
on_user_input:
7579
- type: command
7680
command: "./scripts/notify.sh"
81+
82+
# Run when the model finishes responding
83+
stop:
84+
- type: command
85+
command: "./scripts/log-response.sh"
86+
87+
# Run on agent errors and warnings
88+
notification:
89+
- type: command
90+
command: "./scripts/alert.sh"
7791
```
7892
7993
## Matcher Patterns
@@ -107,22 +121,29 @@ Hooks receive JSON input via stdin with context about the event:
107121

108122
### Input Fields by Event Type
109123

110-
| Field | pre_tool_use | post_tool_use | session_start | session_end | on_user_input |
111-
| ----------------- | ------------ | ------------- | ------------- | ----------- | ------------- |
112-
| `session_id` | ✓ | ✓ | ✓ | ✓ | ✓ |
113-
| `cwd` | ✓ | ✓ | ✓ | ✓ | ✓ |
114-
| `hook_event_name` | ✓ | ✓ | ✓ | ✓ | ✓ |
115-
| `tool_name` | ✓ | ✓ | | | |
116-
| `tool_use_id` | ✓ | ✓ | | | |
117-
| `tool_input` | ✓ | ✓ | | | |
118-
| `tool_response` | | ✓ | | | |
119-
| `source` | | | ✓ | | |
120-
| `reason` | | | | ✓ | |
124+
| Field | pre_tool_use | post_tool_use | session_start | session_end | on_user_input | stop | notification |
125+
| ---------------------- | ------------ | ------------- | ------------- | ----------- | ------------- | ---- | ------------ |
126+
| `session_id` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
127+
| `cwd` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
128+
| `hook_event_name` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
129+
| `tool_name` | ✓ | ✓ | | | | | |
130+
| `tool_use_id` | ✓ | ✓ | | | | | |
131+
| `tool_input` | ✓ | ✓ | | | | | |
132+
| `tool_response` | | ✓ | | | | | |
133+
| `source` | | | ✓ | | | | |
134+
| `reason` | | | | ✓ | | | |
135+
| `stop_response` | | | | | | ✓ | |
136+
| `notification_level` | | | | | | | ✓ |
137+
| `notification_message` | | | | | | | ✓ |
121138

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

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

143+
The `stop_response` field contains the model's final text response.
144+
145+
The `notification_level` field can be: `error` or `warning`.
146+
126147
## Hook Output
127148

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

189+
### Plain Text Output
190+
191+
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.
192+
168193
## Exit Codes
169194

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

178-
## Example: Validation Script
203+
## Timeout
204+
205+
Hooks have a default timeout of 60 seconds. You can customize this per hook:
206+
207+
```yaml
208+
hooks:
209+
pre_tool_use:
210+
- matcher: "*"
211+
hooks:
212+
- type: command
213+
command: "./slow-validation.sh"
214+
timeout: 120 # 2 minutes
215+
```
216+
217+
<div class="callout callout-warning">
218+
<div class="callout-title">⚠️ Performance
219+
</div>
220+
<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>
221+
222+
</div>
223+
224+
<div class="callout callout-info">
225+
<div class="callout-title">ℹ️ Session End and Cancellation
226+
</div>
227+
<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>
228+
229+
</div>
230+
231+
## Examples
232+
233+
### Validation Script
179234

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

@@ -201,7 +256,7 @@ echo '{"decision": "allow"}'
201256
exit 0
202257
```
203258

204-
## Example: Audit Logging
259+
### Audit Logging
205260

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

@@ -222,24 +277,74 @@ echo '{"continue": true}'
222277
exit 0
223278
```
224279

225-
## Timeout
280+
### Session Lifecycle
226281

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

229284
```yaml
230285
hooks:
231-
pre_tool_use:
232-
- matcher: "*"
233-
hooks:
234-
- type: command
235-
command: "./slow-validation.sh"
236-
timeout: 120 # 2 minutes
286+
session_start:
287+
- type: command
288+
timeout: 10
289+
command: |
290+
INPUT=$(cat)
291+
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // "unknown"')
292+
echo "Session $SESSION_ID started at $(date)" >> /tmp/agent-session.log
293+
echo '{"hook_specific_output":{"additional_context":"Session initialized."}}'
294+
295+
session_end:
296+
- type: command
297+
timeout: 10
298+
command: |
299+
INPUT=$(cat)
300+
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // "unknown"')
301+
REASON=$(echo "$INPUT" | jq -r '.reason // "unknown"')
302+
echo "Session $SESSION_ID ended ($REASON) at $(date)" >> /tmp/agent-session.log
237303
```
238304

239-
<div class="callout callout-warning">
240-
<div class="callout-title">⚠️ Performance
241-
</div>
242-
<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>
305+
### Response Logging with Stop Hook
306+
307+
Log every model response for analytics or compliance:
308+
309+
```yaml
310+
hooks:
311+
stop:
312+
- type: command
313+
timeout: 10
314+
command: |
315+
INPUT=$(cat)
316+
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // "unknown"')
317+
RESPONSE_LENGTH=$(echo "$INPUT" | jq -r '.stop_response // ""' | wc -c | tr -d ' ')
318+
echo "[$(date)] Session $SESSION_ID - Response: $RESPONSE_LENGTH chars" >> /tmp/agent-responses.log
319+
```
320+
321+
The `stop` hook is useful for:
322+
323+
- **Response quality checks** — validate that responses meet criteria before returning
324+
- **Analytics** — track response lengths, patterns, or content
325+
- **Compliance logging** — record all agent outputs for audit
326+
327+
### Error Notifications
328+
329+
Send alerts when the agent encounters errors:
330+
331+
```yaml
332+
hooks:
333+
notification:
334+
- type: command
335+
timeout: 10
336+
command: |
337+
INPUT=$(cat)
338+
LEVEL=$(echo "$INPUT" | jq -r '.notification_level // "unknown"')
339+
MESSAGE=$(echo "$INPUT" | jq -r '.notification_message // "no message"')
340+
echo "[$(date)] [$LEVEL] $MESSAGE" >> /tmp/agent-notifications.log
341+
```
342+
343+
The `notification` hook fires when:
344+
345+
- The model returns an error (all models failed)
346+
- A degenerate tool call loop is detected
347+
- The maximum iteration limit is reached
243348

244349
</div>
245350

examples/hooks_notification.yaml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#!/usr/bin/env docker agent run
2+
#
3+
# Notification Hook Example
4+
#
5+
# This example demonstrates the notification hook, which fires whenever
6+
# the agent sends a notification to the user — such as errors, warnings,
7+
# or when the agent pauses for user input (max iterations reached).
8+
#
9+
# The hook receives JSON on stdin with:
10+
# - notification_level: "error" or "warning"
11+
# - notification_message: the notification content
12+
#
13+
# Use cases:
14+
# - Send Slack/Teams notifications when errors occur
15+
# - Log all agent notifications for audit trails
16+
# - Send desktop notifications (e.g., via osascript on macOS)
17+
#
18+
# Try it:
19+
# - Run the agent and trigger an error condition to see the notification
20+
# - Check /tmp/agent-notifications.log for logged notifications
21+
#
22+
23+
agents:
24+
root:
25+
model: openai/gpt-4o
26+
description: An agent with notification hooks
27+
instruction: |
28+
You are a helpful assistant.
29+
toolsets:
30+
- type: shell
31+
32+
hooks:
33+
notification:
34+
- type: command
35+
timeout: 10
36+
command: |
37+
INPUT=$(cat)
38+
LEVEL=$(echo "$INPUT" | jq -r '.notification_level // "unknown"')
39+
MESSAGE=$(echo "$INPUT" | jq -r '.notification_message // "no message"')
40+
echo "[$(date)] [$LEVEL] $MESSAGE" >> /tmp/agent-notifications.log
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#!/usr/bin/env docker agent run
2+
#
3+
# Session Lifecycle Hooks Example
4+
#
5+
# This example demonstrates session_start and session_end hooks.
6+
# These hooks run when the agent session begins and ends, allowing
7+
# you to set up the environment, load context, or perform cleanup.
8+
#
9+
# Try these scenarios:
10+
# - Start the agent and see the session start message
11+
# - Ask a question, then exit to see the session end message
12+
# - Check /tmp/agent-session.log for the session log
13+
#
14+
15+
agents:
16+
root:
17+
model: openai/gpt-4o
18+
description: An agent with session lifecycle hooks
19+
instruction: |
20+
You are a helpful assistant. When the user asks what happened at startup,
21+
tell them about the session hooks that ran.
22+
toolsets:
23+
- type: shell
24+
25+
hooks:
26+
session_start:
27+
- type: command
28+
timeout: 10
29+
command: |
30+
INPUT=$(cat)
31+
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // "unknown"')
32+
echo "🚀 Session $SESSION_ID started at $(date)" >> /tmp/agent-session.log
33+
echo '{"hook_specific_output":{"additional_context":"Session initialized. Log file: /tmp/agent-session.log"}}'
34+
35+
session_end:
36+
- type: command
37+
timeout: 10
38+
command: |
39+
INPUT=$(cat)
40+
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // "unknown"')
41+
REASON=$(echo "$INPUT" | jq -r '.reason // "unknown"')
42+
echo "👋 Session $SESSION_ID ended (reason: $REASON) at $(date)" >> /tmp/agent-session.log

examples/hooks_stop.yaml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#!/usr/bin/env docker agent run
2+
#
3+
# Stop Hook Example
4+
#
5+
# This example demonstrates the stop hook, which fires whenever the model
6+
# finishes its response and is about to return control to the user.
7+
#
8+
# The hook receives the model's final response content via stdin as JSON
9+
# (in the "stop_response" field), enabling use cases like:
10+
# - Response quality validation
11+
# - Logging and analytics
12+
# - Sending notifications when the agent replies
13+
#
14+
# Try these scenarios:
15+
# - Ask the agent a question and see the stop hook log the response length
16+
# - Check /tmp/agent-responses.log for a log of all responses
17+
#
18+
19+
agents:
20+
root:
21+
model: openai/gpt-4o
22+
description: An agent with a stop hook that logs responses
23+
instruction: |
24+
You are a helpful assistant. Answer questions concisely.
25+
toolsets:
26+
- type: shell
27+
28+
hooks:
29+
stop:
30+
- type: command
31+
timeout: 10
32+
command: |
33+
INPUT=$(cat)
34+
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // "unknown"')
35+
RESPONSE_LENGTH=$(echo "$INPUT" | jq -r '.stop_response // ""' | wc -c | tr -d ' ')
36+
echo "[$(date)] Session $SESSION_ID - Response length: $RESPONSE_LENGTH chars" >> /tmp/agent-responses.log

0 commit comments

Comments
 (0)