Consider one concrete job: the main Agent is asked to read a repository, run tests, and recommend a fix. Source searches can run independently, tests may take time, and the final answer still has to preserve the current conversation’s goal, authority, and tone. Turning every worker into a complete configured Agent copies far more long-lived state than the task needs. Letting each worker message Discord directly bypasses the parent’s synthesis and target selection. Treating Claude Code or another external runtime as a native subagent misattributes tool, login, and filesystem ownership.

OpenClaw resolves those tensions by separating topology from inheritance. A configured Agent is a durable identity boundary. A native subagent is a controlled child run created by an existing session. An ACP session is routed and supervised by OpenClaw but executed by an external harness. All three may look like “another model is working,” yet their contracts are intentionally different.

Reading contract.By the end, you should be able to explain why binding is not delegation; why sessions_spawn returns immediately; what isolated and fork copy; why workspace and cwd are separate; how child tools are narrowed from the parent’s effective surface; why sessions_send is not user delivery; how completion chooses wake, handoff, or a durable queue; and when native subagents or ACP are the appropriate runtime.

Evidence boundary.This article is pinned to e6b4264. Behaviors described as guarantees are grounded in that snapshot and the official documentation. Advice about task granularity and model cost is called out as engineering judgment, not runtime behavior.

1. How the three multi-agent paths establish relationships

1.1 Configured Agents, native subagents, and ACP differ

TopologySelected or created byMain state boundaryBest fit
Configured AgentStatic configuration and channel bindingOwn workspace, agentDir, auth/model registry, and session storeLong-lived personas, accounts, or trust domains
Native subagentA parent session calling sessions_spawnNew child session and run under OpenClaw-native policyBounded work delegated inside one conversation
ACP harnesssessions_spawn(runtime="acp") or the ACP control surfaceOpenClaw owns routing/binding/delivery; the harness owns execution semanticsExternal runtimes such as Claude Code, Gemini CLI, or OpenCode

The first row is not a child task. A binding chooses an agentId from channel, account, peer, guild, and related ingress attributes; session routing then proceeds under that Agent. The second row is delegation: a running Agent creates a child identity and hands it a bounded contract. The third can also be launched through sessions_spawn, but the child’s actual executor sits behind the ACP backend.

This distinction governs every later claim. The isolation promised by a configured-Agent boundary does not automatically describe a spawn, and the tool policy of a native child does not prove which native tools an ACP harness exposes.

1.2 A configured Agent has separate workspace, auth, and sessions

A configured Agent is more than a different system prompt. Its workspace supplies AGENTS.md, SOUL.md, USER.md, and memory bootstrap files. Its agentDir holds authentication profiles, model registry state, and related private configuration. Its sessions live under the Agent’s own store. The official multi-agent documentation warns against reusing one agentDir, because credentials and model state would be mixed.

The workspace is not, by itself, a security sandbox. It is primarily a working directory and bootstrap source; container placement and host-path access still come from sandbox and filesystem policy. In the other direction, a plugin-owned store can remain Gateway-global unless the plugin explicitly scopes it per Agent. Two personas in the UI do not prove that every extension’s data has been partitioned.

Use a configured Agent when you need a durable identity, a separate login, or a different inbox. Do not manufacture a whole persona merely to parallelize one repository search.

1.3 Binding chooses the handler for an external message

A binding answers “who owns this external message at the start?” Discord account A might route to agentId=ops, while Telegram account B routes to agentId=personal. Once selected, session keys, bootstrap, authentication, and policy are resolved around that Agent.

A binding does not create a parent/child edge, return a runId, or install a completion path. Binding precedence belongs to routing; subagent visibility, cancellation, and announce flow follow the spawn tree. Confusing the two often produces an awkward design: a complete second Agent exists, but no lifecycle relationship can bring its work back to the current requester.

1.4 sessions_spawn returns runId and childSessionKey

