Chapter I · AI agent stack

Sub-Agent Patterns: Orchestration, Spawning, and Gotchas

Tested on
OpenClaw 2026.10.4 (main on GPT 6.1 Sol, `browser-operator` on GPT 6 Luna), brigade 0.28.0 worker seats, Claude Code 2.1.302 via tmux relay, ACPX 0.19.x, T3 Code delegated tasks
Last updated
2026-10-17

How to use OpenClaw sub-agents effectively. Spawn patterns, model assignment, error handling, and the lessons we learned from breaking things.

Why Sub-Agents

Your main agent carries heavy context (memory, personality, conversation history) and is on the path of every incoming message. Sub-agents are isolated and disposable. They start clean, do one job, and report back.

Use sub-agents when:

Keep on the main agent when:

Where sub-agents live now. There are two layers, and most of the work moved to the second:

  1. In-gateway sub-agents (sessions_spawn, sessions_send). OpenClaw runs them in the same gateway as the main agent. I keep one real specialist here, a browser-operator agent with a minimal tool profile. The default sub-agent model is GPT 6 Luna and the gateway allows 8 concurrent.
  2. Worker seats outside the gateway. brigade run dispatches to roster seats (Codex, Claude, Antigravity, and others), and T3 Code sessions can launch delegated child tasks on any connected host. Building, refactoring, review, and research go here, because each seat has its own session, worktree, and artifact trail.

The earlier “coder” and “researcher” gateway agents are gone. A gateway sub-agent is the right tool for a narrow job that needs OpenClaw’s own tools and channel context. A seat is the right tool for repo work. Prefer the tmux relay or a Claude seat for review escalation. Keep ACP escalation for setups that explicitly need ACP.

Spawn Patterns

Pattern 1: Fire-and-Forget

Spawn a sub-agent for a task you don’t need immediate results from. The sub-agent runs asynchronously and announces completion to the user.

sessions_spawn(
  agentId: "browser-operator",
  task: "Open the status page and export the incident table as CSV.",
  runTimeoutSeconds: 600
)

Repo-scale equivalent on the worker fleet, detached so the orchestrator is not blocked:

brigade run --worker luna6 --read-only --detach \
  "Scan all Python files in /project for hardcoded credentials. Report findings."

Use when: Background tasks, long-running scans, builds you’ll check later.

Gotcha: You can’t chain dependent tasks this way. The main agent doesn’t see the sub-agent’s output.

Pattern 2: Send-and-Wait

Send a message to a persistent sub-agent session and block until it responds. Results come back inline.

sessions_send(
  agentId: "browser-operator",
  message: "Open the dashboard and tell me the current build status.",
  timeoutSeconds: 120
)

Use when: You need the result to continue your workflow. The main agent blocks, gets the result, and processes it in the same turn.

Timeout guidelines:

Pattern 3: Background Agent with Wrapper

For long-running coding agents or relay commands that might crash silently, use a wrapper script that guarantees notification on completion or failure.

#!/bin/bash
# agent-wrapper.sh - Always notifies, even on crash
LABEL="$1"
shift
START=$(date +%s)

"$@"
EXIT_CODE=$?

DURATION=$(( $(date +%s) - START ))
if [ $EXIT_CODE -eq 0 ]; then
  openclaw system event --text "${LABEL} completed in ${DURATION}s" --mode now
else
  openclaw system event --text "${LABEL} failed (exit ${EXIT_CODE}) after ${DURATION}s" --mode now
fi

Usage with the Claude Code tmux relay:

agent-wrapper.sh "claude review" \
  templates/ai-stack/claude-tmux-relay.sh send-file /tmp/claude-review-prompt.txt

Why this exists: Background coding agents and relay scripts can fail silently. The “I’ll run an openclaw system event when done” trick fails because the agent dies before executing it. The wrapper captures the exit code and ALWAYS fires the notification, whether the command succeeds or crashes.

Rule: Never spawn a background coding agent without the wrapper. No exceptions.

Model Assignment for Sub-Agents

Configure Agents in openclaw.json

On 2026.10 agents live under agents.entries (not agents.list), with agents.ownership set to explicit. Mine are main and browser-operator:

{
  "agents": {
    "defaults": {
      "subagents": { "maxConcurrent": 8, "archiveAfterMinutes": 60, "model": "openai/gpt-6-luna" }
    },
    "ownership": "explicit",
    "entries": {
      "main": {
        "model": { "primary": "openai/gpt-6.1-sol", "fallbacks": ["openai/gpt-6-luna", "anthropic/claude-opus-5-5"] },
        "subagents": { "allowAgents": ["browser-operator"] }
      },
      "browser-operator": {
        "model": { "primary": "openai/gpt-6-luna", "fallbacks": ["anthropic/claude-sonnet-5-5"] },
        "thinkingDefault": "low",
        "skills": ["browser-automation"],
        "tools": {
          "profile": "minimal",
          "alsoAllow": ["browser", "browser_credential_fill"],
          "elevated": { "enabled": false }
        }
      }
    }
  }
}

