🤖HermesBlog
Hermes Official Tutorials · Part 228/9/2026

Tutorial 22: Subagent Delegation

Tutorial 22: Subagent Delegation — easy-to-understand guide based on official docs

This is part of the Hermes Agent official tutorial series. View all tutorials

Imagine you’re the team lead on a project. You could do everything yourself, but that’s slow. Instead, you hand out specific tasks to your teammates, give them the details they need, and wait for them to report back. That’s exactly what Subagent Delegation does for Hermes Agent — it lets your main agent hire temporary “workers” to handle tasks in the background.


Subagent delegation: clone yourself

Step 1: Understand the Basic Idea

When you call delegate_task, Hermes creates one or more child agents (subagents). Each child:

  • Gets a completely fresh conversation — it knows nothing about your chat history.
  • Has access to the same tools as the parent agent.
  • Runs in its own isolated terminal session.
  • Returns only a final summary to the parent.

Think of it like sending a colleague to research a topic. They don’t sit through your entire meeting — you just hand them a note with the task and context, and they come back with a report.

Background terminal processes belong to the agent that starts them: closing a child during delegation teardown terminates its remaining processes, including work started in earlier turns, without stopping processes owned by the parent or sibling agents. A child should wait for its builds, tests, and other bounded background commands before returning its final summary; start a CI watcher or server in the parent session if it must continue after the child finishes, since returning a process ID does not transfer ownership to the parent.


Step 2: Delegate a Single Task

The simplest use case: one task, one subagent.

delegate_task(
    goal="Debug why tests fail",
    context="Error: assertion in test_foo.py line 42"
)

The goal tells the subagent what to do. The context gives it everything it needs to do it.


Step 3: Delegate Multiple Tasks in Parallel

Need several things done at once? Pass a list of tasks. By default, Hermes runs up to 10 subagents concurrently (you can change this — there’s no hard limit).

delegate_task(tasks=[
    {"goal": "Research topic A", "context": "Focus on recent primary sources"},
    {"goal": "Research topic B", "context": "Compare the leading explanations"},
    {"goal": "Fix the build", "context": "Project root: /home/user/project"}
])

Each subagent works independently. The parent waits for all of them, then combines the results.


Step 4: The Golden Rule — Subagents Know Nothing

This is the most important point, so listen up:

Subagents start with zero knowledge of your conversation.

If you say “Fix the error” without explaining what “the error” is, the subagent will be lost. You must pass all necessary context in the goal and context fields.

Here’s a bad example:

# BAD - subagent has no idea what "the error" is
delegate_task(goal="Fix the error")

And the good version:

# GOOD - subagent has all context it needs
delegate_task(
    goal="Fix the TypeError in api/handlers.py",
    context="""The file api/handlers.py has a TypeError on line 47:
    'NoneType' object has no attribute 'get'.
    The function process_request() receives a dict from parse_body(),
    but parse_body() returns None when Content-Type is missing.
    The project is at /home/user/myproject and uses Python 3.11."""
)

Think of the context as a briefing document. The more specific you are, the better the subagent’s work will be.

One exception: when the parent has a resolved workspace directory, every subagent’s system prompt embeds that workspace’s project context files (.hermes.md > AGENTS.md chain > CLAUDE.md > .cursorrules — the same discovery, priority, and size caps as the main agent’s system prompt; SOUL.md is excluded). Subagents working in a repo operate under the repo’s own conventions without having to rediscover them.


Step 5: Use It for Real-World Workflows

Parallel Research

Gather information on multiple topics at once:

delegate_task(tasks=[
    {
        "goal": "Research the current state of WebAssembly in 2025",
        "context": "Focus on: browser support, non-browser runtimes, language support"
    },
    {
        "goal": "Research the current state of RISC-V adoption in 2025",
        "context": "Focus on: server chips, embedded systems, software ecosystem"
    },
    {
        "goal": "Research quantum computing progress in 2025",
        "context": "Focus on: error correction breakthroughs, practical applications, key players"
    }
])

Code Review + Fix

Hand off a full review-and-fix job to a fresh context:

delegate_task(
    goal="Review the authentication module for security issues and fix any found",
    context="""Project at /home/user/webapp.
    Auth module files: src/auth/login.py, src/auth/jwt.py, src/auth/middleware.py.
    The project uses Flask, PyJWT, and bcrypt.
    Focus on: SQL injection, JWT validation, password handling, session management.
    Fix any issues found and run the test suite (pytest tests/auth/)."""
)

Structured Output with output_schema

Each task can carry an optional output_schema, a JSON Schema object the child’s final answer must validate against. The child sees the schema up front as an output contract (“return ONLY the JSON value — no prose, no code fence”); when the answer comes back the parent validates it, and on failure sends the child exactly one bounded correction turn carrying the validation errors verbatim (the schema is not re-pasted). The task’s result then gains schema_valid (true/false) and, on failure, schema_errors.