Isolated and fork are alternative context sources; a separate path narrows parent effective tools for the child, while workspace and cwd remain distinct
The illustrated child has sandboxing enabled. Actual configuration and required isolation determine whether that boundary applies. Context selection neither replaces tool policy nor expands tool authority.

sessions-spawn-tool.ts accepts the task, target agent or model, cwd, timeout, thread and mode, cleanup, sandbox, and context settings. Execution continues in the background. A successful call returns accepted, with runId and childSessionKey as its essential fields.

{
  "status": "accepted",
  "runId": "...",
  "childSessionKey": "agent:main:subagent:...",
  "mode": "run"
}

This is not an incomplete synchronous result; it is the asynchronous protocol. The runId identifies one execution. The childSessionKey names the session that history, status, steering, and cancellation can address. A caller should not poll every few seconds inside the same turn. It can keep doing useful work, or yield when the only remaining dependency is the pushed completion.

A native key takes the form agent:<agentId>:subagent:<uuid>; ACP uses agent:<agentId>:acp:<uuid>. These keys are not decorative labels. Scope checks, ownership, visibility, persistence, and delivery all depend on them.

Two explicit options change what follows that receipt. completionTarget="parent" returns a hidden native child result in the original requester's private turn. collect=true belongs to a batch-collection path gated by tools.swarm.enabled, can accept outputSchema, and sends no individual completion notifications. Collection cannot be combined with visible, thread, or session mode. Not every spawn follows the same announce lifecycle.

2. What content, directories, and authority a child inherits

2.1 The child receives an explicit task with delegation provenance

An ordinary isolated child receives the delegated task as a [Subagent Task] message. A forked child may already have copied parent history, so the task is not necessarily the transcript's first message. The runtime system prompt supplies routing, tool, and completion rules separately. That provenance matters: the child can tell that a parent delegated this work rather than believing the end user directly opened another conversation.

The result that later reaches the parent is not promoted into a new user instruction either. Cross-session data carries tool-routed/internal provenance. It is evidence, a draft, or a status report for the parent to evaluate; it cannot override system instructions, owner policy, or the user’s current goal.

This boundary is especially important when the child reads untrusted pages or repositories. A returned string that says “ignore previous instructions” does not become trusted because it came through a subagent. The parent still treats the material as untrusted input.

2.2 isolated sends the task; fork copies the parent transcript

subagent-spawn-context.ts distinguishes isolated from fork. An isolated spawn starts a clean child session. It receives its own bootstrap, the explicit task, and current runtime context, but it does not silently inherit the parent transcript. That is usually cheaper and safer for bounded searches, tests, or file reviews.

context="fork" copies or forks the requester transcript so the child can reason from decisions already made in the conversation. The source requires requester and target to belong to the same Agent and requires a usable parent transcript. A preparation failure is an error; only an explicit context-engine skip can attach a fallback note and continue isolated. A channel’s thread-binding policy can select fork as the default for a thread-bound native spawn, but ordinary spawns are not implicitly forked.

Treat fork as a high-bandwidth dependency. Use it when the task truly cannot be expressed with a compact contract and must understand a long chain of prior decisions. Copying tens of thousands of tokens merely to convey three filenames increases cost, ambiguity, and exposure at the same time.

2.3 Workspace supplies rules; cwd locates this tool run

subagent-spawn-child-plan.ts resolves spawnedWorkspaceDir and spawnedCwd separately. The target Agent’s workspace determines bootstrap files, identity, and its default working area. cwd changes only the current directory of this run’s tools. Pointing a child at /repo/service-a does not turn arbitrary instructions in that directory into another Agent persona.

For cross-Agent spawns, the requester’s explicit workspace is not simply copied. The target resolves its own workspace, and authentication comes from the target Agent’s agentDir rather than copied parent tokens. An agentId override must therefore pass allowAgents; otherwise one delegation could cross the long-lived identity boundary.

