Conversation
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
7202a84 to
e26d927
Compare
|
Thanks for the focused hook and the concrete external-RPC cancellation use case. The premise remains valid on current main: Problems
Suggested changes
This is an automated hermes-sweeper review. |
e26d927 to
681e061
Compare
681e061 to
a102830
Compare
a102830 to
caed7c5
Compare
|
Updated onto current main ( The branch had drifted ~3348 commits and a trial merge produced 48 conflict markers, so I re-landed the change rather than rebasing the old history. Addressing the review points directly:
Verification: the 5 tests in Motivation from a real consumer, for what it's worth: the |
caed7c5 to
8dc80a1
Compare
8dc80a1 to
1aa2ea2
Compare
1aa2ea2 to
f6c3716
Compare
f6c3716 to
d78cef6
Compare
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 NousResearch#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.
d78cef6 to
75928f9
Compare
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.
|
Salvaged into #99930 with your commit and authorship preserved (cherry-pick of 75928f9), plus a widening commit that fires the same hook from the TUI/desktop |
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 NousResearch#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.
75928f9 to
8484437
Compare
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 NousResearch#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. (cherry picked from commit 8484437)
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 NousResearch#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.
8484437 to
0f79b00
Compare
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.
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.
|
Landed via #99930 (merge commit If anything from this PR did not survive the salvage, reopen with the specific hunk and I'll follow up. |
* feat(gateway): fire agent_loop_stopped plugin hook on interrupt 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 NousResearch#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. * feat(tui_gateway): fire agent_loop_stopped on session.interrupt too 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). * docs: document OpenRouter @preset references in /model (follow-up to NousResearch#99633) * feat: resume x402 MCP tool calls Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * ci: publish AceDataCloud Hermes image * chore: map AceDataCloud contributor * ci: use hosted runners in fork * feat: allow managed x402 payment enablement * ci: preserve full tests on hosted runners * ci: bound test workers on hosted runner * fix: release quickstart lock before completion --------- Co-authored-by: Franci Penov <francip@gmail.com> Co-authored-by: Teknium <127238744+teknium1@users.noreply.github.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
What does this PR do?
When
/stopor the running-agent fast path of/newinterrupts a gateway turn, plugins need a prompt signal to release external work waiting for a tool result. This adds the observer hookagent_loop_stoppedimmediately afterrequest_hard_interrupt()ingateway/run_agent_cache.py::_interrupt_and_clear_session.Fixes #27206. The motivating consumer is hermes-livekit, which uses interruption signals to cancel remote tool requests and notify its client.
Behavior
session_key,platform,reasonandinvalidation_reason; callback return values are ignored.on_session_finalizeevent.The rebase onto upstream
3a7bf7455fmoves dispatch into the current gateway sibling and preserves the existing hard-interrupt, process-reaping and session-state paths.Validation
Two behavior tests cover seven cases through real temporary-plugin discovery, real hook dispatch and real session state: running/pending/missing turns for stop/new, plus a raising plugin. The three positive-observation cases fail against upstream source at
520e63661c; all seven pass with this change. The subsequent upstream commit changes desktop files only.87 passed, 1 existing environment-dependent failure, macOS / Python 3.11. The failure is
test_failed_discovery_is_not_cached: installed third-party plugin entry points make its isolated count assertion fail. The identical failure was reproduced with unchanged upstream source. The hook and session-cleanup files pass completely.Changed-file Ruff, compatibility-pointer checks, contributor attribution and
git diff --checkpass. The full repository suite was not run for this rebase.Related proposal
#99930 contains this gateway patch with its original authorship and adds TUI/desktop coverage. It is a broader successor that remains open; this PR retains the narrower gateway scope for review.
Checklist