run-optimizer — one runner, a registry of agents
An optimizer is the agent that reads the current capability + the failure
diagnosis and proposes an edit. Every such agent follows the same contract: given
a working directory (a copy of the parent candidate) and an INSTRUCTIONS.md,
edit the files in place. Because the contract is identical, one runner serves them
all — the only thing that varies per agent is the shell command, which lives as a
row in optimizers/registry.yaml. Adding an optimizer is one YAML row, not a
new skill directory.
How it works
- The loop calls
run.py --name <optimizer> --workdir <copy> --prompt INSTRUCTIONS.md.
- The runner reads the registry, resolves the row, and expands its
command_template (placeholders below) into argv.
- It runs that command with
cwd = workdir, so the agent edits the candidate
files directly. Output is summarized as JSON (returncode, auth_present,
stdout_tail).
- Streams: stdout is exactly one JSON object; the agent CLI's stderr is
relayed to the runner's stderr on success as well as failure, so a CLI that
prints a diagnostic and exits 0 is not silently successful.
--prompt must name an existing file — the caller resolves the path. A
missing one is an error (exit 2), never an empty prompt: the {prompt_text} rows
would otherwise bill a real agent CLI to run with no instructions at all.
Template placeholders
| placeholder |
expands to |
{workdir} |
the candidate working copy (also the cwd) |
{prompt} |
path to INSTRUCTIONS.md |
{prompt_text} |
the contents of INSTRUCTIONS.md (for CLIs that take the prompt inline) |
{model} |
the resolved model id; an empty {model} drops itself and a preceding -m/--model |
{self_dir} |
the runner's own scripts dir (used by the mock row) |
${VAR} |
environment expansion (the generic/openclaw/antigravity escape hatches read their command from env) |
Choosing an optimizer (--name / optimizer_skill)
mock, generic, claude-code, codex, gemini-cli, opencode, cursor,
droid (Factory Droid), copilot (GitHub Copilot CLI), kimi, pi,
antigravity, openclaw, ibm-bob. Per-CLI install / auth / flag details are in
references/<name>.md.
mock is fully offline (runs a shipped JSON-driven editor, never a network
CLI), so the end-to-end proof slice costs nothing and never flakes.
generic / openclaw / antigravity read their command from
CAPEVOLVE_OPTIMIZER_CMD / CAPEVOLVE_OPENCLAW_CMD / CAPEVOLVE_ANTIGRAVITY_CMD
— the escape hatch that makes "any optimizer" literal. (antigravity is a wrapper
because its auth is Google-Sign-In-only and its headless approve flag is unconfirmed.)
cursor, droid, copilot, kimi, pi are verified
headless commands; matches the agent set supported by obra/superpowers.
Standalone use
# list known optimizers
python scripts/run.py --list
# drive one agent over a candidate dir
python scripts/run.py --name claude-code --workdir ./cand --prompt ./cand/INSTRUCTIONS.md --model claude-opus-4-6
In a run, the orchestrator builds this command for you from the spec's
optimizer_skill (the optimizer NAME) and optimizer_model.
Headless JSON cost capture (optional, best-effort)
Coding-agent CLIs can emit a structured result that carries the exact spend.
Pass --json (or set CAPEVOLVE_OPTIMIZER_JSON=1) and the runner appends the
registry row's json_flag to the command, then parses total_cost_usd
(and token usage) out of the output:
python scripts/run.py --name claude-code --json \
--workdir ./cand --prompt ./cand/INSTRUCTIONS.md --model claude-opus-4-6
# -> {... "cost": {"total_cost_usd": 0.0123, "tokens": 4210}}
Per CLI (verify with <cli> --help):
- claude-code —
json_flag: --output-format json; the JSON result has
total_cost_usd, usage, and per-model cost under modelUsage. Add
--json-schema '<JSONSchema>' (via --json-schema here, or
CAPEVOLVE_OPTIMIZER_JSON_SCHEMA) to also get .structured_output for a
decision step. The runner only appends --json-schema when the row's json_flag
contains --output-format.
- codex —
json_flag: --json (a JSONL stream); pair with
codex exec --json --output-last-message <file> when you want the final message
on disk. The runner parses the last JSON line for cost/usage.
- gemini-cli —
json_flag: --output-format json; total_cost_usd/usage are
parsed when present.
This is strictly additive. With no --json, or for a row whose json_flag is
empty (mock, generic, opencode, openclaw, ibm-bob), nothing changes —
the optimizer runs prose-fed exactly as before, and mock stays fully offline. If
the output isn't parseable JSON, cost.total_cost_usd is null and the loop
continues without a cost figure (never an error).
Back-compat
A spec that still names an old per-CLI optimizer skill (claude-code, ibm-bob,
…) resolves to the registry row of the same name, so existing specs keep working
without edits.
References
references/<name>.md — install, auth, and flags for each CLI.
1---2name: run-optimizer3description: Drives any shell-invokable coding agent (Claude Code, Codex, Gemini CLI, opencode, Cursor, Factory Droid, GitHub Copilot CLI, Kimi, Pi, Antigravity, OpenClaw, IBM Bob, or a fully custom command) as the edit proposer in a cap-evolve run, resolving the named optimizer from optimizers/registry.yaml. Use this as the optimizer for every run; pick the concrete agent with --name (or optimizer_skill in the spec). Use --name mock for a deterministic, zero-API proposer in tests and CI.4---56# run-optimizer — one runner, a registry of agents78An *optimizer* is the agent that reads the current capability + the failure9diagnosis and proposes an edit. Every such agent follows the same contract: given10a working directory (a copy of the parent candidate) and an `INSTRUCTIONS.md`,11edit the files in place. Because the contract is identical, one runner serves them12all — the only thing that varies per agent is the shell command, which lives as a13row in `optimizers/registry.yaml`. Adding an optimizer is **one YAML row**, not a14new skill directory.1516## How it works17181. The loop calls `run.py --name <optimizer> --workdir <copy> --prompt INSTRUCTIONS.md`.192. The runner reads the registry, resolves the row, and expands its20 `command_template` (placeholders below) into argv.213. It runs that command with `cwd = workdir`, so the agent edits the candidate22 files directly. Output is summarized as JSON (`returncode`, `auth_present`,23 `stdout_tail`).244. Streams: **stdout is exactly one JSON object**; the agent CLI's **stderr is25 relayed** to the runner's stderr on success as well as failure, so a CLI that26 prints a diagnostic and exits 0 is not silently successful.275. `--prompt` must name an **existing** file — the caller resolves the path. A28 missing one is an error (exit 2), never an empty prompt: the `{prompt_text}` rows29 would otherwise bill a real agent CLI to run with no instructions at all.3031### Template placeholders32| placeholder | expands to |33|---|---|34| `{workdir}` | the candidate working copy (also the cwd) |35| `{prompt}` | path to `INSTRUCTIONS.md` |36| `{prompt_text}` | the *contents* of `INSTRUCTIONS.md` (for CLIs that take the prompt inline) |37| `{model}` | the resolved model id; an empty `{model}` drops itself and a preceding `-m`/`--model` |38| `{self_dir}` | the runner's own scripts dir (used by the `mock` row) |39| `${VAR}` | environment expansion (the `generic`/`openclaw`/`antigravity` escape hatches read their command from env) |4041## Choosing an optimizer (`--name` / `optimizer_skill`)4243`mock`, `generic`, `claude-code`, `codex`, `gemini-cli`, `opencode`, `cursor`,44`droid` (Factory Droid), `copilot` (GitHub Copilot CLI), `kimi`, `pi`,45`antigravity`, `openclaw`, `ibm-bob`. Per-CLI install / auth / flag details are in46`references/<name>.md`.4748- **`mock`** is fully offline (runs a shipped JSON-driven editor, never a network49 CLI), so the end-to-end proof slice costs nothing and never flakes.50- **`generic`** / **`openclaw`** / **`antigravity`** read their command from51 `CAPEVOLVE_OPTIMIZER_CMD` / `CAPEVOLVE_OPENCLAW_CMD` / `CAPEVOLVE_ANTIGRAVITY_CMD`52 — the escape hatch that makes "any optimizer" literal. (`antigravity` is a wrapper53 because its auth is Google-Sign-In-only and its headless approve flag is unconfirmed.)54- **`cursor`**, **`droid`**, **`copilot`**, **`kimi`**, **`pi`** are verified55 headless commands; matches the agent set supported by obra/superpowers.5657## Standalone use5859```bash60# list known optimizers61python scripts/run.py --list62# drive one agent over a candidate dir63python scripts/run.py --name claude-code --workdir ./cand --prompt ./cand/INSTRUCTIONS.md --model claude-opus-4-664```6566In a run, the orchestrator builds this command for you from the spec's67`optimizer_skill` (the optimizer NAME) and `optimizer_model`.6869## Headless JSON cost capture (optional, best-effort)7071Coding-agent CLIs can emit a structured result that carries the exact spend.72Pass `--json` (or set `CAPEVOLVE_OPTIMIZER_JSON=1`) and the runner appends the73registry row's **`json_flag`** to the command, then parses `total_cost_usd`74(and token usage) out of the output:7576```bash77python scripts/run.py --name claude-code --json \78 --workdir ./cand --prompt ./cand/INSTRUCTIONS.md --model claude-opus-4-679# -> {... "cost": {"total_cost_usd": 0.0123, "tokens": 4210}}80```8182Per CLI (verify with `<cli> --help`):83- **claude-code** — `json_flag: --output-format json`; the JSON result has84 `total_cost_usd`, `usage`, and per-model cost under `modelUsage`. Add85 `--json-schema '<JSONSchema>'` (via `--json-schema` here, or86 `CAPEVOLVE_OPTIMIZER_JSON_SCHEMA`) to also get `.structured_output` for a87 decision step. The runner only appends `--json-schema` when the row's json_flag88 contains `--output-format`.89- **codex** — `json_flag: --json` (a JSONL stream); pair with90 `codex exec --json --output-last-message <file>` when you want the final message91 on disk. The runner parses the last JSON line for cost/usage.92- **gemini-cli** — `json_flag: --output-format json`; `total_cost_usd`/`usage` are93 parsed when present.9495**This is strictly additive.** With no `--json`, or for a row whose `json_flag` is96empty (`mock`, `generic`, `opencode`, `openclaw`, `ibm-bob`), nothing changes —97the optimizer runs prose-fed exactly as before, and `mock` stays fully offline. If98the output isn't parseable JSON, `cost.total_cost_usd` is `null` and the loop99continues without a cost figure (never an error).100101## Back-compat102103A spec that still names an old per-CLI optimizer skill (`claude-code`, `ibm-bob`,104…) resolves to the registry row of the same name, so existing specs keep working105without edits.106107## References108- `references/<name>.md` — install, auth, and flags for each CLI.