Sandbox is also a resolved runtime fact, not a wish encoded in one field. sandbox="inherit" follows the effective configuration. sandbox="require" rejects the spawn unless the child really is sandboxed. A creator-role requirement of sandbox="required" also propagates into child creation policy; changing the target Agent or its ordinary configuration cannot discard it. For a sandboxed native child, arbitrary cwd overrides are not accepted in this snapshot; cwd must align with the target workspace so path semantics remain inside the isolation model.

2.4 Child tools can only narrow from the parent's effective tools

Ordinary configured tools first pass through the parent’s policy pipeline. At spawn time, that effective allowlist becomes an inherited upper bound. The child then applies subagent policy, sandbox policy, and target-Agent policy. agent-tools.policy.ts reads persisted inheritedToolAllow and inheritedToolDeny values on later child turns, so the boundary survives beyond initial construction.

If the parent did not have exec, a child allow rule cannot create it. If the parent had read and exec, subagent policy can still reduce the child to read. Delegation follows the same monotonic rule as the security chapter: no later layer may turn a prior deny into authority.

The current subagent hard-deny layer removes message, sessions_send, conversations_*, gateway, cron, and related interactive or direct-delivery tools at every depth. Ordinary allow/alsoAllow entries cannot restore them; resumed and visible child turns rebuild the restriction from the persisted envelope. The default maximum depth is 5. Children below the cap can receive bounded spawn/list/history capabilities, while children at the cap lose those controls too. Depth, active children per session, and group totals have separate admission limits.

2.5 Parallel children consume separate model, container, and host resources

Each isolated child owns a context window and token bill. It also consumes provider concurrency, Gateway task capacity, sandbox or container resources, and host CPU. Spawning ten mutually dependent steps merely makes them reread the same material, produce conflicting intermediate results, and push the integration burden back to the parent.

Good parallel units have independent inputs and independently verifiable outputs: inspect three modules, run three test groups that do not write the same directory, or review API, state, and security separately. When steps share an ordered write path or the next input depends on the previous output, parent-controlled sequencing is clearer. A cheaper child model can handle mechanical retrieval while the parent retains the difficult synthesis, but that is a scheduling decision rather than an automatic runtime optimization.

3. How results return to the parent and avoid duplicate delivery

3.1 Session visibility limits the readable session tree

Current session visibility defaults to all: unsandboxed sessions can inspect, search, and contact sessions across agents, potentially including other users’ transcripts. tools.agentToAgent can disable ordinary cross-agent access or restrict agent pairs. agent stays within one Agent. tree normally covers the current session and spawned subtree, but a canonical main caller also sees other same-agent sessions; use self for strict current-session scope. Requester-owned native and ACP children retain a special reachability exception under tree/all, so agent-pair policy is not the sole boundary.

A sandboxed caller under the default spawned-only session-tool clamp remains limited to its own spawn subtree. That is an independent sandbox restriction, not the global visibility default. Incognito sessions stay hidden from every cross-session tool.

sessions_history returns a bounded and processed transcript view, which is safer for cross-session recall than reading session files directly. Cancellation is scoped along the same ownership tree: a caller can stop descendants it controls, while a leaf cannot cancel another tree. Knowing or guessing a session key is never the same as owning it.

3.2 sessions_send selects context; its reply can still be delivered

sessions-send-tool.ts injects a message into another local model context for collaboration, follow-up, or steering. Its sessionKey, label, or agentId selects local model context rather than an external recipient. A later reply can nevertheless be announced through the established requester or target delivery context. Context selection does not imply a guarantee of no external delivery.

External delivery belongs to the conversation/message tool path and requires a resolved channel, account, target, and thread context. Use conversation/message tools when you need an exact external recipient. The sessions_send receipt separates target admission from later delivery.status. Steering retains the active run's completion owner; notify queues context without starting a turn. Native subagent hard-deny also removes message and sessions_send so a child cannot create its own direct-delivery route.

3.3 Ordinary completion hands off first; private results wait for the parent turn

