Skip to content

fix: async subagent background=True drops user interrupts to queue (#46864) - #46950

Closed
argus-metis wants to merge 2 commits into
NousResearch:mainfrom
argus-metis:fix/async-background-delegate-46864
Closed

fix: async subagent background=True drops user interrupts to queue (#46864)#46950
argus-metis wants to merge 2 commits into
NousResearch:mainfrom
argus-metis:fix/async-background-delegate-46864

Conversation

@argus-metis

Copy link
Copy Markdown

Summary

Fixes #46864delegate_task(background=True) silently blocked user messages by dropping them to queue mode instead of allowing interrupts.

Root Cause

Three interacting issues:

  1. run_agent.py_dispatch_delegate_task() didn't pass the background parameter through to delegate_task(). The downstream function has full async support (removes child from _active_children, dispatches via async_delegation) but never received the flag.

  2. tools/delegate_tool.py_build_child_agent() unconditionally registered ALL children in parent_agent._active_children for interrupt propagation. For background children, this created a race window where the child appeared briefly before the async path removed it — causing _agent_has_active_subagents() to see a non-empty list and demote user interrupts to queue.

  3. gateway/run.py_agent_has_active_subagents() had no way to distinguish background children (async, parent's tool loop has returned) from sync children (parent blocked mid-tool-loop). Both looked identical: non-empty _active_children.

Changes

run_agent.py (+1 line)

Pass background from function args through _dispatch_delegate_task() to delegate_task().

tools/delegate_tool.py (+41/-8)

  • Moved child registration for interrupt propagation out of _build_child_agent() and into the sync-only call sites (delegate_task() single and batch branches).
  • Background subagents are never registered — lifecycle is managed by the async-delegation registry.
  • Eliminates the race window where background children briefly appeared before the async path detached them.

gateway/run.py (+15 lines)

  • Added _executing_tools check: when the parent's tool loop has returned and _executing_tools is False, any remaining _active_children must be background subagents. The gateway does NOT demote user interrupts to queue.
  • Safe-by-default: getattr(..., True) so attribute-missing stubs preserve the existing interrupt path.

Tests (+17 lines)

  • test_returns_false_when_parent_not_executing_tools: Background children with _executing_tools=False must NOT trigger demotion.
  • test_returns_false_when_executing_tools_attr_missing: Missing attribute defaults to True (backward compat).

Background

Builds on the async background delegation infrastructure from #40946 (feat(delegation): async background subagents via delegate_task(background=true)).

Checklist

  • fix: commit follows conventional commit format
  • Change is minimal — only touches the broken call path
  • No speculative infrastructure or unrelated refactoring
  • Root cause fully documented
  • Tests added for new behaviour
  • Existing tests pass
  • No new dependencies, config keys, or tools

…ousResearch#46864)

Root cause: _dispatch_delegate_task in run_agent.py silently dropped
the 'background' parameter, so delegate_task(background=True) never
received the flag. The downstream delegate_task() has full async
background support (removes child from _active_children, dispatches
via async_delegation) but never knew the caller requested it.

Additionally:
- _build_child_agent unconditionally registered ALL children in
  _active_children, creating a race where background children were
  briefly visible before the caller removed them. Registration is now
  done by the sync caller only.
- The gateway's _agent_has_active_subagents() gains a defense-in-depth
  _executing_tools check so even if a background child is somehow in
  _active_children, it won't demote user interrupts when the parent's
  tool loop has already returned.
@alt-glitch alt-glitch added type/bug Something isn't working comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/gateway Gateway runner, session dispatch, delivery tool/delegate Subagent delegation P2 Medium — degraded but workaround exists labels Jun 16, 2026
@teknium1

Copy link
Copy Markdown
Contributor

Thanks for tracing the original background-delegation failure path. This is an automated hermes-sweeper review; current main already provides the requested non-blocking behavior.

@teknium1 teknium1 closed this Jul 14, 2026
@teknium1 teknium1 added sweeper:implemented-on-main Sweeper: behavior already present on current main sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:implemented-on-main Sweeper: behavior already present on current main sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state tool/delegate Subagent delegation type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Async subagents queue user messages instead of allowing parallel conversation

4 participants