Skip to content

feat(hooks): plugins learn when a live turn is interrupted (agent_loop_stopped, salvage #27208) - #99930

Merged
teknium1 merged 2 commits into
mainfrom
chatgpt-work-inspired/agent-loop-stopped-hook
Sep 13, 2026
Merged

teknium1 merged 2 commits into
mainfrom
chatgpt-work-inspired/agent-loop-stopped-hook

Conversation

@teknium1

@teknium1 teknium1 commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

Summary

Plugins now get an agent_loop_stopped signal whenever a live agent turn is interrupted — gateway /stop and the /new fast-path, plus the TUI/desktop session.interrupt path — 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 Interrupt hooks — "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: register agent_loop_stopped in VALID_HOOKS (observer-only; kwargs: session_key, platform, reason, invalidation_reason).
  • gateway/run.py: dispatch the hook in _interrupt_and_clear_session right after running_agent.interrupt(), gated on a real running agent (pending-sentinel /stop has no in-flight work). (@francip)
  • tui_gateway/server.py: sibling site — _interrupt_session_turn dispatches the same hook (platform="tui") when a genuinely running turn is stopped from the TUI/desktop. (widening commit)
  • Tests: 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).
  • Docs: website/docs/user-guide/features/hooks.md hook table + section covering both surfaces; plugins.md observer 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

Check Result
tests/gateway/test_agent_loop_stopped_hook.py 5 passed
tests/tui_gateway/test_interrupt_agent_loop_stopped_hook.py 3 passed
tests/hermes_cli/test_plugins.py + tests/gateway/test_session_boundary_hooks.py 76 passed
E2E: real PluginManager + real invoke_hook through _interrupt_session_turn callback received {session_key, platform:"tui", reason:"user_stop", invalidation_reason:"session_interrupt"}
scripts/audit_pr_attribution.py --fix all contributor emails mapped

Live repro: on origin/main, _interrupt_session_turn and _interrupt_and_clear_session interrupt the agent with zero plugin observer (E2E harness callback never fires); with this branch the same harness receives the payload above.

Infographic

agent_loop_stopped hook infographic

@github-actions

github-actions Bot commented Sep 1, 2026 •

Copy link
Copy Markdown

૮ >ﻌ< ა ci review

ran on 22ba779 — feat(tui_gateway): fire agent_loop_stopped on session.interr

⚠️ Warnings

OSV vulnerability scan · View job

76 known vulnerabilities found in pinned dependencies.

How to fix:

Review the findings in the Security tab. Update the affected dependencies if a patched version is available.


CI timings · View report · View job

Wall time 30m31s vs 4m55s (+520.7%). 4 job(s) slower, 8 faster, 3 unchanged.

  • Python tests / Run tests: +195.0s
  • OS-specific tests / macOS-only tests: -17.0s
  • Docs Site / docs-site-checks: -16.0s
  • Python lints / Windows footguns (blocking): +12.0s
  • OSV scan / Scan lockfiles / osv-scan: -5.0s

@alt-glitch alt-glitch added type/feature New feature or request P3 Low — cosmetic, nice to have comp/gateway Gateway runner, session dispatch, delivery comp/cli CLI entry point, hermes_cli/, setup wizard comp/plugins Plugin system and bundled plugins comp/tui Terminal UI (ui-tui/ + tui_gateway/) sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state labels Sep 1, 2026
@alt-glitch

Copy link
Copy Markdown

This was generated by AI during triage.

Supersedes #27208 as the broader core-team salvage: it preserves the gateway hook and adds TUI/Desktop interrupt coverage.

francip and others added 2 commits September 12, 2026 20:14
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
teknium1 force-pushed the chatgpt-work-inspired/agent-loop-stopped-hook branch from 53e6d7c to 22ba779 Compare September 13, 2026 03:17
@teknium1
teknium1 merged commit d3202bb into main Sep 13, 2026
64 of 66 checks passed
@teknium1
teknium1 deleted the chatgpt-work-inspired/agent-loop-stopped-hook branch September 13, 2026 05:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/cli CLI entry point, hermes_cli/, setup wizard comp/gateway Gateway runner, session dispatch, delivery comp/plugins Plugin system and bundled plugins comp/tui Terminal UI (ui-tui/ + tui_gateway/) P3 Low — cosmetic, nice to have sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants