feat(gateway): fire agent_loop_stopped plugin hook on interrupt#27208
Open
francip wants to merge 1 commit into
Open
feat(gateway): fire agent_loop_stopped plugin hook on interrupt#27208francip wants to merge 1 commit into
francip wants to merge 1 commit into
Conversation
francip
added a commit
to kortexa-ai/hermes-livekit
that referenced
this pull request
May 17, 2026
Connected clients can register tools the agent's LLM can invoke. The agent calls them with a targeted agent:tool-call JSON message; the client runs the tool locally and replies with client:tool-result. New inbound types client:tool-register / -unregister / -result on the hermes-control topic; matching outbound agent:tool-registered / -unregistered / agent:tool-call / -cancelled / -timeout events. Flat JSON envelope (no payload wrapper) to keep the protocol terse. Tools register into the hermes registry under toolset 'hermes-livekit-tools'. Operators must add that toolset to their livekit platform_toolsets list in ~/.hermes/config.yaml -- the plugin does not auto-activate it. Cleanup paths: - participant_disconnected: deregister that client's tools, fail their pending calls - full adapter teardown / room rejoin: wipe all tool state - on_session_finalize (existing hook, fires on /new): cancel pending remote tool calls, notify owners with agent:tool-call-cancelled - agent_loop_stopped (proposed hook, fires on /stop): no-op until upstream PR NousResearch/hermes-agent#27208 lands; once merged, /stop mid-call cancellation comes online with no plugin change - per-call timeout (default 30s, override via HERMES_LIVEKIT_TOOL_TIMEOUT_SEC) Full design including v0.4 RPC pivot and v0.5 byte-streams roadmap in docs/remote-tools-design.md. Execution status + cross-session breadcrumbs (notably the pending upstream PR) in PLAN.md. Adds examples/test_client.py: an interactive + oneshot Python client that registers desktop_notify (pops a macOS notification via osascript) and lets you drive the agent over the data channel without the voice path. Single file, no extra deps beyond what the plugin already pins. Verified end-to-end against Avery (the hermes-agent gateway).
01686e2 to
7202a84
Compare
Plugins that hold per-turn external resources (e.g. an outbound RPC call the agent loop is currently awaiting a result from) have no way to learn that the user interrupted the run with /stop. Today they can react to on_session_finalize for /new, but /stop and the /new fast-path inside _dispatch_message bypass that hook entirely — the resources keep waiting until they time out. Add an agent_loop_stopped invoke_hook dispatch right after running_agent.interrupt() in _interrupt_and_clear_session, so every abort path (/stop, the /new fast-path, the pending-sentinel and hung-agent stop paths) gives plugins one consistent signal. Kwargs are session_key, platform, reason, invalidation_reason. Dispatch is wrapped in try/except so a misbehaving plugin can't block the interrupt. Motivating use case: hermes-livekit (https://github.com/kortexa-ai/hermes-livekit) v0.3.0 exposes client-registered remote tools that the agent calls over a WebRTC data channel. When the user runs /stop mid-call, the plugin's proxy is blocked on asyncio.wait_for(future, timeout=30s) and the client stays in "we owe a result" mode for that whole window. This hook lets the plugin fail the future and notify the client immediately. Adds agent_loop_stopped to VALID_HOOKS with comments documenting the kwargs. Adds tests covering hook presence, dispatch on interrupt, and fault tolerance when a plugin handler raises.
7202a84 to
e26d927
Compare
Contributor
|
Thanks for the focused hook addition and the concrete Problems
Suggested changes
This is an automated hermes-sweeper review. |
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.
What does this PR do?
Adds an
agent_loop_stoppedplugin hook fired fromgateway/run.py::_interrupt_and_clear_sessionright afterrunning_agent.interrupt(). Lets plugins drop per-turn external resources (e.g. an outbound RPC call the agent loop was awaiting a result from) when the user interrupts the run with/stopor the fast-path of/new.Today plugins can react to
on_session_finalizefor the/newslow path, but/stopand the/newfast-path inside_dispatch_messagebypass that hook entirely — outbound work just sits until it times out, while the agent has already moved on.Related Issue
Fixes #27206
Type of Change
Changes Made
gateway/run.py— emitagent_loop_stoppedviainvoke_hookright afterrunning_agent.interrupt()in_interrupt_and_clear_session. Wrapped in try/except so a misbehaving handler can't block the interrupt. Same dispatch shape ason_session_finalizeatgateway/run.py:8310.hermes_cli/plugins.py— addagent_loop_stoppedtoVALID_HOOKSwith inline docstring covering kwargs (session_key,platform,reason,invalidation_reason).tests/gateway/test_agent_loop_stopped_hook.py— three pytest cases covering hook presence inVALID_HOOKS, dispatch with expected kwargs on interrupt, and fault tolerance when a registered handler raises.How to Test
All three cases pass. End-to-end smoke against
hermes-livekit(the motivating consumer) is pending v0.3.0 release of that plugin; the plugin's hook subscription is wired but is currently a no-op against an unpatched core.Motivating use case
hermes-livekitv0.3.0 exposes client-registered remote tools the agent can call over a WebRTC data channel:When the user hits
/stop, the agent's interrupt flag flips but the proxy keeps waiting; the client stays in "owe a result" mode for 30s and theagent:tool-call-cancellednotification never goes out. This hook gives the plugin one signal to fail the future and notify the client immediately.Checklist
Code
feat(gateway):)pytest tests/gateway/test_agent_loop_stopped_hook.py -vand all 3 tests passDocumentation & Housekeeping
VALID_HOOKSinhermes_cli/plugins.pyhas an inline docstring describing the new hook's kwargs. No separate hooks documentation file exists; the inline comments next toVALID_HOOKSare the canonical source today.cli-config.yaml.exampleif I added/changed config keys — N/A (no config keys)CONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — N/A (additive plugin hook, no workflow change)