feat(hooks): Stop event — fires once per response when yielding to user#223
Merged
emal-avala merged 1 commit intomainfrom Apr 23, 2026
Merged
feat(hooks): Stop event — fires once per response when yielding to user#223emal-avala merged 1 commit intomainfrom
emal-avala merged 1 commit intomainfrom
Conversation
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
4 tasks
53362a2 to
7259a80
Compare
6 tasks
Adds a `Stop` hook event that fires exactly once per user-visible
response, right after the final `PostTurn` and before control returns
to the REPL / one-shot exit. `PostTurn` fires once per LLM round-trip
— a multi-step turn with N tool calls fires `PostTurn` N+1 times,
which is too noisy for auto-commit / transcript-shipping /
desktop-notification workflows that want "agent finished; now react."
Context payload:
{
"session_id": "...",
"turn_count": N,
"last_assistant_message": "..." // or null if pure tool-use
}
`last_assistant_message` is extracted via a new `last_assistant_text`
helper that walks the message history backward, skipping pure
tool-use assistant turns so the hook sees the actual user-visible
reply.
Wiring:
- HookEvent::Stop added to schema enum with snake_case serde
- fire_stop_hooks(last_assistant_message) async method on QueryEngine
- Called from run_turn_with_sink's no-more-tools completion branch,
after PostTurn fires
- HOOK_EVENT_CATALOG, format_hook_event, parse_hook_event updated
- hooks/mod.rs module docs updated
Tests: 6 new
- Schema serde round-trip (stop <-> "stop")
- run_hooks dispatches for Stop (async tokio test)
- parse_hook_event accepts "stop" / "Stop" / "STOP"
- last_assistant_text: empty history returns None
- last_assistant_text: returns most recent assistant text
- last_assistant_text: joins multiple text blocks
- last_assistant_text: skips pure-tool-use assistant messages
7259a80 to
c6aadbb
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a
Stophook event that fires exactly once per user-visible response, right after the finalPostTurnand before control returns to the REPL / one-shot exit.PostTurnfires once per LLM round-trip — a multi-step turn with N tool calls firesPostTurnN+1 times, which is too noisy for auto-commit / transcript-shipping / desktop-notification workflows that want "agent finished; now react."Context payload
{ "session_id": "...", "turn_count": 3, "last_assistant_message": "..." }last_assistant_messageis extracted via a newlast_assistant_texthelper that walks the message history backward, skipping pure tool-use assistant turns so the hook sees the actual user-visible reply — not the internal "I'm about to call Bash" step.Wiring
HookEvent::Stopadded to the schema enum withsnake_caseserdefire_stop_hooks(last_assistant_message: Option<&str>)async method onQueryEnginerun_turn_with_sink's no-more-tools completion branch, right afterPostTurnHOOK_EVENT_CATALOG,format_hook_event,parse_hook_eventupdatedhooks/mod.rsmodule docs updatedTest plan
cargo clippy --workspace --tests --no-deps -- -D warnings— cleancargo test -p agent-code-lib --lib hooks::tests— 6 passcargo test -p agent-code-lib --lib query::tests::last_assistant_text— 4 passcargo test -p agent-code-lib --lib config::schema::tests::hook_event_serde_roundtrip— 10 passcargo test -p agent-code --bin agent commands::tests::parse_hook_event_accepts_stop— pass6 new tests:
stop↔"stop")run_hooksdispatches forStopparse_hook_eventaccepts"stop" / "Stop" / "STOP"last_assistant_text: empty history returns Nonelast_assistant_text: returns most recent assistant text / joins multiple blockslast_assistant_text: skips pure-tool-use assistant messagesFollow-up
SubagentStopneeds the parent'sHookRegistryplumbed intoToolContext(our subagents are subprocesses, so the parent fires on child exit). Separate PR once this lands.