With ownership: "explicit" and more than one agent, also set agents.defaults.systemAgent.agentId (mine is main) or cover every channel with a binding. Otherwise traffic and jobs with no matching route have no owner. main is deliberately absent from its own allowAgents: listing it would let the orchestrator spawn more orchestrators instead of staying in the narrow lane.

subagents.allowAgents on the parent is the spawn allowlist: main can spawn browser-operator and itself, nothing else. Model aliases (sol61, luna6) are defined in agents.defaults.models. See multi-model orchestration for the full alias setup.

Claude review is not modeled as a fake gateway agent. Launch it through a Brigade seat or the tmux relay script so Claude stays inside the first-party harness.

Assignment Rules

Task TypeTargetWhy
Browser flows against a logged-in sessionbrowser-operator sub-agentMinimal tool profile, credential fill brokered, Luna is enough
Routine implementation, triageBrigade luna6 seatCheap, isolated worktree
Harder implementation slicesBrigade sol61 seatSame family as the orchestrator, higher reasoning
Fast read-only scouting and researchGemini Flash seat via Antigravity, or a read-only Luna seatKeeps orchestrator quota for judgment
Building, drafting, QA with ClaudeSonnet 5.5 seatDefault Claude worker
Architecture, final review, disputed callsOpus 5.5 seat or tmux relayStronger judgment on tradeoffs
Security reviewDedicated security-review seatSeparate route, independent of the builder
Second and third opinionsFable 5.1 or GPT 6 AstraDifferent model families catch different mistakes
Security evaluation of untrusted inputmainOrchestrator handles untrusted input
Quick one-linersmainNot worth spawn overhead

The roster file decides which of those seats are enabled on a given day, so check before promising one.

T3 Code delegated tasks

When the orchestrator is a T3 Code session, delegate_task launches a child task in its own session, and the fleet client can launch, monitor, steer, and recover child sessions on any connected host. Use it for Claude or Codex work that benefits from its own worktree. The same rules as every other sub-agent apply: bounded task, explicit model, a stated terminal state. Set the model on every dispatch instead of inheriting the session default, which is how an expensive model ends up doing grunt work.

Pre-Flight Check

Verify your agent configuration matches what you expect before spawning:

jq '.agents.entries | map_values(.model.primary)' ~/.openclaw/openclaw.json
# Expected: main on openai/gpt-6.1-sol (it may carry an @auth-profile suffix), browser-operator on openai/gpt-6-luna

brigade fleet models list
# Expected: seat table with enabled yes/no per seat

We’ve been burned multiple times by agent misconfigurations:

Always check before assuming. After any OpenClaw upgrade, re-verify agents.entries and plugins.entries. Both have been observed to reset.

Sub-Agent Isolation

What Sub-Agents Can’t Do

Isolated sub-agents in OpenClaw have limitations:

What Sub-Agents Are Good At

Error Handling

Sub-Agent Failures Are Silent by Default

If a sub-agent crashes, the main agent might never know. This is why the wrapper script pattern exists. For gateway sub-agents spawned via sessions_spawn, OpenClaw announces completion or failure, but timeouts and edge cases can cause silent drops. For Brigade runs, the run directory under .brigade/runs/<id> holds the artifacts and the exit state, and --detach writes run metadata before returning.

Timeout Strategy

Set appropriate timeouts and handle them:

# Short task - fail fast
sessions_send(agentId: "browser-operator", message: "...", timeoutSeconds: 30)

# Long task - generous timeout
sessions_send(agentId: "browser-operator", message: "...", timeoutSeconds: 300)

If a task times out, it might still be running. Check with:

subagents(action: "list")

Cancel a stuck run (and its descendants) by run id:

subagents(action: "cancel", runId: "<run-id>")

A wait timeout on subagents never cancels execution.

Orchestration Patterns

Sequential Pipeline

Main agent writes spec, dispatches a seat, reviews output:

1. Main: Write detailed spec for API routes
2. Main: brigade run --worker sol61 --worktree "<spec>"
3. Main: Review the changes.patch artifact
4. Main: Fix issues or approve and merge

Parallel Fan-Out

Multiple workers at once. Let Brigade plan and synthesize when the pieces are independent:

1. brigade run "Build the frontend and backend for X, and gather the API docs"
   → plans assignments, dispatches seats in parallel (scheduler: dag),
     synthesizes one answer
2. Main: integrate and review

A repository claim on the fleet hub prevents two runs from editing the same repo at once. brigade fleet claims lists live claims, and a live claim is ownership: wait for it instead of working around it.

Triage Escalation (Three Tiers)

A cheap lane screens, the orchestrator handles most work, and a Claude seat gets the quality-critical review tasks:

