feat(hooks): plugins learn when a live turn is interrupted (agent_loop_stopped, salvage #27208) - #99930
Merged
Conversation
4 tasks done
૮ >ﻌ< ა ci reviewran on 22ba779 — feat(tui_gateway): fire agent_loop_stopped on session.interr
|
Supersedes #27208 as the broader core-team salvage: it preserves the gateway hook and adds TUI/Desktop interrupt coverage. |
Reapplied onto current main. The branch had drifted ~3348 commits and a trial merge produced 48 conflict markers, so this is the same change re-landed rather than a rebase of the old history. _interrupt_and_clear_session interrupts the running agent without signalling plugins, so a plugin holding a per-turn external resource — an outbound RPC waiting on a tool result the loop will never consume — has no way to learn the turn is gone. Dispatch agent_loop_stopped immediately after running_agent.interrupt(), gated on a real running agent: the pending-sentinel /stop path has no in-flight work, so firing there would be noise. Per review on #27208, the current helper's behaviour is preserved untouched — multiplex-aware _adapter_for_source() resolution and cached-agent eviction both still run; the hook is additive and its dispatch failures are swallowed so a misbehaving plugin cannot break an interrupt. Tests fail without the change (hook registration and dispatch) and pass with it. The three failures in tests/hermes_cli/test_plugins.py::TestPluginDiscovery are pre-existing on this checkout and reproduce with the change stashed.
Widens the new hook to the sibling interrupt surface: the TUI/desktop session.interrupt path stops a live turn exactly like the gateway's /stop, so plugins holding per-turn external resources get the same signal there (platform='tui'). Gated on a genuinely running turn; dispatch failures are swallowed so a plugin can never break the interrupt. Docs updated to describe both surfaces. Inspired by ChatGPT Work / Codex CLI 0.150.0 'Interrupt' hooks (hooks that run when an active top-level turn is interrupted).
teknium1
force-pushed
the
chatgpt-work-inspired/agent-loop-stopped-hook
branch
from
September 13, 2026 03:17
53e6d7c to
22ba779
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
Plugins now get an
agent_loop_stoppedsignal whenever a live agent turn is interrupted — gateway/stopand the/newfast-path, plus the TUI/desktopsession.interruptpath — so they can drop per-turn external resources (e.g. an outbound RPC blocked on a tool result the loop will never consume) instead of waiting for a timeout.Salvage of #27208 by @francip (authorship preserved via cherry-pick), widened to the TUI/desktop sibling surface.
Inspiration
Weekly ChatGPT Work scout: Codex CLI 0.150.0 (Aug 26, 2026) shipped
Interrupthooks — "run commands or MCP handlers when an active top-level turn is interrupted" (changelog, openai/codex#40511). Hermes had hooks for session finalize/reset but nothing that fired at the moment a mid-flight turn is killed. @francip's open PR #27208 was exactly this feature for the gateway surface, so this salvages it rather than reimplementing.Changes
hermes_cli/plugins.py: registeragent_loop_stoppedinVALID_HOOKS(observer-only; kwargs:session_key,platform,reason,invalidation_reason).gateway/run.py: dispatch the hook in_interrupt_and_clear_sessionright afterrunning_agent.interrupt(), gated on a real running agent (pending-sentinel/stophas no in-flight work). (@francip)tui_gateway/server.py: sibling site —_interrupt_session_turndispatches the same hook (platform="tui") when a genuinely running turn is stopped from the TUI/desktop. (widening commit)tests/gateway/test_agent_loop_stopped_hook.py(5, from feat(gateway): fire agent_loop_stopped plugin hook on interrupt #27208) +tests/tui_gateway/test_interrupt_agent_loop_stopped_hook.py(3, new).website/docs/user-guide/features/hooks.mdhook table + section covering both surfaces;plugins.mdobserver list.Hook dispatch failures are swallowed on both sites — a misbehaving plugin can never break an interrupt. Observer-only, fires nothing when no plugin subscribes; no system-prompt or cache impact.
Validation
tests/gateway/test_agent_loop_stopped_hook.pytests/tui_gateway/test_interrupt_agent_loop_stopped_hook.pytests/hermes_cli/test_plugins.py+tests/gateway/test_session_boundary_hooks.pyPluginManager+ realinvoke_hookthrough_interrupt_session_turn{session_key, platform:"tui", reason:"user_stop", invalidation_reason:"session_interrupt"}scripts/audit_pr_attribution.py --fixLive repro: on
origin/main,_interrupt_session_turnand_interrupt_and_clear_sessioninterrupt the agent with zero plugin observer (E2E harness callback never fires); with this branch the same harness receives the payload above.Infographic