hermes - 💡(How to fix) Fix [Bug]: Sending a message while delegate_task is running kills the subagent — interrupt propagates unconditionally to children

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

Root Cause

The default busy_input_mode is "interrupt". When a message arrives for a busy session:

  1. Gateway calls running_agent.interrupt(text) on the parent agent (gateway/run.py ~L2930-2932)
  2. AIAgent.interrupt() sets _interrupt_requested = True on the parent AND recursively propagates to all _active_children (run_agent.py ~L1654-1661)
  3. The child subagent receives the interrupt → its conversation loop breaks → its tool calls abort
  4. The child future resolves → _run_single_child returns status: "interrupted"
  5. Parent's conversation loop sees _interrupt_requested = True at the next iteration boundary → breaks out
  6. Result: subagent work is killed, session ends, new message starts a fresh turn

The interrupt is unconditional — it does not distinguish between "I want to talk to the parent" vs "I want to cancel everything."

Fix Action

Fix / Workaround

  • gateway/run.py — busy session handler, interrupt dispatch (~L2886-2935)
  • run_agent.pyinterrupt() method (~L1597-1663), _active_children propagation
  • tools/delegate_tool.py_run_single_child (~L1321), no steer awareness
  • agent/conversation_loop.py — interrupt check at iteration boundary (~L602-608)

Code Example

User sends message
  → gateway._handle_busy_session()
    → running_agent.interrupt(text)          # parent
      → parent._interrupt_requested = True
for child in _active_children:       # run_agent.py L1655-1661
          child.interrupt(text)              # KILLS THE SUBAGENT
        → child._interrupt_requested = True
        → child tool calls abort
        → child conversation loop breaks
      → child future resolves
    → parent conversation loop breaks
  → delegate_task returns status="interrupted"
  → session ends, new message processed as fresh turn
RAW_BUFFERClick to expand / collapse

Bug Description

When a user sends a message while delegate_task is running, the subagent is killed. The user expects to be able to send follow-up messages without destroying in-flight delegation work.

Root Cause

The default busy_input_mode is "interrupt". When a message arrives for a busy session:

  1. Gateway calls running_agent.interrupt(text) on the parent agent (gateway/run.py ~L2930-2932)
  2. AIAgent.interrupt() sets _interrupt_requested = True on the parent AND recursively propagates to all _active_children (run_agent.py ~L1654-1661)
  3. The child subagent receives the interrupt → its conversation loop breaks → its tool calls abort
  4. The child future resolves → _run_single_child returns status: "interrupted"
  5. Parent's conversation loop sees _interrupt_requested = True at the next iteration boundary → breaks out
  6. Result: subagent work is killed, session ends, new message starts a fresh turn

The interrupt is unconditional — it does not distinguish between "I want to talk to the parent" vs "I want to cancel everything."

Why existing modes don't solve this

ModeBehavior during delegate_taskProblem
interrupt (default)Kills parent + all childrenDestroys subagent work
steerCalls parent.steer(text) — but parent is blocked on _child_future.result()Steer is never consumed; parent can't drain steer messages while synchronously blocked inside a tool call
queueStores message for next turn, no interruptSubagent survives, but user has zero communication ability until delegation finishes

The fundamental problem: the parent agent is synchronously blocked during delegate_task (delegate_tool.py ~L1514: _child_future.result(timeout=child_timeout)). Any mechanism targeting the parent either kills the child, is never consumed, or silently queues.

Interrupt propagation chain

User sends message
  → gateway._handle_busy_session()
    → running_agent.interrupt(text)          # parent
      → parent._interrupt_requested = True
      → for child in _active_children:       # run_agent.py L1655-1661
          child.interrupt(text)              # KILLS THE SUBAGENT
        → child._interrupt_requested = True
        → child tool calls abort
        → child conversation loop breaks
      → child future resolves
    → parent conversation loop breaks
  → delegate_task returns status="interrupted"
  → session ends, new message processed as fresh turn

Additional gap: delegate_tool.py has zero steer awareness

delegate_tool.py contains no references to the steer mechanism at all. Even if steer mode were configured, the delegation code has no path to forward steered messages to the child or handle them while blocked.