A contract miss after the retry does not discard the child’s work: the result keeps status: completed with the child’s raw final text in summary, schema_valid: false, the schema_errors, and a schema_note saying the text is unvalidated. The parent extracts what it needs from the raw text instead of re-running a task that may have taken an hour. Prose or a code fence around otherwise-valid JSON (object or array) is tolerated by the validator. Keep schemas forgiving: require only the fields you will actually read, and tasks without an output_schema are unaffected.

delegate_task(
    tasks=[{
        "goal": "Check which of these three endpoints return 200",
        "context": "https://a.example, https://b.example, https://c.example",
        "output_schema": {
            "type": "object",
            "properties": {
                "healthy": {"type": "array", "items": {"type": "string"}},
                "failing": {"type": "array", "items": {"type": "string"}}
            },
            "required": ["healthy", "failing"]
        }
    }]
)

Forwarding Images to a Subagent

Text context is not enough when the task is inherently visual — a screenshot the user sent, a design mock, a rendered chart. Each task accepts an optional images list (up to 8 entries; local file paths, http(s) URLs or data:image/... URLs):

delegate_task(tasks=[{
    "goal": "Compare the rendered dashboard against the design mock and list layout deviations",
    "context": "The app runs at http://localhost:3000; the repo is at /home/user/dash.",
    "images": ["/home/user/mocks/dashboard-v2.png",
               "https://cdn.example.com/current-render.png"],
}])

Delivery follows the same routing as user-attached images (agent.image_input_mode):

  • Vision-capable child model — the images arrive as native multimodal content on the child’s first turn: local files are embedded as data URLs (subject to the same read guard as every other file read), remote and data: URLs pass through verbatim. The child sees the actual pixels.
  • Non-vision child model — the goal gains [Image attached at: <path>] hint lines and the child is told to inspect them with vision_analyze.

Forwarding is best-effort: unreadable paths are skipped with a log line, and any failure in the image plumbing falls back to the plain text goal — it can never break a spawn. Images are for things the child must see; put text file paths in context as usual.

Delegation strategies (delegation.backend)

delegation.backend selects how each task becomes a subagent. default (the default) is everything above. Strategy plugins can let the model choose per task, from options you define:

  • a profile per subagent (profile_routes): each child runs as one of your Hermes profiles, with that profile’s model, credentials, skills and persona;
  • a named model map (model_map): you list titled options (fast, deep, …) with a description and a provider/model each, and the model picks one per task.

Both come from the official advanced-delegation plugin, which also adds hermes delegation config (create, edit and delete model routes, pick profiles) and the /delegation slash command. While model_map or profile_routes is active, delegation.model / delegation.provider are inactive: the routes decide.

Each strategy’s settings live under delegation.backends.<backend name>:

delegation:
  backend: model_map
  backends:
    model_map:
      options:
        - name: fast
          description: Cheap and quick; lookups, summaries, simple edits.
          provider: openrouter
          model: google/gemini-3-flash-preview
        - name: deep
          description: Slow and careful; hard debugging and design work.
          provider: anthropic
          model: claude-opus-5
          reasoning_effort: high
          service_tier: fast            # optional; refused at spawn if this route has no fast tier
      default: fast                     # optional: option for tasks that set none
    profile_routes:                     # read when backend: profile_routes
      profiles:
        - name: coder
          description: Writes and refactors code; runs tests.

The model only ever sees the names and descriptions you configured, never raw provider, model or key fields. Each result of a backend-routed task carries route: the backend, the option or profile name, and the provider, model, reasoning effort and speed tier the child actually ran on (fallback_activated: true when a fallback provider took over). Writing your own: Delegation Provider Plugins.

Failures are explicit, never a silent switch. If the selected backend is missing (plugin not installed or disabled) or reports it cannot run, delegate_task returns a tool error naming the backend and the fix, and no subagent starts; it never quietly runs the children on the default route. Within a batch, a task its backend cannot route (an unknown option, a parameter the selected backend does not take, a profile that does not exist, a service_tier the route does not offer, an unknown reasoning_effort, missing credentials) is refused alone: its result entry has status: "error", the reason, and route.refused: true, while the other tasks run. A model route without its own fallback_providers uses delegation.fallback_providers; fallback_providers: [] on the route disables fallback for it. A profile route uses that profile’s own fallback chain.

Switching back. /delegation default (from the advanced-delegation plugin) saves delegation.backend to config.yaml and applies from the next session (/new): a running chat keeps routing on the backend it started with, because the per-task choices the model sees (option or profile names) are part of the cached tool list and the two switch together. A hand edit of delegation.backend behaves the same. /delegation <backend> --now also rebuilds the current chat’s tools at its next turn, so it routes on and offers the new backend from there, at the cost of one prompt-cache miss (context compression, which rebuilds the tools anyway, picks up the saved backend too). A task that sends a parameter its chat’s backend does not take is refused with a message naming the backend, the parameter and the fix (/delegation <backend> --now, or /new); it never runs on the backend’s default route. Without the plugin, hermes config set delegation.backend default restores the default.

