Designing context isolation between parent-child agents and result merging in OpenClaw
Honestly, when I first introduced the OpenClaw multi-agent architecture, I started the same way everyone does — just attaching a bunch of tools to a single parent agent. It worked, but the context window hit saturation fast. The problem was that a single agent was sequentially handling code review, style linting, and security scanning, with intermediate outputs from each step bleeding into the next round of reasoning. That experience is ultimately what pushed me to properly dig into the spawn_subagent-based architecture.
OpenClaw's subagent system is not simply about "handing tools off to children." The core architectural philosophy is isolating context, running tasks in parallel, and selectively merging only the results back into the parent context. Without a solid grasp of this, you end up with a multi-agent system that is slower and more expensive than a single agent.
A beam.ai analysis notes that a significant portion of multi-agent pilots fail within months of entering production (it is unclear whether the original cites Gartner or uses its own aggregated data, so treat the figures as a directional indicator rather than quoting them verbatim). The causes are not singular, but in my experience, poor context handoff design is a particularly frequent culprit. This post walks through a parent-child context handoff structure — with code — designed to avoid those pitfalls.
Why Context Isolation Is the Key
The Problem the Parent-Child Structure Solves
In the typical single-agent approach, the context window grows as tasks accumulate. Intermediate results, error messages, and tool call histories from previous steps pile up until the model effectively starts ignoring content from earlier in the context.
OpenClaw's subagent system solves this with context isolation. A child agent runs in an independent in-memory session, and when its work is done, it returns a summary message to the parent as a tool result. The parent never sees the child's intermediate steps — it only receives the final result.
There is an unofficial experiment gist that compares token usage, but the sample size is small and the reproduction conditions are not explicitly stated, so I will not cite the absolute numbers. The directional trend from repeated measurements in my own projects was consistent — the more independent the child sessions, the flatter the parent's cumulative token growth curve.
Three Collaboration Layers
The multi-agent collaboration structure OpenClaw provides is roughly divided into three tiers.
| Layer | Structure | Best Suited For |
|---|---|---|
| SubAgent | Parent 1 → Child N (vertical) | Delegating independent subtasks |
| Agent Teams | Horizontal role-based agents | Role separation within the same workflow |
| A2A (Google's Agent-to-Agent protocol, adopted by OpenClaw) | Cross-instance and cross-environment communication | Cross-environment agent collaboration |
A2A is not an OpenClaw-exclusive feature; it is Google's general-purpose inter-agent communication protocol, which OpenClaw has adopted in adapter form. This post focuses on the SubAgent layer — the most fundamental and most commonly used in practice.
Context Passing Modes: fork vs isolated
When calling spawn_subagent, you can choose how context is passed.
- fork mode (default): The parent's current transcript is cloned and passed to the child. Useful when the child needs prior context, but the larger the parent context, the higher the initial token cost of the child session.
- isolated mode: The child starts with a completely empty context. Choose this when the subtask is clearly defined and token efficiency matters.
In practice, defaulting to isolated mode and directly embedding the necessary context in the prompt produces more predictable behavior.
Context Handoff in Code
Parallel Delegation: PR Review Pipeline
When the parent agent receives a PR diff, it delegates security scanning, style linting, and logic verification to separate children in parallel.
The following is a conceptual example. Refer to the official documentation for actual OpenClaw method signatures.
# Conceptual example - OpenClaw Python SDK style
# Assumed return type: SpawnResult(summary: str, artifacts: dict)
import asyncio
from openclaw import spawn_subagent
async def run_pr_review(pr_diff: str) -> dict:
subtasks = [
{
"role": "security_reviewer",
"prompt": f"Review the following PR diff for security vulnerabilities:\n\n{pr_diff}",
},
{
"role": "style_linter",
"prompt": f"Review the following PR diff for code style violations:\n\n{pr_diff}",
},
{
"role": "logic_verifier",
"prompt": f"Review the following PR diff for logic errors and edge cases:\n\n{pr_diff}",
},
]
tasks = [
spawn_subagent(
prompt=task["prompt"],
context_mode="isolated",
timeout_seconds=300,
)
for task in subtasks
]
results = await asyncio.gather(*tasks)
return {
"security": results[0].summary,
"style": results[1].summary,
"logic": results[2].summary,
}Sequential Delegation: When Order Dependencies Exist
When the output of one child becomes the input to the next, parallel spawning is actually counterproductive. In this case, chain await calls explicitly to serialize execution.
# Conceptual example - sequential execution with order dependencies
async def run_incident_triage(alert: str) -> dict:
classify = await spawn_subagent(
prompt=f"Classify the following alert:\n\n{alert}",
context_mode="isolated",
timeout_seconds=120,
)
investigate = await spawn_subagent(
prompt=(
f"Classification result: {classify.summary}\n"
f"Original alert: {alert}\n"
"Based on this classification, investigate the relevant logs and metrics and summarize your findings."
),
context_mode="isolated",
timeout_seconds=600,
)
remediate = await spawn_subagent(
prompt=(
f"Investigation summary: {investigate.summary}\n"
"Suggest recommended actions step by step."
),
context_mode="isolated",
timeout_seconds=300,
)
return {
"classification": classify.summary,
"investigation": investigate.summary,
"remediation": remediate.summary,
}File-Based Context Handoff
As the number of child agents grows, tool results accumulate in the parent context and saturation becomes a problem. The pattern introduced in a DEV Community case study shares each step's artifacts via files.
# Conceptual example - file-based state sharing
import json
from pathlib import Path
HANDOFF_DIR = Path("~/.openclaw/coding-agent").expanduser()
HANDOFF_DIR.mkdir(parents=True, exist_ok=True)
def write_handoff(task_name: str, payload: dict) -> None:
path = HANDOFF_DIR / f"{task_name}.json"
path.write_text(json.dumps(payload, ensure_ascii=False, indent=2))
def read_handoff(task_name: str) -> dict:
path = HANDOFF_DIR / f"{task_name}.json"
return json.loads(path.read_text())
# Child 1: saves requirements analysis results to a file
async def requirements_agent(spec: str) -> str:
# Child agent logic: analyze spec to derive components and constraints
result = {
"components": ["auth", "billing"],
"constraints": ["PCI-DSS", "GDPR"],
}
write_handoff("requirements", result)
return "requirements.json written successfully"
# Child 2: reads the previous file and generates a list of implementation artifacts
async def implementation_agent() -> str:
req = read_handoff("requirements")
# Child agent logic: generate implementation artifact list based on req
write_handoff("implementation", {"files": [f"{c}.py" for c in req["components"]]})
return "implementation.json written successfully"The key to this pattern is that state is passed between children without going through the parent context. The parent only receives a tool result confirming whether each step succeeded; the actual artifacts are passed to the next child via the filesystem.
When File Handoff Hits Its Limits: Tracking State with DuckDB
File-based handoff works well for passing artifacts between steps, but once you start querying "what state is which task in" across multiple sessions, the logic for scanning directories keeps growing. At this point, keeping a lightweight embedded DB as a separate state index makes management much easier. DuckDB runs as a single file and supports plain SQL, making it a good fit for this purpose.
# Conceptual example - tracking subagent state with DuckDB
import json
from pathlib import Path
import duckdb
db_path = str(Path("~/.openclaw/sessions.db").expanduser())
conn = duckdb.connect(db_path)
conn.execute("""
CREATE TABLE IF NOT EXISTS subagent_tasks (
session_id VARCHAR,
task_name VARCHAR,
status VARCHAR, -- pending | running | done | failed
summary TEXT,
next_actions JSON,
created_at TIMESTAMP DEFAULT now()
)
""")
def register_task(session_id: str, task_name: str) -> None:
conn.execute(
"INSERT INTO subagent_tasks (session_id, task_name, status) VALUES (?, ?, 'pending')",
[session_id, task_name],
)
def update_task(session_id: str, task_name: str, summary: str, next_actions: list) -> None:
conn.execute(
"""UPDATE subagent_tasks
SET status = 'done', summary = ?, next_actions = ?
WHERE session_id = ? AND task_name = ?""",
[summary, json.dumps(next_actions), session_id, task_name],
)Full Execution Flow
Tradeoffs and Practical Pitfalls
Design Decision Branches
Summary of Pros and Cons
| Item | Advantage | Caveat |
|---|---|---|
| Context isolation | Prevents parent context pollution | In isolated mode, context must be passed directly to the child via the prompt |
| Parallel execution | Simultaneous processing of independent tasks | Can cause delays if outputs are interdependent |
| Role focus | Improved accuracy from concentration on a single task | If tasks are too narrow, re-delegation loops can occur |
| Per-role model selection | Adjusts cost by separating precision-heavy tasks from pattern-matching tasks | More backends means higher operational complexity |
| Depth limits | Blocks recursive spawning | Complex nested structures are not possible; grouping is required |
For per-role model selection, the NVIDIA Playbooks OpenClaw local LLM documentation is worth referencing. It includes examples of attaching different Ollama or vLLM backends per child — but always verify actual tag names (e.g., llama3.1:8b, qwen2.5:7b) against ollama list or each backend's model catalog at deploy time. Copy-pasting tags as they appear in docs usually fails.
Common Mistakes
1. Over-delegation
Splitting tasks too granularly makes it hard for children to do meaningful work. Delegating something like "review this function name" to a child yields results so thin that the parent ends up doing the same work again. Each child's task should be a unit that can "make its own judgement and produce a complete result."
2. Context saturation as child count grows
As the number of children grows, tool results accumulate in the parent context and saturation sets in — but the exact threshold depends on the model's context window, the length of each child's returned summary, and the size of the parent prompt. In my projects, once we exceeded five or six children, we either switched to the file-based handoff pattern or redesigned into a hierarchical structure with intermediate parents to group children. Rather than relying on absolute numbers, measuring parent context usage in real sessions and deciding empirically is the safer approach.
3. Not setting timeouts
The default subagent timeout (runTimeoutSeconds) varies by deployment environment but is generally short. Long-running data processing or complex code generation tasks will be cut off midway if this value is not explicitly adjusted. It is worth developing the habit of setting different timeouts based on the nature of each task.
Security: Prompt Injection via the Child Spawn Path
Academic literature on security threats in multi-agent systems (e.g., the arXiv case study on threat models for autonomous agents) repeatedly emphasizes prompt injection through the child agent spawn path. When a child processes external data — web pages, user input, API responses — it is advisable to add a sandboxing layer so that data is not included directly in the child's prompt. Isolated mode itself protects the parent context, but injection inside the child is a separate concern that must be defended against independently (input filtering, tool whitelisting, restricting what the child can spawn next, etc.).
Closing Thoughts
If I had to name the single habit that changed most after adopting multi-agent design, it would be this: define the child's output format first, then work backwards to divide the tasks. Instead of asking "how do I best break up this review," start by drawing "what fields in what shape does the parent need to receive in order to make the next decision" — and the child's role, prompt, context mode, and timeout almost define themselves. Context isolation and result merging only become meaningful when that output contract exists.
spawn_subagent itself has reached the point where it is production-ready, but structured context handoff conventions are still being refined by the community. Proposals to standardize handoff specs — like GitHub Discussion #30991 — continue to emerge, so if you hit a wall while implementing, checking those discussions first is recommended.
References
- Sub-agents — OpenClaw Official Documentation
- openclaw/openclaw — docs/tools/subagents.md (GitHub source)
- How I Built a Deterministic Multi-Agent Dev Pipeline Inside OpenClaw — DEV Community (deterministic pipeline case study)
- Multi-Agent Architectures in OpenClaw — personal research gist (unofficial, reproduction conditions not specified)
- Run OpenClaw with a Local LLM — NVIDIA Playbooks (local LLM backend integration)
- 6 Multi-Agent Orchestration Patterns for Production — beam.ai (mentions production failure rates; verify whether Gartner is cited in the original)
- Proposal: Structured context handoff for coding-agent skill — GitHub Discussion #30991 (community proposal stage)
- Security Threat Analysis for Autonomous Agents — arXiv case study