Skip to content

Orchestrator + sub-agents

Anything Chad can't reasonably finish in a single turn becomes a sub-agent spawn. The orchestrator contract is the heart of Chad's "do one thing well" pattern: each spawn is a discrete unit of work with a kind, a budget, a rendered prompt, and a structured result.

The six helpers

Six binaries form the orchestrator runtime. All baked into /usr/local/bin/ at image build, also synced from .github/skills/chad-orchestrator/scripts/ at setup so they can iterate without a rebuild.

Binary Job
chad-route Classify a task into a kind deterministically
chad-budget Token-budget bookkeeping with UTC reset (show / reserve / refund)
chad-spawn Canonical spawner: load manifest, render prompt, run, write result.json
chad-spawn-status Query the task ledger by id / state
chad-collect Merge recent result.json files into today's memory
chad-intake Source-agnostic wrapper: --from chat\|proton\|cron\|issue

Anatomy of a kind

A kind is a YAML manifest. Below is the shape (not a real kind):

kind: example
description: "what this kind is good for"
binary: /usr/local/bin/<binary>
invocation: prompt-stdin              # prompt-stdin | prompt-arg | openclaw-agent
network_policy_preset: subagent-example
substrate: local                      # local | gha (default: local)
default_timeout: 600
default_budget_tokens: 25000
prompt_template: |
  You are a sub-agent spawned by Chad.

  ## Step 0 — Brain first (mandatory)
  Run: gbrain query "<keywords>"

  ## Step 1 — Do the work
  ...

  ## Output
  Last non-empty stdout line MUST be JSON:
    {"status": "done|failed", "summary": "...", ...}

  Task id: {{task_id}}
  Task:
  {{task}}

Three things the manifest is doing:

  1. Pinning a binary path — the L7 proxy authorizes egress by /proc/self/exe, so the binary must match the policy preset's allowlist exactly.
  2. Constraining tokens and time — budget is reserved before the spawn runs; timeout is wall-clock enforced via timeout(1).
  3. Picking a substratelocal (default) runs in-container; gha runs on a GitHub Actions runner per spawn. See Substrates.

The seven kinds today

Kind Binary Policy Substrate Timeout / Budget Use case
coder pi pi-agent local 600s / 50k Write/refactor code, run build + tests
researcher claude subagent-researcher local 300s / 20k gh search, web facts, report
writer claude subagent-writer local 600s / 25k Draft mail, docs, articles (never publishes)
reviewer claude subagent-reviewer local 300s / 25k Audit PR diff (read-only gh)
fitness claude subagent-researcher local 300s / 15k Strength + mobility from gbrain books
codex codex (npm) subagent-codex gha 600s / 50k OpenAI Codex (NVIDIA fallback)
opencode opencode (npm) subagent-opencode gha 600s / 50k Multi-provider coding CLI

The canonical flow

# 1. Classify a task (optional — Chad can pick the kind directly).
kind="$(chad-route --task-file /tmp/task.json)"

# 2. Spawn.
task_id="$(chad-spawn --kind "$kind" --task-file /tmp/task.json)"

# 3. Wait or poll.
chad-spawn-status --id "$task_id"     # queued|running|done|failed

# 4. Merge results into today's memory.
chad-collect --today

Or the source-agnostic shortcut:

chad-intake --from chat   --task-file /tmp/task.json
chad-intake --from proton --message-id MSGID
chad-intake --from issue  --repo tantodefi/NemoClaw --issue 42
chad-intake --from cron   --task-file queue/cron-task.md

Per-spawn overrides

Four flags let one spawn deviate from its kind's defaults:

# Force a substrate
chad-spawn --kind writer --substrate gha --task-file ...

# Async dispatch (gha-only) — returns task_id immediately
chad-spawn --kind codex --async --task-file ...

# Per-spawn binary swap (e.g., codex on a writer kind)
chad-spawn --kind writer --binary-override /usr/local/bin/codex --task-file ...

# Validate without spawning — renders prompt, checks budget, exits
chad-spawn --kind writer --dry-run --task-file ...

The --binary-override flag is best-effort: the kind's L7 policy preset still applies, so the override binary must be in that preset's allowlist or the proxy will 403 the egress.

