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:
- Pinning a binary path — the L7 proxy authorizes egress by
/proc/self/exe, so the binary must match the policy preset's allowlist exactly. - Constraining tokens and time — budget is reserved before the
spawn runs; timeout is wall-clock enforced via
timeout(1). - Picking a substrate —
local(default) runs in-container;gharuns 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:
- Manifest —
kinds/<name>.yaml(binary, policy, substrate, prompt template, timeout, budget). - Policy preset —
nemoclaw-blueprint/policies/presets/subagent-<name>.yamlif existing presets don't fit (egress hosts + binary allowlist). - For
ghasubstrate kinds: add an "Install" step to theagent-job.ymlworkflow in chad-state, plus any provider keys to the chad-state secrets. - Sync + dry-run —
chad-setup.shsyncs the new files into the sandbox;chad-spawn --kind <name> --task-file /tmp/t.md --dry-runvalidates 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-route →
route() 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.