Two successful completion handoffs: an ordinary top-level requester turn can use the established external route, while completionTarget=parent waits for the spawning turn and runs privately without automatic sending
This focus map compares ordinary and private successful handoffs. A sessions_yield batch can take ownership instead; nested requesters, conditional steering, durable queueing, and bounded retry are explained in the text.

When the child ends, the registry preserves its deliverable result and derives a stable idempotency key from childSessionKey and child runId. Ordinary completion dispatch tries direct handoff first, asking a requester follow-up Agent turn to handle the result. Only an eligible direct failure falls back to steering. Already-queued, ambiguous, and permanently failed results do not blindly try another delivery path. Non-completion notifications can steer first, so wake → handoff → queue is not one universal sequence.

By default, a top-level requester follow-up can deliver through its resolved channel/thread route; nested requesters receive internal injection for synthesis. For work that needs internal review first, use this minimal request:

{
  "task": "Inspect the test failure read-only; return evidence and recommendations",
  "context": "isolated",
  "completionTarget": "parent"
}

Private parent completion waits for the spawning parent turn to finish normally, then delivers in the original requester's private turn; sessions_yield transfers the result to its existing yielded batch. Neither the child result, parent final, nor generated media is automatically sent to a channel, though the parent may explicitly use its permitted message tools. This option supports hidden native one-shot runs only, not ACP, collect, visible, thread, session mode, or disabled completion notifications. A reset or replaced parent does not hand a private result to a new session at the same key.

sessions_yield complements pushed completion. Once the parent has launched background work and has nothing useful left to do, yield ends the current turn. Completion then arrives as the next model-visible event. This avoids polling tokens and keeps repeated “not finished yet” messages out of the transcript.

Nested delegation converges leaf → parent → parent. A leaf reports to its direct parent. An intermediate orchestrator may combine several leaf results and announce one coherent result upward. The external user need not receive every internal fan-out event.

3.4 Idempotency and durable delivery prevent duplicate insertion

announce-idempotency.ts derives a stable key from child and run identity. Beyond in-memory bookkeeping, the registry tracks execution, completion, and delivery. After network loss or a Gateway restart, recovery can distinguish “execution ended but completion was not delivered” from “the requester transcript already contains the delivery mirror,” preventing duplicate insertion.

Retry is bounded rather than endless. Retryable delivery maintains backoff and a deadline; a final blocked completion remains the canonical result for explicit retry or dismissal instead of disappearing silently. The architectural lesson is more durable than a particular timeout: execution terminal and delivery terminal are separate states. Work can be finished before its parent has observed the result.

3.5 The parent combines the result with the latest user goal

Announce extracts the child’s latest visible assistant result. Raw internal tool results are not automatically promoted into user-facing prose. Delivery preserves the child's complete visible final answer. The bounded lifecycle snapshot and the redacted, paginated sessions_history view are separate records, not a shared truncation rule. The parent combines the answer with the original request, other child results, and the newest conversation state, then decides what remains relevant. If the user changes direction while children are working, the parent can discard stale output instead of allowing a child to force delivery to an old target.

A useful task contract therefore names the goal, input boundary, expected output, and forbidden side effects. “Compare these three files read-only, return five differences with source paths, and make no edits” is a stronger isolated task than “look at the code.” A good result carries evidence and uncertainty rather than presenting a suggestion as an implemented fact.

4. How persistent sessions, external harnesses, and cleanup interact

4.1 Thread binding routes follow-ups to the same session

A one-shot mode="run" task ends after its result. A persistent channel session uses thread binding so later input in the same Discord thread or equivalent surface returns to the same child or harness. The binding maps a channel surface to a session; it does not move the runtime workspace into the chat platform.

A thread-bound native spawn may select fork context through channel policy. A persistent ACP session requires supported thread binding and an explicit session mode. When the binding closes or expires, new messages resume normal routing. Canceling an active turn stops the current execution; closing a session terminates the persistent relationship and clears its binding. Those operations should not be collapsed into one vague “stop.”