--dry-run is the primary tool when adding a new kind or tweaking a manifest. It renders the prompt template, resolves the binary path, checks the budget reservation, and prints the resolved invocation — without actually spawning. Combine with --substrate gha --dry-run to verify the agent-job workflow file would receive a valid task record.

Helper wrappers

Beyond the orchestrator binaries above, three thin wrappers handle glue tasks the agent calls inline rather than spawning for:

Wrapper Purpose
chad-route-prompt Dashboard /premium <prompt> prefix → drops AuthContext → calls chad-premium. Also classifies plain-text prompts so chat → kind routing skips a dedicated chad-route call when the prefix is unambiguous.
chad-log-event Append a single structured event line to ~/.openclaw/cron-runs.jsonl from any wrapper. Used by every cron tick to confirm completion without round-tripping through the model.
chad-cron-reload Re-reads cron/jobs.json and applies it to the running gateway without a full chad-setup rerun. Useful when chad-proposal-apply lands a tuning change.

chad-spawn-gha.sh (under .github/skills/chad-orchestrator/scripts/) is the helper that pushes a chad-spawn/<id> branch and dispatches the workflow when --substrate gha. It's invoked by chad-spawn, not the operator — listed here for completeness.

Sub-agents draft, parents publish

The single most-cited safety property of the orchestrator is that sub-agents never publish. The reviewer policy preset is the canonical example:

network_policies:
  subagent_reviewer_github:
    endpoints:
      - host: api.github.com
        port: 443
        rules:
          - allow: { method: GET,  path: "/**" }
          # POST not in the allowlist — proxy 403s it

A reviewer sub-agent can clone, diff, and read PRs. It cannot post a review. If its prompt is hijacked into "post a malicious review," the L7 proxy rejects the request. Chad reads the reviewer's result.json, decides whether to act on it, and (if so) makes the GitHub API call from its own context — under its own policy.

What every spawn writes to disk

/sandbox/.openclaw-data/
├── subagents/
│   └── <task-id>/
│       ├── task.{json|txt}     # input (copied from --task-file)
│       ├── prompt.txt          # rendered prompt (template + task)
│       ├── stdout.log          # sub-agent stdout
│       ├── stderr.log          # sub-agent stderr
│       └── result.json         # structured result
├── queue/
│   └── tasks.jsonl             # append-only ledger
└── budget.json                 # daily token budget (UTC reset)

The result.json shape:

{
  "status": "done",
  "task_id": "3e4f...",
  "kind": "coder",
  "exit_code": 0,
  "substrate": "local",
  "summary": "refactored runner.ts; tests pass",
  "files_touched": ["nemoclaw/src/blueprint/runner.ts"],
  "follow_ups": ["add an integration test for the snapshot path"]
}

Sub-agents emit this JSON on the last non-empty line of stdout. If a sub-agent doesn't, chad-spawn synthesizes a minimal version from the exit code.

Adding a new kind

Four files, no rebuild:

  1. Manifestkinds/<name>.yaml (binary, policy, substrate, prompt template, timeout, budget).
  2. Policy presetnemoclaw-blueprint/policies/presets/subagent-<name>.yaml if existing presets don't fit (egress hosts + binary allowlist).
  3. For gha substrate kinds: add an "Install" step to the agent-job.yml workflow in chad-state, plus any provider keys to the chad-state secrets.
  4. Sync + dry-runchad-setup.sh syncs the new files into the sandbox; chad-spawn --kind <name> --task-file /tmp/t.md --dry-run validates without burning tokens.

The full path:

# Local: manifest + preset
$EDITOR .github/skills/chad-orchestrator/kinds/<name>.yaml
$EDITOR nemoclaw-blueprint/policies/presets/subagent-<name>.yaml

# Sync + dry-run
./scripts/chad-setup.sh chad
ssh openshell-chad 'chad-spawn --kind <name> --task-file /tmp/t.md --dry-run'

# Real spawn
ssh openshell-chad 'chad-spawn --kind <name> --task-file /tmp/t.md'

Higher-level control surfaces (skill layer)

Alongside the six helpers, Chad has two runtime-synced skills that act as orchestrator-layer playbooks. They live at /sandbox/.openclaw-data/skills/<name>/SKILL.md and load via openclaw skills auto-discovery.

