Skip to content

fix(delegation): forward background flag from agent dispatcher - #46849

Closed
aneym wants to merge 5 commits into
NousResearch:mainfrom
aneym:aneym/fix-async-delegation-background-dispatch
Closed

fix(delegation): forward background flag from agent dispatcher#46849
aneym wants to merge 5 commits into
NousResearch:mainfrom
aneym:aneym/fix-async-delegation-background-dispatch

Conversation

@aneym

@aneym aneym commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Forward background through AIAgent._dispatch_delegate_task to delegate_task
  • Add a regression test for the inline dispatcher seam

Why

PR #40946 adds delegate_task(background=true) to the schema and tool implementation, but delegate_task uses an inline run_agent dispatcher instead of the generic registry path. That dispatcher dropped the new field, so model calls with background: true silently ran synchronously.

Test Plan

  • python -m pytest tests/tools/test_async_delegation.py -q -o addopts=
  • python -m compileall run_agent.py tools/delegate_tool.py tools/async_delegation.py tools/process_registry.py gateway/run.py cli.py

teknium1 and others added 5 commits June 15, 2026 07:32
…ound=true)

delegate_task(background=true) dispatches a subagent that runs in the
background and returns a handle immediately, so the user and model keep
working while it runs. The full result — plus the original task source —
re-enters the conversation as a new turn when the subagent finishes,
riding the same completion-queue rail as terminal background processes.

- tools/async_delegation.py: daemon-executor registry, capacity cap,
  rich self-contained completion event pushed onto the shared
  process_registry.completion_queue (type='async_delegation').
- delegate_tool.py: background param + single-task dispatch branch;
  batch async rejected (v1).
- process_registry.py: format_process_notification renders the rich
  task-source block (goal/context/toolsets/model/status/result).
- gateway/run.py: dedicated _async_delegation_watcher drains + injects
  results into the originating session (idle + post-turn), session_key
  routing enrichment, shutdown interrupt of dangling delegations.
- config: delegation.max_async_children (default 3).

Reuses the existing idle-drain wiring rather than mutating a running
agent loop, preserving message-role alternation and prompt-cache
invariants. 13 targeted tests; CLI + gateway paths E2E-verified.
CI 'test (5)' flaked on a cold, 8-worker runner: the first
delegate_task(background=true) call measured 2.27s of one-time setup
(config load + child-agent construction + imports), tripping the
elapsed < 1.0 wall-clock assertion. That assertion was testing setup
overhead, not blocking.

Replace the wall-clock thresholds with the real invariant: dispatch
returns while the child is still gated (active_count == 1, completion
queue empty), which a synchronous impl could not do. Keep only a loose
4s sanity backstop well under the runner's 5s gate.
Follow-up review fixes:
- Detach background child from parent._active_children at dispatch —
  otherwise parent-turn interrupts (Ctrl+C, mid-turn steering), cache
  evicts (release_clients), and session close (/new) kill/close the
  detached subagent mid-run, defeating the point of background mode.
  Lifecycle is owned by the async registry's interrupt_fn.
- Make the capacity check atomic with the record insert (TOCTOU: two
  concurrent dispatches could both pass active_count() and exceed the cap).
- TUI dedup: key async_delegation events by delegation_id — the
  fallthrough keyed them all as ("", type), suppressing every completion
  after the first in the desktop/TUI status feed.
- CLI /stop now interrupts running background delegations and /agents
  lists them (they live outside the process registry and were invisible).
- Drop stray unbalanced ']' line from the re-injection block and the
  unused _ASYNC_DEFAULT import.

Tests: detach-at-dispatch + concurrent-capacity race added (15 total in
test_async_delegation.py); 137 delegate + 140 process-registry/notify/watch
+ 7 TUI dedup tests pass.
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint tool/delegate Subagent delegation labels Jun 15, 2026
@teknium1

Copy link
Copy Markdown
Contributor

Thanks for identifying the dispatcher seam. This is already implemented on current main via the merged PR #46968 (0a8f3e21b8085f24d1dafba6c34911d87b756cfb).

  • run_agent.py:5724 now gives top-level model delegations background=True directly, while retaining synchronous nested orchestrator delegation.
  • tests/tools/test_async_delegation.py:457 covers the live dispatcher for top-level single and batch delegation.
  • The earlier forwarding fix shipped in v2026.6.19.

Automated hermes-sweeper review.

@teknium1 teknium1 closed this Jul 14, 2026
@teknium1 teknium1 added the sweeper:implemented-on-main Sweeper: behavior already present on current main label 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 P2 Medium — degraded but workaround exists sweeper:implemented-on-main Sweeper: behavior already present on current main tool/delegate Subagent delegation type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants