Conversation
teknium1
left a comment
There was a problem hiding this comment.
Thanks for the focused shutdown follow-up. The /v1/runs premise is real on current main: gateway/run.py:5773-5829 includes adapter work in drain accounting, while gateway/run.py:5832-5840 interrupts only self._running_agents.
Problems
gateway/platforms/api_server.py:983-996counts_inflight_agent_runsas active work, but the proposed hook only reaches_active_run_agents. The synchronous session-chat path calls_run_agent()without anagent_refatgateway/platforms/api_server.py:2063-2069;_run_agent()only retains the created agent when that optional reference is supplied (gateway/platforms/api_server.py:4246-4247). Those active API turns would still reach timeout without a cooperative interrupt.
Suggested changes
- Track interruptable agents for every
_run_agent()path included in_inflight_agent_runs, and interrupt that complete adapter-owned set during shutdown. - Add a timeout regression for a non-
/v1/runsAPI turn that verifiesagent.interrupt()is called.
This is an automated hermes-sweeper review.
| try: | ||
| adapter = getattr(self, "adapters", {}).get(Platform.API_SERVER) | ||
| helper = getattr(adapter, "interrupt_active_runs", None) | ||
| return max(0, int(helper(reason))) if callable(helper) else 0 |
There was a problem hiding this comment.
This hook reaches only /v1/runs agents. active_agent_work_count() also includes _inflight_agent_runs, whose agents are not retained unless callers pass _run_agent(agent_ref=...); please extend tracking/interrupt coverage to those counted API routes as well.
|
Closing as superseded by #79881, which keeps this PR's architecture and extends it to every API entry point. @necoweb3 — you found this bug and got the design right: the duck-typed Thanks for the find and the architecture — it's shipping, just via the superset PR. |
…window exit Review follow-up for the salvaged NousResearch#79881/NousResearch#63963 stack: the shutdown interrupt fires exactly once, but work can materialize AFTER that one shot on BOTH sibling paths: - a /v1/runs task admitted before the drain populates _active_run_agents only once _create_agent returns (queued-before-agent window); - a _running_agents entry claimed as _AGENT_PENDING_SENTINEL is promoted to the real agent by track_agent() on its own schedule, after the one-shot walk skipped the sentinel. Either way the settle loop waited on work nothing signaled, and the turn went straight to the post-interrupt tool-subprocess kill — the exact amputation the fix exists to avoid, in a rarer window. If any work is still live when the settle loop exits, re-invoke _interrupt_running_agents (which already skips sentinels and folds in the API-server helper) so late-materializing agents on either path get the cooperative interrupt. Regression test drives the real stop() path with an accelerated loop clock and asserts exactly two interrupt signals.
|
Superseded by #80917 (merging via auto-rebase), which lands this stack with per-commit authorship preserved: your commit |
…window exit Review follow-up for the salvaged #79881/#63963 stack: the shutdown interrupt fires exactly once, but work can materialize AFTER that one shot on BOTH sibling paths: - a /v1/runs task admitted before the drain populates _active_run_agents only once _create_agent returns (queued-before-agent window); - a _running_agents entry claimed as _AGENT_PENDING_SENTINEL is promoted to the real agent by track_agent() on its own schedule, after the one-shot walk skipped the sentinel. Either way the settle loop waited on work nothing signaled, and the turn went straight to the post-interrupt tool-subprocess kill — the exact amputation the fix exists to avoid, in a rarer window. If any work is still live when the settle loop exits, re-invoke _interrupt_running_agents (which already skips sentinels and folds in the API-server helper) so late-materializing agents on either path get the cooperative interrupt. Regression test drives the real stop() path with an accelerated loop clock and asserts exactly two interrupt signals.
…window exit Review follow-up for the salvaged NousResearch#79881/NousResearch#63963 stack: the shutdown interrupt fires exactly once, but work can materialize AFTER that one shot on BOTH sibling paths: - a /v1/runs task admitted before the drain populates _active_run_agents only once _create_agent returns (queued-before-agent window); - a _running_agents entry claimed as _AGENT_PENDING_SENTINEL is promoted to the real agent by track_agent() on its own schedule, after the one-shot walk skipped the sentinel. Either way the settle loop waited on work nothing signaled, and the turn went straight to the post-interrupt tool-subprocess kill — the exact amputation the fix exists to avoid, in a rarer window. If any work is still live when the settle loop exits, re-invoke _interrupt_running_agents (which already skips sentinels and folds in the API-server helper) so late-materializing agents on either path get the cooperative interrupt. Regression test drives the real stop() path with an accelerated loop clock and asserts exactly two interrupt signals.
…window exit Review follow-up for the salvaged NousResearch#79881/NousResearch#63963 stack: the shutdown interrupt fires exactly once, but work can materialize AFTER that one shot on BOTH sibling paths: - a /v1/runs task admitted before the drain populates _active_run_agents only once _create_agent returns (queued-before-agent window); - a _running_agents entry claimed as _AGENT_PENDING_SENTINEL is promoted to the real agent by track_agent() on its own schedule, after the one-shot walk skipped the sentinel. Either way the settle loop waited on work nothing signaled, and the turn went straight to the post-interrupt tool-subprocess kill — the exact amputation the fix exists to avoid, in a rarer window. If any work is still live when the settle loop exits, re-invoke _interrupt_running_agents (which already skips sentinels and folds in the API-server helper) so late-materializing agents on either path get the cooperative interrupt. Regression test drives the real stop() path with an accelerated loop clock and asserts exactly two interrupt signals.
…window exit Review follow-up for the salvaged NousResearch#79881/NousResearch#63963 stack: the shutdown interrupt fires exactly once, but work can materialize AFTER that one shot on BOTH sibling paths: - a /v1/runs task admitted before the drain populates _active_run_agents only once _create_agent returns (queued-before-agent window); - a _running_agents entry claimed as _AGENT_PENDING_SENTINEL is promoted to the real agent by track_agent() on its own schedule, after the one-shot walk skipped the sentinel. Either way the settle loop waited on work nothing signaled, and the turn went straight to the post-interrupt tool-subprocess kill — the exact amputation the fix exists to avoid, in a rarer window. If any work is still live when the settle loop exits, re-invoke _interrupt_running_agents (which already skips sentinels and folds in the API-server helper) so late-materializing agents on either path get the cooperative interrupt. Regression test drives the real stop() path with an accelerated loop clock and asserts exactly two interrupt signals.
…window exit Review follow-up for the salvaged NousResearch#79881/NousResearch#63963 stack: the shutdown interrupt fires exactly once, but work can materialize AFTER that one shot on BOTH sibling paths: - a /v1/runs task admitted before the drain populates _active_run_agents only once _create_agent returns (queued-before-agent window); - a _running_agents entry claimed as _AGENT_PENDING_SENTINEL is promoted to the real agent by track_agent() on its own schedule, after the one-shot walk skipped the sentinel. Either way the settle loop waited on work nothing signaled, and the turn went straight to the post-interrupt tool-subprocess kill — the exact amputation the fix exists to avoid, in a rarer window. If any work is still live when the settle loop exits, re-invoke _interrupt_running_agents (which already skips sentinels and folds in the API-server helper) so late-materializing agents on either path get the cooperative interrupt. Regression test drives the real stop() path with an accelerated loop clock and asserts exactly two interrupt signals.
Summary
This closes the shutdown-timeout follow-up gap for API-server runs.
The gateway drain now counts adapter-owned API-server work via
active_agent_work_count(), but when the drain times out it still only interrupts agents stored inGatewayRunner._running_agents./v1/runsagents live insideAPIServerAdapter._active_run_agents, so they can remain running after the gateway has decided shutdown/restart must interrupt remaining work.Why
GatewayRunner._drain_active_agents()can now time out because of active API-server work, and the timeout log explicitly reportsapi_server run(s). However, the interrupt path did not reach those adapter-owned agents.That means a long-running
/v1/runstask can survive the shutdown interrupt phase until process teardown, instead of receiving the same cooperative interrupt used byPOST /v1/runs/{run_id}/stop.Changes
APIServerAdapter.interrupt_active_runs(reason)to interrupt active/v1/runsagents owned by the adapter.GatewayRunner._interrupt_running_agents()also interrupt active API-server runs._running_agents.Tests