1. Luna-pinned cron job: screen incoming email - SKIP or ESCALATE
2. If ESCALATE: Main (Sol 6.1) reads and processes
3. If action needed:
   - Mechanical/code work → main dispatches a Brigade seat
   - Design/review/security analysis → main dispatches the Opus seat or a bounded tmux prompt
   - Research → read-only seat or the browser skill

Claude Code tmux Escalation Pattern

To reach Claude for review through a recoverable session, start or reuse a tmux session and send a bounded prompt:

CLAUDE_TMUX_SESSION=claude-code-review \
CLAUDE_WORKSPACE=/path/to/repo \
CLAUDE_PERMISSION_MODE=plan \
templates/ai-stack/claude-tmux-relay.sh start

templates/ai-stack/claude-tmux-relay.sh send \
  "Review this architecture for hidden failure modes, unclear ownership boundaries, and risky assumptions. Return structured notes with priorities. Do not edit files."

templates/ai-stack/claude-tmux-relay.sh capture -200

For one-shot reviews, put the prompt in a file and use send-file. claude --print works for simple scripted calls: pin --model opus, keep it to one flat pass, and pass Task and Agent through --disallowedTools. The relay gives you recoverable sessions.

ACP remains a compatibility path when OpenClaw needs a formal ACP endpoint. The tmux relay has no access to your main agent’s conversation history, so pass all necessary context in the prompt itself.

When to escalate: Design, PR review that needs taste, security analysis, architecture decisions. When NOT to escalate: Code generation, file scanning, bulk ops, anything mechanical. A Sol or Luna seat handles those faster without burning Claude quota.

Verification

# Check configured agents
echo "=== Agent Configuration ==="
jq '.agents.entries | map_values({model: .model.primary, elevated: (.tools.elevated.enabled // false)})' \
  ~/.openclaw/openclaw.json

# Check fallback chain and sub-agent defaults
echo ""
echo "=== Fallback Chain and Sub-agent Defaults ==="
jq '{model: .agents.defaults.model, subagents: .agents.defaults.subagents}' ~/.openclaw/openclaw.json

# Check the worker fleet
echo ""
echo "=== Brigade seats ==="
brigade fleet models list | head -10

# Check Claude Code tmux relay
echo ""
echo "=== Claude Code tmux ==="
tmux has-session -t claude-code-review && echo "tmux review session present" || echo "tmux review session not running"
test -x templates/ai-stack/claude-tmux-relay.sh && echo "relay template present"

# Check ACP plugin only if you still use ACP compatibility
echo ""
echo "=== ACPX compatibility ==="
jq '.plugins.allow | index("acpx") != null' ~/.openclaw/openclaw.json 2>/dev/null || true
test -x ~/.openclaw/vendor/acpx/node_modules/.bin/acpx && echo "acpx binary present" || true

# Check for wrapper script
echo ""
echo "=== Agent Wrapper ==="
if [ -f ~/.openclaw/workspace/scripts/agent-wrapper.sh ]; then
  echo "agent-wrapper.sh exists"
else
  echo "agent-wrapper.sh not found: background agents will fail silently"
fi

Gotchas

  1. Don’t spawn dependent sub-agents without coordination. Sub-agent A’s output isn’t automatically available to sub-agent B. Use the send-and-wait pattern, or one Brigade run, for sequential dependencies.

  2. Batch git operations. If multiple workers produce files, collect them in the main session and do one commit/push. Don’t have workers fighting over git. --worktree gives each run an isolated tree and a changes.patch to review.

  3. Sandbox limitations are per-agent. The main agent might have broad exec while sub-agents have a minimal tool profile. A task that works in the main session might fail in a sub-agent because of missing permissions.

  4. Context isolation is a feature, not a bug. Sub-agents starting clean means they don’t carry your 50K token conversation history. This is good for token efficiency and bad for tasks that need context. Choose the right pattern for the job.

  5. Auto-announce goes directly to the user. Fire-and-forget sub-agent output is announced to the user (via Telegram, Discord, etc.), not returned to the main agent. If you need the result in the main agent’s workflow, use send-and-wait instead.

  6. Auto-announce doesn’t trigger a parent turn. When a sub-agent finishes and auto-announces, the result appears in the main agent’s transcript but does NOT trigger a new inference turn. If the main says “I’ll do X when it gets back,” it structurally can’t follow through without another message. Chain via send-and-wait, or have the user nudge.

  7. Tool narration instead of tool calls. The orchestrator model occasionally narrates what it’s about to do (“I’m running the build now”) instead of calling the tool. The tool-narration-guard plugin catches this with run-level tracking and prependContext injection. See GPT 6.1 Sol orchestration.

  8. executionContract is not a substitute for the guard. strict-agentic is set in my agent defaults, but the 2026.10.4 build only treats gpt-5 family ids as supported, so it does nothing for GPT 6 models. Details in the Sol orchestration guide.