feat(hooks): add turn_end observer hook
Harvested the narrow Rust/docs slice of PR #2578 by @AresNing for #1364. The event uses the maintained structured observer path: JSON stdin, stdout ignored, warn-only failures, and no ability to block or mutate the turn. The hook fires after post-turn app state, usage totals, cost, notification, receipt, and queue-recovery state are updated, before queued follow-up dispatch. Docs, RFC notes, /hooks discovery, and v0.9 tracking now describe the observer-only contract. Co-authored-by: AresNing <49557311+AresNing@users.noreply.github.com>
This commit is contained in:
@@ -592,6 +592,61 @@ the message. Existing environment variables remain available.
|
||||
`shell_env` hooks keep their existing `KEY=VALUE` stdout contract;
|
||||
the JSON stdout contract applies only to `message_submit`.
|
||||
|
||||
### Turn-end observer hooks
|
||||
|
||||
`turn_end` hooks observe the end of each model turn after post-turn
|
||||
state, usage totals, cost accounting, notifications, receipts, and
|
||||
queue recovery have been updated. They receive JSON on stdin and are
|
||||
observer-only: stdout is ignored, failures are logged as warnings, and
|
||||
the hook cannot block user input, mutate the transcript, or change the
|
||||
next queued follow-up.
|
||||
|
||||
```toml
|
||||
[[hooks.hooks]]
|
||||
event = "turn_end"
|
||||
command = "~/.codewhale/hooks/turn-audit.sh"
|
||||
timeout_secs = 2
|
||||
continue_on_error = true
|
||||
```
|
||||
|
||||
The payload includes common hook metadata plus post-turn accounting:
|
||||
|
||||
```json
|
||||
{
|
||||
"event": "turn_end",
|
||||
"session_id": "sess_12345678",
|
||||
"workspace": "/path/to/workspace",
|
||||
"mode": "agent",
|
||||
"model": "deepseek-chat",
|
||||
"turn_id": "turn_12345678",
|
||||
"status": "completed",
|
||||
"error": null,
|
||||
"duration_ms": 1834,
|
||||
"usage": {
|
||||
"input_tokens": 1200,
|
||||
"output_tokens": 180,
|
||||
"prompt_cache_hit_tokens": 900,
|
||||
"prompt_cache_miss_tokens": 300,
|
||||
"reasoning_tokens": null,
|
||||
"reasoning_replay_tokens": null
|
||||
},
|
||||
"totals": {
|
||||
"session_tokens": 1380,
|
||||
"conversation_tokens": 1380,
|
||||
"input_tokens": 1200,
|
||||
"output_tokens": 180
|
||||
},
|
||||
"tool_count": 2,
|
||||
"queued_message_count": 1,
|
||||
"stop_hook_active": false
|
||||
}
|
||||
```
|
||||
|
||||
For `interrupted` or `failed` turns, `status` reflects that terminal
|
||||
state and `error` carries the engine error string when one is available.
|
||||
`stop_hook_active` is reserved for future re-entry protection and is
|
||||
currently always `false`.
|
||||
|
||||
### Sub-agent lifecycle hooks
|
||||
|
||||
`subagent_spawn` and `subagent_complete` hooks observe sub-agent lifecycle
|
||||
|
||||
@@ -124,7 +124,7 @@ v0.9 branch so the remaining Windows/manual checks are explicit.
|
||||
| #2529 workspace shell opt-in | Draft/conflicting | Review with permissions/sandbox stabilization. |
|
||||
| #2530 mention depth cap hint | Draft/mergeable | Already present locally as `a97675824` and `29f57665e`; close/comment after branch is public. |
|
||||
| #2576 PrefixCacheChange events | Mergeable | Already present locally through `29acb87a9d`; close/comment after branch is public or merged. |
|
||||
| #2578 turn_end observer hook | Conflicting | Defer to hook lifecycle lane. |
|
||||
| #2578 turn_end observer hook | Conflicting / locally harvested | Narrow Rust/docs slice landed in the hook lifecycle lane: `turn_end` now uses the existing structured observer path, fires after post-turn state updates and before queued follow-up dispatch, and includes status, usage, totals, duration, tool count, and queued-message count. Close/comment after branch is public, crediting @AresNing and #1364 reporter @esinecan. |
|
||||
| #2579 AppendLog session messages | Conflicting | Defer; large architectural change. |
|
||||
| #2581 provider fallback chain design doc | Mergeable / empty diff | Manually harvested into `docs/rfcs/2574-provider-fallback-chain.md`; close original PR after branch is public, keep #2574 open for implementation. |
|
||||
| #2623 plan prompt modal scroll support | Mergeable | Already harvested into the 22-commit stack. Comment/close original after integration branch is public. |
|
||||
|
||||
@@ -64,6 +64,13 @@ Non-goals:
|
||||
- no blocking of user input
|
||||
- no transcript mutation from `turn_end`
|
||||
|
||||
Implementation note for the v0.9 branch: the narrow #2578 harvest uses the
|
||||
shared structured observer path introduced for sub-agent lifecycle hooks. It
|
||||
fires before queued follow-up dispatch, after queue-recovery state is known, so
|
||||
the payload can report the queued-message count without letting a hook change
|
||||
what gets sent next. Stdout is ignored for `turn_end`; only `message_submit`
|
||||
has a stdout mutation contract.
|
||||
|
||||
### PR 3: Subagent lifecycle observer hooks
|
||||
|
||||
Expose subagent start and completion as observer-only hook events.
|
||||
@@ -251,7 +258,9 @@ transcript content in the first version.
|
||||
- Existing observer-only hooks keep working.
|
||||
- Existing env vars remain available.
|
||||
- `shell_env` keeps its existing stdout `KEY=VALUE` contract.
|
||||
- Structured stdout is interpreted only by `message_submit` in PR 1.
|
||||
- Structured stdout is interpreted only by `message_submit` in PR 1. Structured
|
||||
observer hooks such as `turn_end`, `subagent_spawn`, and `subagent_complete`
|
||||
receive JSON on stdin, but their stdout is ignored by the caller.
|
||||
|
||||
## 6. Review checkpoints
|
||||
|
||||
|
||||
Reference in New Issue
Block a user