Commit Graph

1 Commits

Author SHA1 Message Date
kshitij 042a2cf3d7 fix(agent): keep the start-order gate under the batch deadline and abort abandoned workers
Follow-up to the salvaged start-order gate bound. Two gaps remained, both
reachable through the same knob.

1. The gate bound ignored the batch deadline it sits under. With
   HERMES_CONCURRENT_TOOL_TIMEOUT_S below 120s the deadline fired first, so
   the parked tools were still reported as "timed out" without ever running --
   the exact bug the bound exists to fix. The gate now clamps to
   min(120s, batch_timeout / 2), matching the sibling constant's documented
   habit of relating the two timeouts.

2. A gate-parked worker released purely by its own timeout could wake up after
   the batch was abandoned and dispatch its tool anyway: wasted work whose
   result nobody reads, a duplicate post_tool_call for a tool_call_id the turn
   already closed as timeout, and agent._current_tool left pointing at a dead
   tool for the rest of the session (the main thread's reset already ran).
   Abandonment is now a first-class wakeup: both abandon sites set an event and
   notify the condition, and a released worker raises _BatchAbandoned instead
   of dispatching. Parked threads are reclaimed in milliseconds rather than one
   full gate timeout plus a tool runtime.

Also names the tool in the gate-timeout warning. The closure's function_name
binds the last-parsed tool, so logging it directly would have printed the wrong
name; it is threaded through _begin_in_order instead.

Measured, 3-tool batch with the first tool wedged during dispatch:

                          main    PR as-is   with this commit
  dispatched in batch       0        0          tool_b, tool_c
  dispatched after return   0        2 (ghost)  0
  _current_tool leaked      no       "tool_b"   no

Adds tests/run_agent/test_start_order_gate.py (3 tests). Mutation-checked
against the parent commit: the starvation guard passes there (it binds the
salvaged fix), while the deadline-clamp and abandonment guards both fail,
reproducing the ghost dispatch as
"tool(s) dispatched after the batch was abandoned: [tool_a, tool_b]".
2026-08-06 03:53:38 +05:30