:::note Profile routes share credentials Listing a profile in delegation.backends.profile_routes lets this profile’s subagents use that profile’s credentials, credential pool, SOUL.md and terminal policy. Only list profiles whose keys and terminal access you are happy for this profile’s agent to use. :::

Opting Into an Inactivity Cap

By default there is no cap on how long a child may run. If you want a cap anyway — say, for cost control on unattended cron-driven delegation — opt in per-install:

# child_timeout_seconds: 1800  # opt-in inactivity cap (floor 30s)

A positive value bounds inactivity, not total runtime: it is the longest a child may go with no progress (no completed API call, no tool change, no activity-clock tick) before it is abandoned. Every sign of progress restarts the window, so a child waiting on a multi-minute completion — the case that used to lose finished work — is never killed for taking long, while a child that has genuinely stopped moving is still caught (and an in-flight request is bounded independently by the per-call stale watchdog). 0 or a negative value disables the cap; the heartbeat staleness monitor stays active either way.

At ~80% of an idle window the child receives a one-line [delegation budget warning] through its steer channel (delivered at its next iteration boundary) telling it how long it has been idle and to return its summary now, so a slow-but-recoverable child can wrap up instead of losing its context. The warning fires once per idle window and re-arms when progress resumes.

When a wait does end in a timeout, the result carries timed_out_after_seconds (actual wall clock), last_event_age (how long the child had been silent when the wait ended — the fast way to tell a slow provider from a runaway), and timeout_phase (before_first_llm_call when the child never reached its first request, after_llm_calls otherwise). All four are null on non-timeout errors.

Watching Background Processes in the Dock

Background processes share the dock. Anything the agent starts with terminal(background=true) (a build, a test run, a dev server, a CI poller) appears in a Processes block under the subagent rows the moment it spawns — ⚙ <command> · 42s · last: <latest output line> — and flips in place to ✔ exit 0 / ✘ exit 1 / ✘ killed when it ends, then leaves the dock about 60 seconds later (the completion notification in the conversation is the durable record). In the Classic CLI monitor, process rows sit under the agents: Enter shows the process log tail and x stops that one process; processes cannot be steered. The TUI /agents overlay lists the same block under the spawn tree (/stop ends every background process). Desktop shows the same processes as tiles above the composer.

Background processes share the dock. Anything the agent starts with terminal(background=true) (a build, a test run, a dev server, a CI poller) appears in a Processes block under the subagent rows the moment it spawns — ⚙ <command> · 42s · last: <latest output line> — and flips in place to ✔ exit 0 / ✘ exit 1 / ✘ killed when it ends, then leaves the dock about 60 seconds later (the completion notification in the conversation is the durable record). In the Classic CLI monitor, process rows sit under the agents: Enter shows the process log tail and x stops that one process; processes cannot be steered. The TUI /agents overlay lists the same block under the spawn tree (/stop ends every background process). Desktop shows the same processes as tiles above the composer.

Press Ctrl+R in the Classic CLI or TUI composer to toggle the dock between its multi-row preview and a single shaded summary line. F7 remains an optional alias when the terminal forwards function keys. The summary retains the live count and expand/restore hints, adding activity when space permits. Typing and sending remain available; opening and closing the monitor preserves your draft and insertion point. This is a local presentation choice, not a saved config change.

How Subagents Compact Their Context

Subagents compact where their parent does: at compression.threshold × window (0.50 by default), or the global compression.threshold_tokens cap when one is set and lower. delegation.compression_threshold_tokens (default 0, off) adds an optional absolute cap on a child’s compaction trigger, applied as the lower of it and the ratio threshold; it never touches the request payload or the parent. A token count of at least 16000 enables it; true or "200k" are config errors that are warned and ignored. It stays off by default because a replay of a 1,393-agent run put 200K–400K caps within 5% of each other in cost once cache prefixes are intact, and every compaction is a chance to lose detail.

What Subagents Cannot Do

Subagents inherit the parent’s enabled toolsets, but certain tools are blocked for them even when the parent has them:

  • delegate_task — blocked for leaf subagents (the default). Retained for role="orchestrator" children, bounded by max_spawn_depth.
  • clarify — subagents cannot interact with the user
  • memory — no writes to shared persistent memory
  • send_message — no cross-platform side effects
  • cronjob — no scheduling more work in the parent’s name
  • start_chat — no opening chats in the user’s name

Both roles retain execute_code (programmatic tool calling) so children can batch mechanical work instead of burning reasoning iterations.


Summary

Subagent delegation is your way to parallelize work in Hermes Agent. Remember these key points:

  • Use delegate_task for one task or a batch of tasks.
  • Always provide full context — subagents start from scratch.
  • Up to 10 concurrent subagents by default, configurable if you need more.
  • Each subagent returns a structured summary — what it did, what it found, files changed, issues encountered.

It’s like having a team of assistants who only know what you tell them — so tell them everything they need.


Next up: Tutorial 23 — Kanban: a shared, persistent board for multi-agent collaboration.

📖 Official Docs

This article is based on the official Hermes Agent documentation:Official docs › user-guide/features/delegation