mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 08:20:43 +00:00
docs(subagents): rewrite around AccordionGroup, ParamField, and Steps
The sub-agents doc was 412 lines of dense bullet lists describing spawn behavior, tool params, thread binding flow, allowlist rules, auto-archive behavior, announce semantics, and the sessions_history sanitization pipeline. Restructure for scan-first reading without losing reference detail: - Move spawn-behavior bullets into an AccordionGroup with four panels (Non-blocking + push-based; Manual-spawn delivery resilience; Completion handoff metadata; Modes and ACP runtime). - Convert sessions_spawn tool params into ParamField definitions so type/default/required render visually. - Wrap the thread-binding flow in a Steps component (spawn -> bind -> route -> inspect timeouts -> detach). - Convert manual thread controls into a 5-row table. - Convert allowlist fields (allowAgents, requireAgentId) into ParamField definitions. - Convert announce-context fields into a 6-row source/field table. - Surface the cost-budget guidance, sessions_spawn delivery-param exclusion, operational guidance, and the PAIRING_REQUIRED caller caveat as Note/Warning callouts where they were buried inline. - Sentence-case 'Tool Policy' to 'Tool policy' (heading-case fix). - Sentence-case 'Configuration Reference' link. - Alphabetize the Related list and add 'Background tasks' which was referenced inline but missing from Related. - Add sidebarTitle for explicit nav. Tool surface, depth tables, slash commands, defaults, allowlist semantics, sandbox-inheritance guard, per-depth tool policy, auto-archive timing, announce status sourcing, sessions_history normalization steps, concurrency lane, recovery rules, and limitations are unchanged. Pure restructure plus Mintlify upgrades.
This commit is contained in:
@@ -1,66 +1,18 @@
|
||||
---
|
||||
summary: "Sub-agents: spawning isolated agent runs that announce results back to the requester chat"
|
||||
summary: "Spawn isolated background agent runs that announce results back to the requester chat"
|
||||
read_when:
|
||||
- You want background/parallel work via the agent
|
||||
- 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
|
||||
title: "Sub-agents"
|
||||
sidebarTitle: "Sub-agents"
|
||||
---
|
||||
|
||||
Sub-agents are background agent runs spawned from an existing agent run. They run in their own session (`agent:<agentId>:subagent:<uuid>`) and, when finished, **announce** their result back to the requester chat channel. Each sub-agent run is tracked as a [background task](/automation/tasks).
|
||||
|
||||
## Slash command
|
||||
|
||||
Use `/subagents` to inspect or control sub-agent runs for the **current session**:
|
||||
|
||||
- `/subagents list`
|
||||
- `/subagents kill <id|#|all>`
|
||||
- `/subagents log <id|#> [limit] [tools]`
|
||||
- `/subagents info <id|#>`
|
||||
- `/subagents send <id|#> <message>`
|
||||
- `/subagents steer <id|#> <message>`
|
||||
- `/subagents spawn <agentId> <task> [--model <model>] [--thinking <level>]`
|
||||
|
||||
Thread binding controls:
|
||||
|
||||
These commands work on channels that support 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>`
|
||||
|
||||
`/subagents info` shows run metadata (status, timestamps, session id, transcript path, cleanup).
|
||||
Use `sessions_history` for a bounded, safety-filtered recall view; inspect the
|
||||
transcript path on disk when you need the raw full transcript.
|
||||
|
||||
### Spawn behavior
|
||||
|
||||
`/subagents spawn` starts a background sub-agent as a user command, not an internal relay, and it sends one final completion update back to the requester chat when the run finishes.
|
||||
|
||||
- The spawn command is non-blocking; it returns a run id immediately.
|
||||
- On completion, the sub-agent announces a summary/result message back to the requester chat channel.
|
||||
- Completion is push-based. Once spawned, do not poll `/subagents list`,
|
||||
`sessions_list`, or `sessions_history` in a loop just to wait for it to
|
||||
finish; inspect status only on-demand for debugging or intervention.
|
||||
- On completion, OpenClaw best-effort closes tracked browser tabs/processes opened by that sub-agent session before the announce cleanup flow continues.
|
||||
- For manual spawns, delivery is resilient:
|
||||
- OpenClaw tries direct `agent` delivery first with a stable idempotency key.
|
||||
- If direct delivery fails, it falls back to queue routing.
|
||||
- If queue routing is still not available, the announce is retried with a short exponential backoff before final give-up.
|
||||
- Completion 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
|
||||
- The completion handoff to the requester session is runtime-generated internal context (not user-authored text) and includes:
|
||||
- `Result` (latest visible `assistant` reply text, otherwise sanitized latest tool/toolResult text; terminal failed runs do not reuse captured reply text)
|
||||
- `Status` (`completed successfully` / `failed` / `timed out` / `unknown`)
|
||||
- compact runtime/token stats
|
||||
- a delivery instruction telling the requester agent to rewrite in normal assistant voice (not forward raw internal metadata)
|
||||
- `--model` and `--thinking` override defaults for that specific run.
|
||||
- Use `info`/`log` to inspect details and output after completion.
|
||||
- `/subagents spawn` is one-shot mode (`mode: "run"`). For persistent thread-bound sessions, use `sessions_spawn` with `thread: true` and `mode: "session"`.
|
||||
- For ACP harness sessions (Claude Code, Gemini CLI, OpenCode, or explicit Codex ACP/acpx), use `sessions_spawn` with `runtime: "acp"` when the tool advertises that runtime, and see [ACP Agents](/tools/acp-agents), especially the [ACP delivery model](/tools/acp-agents#delivery-model) when debugging completions or agent-to-agent loops. When the `codex` plugin 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 as `acpx` is loaded. `runtime: "acp"` expects an external ACP harness id, or an `agents.list[]` entry with `runtime.type="acp"`; use the default sub-agent runtime for normal OpenClaw config agents from `agents_list`.
|
||||
Sub-agents are background agent runs spawned from an existing agent run.
|
||||
They run in their own session (`agent:<agentId>:subagent:<uuid>`) and,
|
||||
when finished, **announce** their result back to the requester chat
|
||||
channel. Each sub-agent run is tracked as a
|
||||
[background task](/automation/tasks).
|
||||
|
||||
Primary goals:
|
||||
|
||||
@@ -69,101 +21,237 @@ Primary goals:
|
||||
- Keep the tool surface hard to misuse: sub-agents do **not** get session tools by default.
|
||||
- Support configurable nesting depth for orchestrator patterns.
|
||||
|
||||
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. You can configure this via `agents.defaults.subagents.model` or per-agent
|
||||
overrides. When a child genuinely needs the requester's current transcript, the agent can request
|
||||
<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. Configure via
|
||||
`agents.defaults.subagents.model` or per-agent overrides. When a child
|
||||
genuinely needs the requester's current transcript, the agent can request
|
||||
`context: "fork"` on that one spawn.
|
||||
</Note>
|
||||
|
||||
## Slash command
|
||||
|
||||
Use `/subagents` to inspect or control sub-agent runs for the **current
|
||||
session**:
|
||||
|
||||
```text
|
||||
/subagents list
|
||||
/subagents kill <id|#|all>
|
||||
/subagents log <id|#> [limit] [tools]
|
||||
/subagents info <id|#>
|
||||
/subagents send <id|#> <message>
|
||||
/subagents steer <id|#> <message>
|
||||
/subagents spawn <agentId> <task> [--model <model>] [--thinking <level>]
|
||||
```
|
||||
|
||||
`/subagents info` shows run metadata (status, timestamps, session id,
|
||||
transcript path, cleanup). Use `sessions_history` for a bounded,
|
||||
safety-filtered recall view; inspect the transcript path on disk when you
|
||||
need the raw full transcript.
|
||||
|
||||
### Thread binding controls
|
||||
|
||||
These commands work on channels that support persistent thread bindings.
|
||||
See [Thread supporting channels](#thread-supporting-channels) below.
|
||||
|
||||
```text
|
||||
/focus <subagent-label|session-key|session-id|session-label>
|
||||
/unfocus
|
||||
/agents
|
||||
/session idle <duration|off>
|
||||
/session max-age <duration|off>
|
||||
```
|
||||
|
||||
### Spawn behavior
|
||||
|
||||
`/subagents spawn` starts a background sub-agent as a user command (not an
|
||||
internal relay) and sends one final completion update back to the
|
||||
requester chat when the run finishes.
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Non-blocking, push-based completion">
|
||||
- The spawn command is non-blocking; it returns a run id immediately.
|
||||
- On completion, the sub-agent announces a summary/result message back to the requester chat channel.
|
||||
- Completion is push-based. Once spawned, do **not** poll `/subagents list`, `sessions_list`, or `sessions_history` in a loop just to wait for it to finish; inspect status only on-demand for debugging or intervention.
|
||||
- On completion, OpenClaw best-effort closes tracked browser tabs/processes opened by that sub-agent session before the announce cleanup flow continues.
|
||||
</Accordion>
|
||||
<Accordion title="Manual-spawn delivery resilience">
|
||||
- OpenClaw tries direct `agent` delivery first with a stable idempotency key.
|
||||
- If direct delivery fails, it falls back to queue routing.
|
||||
- If queue routing is still not available, the announce is retried with a short exponential backoff before final give-up.
|
||||
- Completion 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.
|
||||
</Accordion>
|
||||
<Accordion title="Completion handoff metadata">
|
||||
The completion handoff to the requester session is runtime-generated
|
||||
internal context (not user-authored text) and includes:
|
||||
|
||||
- `Result` — latest visible `assistant` reply text, otherwise sanitized latest tool/toolResult text. Terminal failed runs do not reuse captured reply text.
|
||||
- `Status` — `completed successfully` / `failed` / `timed out` / `unknown`.
|
||||
- Compact runtime/token stats.
|
||||
- A delivery instruction telling the requester agent to rewrite in normal assistant voice (not forward raw internal metadata).
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="Modes and ACP runtime">
|
||||
- `--model` and `--thinking` override defaults for that specific run.
|
||||
- Use `info`/`log` to inspect details and output after completion.
|
||||
- `/subagents spawn` is one-shot mode (`mode: "run"`). For persistent thread-bound sessions, use `sessions_spawn` with `thread: true` and `mode: "session"`.
|
||||
- For ACP harness sessions (Claude Code, Gemini CLI, OpenCode, or explicit Codex ACP/acpx), use `sessions_spawn` with `runtime: "acp"` when the tool advertises that runtime. See [ACP delivery model](/tools/acp-agents#delivery-model) when debugging completions or agent-to-agent loops. When the `codex` plugin 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 as `acpx` is loaded. `runtime: "acp"` expects an external ACP harness id, or an `agents.list[]` entry with `runtime.type="acp"`; use the default sub-agent runtime for normal OpenClaw config agents from `agents_list`.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Context modes
|
||||
|
||||
Native sub-agents start isolated unless the caller explicitly asks to fork the
|
||||
current transcript.
|
||||
Native sub-agents start isolated unless the caller explicitly asks to fork
|
||||
the current transcript.
|
||||
|
||||
| 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. This is the default and 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. |
|
||||
|
||||
Use `fork` sparingly. It is for context-sensitive delegation, not a replacement
|
||||
for writing a clear task prompt.
|
||||
Use `fork` sparingly. It is for context-sensitive delegation, not a
|
||||
replacement for writing a clear task prompt.
|
||||
|
||||
## Tool
|
||||
## Tool: `sessions_spawn`
|
||||
|
||||
Use `sessions_spawn`:
|
||||
Starts a sub-agent run with `deliver: false` on the global `subagent` lane,
|
||||
then runs an announce step and posts the announce reply to the requester
|
||||
chat channel.
|
||||
|
||||
- Starts a sub-agent run (`deliver: false`, global lane: `subagent`)
|
||||
- Then runs an announce step and posts the announce reply to the requester chat channel
|
||||
- Default model: inherits the caller unless you set `agents.defaults.subagents.model` (or per-agent `agents.list[].subagents.model`); an explicit `sessions_spawn.model` still wins.
|
||||
- Default thinking: inherits the caller unless you set `agents.defaults.subagents.thinking` (or per-agent `agents.list[].subagents.thinking`); an explicit `sessions_spawn.thinking` still wins.
|
||||
- Default run timeout: if `sessions_spawn.runTimeoutSeconds` is omitted, OpenClaw uses `agents.defaults.subagents.runTimeoutSeconds` when set; otherwise it falls back to `0` (no timeout).
|
||||
**Defaults:**
|
||||
|
||||
Tool params:
|
||||
- **Model:** inherits the caller unless you set `agents.defaults.subagents.model` (or per-agent `agents.list[].subagents.model`); an explicit `sessions_spawn.model` still wins.
|
||||
- **Thinking:** inherits the caller unless you set `agents.defaults.subagents.thinking` (or per-agent `agents.list[].subagents.thinking`); an explicit `sessions_spawn.thinking` still wins.
|
||||
- **Run timeout:** if `sessions_spawn.runTimeoutSeconds` is omitted, OpenClaw uses `agents.defaults.subagents.runTimeoutSeconds` when set; otherwise it falls back to `0` (no timeout).
|
||||
|
||||
- `task` (required)
|
||||
- `label?` (optional)
|
||||
- `agentId?` (optional; spawn under another agent id if allowed)
|
||||
- `runtime?` (`subagent|acp`, default `subagent`; `acp` is only for external ACP harnesses such as `claude`, `droid`, `gemini`, `opencode`, or explicitly requested Codex ACP/acpx, or for `agents.list[]` entries whose `runtime.type` is `acp`)
|
||||
- `model?` (optional; overrides the sub-agent model; invalid values are skipped and the sub-agent runs on the default model with a warning in the tool result)
|
||||
- `thinking?` (optional; overrides thinking level for the sub-agent run)
|
||||
- `runTimeoutSeconds?` (defaults to `agents.defaults.subagents.runTimeoutSeconds` when set, otherwise `0`; when set, the sub-agent run is aborted after N seconds)
|
||||
- `thread?` (default `false`; when `true`, requests channel thread binding for this sub-agent session)
|
||||
- `mode?` (`run|session`)
|
||||
- default is `run`
|
||||
- if `thread: true` and `mode` omitted, default becomes `session`
|
||||
- `mode: "session"` requires `thread: true`
|
||||
- `cleanup?` (`delete|keep`, default `keep`)
|
||||
- `sandbox?` (`inherit|require`, default `inherit`; `require` rejects spawn unless target child runtime is sandboxed)
|
||||
- `context?` (`isolated|fork`, default `isolated`; native sub-agents only)
|
||||
- `isolated` creates a clean child transcript and is the default.
|
||||
- `fork` branches the requester's current transcript into the child session so the child starts with the same conversation context.
|
||||
- Use `fork` only when the child needs the current transcript. For scoped work, omit `context`.
|
||||
- `sessions_spawn` does **not** accept channel-delivery params (`target`, `channel`, `to`, `threadId`, `replyTo`, `transport`). For delivery, use `message`/`sessions_send` from the spawned run.
|
||||
### Tool parameters
|
||||
|
||||
<ParamField path="task" type="string" required>
|
||||
The task description for the sub-agent.
|
||||
</ParamField>
|
||||
<ParamField path="label" type="string">
|
||||
Optional human-readable label.
|
||||
</ParamField>
|
||||
<ParamField path="agentId" type="string">
|
||||
Spawn under another agent id when allowed by `subagents.allowAgents`.
|
||||
</ParamField>
|
||||
<ParamField path="runtime" type='"subagent" | "acp"' default="subagent">
|
||||
`acp` is only for external ACP harnesses (`claude`, `droid`, `gemini`, `opencode`, or explicitly requested Codex ACP/acpx) and for `agents.list[]` entries whose `runtime.type` is `acp`.
|
||||
</ParamField>
|
||||
<ParamField path="model" type="string">
|
||||
Override the sub-agent model. Invalid values are skipped and the sub-agent runs on the default model with a warning in the tool result.
|
||||
</ParamField>
|
||||
<ParamField path="thinking" type="string">
|
||||
Override thinking level for the sub-agent run.
|
||||
</ParamField>
|
||||
<ParamField path="runTimeoutSeconds" type="number">
|
||||
Defaults to `agents.defaults.subagents.runTimeoutSeconds` when set, otherwise `0`. When set, the sub-agent run is aborted after N seconds.
|
||||
</ParamField>
|
||||
<ParamField path="thread" type="boolean" default="false">
|
||||
When `true`, requests channel thread binding for this sub-agent session.
|
||||
</ParamField>
|
||||
<ParamField path="mode" type='"run" | "session"' default="run">
|
||||
If `thread: true` and `mode` omitted, default becomes `session`. `mode: "session"` requires `thread: true`.
|
||||
</ParamField>
|
||||
<ParamField path="cleanup" type='"delete" | "keep"' default="keep">
|
||||
`"delete"` archives immediately after announce (still keeps the transcript via rename).
|
||||
</ParamField>
|
||||
<ParamField path="sandbox" type='"inherit" | "require"' default="inherit">
|
||||
`require` rejects spawn unless the target child runtime is sandboxed.
|
||||
</ParamField>
|
||||
<ParamField path="context" type='"isolated" | "fork"' default="isolated">
|
||||
`fork` branches the requester's current transcript into the child session. Native sub-agents only. Use `fork` only when the child needs the current transcript.
|
||||
</ParamField>
|
||||
|
||||
<Warning>
|
||||
`sessions_spawn` does **not** accept channel-delivery params (`target`,
|
||||
`channel`, `to`, `threadId`, `replyTo`, `transport`). For delivery, use
|
||||
`message`/`sessions_send` from the spawned run.
|
||||
</Warning>
|
||||
|
||||
## Thread-bound sessions
|
||||
|
||||
When thread bindings are enabled for a channel, a sub-agent can stay bound to a thread so follow-up user messages in that thread keep routing to the same sub-agent session.
|
||||
When thread bindings are enabled for a channel, a sub-agent can stay bound
|
||||
to a thread so follow-up user messages in that thread keep routing to the
|
||||
same sub-agent session.
|
||||
|
||||
### Thread supporting channels
|
||||
|
||||
- Discord (currently the only supported channel): supports persistent thread-bound subagent sessions (`sessions_spawn` with `thread: true`), manual thread controls (`/focus`, `/unfocus`, `/agents`, `/session idle`, `/session max-age`), and adapter keys `channels.discord.threadBindings.enabled`, `channels.discord.threadBindings.idleHours`, `channels.discord.threadBindings.maxAgeHours`, and `channels.discord.threadBindings.spawnSubagentSessions`.
|
||||
**Discord** is currently the only supported channel. It supports
|
||||
persistent thread-bound subagent sessions (`sessions_spawn` with
|
||||
`thread: true`), manual thread controls (`/focus`, `/unfocus`, `/agents`,
|
||||
`/session idle`, `/session max-age`), and adapter keys
|
||||
`channels.discord.threadBindings.enabled`,
|
||||
`channels.discord.threadBindings.idleHours`,
|
||||
`channels.discord.threadBindings.maxAgeHours`, and
|
||||
`channels.discord.threadBindings.spawnSubagentSessions`.
|
||||
|
||||
Quick flow:
|
||||
### Quick flow
|
||||
|
||||
1. Spawn with `sessions_spawn` using `thread: true` (and optionally `mode: "session"`).
|
||||
2. OpenClaw creates or binds a thread to that session target in the active channel.
|
||||
3. Replies and follow-up messages in that thread route to the bound session.
|
||||
4. Use `/session idle` to inspect/update inactivity auto-unfocus and `/session max-age` to control the hard cap.
|
||||
5. Use `/unfocus` to detach manually.
|
||||
<Steps>
|
||||
<Step title="Spawn">
|
||||
`sessions_spawn` with `thread: true` (and optionally `mode: "session"`).
|
||||
</Step>
|
||||
<Step title="Bind">
|
||||
OpenClaw creates or binds a thread to that session target in the active channel.
|
||||
</Step>
|
||||
<Step title="Route follow-ups">
|
||||
Replies and follow-up messages in that thread route to the bound session.
|
||||
</Step>
|
||||
<Step title="Inspect timeouts">
|
||||
Use `/session idle` to inspect/update inactivity auto-unfocus and
|
||||
`/session max-age` to control the hard cap.
|
||||
</Step>
|
||||
<Step title="Detach">
|
||||
Use `/unfocus` to detach manually.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
Manual controls:
|
||||
### Manual controls
|
||||
|
||||
- `/focus <target>` binds the current thread (or creates one) to a sub-agent/session target.
|
||||
- `/unfocus` removes the binding for the current bound thread.
|
||||
- `/agents` lists active runs and binding state (`thread:<id>` or `unbound`).
|
||||
- `/session idle` and `/session max-age` only work for focused bound threads.
|
||||
| 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 (`thread:<id>` or `unbound`) |
|
||||
| `/session idle` | Inspect/update idle auto-unfocus (focused bound threads only) |
|
||||
| `/session max-age` | Inspect/update hard cap (focused bound threads only) |
|
||||
|
||||
Config switches:
|
||||
### 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.
|
||||
- **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](#thread-supporting-channels) above.
|
||||
|
||||
See [Configuration Reference](/gateway/configuration-reference) and [Slash commands](/tools/slash-commands) for current adapter details.
|
||||
See [Configuration reference](/gateway/configuration-reference) and
|
||||
[Slash commands](/tools/slash-commands) for current adapter details.
|
||||
|
||||
Allowlist:
|
||||
### Allowlist
|
||||
|
||||
- `agents.list[].subagents.allowAgents`: list of agent ids that can be targeted via `agentId` (`["*"]` to allow any). Default: only the requester agent.
|
||||
- `agents.defaults.subagents.allowAgents`: default target-agent allowlist used when the requester agent does not set its own `subagents.allowAgents`.
|
||||
- Sandbox inheritance guard: if the requester session is sandboxed, `sessions_spawn` rejects targets that would run unsandboxed.
|
||||
- `agents.defaults.subagents.requireAgentId` / `agents.list[].subagents.requireAgentId`: when true, block `sessions_spawn` calls that omit `agentId` (forces explicit profile selection). Default: false.
|
||||
<ParamField path="agents.list[].subagents.allowAgents" type="string[]">
|
||||
List of agent ids that can be targeted via `agentId` (`["*"]` allows any). Default: only the requester agent.
|
||||
</ParamField>
|
||||
<ParamField path="agents.defaults.subagents.allowAgents" type="string[]">
|
||||
Default target-agent allowlist used when the requester agent does not set its own `subagents.allowAgents`.
|
||||
</ParamField>
|
||||
<ParamField path="agents.defaults.subagents.requireAgentId" type="boolean" default="false">
|
||||
Block `sessions_spawn` calls that omit `agentId` (forces explicit profile selection). Per-agent override: `agents.list[].subagents.requireAgentId`.
|
||||
</ParamField>
|
||||
|
||||
Discovery:
|
||||
If the requester session is sandboxed, `sessions_spawn` rejects targets
|
||||
that would run unsandboxed.
|
||||
|
||||
- Use `agents_list` to see which agent ids are currently allowed for `sessions_spawn`. The response includes each listed agent's effective model and embedded runtime metadata so callers can distinguish PI, Codex app-server, and other configured native runtimes.
|
||||
### Discovery
|
||||
|
||||
Auto-archive:
|
||||
Use `agents_list` to see which agent ids are currently allowed for
|
||||
`sessions_spawn`. The response includes each listed agent's effective
|
||||
model and embedded runtime metadata so callers can distinguish PI, Codex
|
||||
app-server, and other configured native runtimes.
|
||||
|
||||
- Sub-agent sessions are automatically archived after `agents.defaults.subagents.archiveAfterMinutes` (default: 60).
|
||||
### Auto-archive
|
||||
|
||||
- Sub-agent sessions are automatically archived after `agents.defaults.subagents.archiveAfterMinutes` (default `60`).
|
||||
- Archive uses `sessions.delete` and renames the transcript to `*.deleted.<timestamp>` (same folder).
|
||||
- `cleanup: "delete"` archives immediately after announce (still keeps the transcript via rename).
|
||||
- Auto-archive is best-effort; pending timers are lost if the gateway restarts.
|
||||
@@ -173,9 +261,10 @@ Auto-archive:
|
||||
|
||||
## Nested sub-agents
|
||||
|
||||
By default, sub-agents cannot spawn their own sub-agents (`maxSpawnDepth: 1`). You can enable one level of nesting by setting `maxSpawnDepth: 2`, which allows the **orchestrator pattern**: main → orchestrator sub-agent → worker sub-sub-agents.
|
||||
|
||||
### How to enable
|
||||
By default, sub-agents cannot spawn their own sub-agents
|
||||
(`maxSpawnDepth: 1`). Set `maxSpawnDepth: 2` to enable one level of
|
||||
nesting — the **orchestrator pattern**: main → orchestrator sub-agent →
|
||||
worker sub-sub-agents.
|
||||
|
||||
```json5
|
||||
{
|
||||
@@ -204,39 +293,43 @@ By default, sub-agents cannot spawn their own sub-agents (`maxSpawnDepth: 1`). Y
|
||||
|
||||
Results flow back up the chain:
|
||||
|
||||
1. Depth-2 worker finishes → announces to its parent (depth-1 orchestrator)
|
||||
2. Depth-1 orchestrator receives the announce, synthesizes results, finishes → announces to main
|
||||
3. Main agent receives the announce and delivers to the user
|
||||
1. Depth-2 worker finishes → announces to its parent (depth-1 orchestrator).
|
||||
2. Depth-1 orchestrator receives the announce, synthesizes results, finishes → announces to main.
|
||||
3. Main agent receives the announce and delivers to the user.
|
||||
|
||||
Each level only sees announces from its direct children.
|
||||
|
||||
Operational guidance:
|
||||
|
||||
- Start child work once and wait for completion events instead of building poll
|
||||
loops around `sessions_list`, `sessions_history`, `/subagents list`, or
|
||||
`exec` sleep commands.
|
||||
- `sessions_list` and `/subagents list` keep child-session relationships focused
|
||||
on live work: live children remain attached, ended children stay visible for a
|
||||
short recent window, and stale store-only child links are ignored after their
|
||||
freshness window. This prevents old `spawnedBy` / `parentSessionKey` metadata
|
||||
from resurrecting ghost children after restart.
|
||||
- If a child completion event arrives after you already sent the final answer,
|
||||
the correct follow-up is the exact silent token `NO_REPLY` / `no_reply`.
|
||||
<Note>
|
||||
**Operational guidance:** start child work once and wait for completion
|
||||
events instead of building poll loops around `sessions_list`,
|
||||
`sessions_history`, `/subagents list`, or `exec` sleep commands.
|
||||
`sessions_list` and `/subagents list` keep child-session relationships
|
||||
focused on live work — live children remain attached, ended children stay
|
||||
visible for a short recent window, and stale store-only child links are
|
||||
ignored after their freshness window. This prevents old `spawnedBy` /
|
||||
`parentSessionKey` metadata from resurrecting ghost children after
|
||||
restart. If a child completion event arrives after you already sent the
|
||||
final answer, the correct follow-up is the exact silent token
|
||||
`NO_REPLY` / `no_reply`.
|
||||
</Note>
|
||||
|
||||
### Tool policy by depth
|
||||
|
||||
- Role and control scope are written into session metadata at spawn time. That keeps flat or restored session keys from accidentally regaining orchestrator privileges.
|
||||
- **Depth 1 (orchestrator, when `maxSpawnDepth >= 2`)**: Gets `sessions_spawn`, `subagents`, `sessions_list`, `sessions_history` so it can manage its children. Other session/system tools remain denied.
|
||||
- **Depth 1 (leaf, when `maxSpawnDepth == 1`)**: No session tools (current default behavior).
|
||||
- **Depth 2 (leaf worker)**: No session tools — `sessions_spawn` is always denied at depth 2. Cannot spawn further children.
|
||||
- **Depth 1 (orchestrator, when `maxSpawnDepth >= 2`):** gets `sessions_spawn`, `subagents`, `sessions_list`, `sessions_history` so it can manage its children. Other session/system tools remain denied.
|
||||
- **Depth 1 (leaf, when `maxSpawnDepth == 1`):** no session tools (current default behavior).
|
||||
- **Depth 2 (leaf worker):** no session tools — `sessions_spawn` is always denied at depth 2. Cannot spawn further children.
|
||||
|
||||
### Per-agent spawn limit
|
||||
|
||||
Each agent session (at any depth) can have at most `maxChildrenPerAgent` (default: 5) active children at a time. This prevents runaway fan-out from a single orchestrator.
|
||||
Each agent session (at any depth) can have at most `maxChildrenPerAgent`
|
||||
(default `5`) active children at a time. This prevents runaway fan-out
|
||||
from a single orchestrator.
|
||||
|
||||
### Cascade stop
|
||||
|
||||
Stopping a depth-1 orchestrator automatically stops all its depth-2 children:
|
||||
Stopping a depth-1 orchestrator automatically stops all its depth-2
|
||||
children:
|
||||
|
||||
- `/stop` in the main chat stops all depth-1 agents and cascades to their depth-2 children.
|
||||
- `/subagents kill <id>` stops a specific sub-agent and cascades to its children.
|
||||
@@ -250,7 +343,8 @@ Sub-agent auth is resolved by **agent id**, not by session type:
|
||||
- The auth store is loaded from that agent's `agentDir`.
|
||||
- The main agent's auth profiles are merged in as a **fallback**; agent profiles override main profiles on conflicts.
|
||||
|
||||
Note: the merge is additive, so main profiles are always available as fallbacks. Fully isolated auth per agent is not supported yet.
|
||||
The merge is additive, so main profiles are always available as
|
||||
fallbacks. Fully isolated auth per agent is not supported yet.
|
||||
|
||||
## Announce
|
||||
|
||||
@@ -258,71 +352,87 @@ Sub-agents report back via an announce step:
|
||||
|
||||
- The announce step runs inside the sub-agent session (not the requester session).
|
||||
- If the sub-agent replies exactly `ANNOUNCE_SKIP`, nothing is posted.
|
||||
- If the latest assistant text is the exact silent token `NO_REPLY` / `no_reply`,
|
||||
announce output is suppressed even if earlier visible progress existed.
|
||||
- Otherwise delivery depends on requester depth:
|
||||
- top-level requester sessions use a follow-up `agent` call with external delivery (`deliver=true`)
|
||||
- nested requester subagent sessions receive an internal follow-up injection (`deliver=false`) so the orchestrator can synthesize child results in-session
|
||||
- if a nested requester subagent session is gone, OpenClaw falls back to that session's requester when 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. That keeps completions on the right chat/topic even when the completion origin only identifies the channel.
|
||||
- Child completion aggregation is scoped to the current requester run when building nested completion findings, preventing stale prior-run child outputs from leaking into the current announce.
|
||||
- Announce replies preserve thread/topic routing when available on channel adapters.
|
||||
- Announce context is normalized to a stable internal event block:
|
||||
- source (`subagent` or `cron`)
|
||||
- child session key/id
|
||||
- announce type + task label
|
||||
- status line derived from runtime outcome (`success`, `error`, `timeout`, or `unknown`)
|
||||
- result content selected from the latest visible assistant text, otherwise sanitized latest tool/toolResult text; terminal failed runs report failure status without replaying captured reply text
|
||||
- a follow-up instruction describing when to reply vs. stay silent
|
||||
- `Status` is not inferred from model output; it comes from runtime outcome signals.
|
||||
- On timeout, if the child only got through tool calls, announce can collapse that history into a short partial-progress summary instead of replaying raw tool output.
|
||||
- If the latest assistant text is the exact silent token `NO_REPLY` / `no_reply`, announce output is suppressed even if earlier visible progress existed.
|
||||
|
||||
Delivery depends on requester depth:
|
||||
|
||||
- Top-level requester sessions use a follow-up `agent` call with external delivery (`deliver=true`).
|
||||
- Nested requester subagent sessions receive an internal follow-up injection (`deliver=false`) so the orchestrator can synthesize child results in-session.
|
||||
- If a nested requester subagent session is gone, OpenClaw falls back to that session's requester when 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.
|
||||
That keeps completions on the right chat/topic even when the completion
|
||||
origin only identifies the channel.
|
||||
|
||||
Child completion aggregation is scoped to the current requester run when
|
||||
building nested completion findings, preventing stale prior-run child
|
||||
outputs from leaking into the current announce. Announce replies preserve
|
||||
thread/topic routing when available on channel adapters.
|
||||
|
||||
### Announce context
|
||||
|
||||
Announce context is normalized to 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 (`success`, `error`, `timeout`, or `unknown`) — **not** inferred from model text |
|
||||
| Result content | Latest visible assistant text, otherwise sanitized latest tool/toolResult text |
|
||||
| Follow-up | Instruction describing when to reply vs stay silent |
|
||||
|
||||
Terminal failed runs report failure status without replaying captured
|
||||
reply text. On timeout, if the child only got through tool calls, announce
|
||||
can collapse that history into a short partial-progress summary instead
|
||||
of replaying raw tool output.
|
||||
|
||||
### Stats line
|
||||
|
||||
Announce payloads include a stats line at the end (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 via `sessions_history` or inspect the file on disk)
|
||||
- Internal metadata is meant for orchestration only; user-facing replies should be rewritten in normal assistant voice.
|
||||
- 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 via `sessions_history` or 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:
|
||||
|
||||
- assistant recall is normalized first:
|
||||
- thinking tags are stripped
|
||||
- `<relevant-memories>` / `<relevant_memories>` scaffolding blocks are stripped
|
||||
- plain-text tool-call XML payload blocks such as `<tool_call>...</tool_call>`,
|
||||
`<function_call>...</function_call>`, `<tool_calls>...</tool_calls>`, and
|
||||
`<function_calls>...</function_calls>` are stripped, including truncated
|
||||
payloads that never close cleanly
|
||||
- downgraded tool-call/result scaffolding and historical-context markers are stripped
|
||||
- leaked model control tokens such as `<|assistant|>`, other ASCII
|
||||
`<|...|>` tokens, and full-width `<|...|>` variants are stripped
|
||||
- malformed MiniMax tool-call XML is stripped
|
||||
- credential/token-like text is redacted
|
||||
- long blocks can be truncated
|
||||
- very large histories can drop older rows or replace an oversized row with
|
||||
`[sessions_history omitted: message too large]`
|
||||
- raw on-disk transcript inspection is the fallback when you need the full byte-for-byte transcript
|
||||
- Assistant recall is normalized first: thinking tags stripped; `<relevant-memories>` / `<relevant_memories>` scaffolding stripped; plain-text tool-call XML payload blocks (`<tool_call>`, `<function_call>`, `<tool_calls>`, `<function_calls>`) stripped, including truncated payloads that never close cleanly; downgraded tool-call/result scaffolding and historical-context markers stripped; leaked model control tokens (`<|assistant|>`, other ASCII `<|...|>`, full-width `<|...|>`) stripped; malformed MiniMax tool-call XML stripped.
|
||||
- Credential/token-like text is redacted.
|
||||
- Long blocks can be truncated.
|
||||
- Very large histories can drop older rows or replace an oversized row with `[sessions_history omitted: message too large]`.
|
||||
- Raw on-disk transcript inspection is the fallback when you need the full byte-for-byte transcript.
|
||||
|
||||
## Tool Policy (sub-agent tools)
|
||||
## 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 use the same profile and tool-policy pipeline as the parent or
|
||||
target agent first. After that, OpenClaw applies the sub-agent restriction
|
||||
layer.
|
||||
|
||||
With no restrictive `tools.profile`, sub-agents get **all tools except session
|
||||
tools** and system tools:
|
||||
With no restrictive `tools.profile`, sub-agents get **all tools except
|
||||
session tools** and system tools:
|
||||
|
||||
- `sessions_list`
|
||||
- `sessions_history`
|
||||
- `sessions_send`
|
||||
- `sessions_spawn`
|
||||
|
||||
`sessions_history` remains a bounded, sanitized recall view here too; it is not
|
||||
a raw transcript dump.
|
||||
`sessions_history` remains a bounded, sanitized recall view here too — it
|
||||
is not a raw transcript dump.
|
||||
|
||||
When `maxSpawnDepth >= 2`, depth-1 orchestrator sub-agents additionally receive `sessions_spawn`, `subagents`, `sessions_list`, and `sessions_history` so they can manage their children.
|
||||
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:
|
||||
### Override via config
|
||||
|
||||
```json5
|
||||
{
|
||||
@@ -346,11 +456,12 @@ Override via config:
|
||||
}
|
||||
```
|
||||
|
||||
`tools.subagents.tools.allow` is a final allow-only filter. It can narrow the
|
||||
already-resolved tool set, but it cannot add back a tool removed by
|
||||
`tools.profile`. For example, `tools.profile: "coding"` includes
|
||||
`web_search`/`web_fetch`, but not the `browser` tool. To let coding-profile
|
||||
sub-agents use browser automation, add browser at the profile stage:
|
||||
`tools.subagents.tools.allow` is a final allow-only filter. It can narrow
|
||||
the already-resolved tool set, but it cannot **add back** a tool removed
|
||||
by `tools.profile`. For example, `tools.profile: "coding"` includes
|
||||
`web_search`/`web_fetch` but not the `browser` tool. To let
|
||||
coding-profile sub-agents use browser automation, add browser at the
|
||||
profile stage:
|
||||
|
||||
```json5
|
||||
{
|
||||
@@ -361,35 +472,39 @@ sub-agents use browser automation, add browser at the profile stage:
|
||||
}
|
||||
```
|
||||
|
||||
Use per-agent `agents.list[].tools.alsoAllow: ["browser"]` when only one agent
|
||||
should get browser automation.
|
||||
Use per-agent `agents.list[].tools.alsoAllow: ["browser"]` when only one
|
||||
agent should get browser automation.
|
||||
|
||||
## Concurrency
|
||||
|
||||
Sub-agents use a dedicated in-process queue lane:
|
||||
|
||||
- Lane name: `subagent`
|
||||
- Concurrency: `agents.defaults.subagents.maxConcurrent` (default `8`)
|
||||
- **Lane name:** `subagent`
|
||||
- **Concurrency:** `agents.defaults.subagents.maxConcurrent` (default `8`)
|
||||
|
||||
## Liveness and recovery
|
||||
|
||||
OpenClaw does not treat `endedAt` absence as permanent proof that a sub-agent
|
||||
is still alive. Unended runs older than the stale-run window stop counting as
|
||||
active/pending in `/subagents list`, status summaries, descendant completion
|
||||
gating, and per-session concurrency checks.
|
||||
OpenClaw does not treat `endedAt` absence as permanent proof that a
|
||||
sub-agent is still alive. Unended runs older than the stale-run window
|
||||
stop counting as active/pending in `/subagents list`, status summaries,
|
||||
descendant completion gating, and per-session concurrency checks.
|
||||
|
||||
After a gateway restart, stale unended restored runs are pruned unless their
|
||||
child session is marked `abortedLastRun: true`. Those restart-aborted child
|
||||
sessions remain recoverable through the sub-agent orphan recovery flow, which
|
||||
sends a synthetic resume message before clearing the aborted marker.
|
||||
After a gateway restart, stale unended restored runs are pruned unless
|
||||
their child session is marked `abortedLastRun: true`. Those
|
||||
restart-aborted child sessions remain recoverable through the sub-agent
|
||||
orphan recovery flow, which sends a synthetic resume message before
|
||||
clearing the aborted marker.
|
||||
|
||||
If a sub-agent spawn fails with Gateway `PAIRING_REQUIRED` / `scope-upgrade`,
|
||||
check the RPC caller before editing pairing state. Internal `sessions_spawn`
|
||||
coordination should connect as `client.id: "gateway-client"` with
|
||||
`client.mode: "backend"` over direct loopback shared-token/password auth; that
|
||||
path does not depend on the CLI's paired-device scope baseline. Remote callers,
|
||||
explicit `deviceIdentity`, explicit device-token paths, and browser/node clients
|
||||
<Note>
|
||||
If a sub-agent spawn fails with Gateway `PAIRING_REQUIRED` /
|
||||
`scope-upgrade`, check the RPC caller before editing pairing state.
|
||||
Internal `sessions_spawn` coordination should connect as
|
||||
`client.id: "gateway-client"` with `client.mode: "backend"` over direct
|
||||
loopback shared-token/password auth; that path does not depend on the
|
||||
CLI's paired-device scope baseline. Remote callers, explicit
|
||||
`deviceIdentity`, explicit device-token paths, and browser/node clients
|
||||
still need normal device approval for scope upgrades.
|
||||
</Note>
|
||||
|
||||
## Stopping
|
||||
|
||||
@@ -403,10 +518,11 @@ still need normal device approval for scope upgrades.
|
||||
- `sessions_spawn` is always non-blocking: it returns `{ status: "accepted", runId, childSessionKey }` immediately.
|
||||
- Sub-agent context only injects `AGENTS.md` + `TOOLS.md` (no `SOUL.md`, `IDENTITY.md`, `USER.md`, `HEARTBEAT.md`, or `BOOTSTRAP.md`).
|
||||
- Maximum nesting depth is 5 (`maxSpawnDepth` range: 1–5). Depth 2 is recommended for most use cases.
|
||||
- `maxChildrenPerAgent` caps active children per session (default: 5, range: 1–20).
|
||||
- `maxChildrenPerAgent` caps active children per session (default `5`, range `1–20`).
|
||||
|
||||
## Related
|
||||
|
||||
- [ACP agents](/tools/acp-agents)
|
||||
- [Multi-agent sandbox tools](/tools/multi-agent-sandbox-tools)
|
||||
- [Agent send](/tools/agent-send)
|
||||
- [Background tasks](/automation/tasks)
|
||||
- [Multi-agent sandbox tools](/tools/multi-agent-sandbox-tools)
|
||||
|
||||
Reference in New Issue
Block a user