Persistent work also has a visible=true path: a normal sidebar conversation that users can revisit and steer, retaining parent navigation and completion relations without a channel thread. Use it when the user needs independently revisitable work; internal tests and reviews remain hidden children. projectId, projectGitUrl, and cwd are mutually exclusive source selectors; worktree can provide an independent checkout. Visible sessions do not accept ACP, thread, or thinking overrides and always keep the session. Persistence, execution placement, and external notification are separate choices.

4.2 An ACP harness owns its model, tools, and filesystem behavior

acp-spawn.ts still owns target selection, requester ownership, session identity, thread and mode, background task state, and completion delivery. Model login, model catalogs, filesystem behavior, and native tools belong to acpx and the selected harness. OpenClaw built-in and plugin tools are not silently injected into ACP; an explicit MCP bridge is required when that integration is desired.

ACP should only appear when the feature is enabled, its backend is loaded and healthy, and requester sandbox policy permits it. For a sandboxed requester, runtime="acp" is hidden or rejected because an external harness is not a natural extension of the current sandbox.

The selection rule is straightforward. Choose a native subagent when the worker should obey OpenClaw-native session, tool, and policy semantics. Choose ACP when the needed worker is an established external harness such as Claude Code, Gemini CLI, or OpenCode, and treat that runtime as another trust boundary. A shared childSessionKey shape does not imply a shared tool surface.

4.3 Cancellation, session cleanup, and result delivery are recorded separately

Cancel or kill addresses execution and can cascade down a caller-owned session tree. Cleanup decides whether the child session is kept or deleted after completion. Delivery records whether the requester received the completion. A task can therefore be “execution succeeded, child cleaned up, delivery still retrying,” or “execution canceled, cancellation result already delivered.”

Diagnosis should not stop at whether a child process is alive. Inspect the spawn receipt, registry run state, child transcript terminal result, requester delivery state, and any thread binding. Compressing those layers into one boolean produces ghost tasks and duplicate replies.

4.4 Diagnose creation, execution, completion, and delivery in order

binding / requester identity
  → sessions_spawn admission
  → target agent + childSessionKey
  → context / workspace / cwd / sandbox
  → inherited tool policy
  → child execution terminal
  → completion capture
  → completionTarget / notify / collect
  → direct handoff | conditional steer | durable queue
  → parent synthesis
  → private result | configured external delivery

If spawn is immediately forbidden, inspect depth, concurrency, allowAgents, sandbox requirements, and ACP availability. If the child starts with the wrong instructions, inspect the target workspace and context mode. If a tool is missing, take the intersection of the parent effective surface and subagent policy. If execution succeeded but the parent saw nothing, inspect idempotency and delivery state. Only after the parent has the result should you debug conversation target, thread, and channel permission.

This order is safer than “spawn it again.” A blind retry may create a second run. Only recovery keyed to the same replay or idempotency identity can safely continue the original task.

5. Nine multi-agent rules to carry forward

  1. Classify the topology first.A configured Agent, native subagent, and ACP harness are not synonyms.
  2. Binding owns ingress; spawn owns delegation.Only spawn creates a parent/child lifecycle.
  3. Treat accepted as a receipt.Keep runId and childSessionKey, then wait for pushed completion.
  4. Default to isolated.Fork a transcript only when a compact task contract cannot express the dependency.
  5. Separate workspace from cwd.The former supplies identity and bootstrap; the latter locates one run.
  6. Authority narrows downward.A child cannot obtain a tool or sandbox escape absent from the parent boundary.
  7. Separate context selection from recipient selection.sessions_send chooses local context but replies can announce through existing routes; use conversation/message for exact external recipients.
  8. Select completion behavior explicitly.Ordinary completion can deliver externally; internal review uses completionTarget="parent", while batch collection uses collect.
  9. Observe execution and delivery separately.A finished task and an observed completion are two terminal conditions.

The final article moves from the spawn tree to the time axis: how heartbeat, cron, and background tasks wake an Agent without a new user message; which state survives a Gateway restart; and how durable recovery avoids turning “always on” into duplicate execution.

Source references