Sub-agents: Spawn Isolated Background Agent Runs
Learn how to spawn isolated background agent runs that announce results back to the requester chat. Ideal for parallelizing research and long tasks without blocking the main run.
Read this when
- You want background or parallel work via the agent
- You are changing sessions_spawn or sub-agent tool policy
- You are implementing or troubleshooting thread-bound subagent sessions
- You are looking for the sub-agent page that matches your task
Sub-agents are background agent runs spawned from an existing agent run.
Each one runs in its own session (agent:<agentId>:subagent:<uuid>) and,
when finished, announces its result back to the requester chat channel.
Every sub-agent run is tracked as a background task.
Goals:
- Parallelize research, long tasks, and slow tool work without blocking the main run.
- Keep sub-agents isolated by default (session separation, optional sandboxing).
- Keep the tool surface hard to misuse: sub-agents do not get session or message tools by default.
- Support configurable nesting depth for orchestrator patterns.
Note
Cost note: each sub-agent has its own context and token usage by default. For heavy or repetitive tasks, set a cheaper model for sub-agents and keep your main agent on a higher-quality model via
agents.defaults.subagents.modelor per-agent overrides. When a child genuinely needs the requester's current transcript, spawn it withcontext: "fork". Thread-bound subagent sessions default tocontext: "fork"because they branch the current conversation into a follow-up thread.
Slash command
/subagents inspects sub-agent runs for the current session:
/subagents list
/subagents log <id|#> [limit] [tools]
/subagents info <id|#>
/subagents info shows run metadata (status, timestamps, session id,
transcript path, cleanup). /subagents log prints recent chat turns for a
run; add the tools token to include tool-call/result messages (omitted
by default). Use sessions_history for a bounded, safety-filtered recall
view from within an agent turn, or inspect the transcript path on disk for
the raw full transcript.
In the Control UI, parent sessions with recent child runs have an expandable sidebar row. The nested rows show child status and runtime, and selecting one opens that child's chat while preserving the parent hierarchy.
Thread binding controls
These commands work on channels with persistent thread bindings. See Thread supporting channels below.
/focus <subagent-label|session-key|session-id|session-label>
/unfocus
/agents
/session idle <duration|off>
/session max-age <duration|off>
Spawn behavior
Agents start background sub-agents with the sessions_spawn tool.
Completions return as internal parent-session events; the parent/requester
agent decides whether a user-facing update is needed.
When execution identity auditing is enabled, each native or ACP child receives a new immutable identity context. Its lineage links the exact parent context/run when available and records bounded references for the parent grant, local policy, runtime assurance, and target policy that constrained the spawn. Neither the private identity token nor task text appears in the tool schema, result, transcript-derived evidence, or public plugin API. External ACP-native actions without a callback remain explicitly unsupported even though the ACP spawn and child are observable.
Non-blocking, push-based completion
sessions_spawnreturns a run id after startup is accepted, without waiting for the child task to finish. Spawns from an OpenClaw cloud worker can first wait for child provisioning and node enrollment.- On completion, the sub-agent reports back to the parent/requester session.
- Agent turns that need child results should call
sessions_yieldafter spawning required work. That ends the current turn and lets the completion event arrive as the next model-visible message. - Completion is push-based. Once spawned, do not poll
/subagents list,sessions_list, orsessions_historyin a loop just to wait for it to finish; check status on-demand only when debugging. - Child output is a report/evidence for the requester agent to synthesize. It is not user-authored instruction text and cannot override system, developer, or user policy.
- On completion, OpenClaw best-effort closes tracked browser tabs/processes opened by that sub-agent session before the announce cleanup flow continues.
Completion delivery
- OpenClaw hands completions back to the requester session through an
agentturn with a stable idempotency key. - If the requester run is still active, OpenClaw first tries to wake/steer that run instead of starting a second visible reply path.
- If an active requester cannot be woken, OpenClaw falls back to a requester-agent handoff with the same completion context instead of dropping the announce.
- A successful parent handoff completes sub-agent delivery even when the parent decides no visible user update is needed.
- Native sub-agents do not get the message tool. They return plain assistant text to the parent/requester agent; human-visible replies stay owned by the parent/requester agent's normal delivery policy.
- If direct handoff cannot be used, delivery falls back to queue routing. A queued completion remains
session_queued, rather than delivered, until the durable queue settles. - Automatic completion delivery retries for up to 30 minutes, starting around 15 seconds and capping the backoff at 5 minutes. Permanent failure or deadline expiry leaves the successful child task visibly blocked instead of discarding its result.
- Blocked canonical results are retained for 7 days. Operators can retry or intentionally dismiss them from the Tasks page or with
openclaw tasks retry/openclaw tasks dismiss; retry can duplicate a visible result after an ambiguous provider acknowledgement. - Delivery keeps the resolved requester route: thread-bound or conversation-bound completion routes win when available. If the completion origin only provides a channel, OpenClaw fills the missing target/account from the requester session's resolved route (
lastChannel/lastTo/lastAccountId) so direct delivery still works.
Completion handoff metadata
The completion handoff to the requester session is runtime-generated internal context (not user-authored text) and includes:
Result, the latest visibleassistantreply text from the child. Tool/toolResult output is not promoted into child results. Terminal failed runs do not reuse captured reply text.Status,completed; ready for parent review/failed/timed out/unknown.- Compact runtime/token stats.
- A review instruction telling the requester agent to verify the result before deciding whether the original task is done.
- Follow-up guidance telling the requester agent to continue the task or record a follow-up when the child result leaves more action.
- A final-update instruction for the no-more-action path, written in normal assistant voice without forwarding raw internal metadata.
Modes and ACP runtime
--modeland--thinkingoverride defaults for that specific run.- Use
info/logto inspect details and output after completion. - For persistent thread-bound sessions, use
sessions_spawnwiththread: trueandmode: "session". - If the requester channel does not support thread bindings, use
mode: "run"instead of retrying an impossible thread-bound combination. - For ACP harness sessions (Claude Code, Gemini CLI, OpenCode, or explicit Codex ACP/acpx), use
sessions_spawnwithruntime: "acp"when the tool advertises that runtime. See ACP delivery model when debugging completions or agent-to-agent loops. When thecodexplugin is enabled, Codex chat/thread control should prefer/codex ...over ACP unless the user explicitly asks for ACP/acpx. - OpenClaw hides
runtime: "acp"until ACP is enabled, the requester is not sandboxed, and a backend plugin such asacpxis loaded.runtime: "acp"expects an external ACP harness id, or anagents.entries.*entry withruntime.type="acp"; use the default sub-agent runtime for normal OpenClaw config agents fromagents_list.
Context modes
Non-thread native sub-agents begin in an isolated state unless the caller explicitly requests a fork of the current transcript. For thread-bound spawns, the behavior follows threadBindings.defaultSpawnContext, which has fork as its default. When the child needs to start with a blank context, pass context: "isolated" explicitly.
| Mode | When to use it | Behavior |
|---|---|---|
isolated | Fresh research, independent implementation, slow tool work, or anything that can be briefed in the task text | Creates a clean child transcript. Default for non-thread spawns; keeps token use lower. |
fork | Work that depends on the current conversation, prior tool results, or nuanced instructions already present in the requester transcript | Branches the requester transcript into the child session before the child starts. |
Reserve fork for cases that truly need it. It serves context-sensitive delegation, not as a substitute for drafting a well-defined task prompt.
Tool: sessions_spawn
A sub-agent run is initiated with deliver: false on the global subagent lane, followed by an announce step and the posting of the announce reply to the requester chat channel.
Whether this is available hinges on the caller's effective tool policy. The built-in coding and messaging profiles come with sessions_spawn, sessions_yield, and subagents; minimal omits them. full grants access to every tool. For an agent on a custom narrower profile that should still delegate work, add those tools with tools.alsoAllow, or pick one of the profiles mentioned above. Even after the profile stage, channel/group, provider, sandbox, and per-agent allow/deny policies can still strip the tool. To verify the effective tool list, use /tools from the same session.
Defaults:
- Model: native sub-agents take on the caller's model unless you override with
agents.defaults.subagents.model(or per-agentagents.entries.*.subagents.model). ACP runtime spawns apply the same configured subagent model when one exists; otherwise the ACP harness falls back to its own default. An explicitsessions_spawn.modelstill takes precedence. - Thinking: native sub-agents take on the caller's thinking setting unless you override with
agents.defaults.subagents.thinking(or per-agentagents.entries.*.subagents.thinking). ACP runtime spawns also applyagents.defaults.models["provider/model"].params.thinkingfor the chosen model. An explicitsessions_spawn.thinkingstill takes precedence. - Run timeout: to set a timeout for a specific native, ACP, or visible sub-agent run, pass
runTimeoutSeconds. If omitted, OpenClaw usesagents.defaults.subagents.runTimeoutSecondswhen it is configured; otherwise it defaults to0(no timeout). An explicit0turns off the timeout for that run. - Process lifetime: a detached OpenClaw sub-agent runs on its own lifecycle. A background task spawned inside an external CLI backend differs: it shares the parent CLI subprocess and terminates when that parent hits
agents.defaults.timeoutSeconds. - Task delivery: native sub-agents get their delegated task in a
[Subagent Task]message appended after any forked history. Inherited task envelopes are context, not the current child's assignment. The sub-agent system prompt carries runtime rules and routing context, not a hidden duplicate of the task.
Accepted native sub-agent spawns report their actual initialized context (fork or isolated), including isolated when a requested fork exceeds the parent-context size cap. They also include resolved child model metadata: resolvedModel holds the applied model ref and resolvedProvider holds the provider prefix when the ref has one.
Delegation prompt mode
agents.defaults.subagents.delegationMode governs prompt guidance only; it does not alter tool policy or enforce delegation. With no explicit setting, OpenClaw uses prefer in each agent's main session and suggest in every other session.
suggest: retain the standard prompt nudge to use sub-agents for larger or slower work.prefer: instruct the agent to remain responsive and route anything more involved than a direct reply throughsessions_spawn.
An explicit default or per-agent setting always wins, including suggest in a main session and prefer elsewhere. Per-agent overrides use agents.entries.*.subagents.delegationMode.
In prefer mode, hidden sub-agents handle internal legwork the user does not need to track. Work the user will watch or return to, or work with its own deliverable such as a URL, PR, or report, should use sessions_spawn with visible: true so it stays in the sidebar.
{
agents: {
defaults: {
subagents: {
delegationMode: "prefer",
maxConcurrent: 4,
},
},
entries: {
coordinator: {
default: true,
subagents: { delegationMode: "prefer" },
},
},
},
}
Tool parameters
-
task(string, required), The task description for the sub-agent. -
taskName(string), Optional stable handle for identifying a specific child in later status output. Must match[a-z][a-z0-9_-]{0,63}and cannot be a reserved target such aslastorall. -
label(string), Optional short task title shown in UI lists (task ledger, session sidebar). Name the work being done, not the agent; it is set on the child session at run start. -
agentId(string), Whensubagents.allowAgentspermits it, launch under a different configured agent identifier. -
cwd(string), Optional working directory for the child task. Native sub-agents still pull bootstrap files from the target agent's workspace;cwdaffects only where runtime tools and CLI harnesses execute the delegated work. For visible sessions, paths outside configured agent workspaces demandoperator.admin. Withworktree: true, leaving outcwdinherits the parent's managed repository from the same agent when one exists; otherwise, the target agent's workspace applies. -
runtime(subagent" | "acp, default: subagent),acpapplies solely to external ACP harnesses (claude,droid,gemini,opencode, or an explicitly requested Codex ACP/acpx) and toagents.entries.*records whoseruntime.typeequalsacp. -
resumeSessionId(string), ACP-only. Whenruntime: "acp"is set, resumes an existing ACP harness session; ignored for native sub-agent launches. -
streamTo(parent), ACP-only. Streams ACP run output to the parent session whenruntime: "acp"; leave out for native sub-agent launches. -
model(string), Replace the sub-agent model. Invalid values get skipped, and the sub-agent falls back to the default model, with a warning in the tool result. -
runTimeoutSeconds(integer), Replace the configured run timeout for this child. Must be a non-negative integer;0turns off the timeout. Works for native, ACP, and visible sessions. -
thinking(string), Replace the thinking level for the sub-agent run. Not supported withvisible: true. -
thread(boolean, default: false), Whentrueis true, asks for channel thread binding on this sub-agent session. -
mode(run" | "session, default: run), Ifthread: trueholds andmodeis absent, the default shifts tosession.mode: "session"calls forthread: true. When thread binding is unavailable on the requester channel, go withmode: "run"instead. Withvisible: true, dropmodeor stick with the default"run"; the visible session stays persistent.mode: "session"is not offered on this route. -
cleanup(delete" | "keep, default: keep),"delete"stores the session right after announce (the transcript is still kept via rename). -
sandbox(inherit" | "require, default: inherit),requireblocks the spawn unless the target child runtime is sandboxed. -
context(isolated" | "fork),forkcopies the requester's current transcript into the child session. Native sub-agents only. Non-thread spawns default toisolated; thread-bound spawns followthreadBindings.defaultSpawnContext, which defaults tofork. Passisolatedexplicitly to ensure clean context. Every native fork, hidden or visible, must point at the same agent as the requester. -
visible(boolean, default: false), Set up a persistent dashboard session for work the user will watch or revisit, or when they request a thread. Visible spawns support onlyruntime: "subagent"and always retain the created session. -
group(string), Optional custom sidebar group for a visible session; a fresh name creates the group. Omitted, empty, and whitespace-only values mean ungrouped and are also fine for hidden or ACP runs. A nonempty group calls forvisible: true. -
worktree(boolean, default: false), Set up a managed git worktree for the new dashboard session. Needsvisible: true. -
worktreeName(string), Optional managed-worktree name. Needsvisible: trueandworktree: true. -
worktreeBaseRef(string), Optional git base ref for the managed worktree. Needsvisible: trueandworktree: true.
Warning
Channel-delivery parameters (
target,channel,to,threadId,replyTo,transport) are rejected bysessions_spawn. Native sub-agents send their most recent assistant turn back to the caller, while external delivery remains with the parent or requesting agent.
With visible: true, group, model, cwd, and a same-agent context: "fork" are supported. This durable mode suits coding, multi-step tasks, or outcomes the user might revisit, guide, or retain; it shows up in the sidebar when the web UI is present and functions even without it. Passing group atomically places the new session in that sidebar group; leaving it out or blank keeps it ungrouped. A sandboxed target confines cwd to that agent's workspace. Non-admin callers can employ cwd only within a configured agent workspace. With worktree: true, omitting cwd takes the same-agent parent's live managed repository and creates a separate worktree. Other spawns rely on the target agent workspace; for a different repository, have the operator launch the session from a registered project. Do not swap a rejected persistent spawn for the synchronous openclaw agent CLI, whose command deadline defaults to 600 seconds. Thread binding, mode: "session", thinking overrides, lightContext, and attachment staging are not available on this path because visible sessions are persistent dashboard sessions created via sessions.create. The default mode: "run", empty attachments, and an empty attachAs.mountPath are accepted without altering that behavior. The new dashboard child inherits the requester's effective tool-policy ceiling before its first turn. Session listing and addressing follow tools.sessions.visibility; the default tree scope covers the current session and its own spawn subtree, while the main session can reach every same-agent session unless self or the sandbox spawned-only clamp applies. See Session tools and Managed worktrees.
When a call fails with Parameters require visible=true, drop the named group or worktree options to preserve the hidden or ACP runtime. To create a visible session instead, use visible: true with runtime: "subagent" and omit mode, thread, thinking, lightContext, attachments, attachAs, swarm options, and the ACP-only streamTo and resumeSessionId. Worktree names and base refs also need worktree: true. Adding visible: true alone does not make an ACP call compatible.
A visible spawn is credited to the requesting agent: that agent becomes the new session's creator and initial owner, shown with its configured identity name and avatar in the sidebar. The accepted result doubles as a receipt with childSessionKey, runId, a Control UI sessionUrl (omitted when the Control UI is disabled), and an owner record. When acknowledging the spawn in a channel, put the session URL on the first line and Owner: <label> on the second so the user can open the session and see who is responsible. Owners can be reassigned later; see Multi-user mode.
Task names and targeting
taskName is a model-facing handle for orchestration, not a session key.
Use it for stable child names such as review_subagents,
linux_validation, or docs_update when a coordinator may need to inspect
that child later.
Target resolution accepts exact taskName matches and unambiguous
prefixes. Matching is scoped to the same active/recent target window used
by numbered /subagents targets, so a stale completed child does not make
a reused handle ambiguous. If two active or recent children share the same
taskName, the target is ambiguous; use the list index, session key, or
run id instead.
The reserved targets last and all are not valid taskName values
because they already have control meanings.
Tool: sessions_yield
Ends the active model turn and blocks until runtime events arrive, with sub-agent completions being the main case. Call this after launching any required child work whenever the parent cannot form a final answer until those completions land.
sessions_yield serves as the waiting primitive. Avoid swapping it out for polling loops built on subagents, sessions_list, sessions_history, shell sleep, or process checks that merely watch for child completion.
The optional message field carries private context for the resumed turn. Use acknowledgment to supply a waiting reply when an interactive parent turn would otherwise finish silently. That acknowledgment is not emitted from sub-agent, heartbeat, or silent turns, nor does it stand in for a reply or message already sent during the turn. This host-owned waiting status sidesteps message-tool-only source suppression; ordinary model replies stay private unless the model sends them through the message tool.
On native Codex harness turns, wait_agent holds the current turn open and is meant for an intentional same-turn wait when the next immediate step depends on the child. Choose sessions_yield instead when a native child's result should bring the parent back in a later turn.
Only reach for sessions_yield when the session's effective tool list actually includes it. Certain minimal or custom tool profiles may offer sessions_spawn and subagents without exposing sessions_yield; in that situation, do not fabricate a polling loop just to await completion.
A sub-agent can also yield on its own to wait on external work, like a remote job or a long-running task it does not drive. That pauses the child run rather than finishing it, so the requester receives no completion event yet and continues waiting. A plugin can later resume that same run by invoking api.runtime.subagent.run with the paused sessionKey, instead of spawning a sibling. The requester is notified once such a follow-up completes normally; a follow-up that yields again leaves the run paused and the requester waiting.
Automatic continuation applies only to the plugin runtime API described above. Ordinary follow-ups via routes not tracked as sub-agent runs neither resume the paused run nor notify its requester. Explicit subagents steering differs: it intentionally replaces the yielded run and carries on with the same child session.
Among plugin runtime follow-ups, continuation holds for those using default delivery. A follow-up that provides its own requester or completion-delivery context is requesting its own audience, so it executes as a separate sibling and delivers there instead. The paused run stays resumable, and a later default follow-up still continues it.
When active children exist, OpenClaw inserts a compact runtime-generated Active Subagents prompt block into normal turns so the requester can view the current child sessions, run ids, statuses, labels, tasks, and taskName aliases without polling. The task and label fields in that block are quoted as data, not instructions, because they can come from user/model-provided spawn arguments.
Tool: subagents
Enumerates spawned sub-agent runs and background-task records owned by the requester session tree. The task rows cover native sub-agents, ACP runs, Gateway CLI/media work, and cron executions. It is scoped to the current requester; a child can only see its own controlled children.
Use subagents for on-demand status and debugging. Use sessions_yield to wait for completion events.
Use action: "cancel" with a taskId returned by action: "list" to stop a task. Cancellation is confined to the controlled session tree; a leaf sub-agent cannot cancel work owned by another session.
Thread-bound sessions
When thread bindings are enabled for a channel, a sub-agent can remain bound to a thread so follow-up user messages in that thread keep routing to the same sub-agent session.
Thread supporting channels
A channel supports persistent thread-bound subagent sessions (sessions_spawn with thread: true) when it registers a conversation binding adapter. Bundled channels with that support: Discord, iMessage, Matrix, and Telegram. Discord and Matrix default to creating a child thread; Telegram and iMessage default to binding the current conversation. Use the per-channel threadBindings config keys for enablement, timeouts, and spawnSessions.
Quick flow
Spawn
sessions_spawn with thread: true (and optionally mode: "session").
Bind
OpenClaw creates or binds a thread to that session target in the active channel.
Route follow-ups
Replies and follow-up messages in that thread route to the bound session.
Inspect timeouts
Use /session idle to inspect/update inactivity auto-unfocus and /session max-age to control the hard cap.
Detach
Use /unfocus to detach manually.
Manual controls
| Command | Effect |
|---|---|
/focus <target> | Bind the current thread (or create one) to a sub-agent/session target |
/unfocus | Remove the binding for the current bound thread |
/agents | List active runs and binding state (binding:<id>, unbound, or bindings unavailable) |
/session idle | Inspect/update idle auto-unfocus (focused bound threads only) |
/session max-age | Inspect/update hard cap (focused bound threads only) |
Config switches
- Global default:
session.threadBindings.enabled,session.threadBindings.idleHours,session.threadBindings.maxAgeHours. - Channel override and spawn auto-bind keys are adapter-specific. See Thread supporting channels above.
See Configuration reference and Slash commands for current adapter details.
Allowlist
-
agents.entries.*.subagents.allowAgents(string[]), The agent ids that may be selected as targets through explicitagentIdare listed here (setting["*"]permits any configured target). By default, only the requester is allowed. Should you define a list yet still want the requester to launch itself viaagentId, add the requester's id to that list. -
agents.defaults.subagents.allowAgents(string[]), When the requester agent doesn't supply its ownsubagents.allowAgents, this default target-agent allowlist is applied. -
agents.defaults.subagents.requireAgentId(boolean, default: false), Anysessions_spawninvocation that leaves outagentIdgets blocked (this enforces explicit profile selection). The per-agent override isagents.entries.*.subagents.requireAgentId. -
agents.defaults.subagents.announceTimeoutMs(number, default: 120000), Each gatewayagentannounce delivery attempt has a timeout of this many milliseconds. Values must be positive integers and are capped at the platform-safe timer limit. Because transient retries occur, the total announce wait can exceed a single configured timeout.
When the requester session operates in a sandbox, sessions_spawn refuses targets that would run outside a sandbox.
Discovery
To see which agent ids are presently allowed for sessions_spawn, run agents_list. The response lists each agent's effective model and embedded runtime metadata, letting callers tell apart OpenClaw, Codex app-server, and other configured native runtimes.
Every allowAgents entry has to reference a configured agent id found in agents.entries.*. ["*"] expands to any configured target agent plus the requester. If an agent config gets removed but its id stays in allowAgents, sessions_spawn rejects that id and agents_list leaves it out. Execute openclaw doctor --fix to purge stale allowlist entries, or create a minimal agents.entries.* entry when the target should stay spawnable while adopting defaults.
Auto-archive
- Once
agents.defaults.subagents.archiveAfterMinuteselapses (default60), sub-agent sessions are archived automatically. - Archiving relies on
sessions.deleteand renames the transcript to*.deleted.<timestamp>(kept in the same folder). cleanup: "delete"archives right after announce (the transcript is still preserved via the rename).- Auto-archive is best-effort; if the gateway restarts, pending timers are lost.
- Configured run timeouts do not trigger auto-archive; they merely halt the run. The session lingers until auto-archive occurs.
- Depth-1 and depth-2 sessions both receive identical auto-archive treatment.
- Browser cleanup is handled independently of archive cleanup: tracked browser tabs/processes are best-effort closed at run completion, even when the transcript/session record is retained.
Nested sub-agents
Sub-agents cannot, by default, spawn their own sub-agents (maxSpawnDepth: 1). Enabling one nesting level requires setting maxSpawnDepth: 2, which yields the orchestrator pattern: main → orchestrator sub-agent → worker sub-sub-agents.
{
agents: {
defaults: {
subagents: {
maxSpawnDepth: 2, // allow sub-agents to spawn children (default: 1, range 1-5)
maxChildrenPerAgent: 5, // max active children per agent session (default: 5, range 1-20)
maxConcurrent: 8, // global concurrency lane cap (default: 8)
runTimeoutSeconds: 900, // default timeout for sessions_spawn (0 = no timeout)
announceTimeoutMs: 120000, // per-call gateway announce timeout
},
},
},
}
Depth levels
| Depth | Session key shape | Role | Can spawn? |
|---|---|---|---|
| 0 | agent:<id>:main | Main agent | Always |
| 1 | agent:<id>:subagent:<uuid> | Sub-agent (orchestrator when depth 2 allowed) | Only if maxSpawnDepth >= 2 |
| 2 | agent:<id>:subagent:<uuid>:subagent:<uuid> | Sub-sub-agent (leaf worker) | Never |
Announce chain
Results travel upward through the chain:
- A depth-2 worker completes, then announces to its parent, the depth-1 orchestrator.
- The depth-1 orchestrator gets that announce, merges the results, finishes, and announces to main.
- Main receives the announce and passes it to the user.
Each level sees announces exclusively from its immediate children.
Note
Operational guidance: launch child work once and await completion events rather than constructing poll loops around
sessions_list,sessions_history,/subagents list, orexecsleep commands.sessions_listand/subagents listkeep child-session relationships tied to active work: live children stay attached, ended children remain visible for a brief recent window, and stale store-only child links are disregarded once their freshness window passes. This stops oldspawnedBy/parentSessionKeymetadata from reviving ghost children after a restart. If a child completion event arrives after you've already sent the final answer, the proper response is the exact silent tokenNO_REPLY/no_reply.
Tool policy by depth
- When a child is spawned, it captures the requester's effective sender policy. Senderless child runs and authenticated operator resumes keep that snapshot even if
toolsBySenderchanges later; current global, agent, provider, sandbox, and sub-agent restrictions still hold. A new external channel turn aimed at the child instead re-resolves the current sender policy. - Role and control scope get written into session metadata at spawn time. This prevents flat or restored session keys from accidentally regaining orchestrator privileges.
- Depth 1 (orchestrator, when
maxSpawnDepth >= 2): receivessessions_spawn,subagents,sessions_list,sessions_historyso it can spawn children and check their status. All other session/system tools are denied. - Depth 1 (leaf, when
maxSpawnDepth == 1): no session tools (the current default behavior). - Depth 2 (leaf worker): no session tools, since
sessions_spawnis always denied at depth 2. Further child spawning is impossible.
Per-agent spawn limit
Each agent session, regardless of depth, can hold no more than maxChildrenPerAgent active child sessions at once, with 5 being the default. This cap stops a single orchestrator from spawning an uncontrolled number of descendants.
Cascade stop
Halting a depth-1 orchestrator also halts every one of its depth-2 children automatically:
- Issuing
/stopin the main chat terminates all depth-1 agents and propagates the stop down to their depth-2 descendants.
Authentication
Authentication for sub-agents is determined by agent id, not by session type:
- The key for the sub-agent session is
agent:<agentId>:subagent:<uuid>. - The local auth overlay comes from that agent's
agentDir. - Shared auth profiles are added as a fallback; when conflicts arise, agent profiles take precedence over shared ones.
Because the merge is additive, shared profiles remain available as fallbacks at all times. Fully isolated auth per agent is not yet possible.
Announce
Sub-agents communicate results through an announce step:
- The announce step executes inside the sub-agent session, not the requester session.
- An exact
ANNOUNCE_SKIPresponse suppresses the announce output. - For runs that require completion, an exact child
NO_REPLYresponse or no output counts as a missing deliverable passed to the requester/parent for display or retry; it is not treated as silent delivery. - Paths that are optional, duplicate, already visible, or otherwise not required may use exact
NO_REPLYto remain intentionally quiet.
Delivery varies with requester depth:
- Top-level requester sessions make a follow-up
agentcall with external delivery (deliver=true). - Nested requester subagent sessions get an internal follow-up injection (
deliver=false), letting the orchestrator compose child results within the session. - If a nested requester subagent session no longer exists, OpenClaw falls back to that session's requester when one is available.
For top-level requester sessions, completion-mode direct delivery first resolves any bound conversation/thread route and hook override, then fills missing channel-target fields from the requester session's stored route. This ensures completions land on the correct chat/topic even when the completion origin only specifies the channel.
Child completion aggregation stays scoped to the current requester run when building nested completion findings, which stops stale prior-run child outputs from entering the current announce. Announce replies keep thread/topic routing when channel adapters support it.
Announce context
Announce context is standardized into a stable internal event block:
| Field | Source |
|---|---|
| Source | subagent or cron |
| Session ids | Child session key/id |
| Type | Announce type + task label |
| Status | Derived from runtime outcome (ok, error, timeout, or unknown), not inferred from model text |
| Result content | Latest visible assistant text from the child |
| Follow-up | Instruction describing when to reply vs stay silent |
Terminal failed runs report failure status without replaying captured reply text. Tool/toolResult output is not promoted into child result text.
Stats line
Announce payloads end with a stats line, even when wrapped:
- Runtime (e.g.
runtime 5m12s). - Token usage (input/output/total).
- Estimated cost when model pricing is configured (
models.providers.*.models[].cost). sessionKey,sessionId, and transcript path so the main agent can fetch history viasessions_historyor inspect the file on disk.
Internal metadata is meant for orchestration only; user-facing replies should be rewritten in normal assistant voice.
Why prefer sessions_history
sessions_history is the safer orchestration path for reading a child's transcript from within an agent turn:
- Redacts credential/token-like text even when general-purpose log redaction is disabled.
- Truncates long text blocks (4000 chars per block) and drops thinking signatures, reasoning replay payloads, and inline image data.
- Caps returned messages at 80 KB; older rows can be dropped or an oversized row replaced with
[sessions_history omitted: message too large]. - Use
nextOffsetwhen present to page backward through older transcript windows. - Returns structured history rather than
/subagents log's plain chat lines. Reasoning tags,<relevant-memories>/<relevant_memories>scaffolding, and tool-call XML can remain in message text:sessions_historydoes not apply the log command's assistant prose sanitizer. See Session tools for the recall guarantees. - Raw on-disk transcript inspection is the fallback when you need the full byte-for-byte transcript.
Tool policy
Sub-agents use the same profile and tool-policy pipeline as the parent or target agent first. After that, OpenClaw applies the sub-agent restriction layer.
Sub-agents always lose gateway, agents_list, session_status, cron, message, sessions_send, and the conversations_* tools regardless of depth or role (system-level/interactive tools, direct delivery surfaces, or tools the main agent should coordinate). This hard-deny layer is derived from the persisted sub-agent session envelope on every turn, including resumed and visible dashboard sessions; ordinary allow/alsoAllow entries cannot override it. Hidden launches also disable message before tool construction as defense in depth. Leaf sub-agents (default depth-1 behavior, and always at depth 2) additionally lose subagents, sessions_list, sessions_history, and sessions_spawn, so sub-agent communication stays on the announce chain.
sessions_history remains a bounded, redacted recall view here too, it is neither a raw transcript dump nor a prose-only rendering.
When maxSpawnDepth >= 2, depth-1 orchestrator sub-agents additionally receive sessions_spawn, subagents, sessions_list, and sessions_history so they can manage their children.
Override via config
{
agents: {
defaults: {
subagents: {
maxConcurrent: 1,
},
},
},
tools: {
subagents: {
tools: {
// deny wins
deny: ["gateway", "cron"],
// if allow is set, it becomes allow-only (deny still wins)
// allow: ["read", "exec", "process"]
},
},
},
}
tools.subagents.tools.allow acts as a terminal allow-only filter. It can restrict the tool set that has already been resolved, but it cannot restore a tool that tools.profile removed. As an example, tools.profile: "coding" includes web_search/web_fetch yet omits the browser tool. To grant browser automation to coding-profile sub-agents, add browser at the profile stage:
{
tools: {
profile: "coding",
alsoAllow: ["browser"],
},
}
Use per-agent agents.entries.*.tools.alsoAllow: ["browser"] when browser automation should be available to only one agent.
Concurrency
Sub-agents rely on a dedicated in-process queue lane:
- Lane name:
subagent - Concurrency:
agents.defaults.subagents.maxConcurrent(default8)
Retained blocked completions also shield the gateway from unbounded fan-out. OpenClaw issues a warning when the delivery backlog hits 25 and halts new subagent spawns at 50 until operators retry or dismiss enough retained deliveries. It does not discard results to free up space.
Liveness and recovery
OpenClaw does not interpret the absence of endedAt as definitive evidence that a
sub-agent remains active. Unended runs older than the stale-run window
(2 hours, or the configured run timeout plus a short grace period,
whichever is longer) no longer count as active/pending in /subagents list,
status summaries, descendant completion gating, and per-session
concurrency checks.
Following a gateway restart, stale unended restored runs are pruned unless
their child session is marked abortedLastRun: true. Restart-aborted
runs stay registered for the sub-agent orphan recovery flow: stale
runs are finalized without a resume, while fresh child sessions receive
a synthetic resume message before the aborted marker is cleared.
Automatic restart recovery is bounded per child session. If the same
sub-agent child is accepted for orphan recovery repeatedly inside the
rapid re-wedge window, OpenClaw persists a recovery tombstone on that
session and stops auto-resuming it on later restarts. Run
openclaw tasks maintenance --apply to reconcile the task record, or
openclaw doctor --fix to clear stale aborted recovery flags on
tombstoned sessions.
Note
If a sub-agent spawn fails with Gateway
PAIRING_REQUIRED/scope-upgrade, check the RPC caller before editing pairing state. Internalsessions_spawncoordination dispatches in process when the caller is already running inside the gateway request context, so it does not open a loopback WebSocket or depend on the CLI's paired-device scope baseline. Callers outside the gateway process still use the WebSocket fallback asclient.id: "gateway-client"withclient.mode: "backend"over direct loopback shared-token/password auth. Remote callers, explicitdeviceIdentity, explicit device-token paths, and browser/node clients still need normal device approval for scope upgrades.
Stopping
- Sending
/stopin the requester chat aborts the requester session and stops any active sub-agent runs spawned from it, cascading to nested children.
Limitations
- Direct announce attempts are best-effort, but admitted session-queued completion handoffs and their owner/task projections survive gateway restarts in the shared SQLite state database.
- Sub-agents still share the same gateway process resources; treat
maxConcurrentas a safety valve. sessions_spawnreturns{ status: "accepted", runId, childSessionKey }when startup is accepted, without waiting for the child task to finish. Cloud-worker spawns can wait for provisioning before returning this receipt.- Sub-agent context only injects
AGENTS.md(noSOUL.md,IDENTITY.md,USER.md,MEMORY.md, orBOOTSTRAP.md). Its## Toolssection carries environment-specific notes. Codex-native subagents follow the same boundary through nativeAGENTS.mddiscovery, while parent-only persona, identity, and user files are injected as turn-scoped collaboration instructions so children do not clone them. - Maximum nesting depth is 5 (
maxSpawnDepthrange: 1-5). Depth 2 is recommended for most use cases. maxChildrenPerAgentcaps active children per session (default5, range1-20).