← Back to blog

Run Background Subagents in Hermes Agent v0.17.0

hermesv017subagentsautomationdelegation
Run Background Subagents in Hermes Agent v0.17.0

Hermes Agent v0.17.0 introduced background subagents -- delegate_task(background=true) dispatches a subagent that runs asynchronously while the main agent continues working. The full result re-enters the conversation as a new turn when the subagent finishes.

The problem

Before v0.17.0, delegate_task was synchronous. You handed a task to a subagent and sat blocked until it returned. For a quick file read that was fine. For a research dive, a multi-step build, or anything that took more than a few seconds, it meant idle time -- the main agent waiting while the subagent worked.

The intent was always there. The delegate_task tool schema accepted a background parameter. But until PR #46968, the value never reached the dispatch function -- it was dropped before run_agent.py's _dispatch_delegate_task forwarded it. Every call ran synchronously regardless of the flag.

How it works

The implementation in PR #40946 by @teknium1 adds 1,268 lines across 9 files. Four new components:

tools/async_delegation.py (386 lines new) -- A daemon-executor registry that manages a pool of background subagent processes. It enforces a capacity cap via delegation.max_async_children (default 3) -- rejects new delegations if the pool is full rather than queuing them. Each completed subagent pushes a type="async_delegation" event onto the shared completion queue.

tools/delegate_tool.py (149 lines changed) -- The background parameter routes to a single-task dispatch branch. It captures the session_key on the parent thread and hands _run_single_child to the dispatcher as a runner. Batch async dispatch is deferred to v1.

gateway/run.py (126 lines changed) -- A dedicated _async_delegation_watcher drains completion events and injects results into the originating session. It covers the idle case -- when the main agent has nothing pending, the result appears as the next turn. On shutdown, it interrupts dangling delegations.

tools/process_registry.py (88 lines changed) -- format_process_notification renders the self-contained task-source block that gets re-injected. This is the critical design choice.

File Added Removed Role
tools/async_delegation.py 386 0 Daemon executor registry
tests/test_async_delegation.py 473 0 Test coverage
gateway/run.py 126 10 Watcher + injection
tools/delegate_tool.py 149 0 Background dispatch branch
tools/process_registry.py 88 5 Task-source block rendering
Total 1,268 15 9 files

The context-carry design

The re-injected message is not just the result. It carries a self-contained block with the original task source: the goal, the context the parent supplied, toolsets, role, model, dispatch time, and status alongside the result.

This is intentional. When the subagent finishes, the main agent may be deep in unrelated context. It will not remember why the subagent exists. The re-injected block lets the agent pick up the result without re-reading the history.

[Async delegation complete - 14.2s]
  Goal: Research Twitter API v2 rate limits
  Context: parent supplied API keys, endpoint list
  Model: claude-sonnet-4-2026
  Tools: web_search, web_extract
  Result: Rate limit data + source links

The design reuses the existing completion queue (process_registry.completion_queue) that terminal background processes already use. This keeps message-role alternation legal and the prompt cache intact -- results surface as a new turn when the agent is idle, never spliced mid-turn.

The bug that made it real

PR #40946 implemented the async infrastructure. But the background flag was silently dropped by _dispatch_delegate_task in run_agent.py. Every delegate call ran synchronously.

PR #46968 by @teknium1 fixed the forwarding. One line in run_agent.py -- background=function_args.get("background") -- closed the gap. After the fix, delegate_task(background=true) actually dispatched async across every surface: CLI, gateway, and desktop/TUI.

What it unblocks

A single delegation creates a parallel workstream. A script or cron job can chain multiple background subagents against independent tasks and collect results as they arrive.

The capacity cap (max_async_children: 3) prevents unbounded resource consumption. Each subagent gets its own model context, toolset, and session -- they run independently and do not share state with the parent or each other. Shared memory (Mnemosyne, Honcho, or the native memory tool) is the bridge if coordination is needed.

This is the model that makes sense for production Hermes deployments: a persistent agent that delegates specialized work to ephemeral children, collects their output, and synthesizes it without blocking on any single task.

Getting started

The feature is on by default in v0.17.0. No new configuration required. The delegation.max_async_children setting in config.yaml controls the pool size.

delegation:
  max_async_children: 5  # default 3

The release is available on GitHub. 800 merged PRs, 235,000 insertions, 300+ issues closed.

[^1]: Teknium. "feat(delegation): async background subagents via delegate_task(background=true)." GitHub. 2026. [^2]: Teknium. "fix(delegation): forward background flag so delegate_task(background=true) runs async." GitHub. 2026. [^3]: Nous Research. "Hermes Agent v0.17.0 Release." GitHub. 2026.

Termagotchi
_

Ryan Underdown

Autodidact. Rarely listens to advice.

Follow on X @catamarammed or GitHub @underdown