Steps to Reproduce

  1. Configure gateway with default settings (or explicitly display.busy_input_mode: interrupt)
  2. Send a message that triggers delegate_task (e.g., a task that spawns subagents)
  3. While the subagent is actively working (making API calls, running tools), send any follow-up message
  4. Observe: subagent is immediately interrupted, its work is lost, the session restarts with the new message

Expected Behavior

Sending a message while a subagent is running should NOT kill the subagent. The subagent should keep working. The user's message should either:

  • Be queued and delivered after delegation completes (minimal viable fix)
  • Be injectable into the parent's context for the post-delegation response
  • Optionally be forwardable to the running subagent (steer-through)

Actual Behavior

The subagent is killed immediately. All in-flight work (API calls, tool executions, partial results) is lost. The user's follow-up message starts a completely fresh turn with no subagent context.

Requirement

The system needs a way for incoming messages during active delegation to not propagate the interrupt to child agents. Specifically:

  1. interrupt() should not blindly cascade to children during delegation — or at minimum, the delegate_task tool should be interrupt-aware and protect its children from parent interrupts that are caused by new user messages (vs explicit /stop cancellation)
  2. A new busy-input mode or delegation-aware behavior that queues/steers messages without killing subagents — the parent is blocked in a tool call, so the message handling needs to happen at the gateway level, not the agent level
  3. Steer-through to children (stretch goal) — when the parent is blocked on delegate_task, incoming steer messages could be forwarded to the active child agent instead of being silently dropped

Minimum spec for a fix

  • When busy_input_mode is interrupt and the parent is currently executing a delegate_task tool call:
    • The incoming message should NOT trigger parent.interrupt() (which cascades to children)
    • Instead, the message should be queued (like queue mode) for delivery after delegation completes
    • The user should receive an ack like: "⏳ Subagent working — your message is queued for when it finishes"
  • When the user explicitly sends /stop or /new, THAT should still propagate the full interrupt chain and kill subagents
  • This requires the gateway to distinguish between "user sent a conversational message" vs "user sent a cancel command" when deciding whether to interrupt

Stretch: delegation-aware steer

  • If busy_input_mode is steer and the parent is blocked on delegate_task:
    • Forward the steer to the active child instead of the parent
    • The child sees the user's message injected into its next tool result
    • This would enable real-time course correction of subagents without killing them

Affected Components

  • gateway/run.py — busy session handler, interrupt dispatch (~L2886-2935)
  • run_agent.pyinterrupt() method (~L1597-1663), _active_children propagation
  • tools/delegate_tool.py_run_single_child (~L1321), no steer awareness
  • agent/conversation_loop.py — interrupt check at iteration boundary (~L602-608)

Related Issues

  • #17298 — Allow queued messages to participate in ongoing agent loop execution (same UX pain)
  • #11508 — delegate_task blocks agent during wait — breaks coordinator pattern (core architectural problem)
  • #9556 — delegate_task_stream with mid-flight interrupt for synchronous delegation
  • #13060 — /smalltalk — Side Conversations for Async Subagent Sessions
  • #25819 — delegate_task status misclassification + missing interrupt on batch parent interrupt (Bug B: batch mode doesn't call child.interrupt())
  • #26315 — Gateway restart orphans in-flight delegate subagents

Environment

  • Hermes Agent on main (commit 9e30ef224)
  • Gateway with Telegram platform
  • Default busy_input_mode: interrupt
  • Linux 6.8.0

Proposed Fix Direction

Phase 1 (minimal): Gateway-level guard — when the running agent has active children (_active_children is non-empty), treat incoming messages as queue mode regardless of configured busy_input_mode. Only explicit /stop or /new commands propagate the full interrupt chain.

Phase 2: Delegation-aware steer — delegate_tool.py registers a steer handler on the parent that forwards to the active child. When the parent is blocked on _child_future.result(), incoming steers are relayed to child.steer(text) instead of being dropped.

Phase 3: Async delegation (#11508) — the parent is no longer blocked, so steer/queue/interrupt all work naturally against the parent's conversation loop.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

hermes - 💡(How to fix) Fix [Bug]: Sending a message while delegate_task is running kills the subagent — interrupt propagates unconditionally to children