Dispatching subagents (all projects)
One of the global rules my agents load on every session, published as they load it. Links to reference files point at documents I do not publish.
- Source
- rob/agents/policy/claude/subagent-dispatch.md
- Revision
- 4303422
operating-model owns which model to route a lane to and how to scope it. This file owns the
dispatch mechanics. Pass model: on every dispatch; it defaults to the parent’s.
Never pass name: to an agent whose output you need back
Re-verified 2026-09-06 on 2.1.261: a named background agent goes idle and remains resident as a
teammate. Its final text arrives late in the result field of an idle_notification, via the
teammate channel rather than a completion notification. Dispatch unnamed for one-shot work.
| Invocation | What the dispatcher receives |
|---|---|
Agent({name:"x", run_in_background:true}) |
delayed idle_notification.result via the teammate channel |
Agent({run_in_background:true}) (no name) |
<task-notification>…<result> — payload intact |
// ✅ Need the output back → NO name.
Agent({ description: "...", subagent_type: "general-purpose", run_in_background: true, prompt: "..." })
// ✅ Need it before continuing → synchronous, returns inline as the tool result.
Agent({ ..., run_in_background: false })
// Output is delayed through the teammate channel.
Agent({ name: "researcher", run_in_background: true, prompt: "...write me a brief..." })
Only use name: for a long-lived peer and account for its delayed teammate-channel result.
A file handoff also works: have the agent write its brief to an absolute path and read it yourself.
Write every prompt so the agent’s last message IS the complete self-contained brief — TL;DR, findings, citations, signatures. The dispatcher never sees its transcript or intermediate work.
Do not tell a top-level subagent to SendMessage(to: "main"). It is rejected — from a subagent’s
seat there is no “main” target; its final message is surfaced automatically. SendMessage-by-name only
works between named peers.
Cross-session SendMessage between separate Claude Code sessions is a different mechanism and does
not relax any of the above. Both configs set "crossSessionInbound": "accept" so peer messages
deliver without a review dialog.
Verify the artifacts yourself
Every agent that failed to return a brief had still done correct work — the reporting failed, not the work. A missing brief is not evidence of a failed task: go look at what it produced.
It cuts both ways. A returned brief is not evidence of correct work either. Check load-bearing claims against the source, especially any claim contradicting something already verified.
A subagent cannot clear an auto-mode soft block
Subagents inherit auto mode and cannot opt out — a permissionMode in agent frontmatter is ignored
when the parent is in auto mode. A soft block clears only on the user’s own message naming the
specific action, and a subagent’s transcript contains no user message. A teammate message or a
dispatch brief is explicitly refused as consent — the classifier calls that permission laundering.
So a lane can be blocked on work the main thread would have been allowed to do, with nothing able to
clear it. Do not re-dispatch a blocked lane — that trips [Auto Mode Bypass], which reads a
re-dispatch with pre-justifying wording as bad faith.
How to apply: dispatched lanes do read-only investigation, code edits, tests and inventory sweeps. SSH to a host, deploys, tenant or cloud mutations, secret-store writes and destructive git stay on the main thread. If a lane comes back blocked, run that step yourself or ask Rob to name it.
~/.claude/logs/permission-denied.jsonl records every denial with a subagent flag.
A lane loads the same shared rules as its dispatcher: do not include verbatim restatements in its brief; include only task-specific narrowing, such as what counts as evidence here and which paths or actions are fenced.