Skill What it does
openwebui (added 2026-05-13) Full operator's manual for the chad-webui CLI (60 sub-commands across 10 groups — calendar, notes, automations, memories, chats, knowledge, models, functions, tools, folders) and its matching MCP tools (webui__*). Documents the per-operator API-key fail-closed pattern, the L7-MITM-TLS network plumbing (Python urllib needs SSL_CERT_FILE pointed at the OpenShell CA bundle), and 6 composed recipes (save chat as note, reschedule events, RAG ingestion, etc.). See Front-Ends.
chad-experiment (added 2026-05-14) Methodology for the autonomous experiment lifecycle (loop 6 on Autonomy). Documents the 13-verb chad-experiment CLI (design / start / observe / evaluate / promote / retire / list / show / budget / ab-start / ab-pick / recent-memory / recent-ledger), the worthy-of-automation 5-criteria checklist, success-metric authoring rules, calendar-tag conventions ([chad-block], [chad-experiment], [operator-sync], [experiment-review]), and the 4-phase nightly cron workflow.

These skills are not in /usr/local/bin/ like the six helpers — they're markdown reference docs that the agent loads on every spawn, and they hand off to underlying CLIs (chad-webui, chad-experiment) that Chad calls during turns.

A second orchestrator: chad-Smithers

Everything above is chad-spawn — imperative, one-shot sub-agents. Alongside it runs chad-Smithers, a durable workflow orchestrator built on Smithers with a web IDE at runs.supachad.com. They are complementary, not a migration.

chad-spawn chad-Smithers
Shape imperative one-shot sub-agents (kind manifests) declarative durable workflows (.jsx)
State branch-as-record on chad-state SQLite + live dashboard + crash-resume
Best for isolated one-shot agents (writer / coder / reviewer), GHA offload multi-step pipelines, experiments, the autonomy ladder, anything inspectable / resumable
Substrate in-container or GHA host (live) — plus GHA via the bridge

Decision: keep both, bridge them. chad-spawn stays the GHA-isolated one-shot substrate; chad-Smithers is the durable workflow orchestrator that can call chad-spawn to offload one heavy step (a build, a big parallel eval) onto a GitHub Actions runner, reconciling its result.json as that task's output. No wholesale migration of the ~1,300-line spawn stack — Smithers wraps it.

The bridge

scripts/chad-smithers/lib/spawn.js is the keystone. runSpawn({kind, task, substrate, id, …}) runs inside a durable Smithers task and shells out to the existing chad-spawn — it does not rebuild any GHA machinery. It never throws (always resolves to a result.json-shaped object), so one bad spawn can't sink a durable run. Transport resolves per call: a host stub for dry runs (CHAD_SPAWN_STUB=1), SSH into the pod for real spawns (CHAD_SPAWN_SSH=<host> — task streamed in, result streamed back, since scp is blocked in the sandbox), or local chad-spawn when Smithers runs inside the pod. It also ports chad-routeroute() and the issue-triage scoring → scoreIssue() as auditable plain JS (unit-tested).

import { runSpawn, route } from "../lib/spawn.js";

<Task id="fix" output={outputs.spawn} sideEffect idempotencyKey={`spawn-${n}`}>
  {() => runSpawn({ kind: route(issue.title), substrate: "gha", id: `iss-${n}`, task })}
</Task>

What migrated

The multi-step / durable / inspectable flows became Smithers workflows (under scripts/chad-smithers/workflows/), each gaining resume, the dashboard task tree, and <Approval> gates:

Workflow Replaces Shape
issue-triage.jsx chad-issue-triage multi-spawn fetch → score+route → Parallel spawn per issue → report
content-pipeline.jsx research→write→review multi-spawn three spawns → reviewer-gated Approval → publish
self-improve.jsx chad-self-improve telemetry → propose → safe-list/Approval → apply
memory-curator.jsx chad-memory-curator inactivity-gate → snapshot → propose → Approval
log-digest.jsx manual cron-log audits cluster host service-log errors → note (quiet if clean)

All are shadow-safe by default (they log what they would do until an explicit env flag flips them to real mode — same draft-only contract as the shell wrappers). What stays a plain wrapper: the mechanical one-shot crons (backups, prune, gc, budget-audit), where a workflow engine is pure overhead.

See Runs IDE for the dashboard and chad-runs CLI, and Substrates for the GHA bridge.