AXP Build Loop
Drive the experiment build loop end-to-end from a coding-agent chat session:
take a goal or insight, generate N candidate variants of the artifact under
test, implement them in parallel worktrees, evaluate them in isolated axp
sandboxes against a naive agent, rank the results, and either promote the
winner to a PR or run another lap mutating it.
Six stages, run sequentially: INTAKE → GENERATE → IMPLEMENT → RUN → CHOOSE → LAP or PROMOTE.
Complete each stage before the next. Conversation context is working state; the
LOOP.md journal (Stage 3) is durable state — keep it current so a fresh
session can resume the loop cold.
Requirements
axp CLI with files: staging support (axp run --help must list --file); Docker running. File staging is local Docker runs only — --remote and --runner e2b reject it.
git ≥ 2.20 (worktrees), jq, and gh (only needed for the final promote stage).
- Run from the repository of the artifact under test.
Command safety
Run freely: axp validate, axp run --dry-run, axp schema, git worktree list, git status, git log, git diff, reading any file under .axp/.
Gate behind explicit user approval (use AskUserQuestion):
axp run (real run — spends tokens and time; show variant count × repeat and the estimated agent invocations)
git worktree add / branch creation
git push, gh pr create (Stage 6 — outward-facing)
Never write to the user's working tree outside .axp/loops/ and the variant worktrees.
Hard conventions (apply to every experiment this skill authors)
- Naive-user prompt.
flow.prompt is a realistic end-user task ("set up logging with this CLI"), never test-aware ("call the foo tool"). A test-aware prompt silently invalidates the experiment.
- Y₀ control. Every matrix includes an unmodified-baseline variant. All claims are relative to Y₀.
- Repeat by default. Agent behavior is stochastic. Default
--repeat 5 (ask the user to lower it only for cost reasons) and report rates ("tool fired 4/5"), not booleans.
- Identical mocks below the varied layer. The surface the test agent touches (help text, errors, tool descriptions, docs, schema messages) must be real; anything beneath it (network, databases) may be stubbed — but with the same stub in every variant, with realistic exit codes and error text.
- One axis per lap. Each lap varies one mutation axis from the playbook; the hypothesis is written down before the run.
Per-artifact playbook
Branch on the artifact type at intake. Each variant's setup installs its build; mutation axes and metrics guide GENERATE and CHOOSE.
| Artifact |
Mutation axes (pick ONE per lap) |
Key introspection metrics |
| CLI |
--help text; error-message wording; flag names; JSON vs text output |
wrong-flag attempts, retries after error, help re-reads |
| MCP server |
tool names; tool descriptions; granularity (one fat tool vs several thin) |
activation rate (did the agent pick the tool at all), schema-validation failures, fallback to bash |
| Agent hooks |
trigger condition; injected message wording; hook event choice |
behavior delta post-hook, false-trigger rate |
| Install script |
error handling; preflight checks; idempotency; verbosity |
exit codes on fresh sandboxes, agent's recovery path on failure |
| Docs |
structure (task-oriented vs reference); placement (README / llms.txt / agent config); length |
task success with docs only, resort-to-reading-source, time to first correct call |
| Schema |
field names; defaults; error message quality |
validate-failure count before first valid instance, hallucinated fields |
Stage 1 — INTAKE
Goal: pin down what is under test and anchor the baseline.
- Identify the artifact type (table above) from the user's goal; ask if ambiguous.
- Fingerprint the cwd against it: binary name in
Cargo.toml / package.json bin, MCP server entrypoint, docs dir. If the repo doesn't contain the stated artifact, stop and ask.
- Capture the baseline:
git rev-parse --short HEAD, dirty-tree status. A dirty tree needs an explicit answer: is the dirty state or the committed state the baseline?
- Pick a loop name (kebab-case, e.g.
mycli-help-text) and create the loop home: .axp/loops/<name>/ (add it to .gitignore if not covered).
- Confirm in one gate (AskUserQuestion): artifact, baseline commit, loop name, run budget (variants × repeat).
Stage 2 — GENERATE
Goal: a falsifiable variant plan, approved before any implementation.
- Pick ONE mutation axis from the playbook for this lap. State the hypothesis in one sentence ("more example-driven --help reduces wrong-flag attempts").
- Propose Y₁–Y₄ concrete mutations along that axis, plus Y₀ control. 3–5 candidates total is the sweet spot; more dilutes the repeat budget.
- Design the evaluation:
flow.prompt: the naive-user task (convention 1).
- Application tests: did the task outcome land (files exist, command exits 0, endpoint answers).
- Introspection tests: read
$AXP_TRACE_PATH with jq for the playbook metrics — e.g. [ "$(jq '.tool_calls | length' "$AXP_TRACE_PATH")" -lt 30 ]. Inspect the trace from a previous run (or lap 1's Y₀) to learn its exact fields before writing fine-grained assertions; do not invent fields.
- Present the plan as a table (variant id, mutation, predicted effect) and gate on user OK.
Stage 3 — IMPLEMENT
Goal: each variant's artifact built and staged; experiment YAML validated.
- For each Yᵢ (i ≥ 1):
git worktree add .axp/loops/<name>/worktrees/y<i> <baseline> (one approval covers all worktrees of the lap), implement the mutation there, and build the artifact. Independent mutations may be implemented by parallel subagents — one per worktree.
- The staged build must be usable inside the sandbox (Linux container). Interpreted artifacts (Node/Python scripts, docs, YAML/JSON) are typically portable as-is. Compiled binaries must target Linux on the sandbox's architecture — a host macOS binary will fail at
setup with exec-format/not-found errors. Either cross-compile (e.g. cargo build --target x86_64-unknown-linux-gnu, GOOS=linux go build) or skip host builds entirely: stage the worktree source and compile in that variant's setup (slower per variant, but always matches the sandbox). Verify the toolchain assumption early — Y₀'s setup failing on the baseline build means every variant will fail the same way.
- Author
.axp/loops/<name>/lap<N>.yaml. If an experiment-authoring skill is available in the host environment (e.g. axp-create-experiment), use it; otherwise follow axp schema and the docs at https://docs.514.ai. Per variant:
overrides.files staging the variant's build, with source relative to the YAML's directory (e.g. ./worktrees/y1/target/release/mycli → dest: tools/mycli); Y₀ stages the baseline build the same way.
overrides.setup for any in-sandbox install steps. Files land before setup, so setup may reference them.
- Add an
.axpignore (gitignore syntax) inside any staged directory to keep junk out; .git/ is excluded automatically.
axp validate lap<N>.yaml, then axp run --dry-run and check the staging table for MISSING/UNBOUND rows.
- Write or update
LOOP.md (format below) with the lap's hypothesis and variant table.
LOOP.md format
# Loop: <name>
Goal: <the user's goal / originating insight>
Baseline: <commit> (<clean|dirty: which>)
## Lap <N> — axis: <mutation axis>
Hypothesis: <one sentence>
| Variant | Mutation | Result (filled after CHOOSE) |
|---|---|---|
| y0 | baseline control | |
| y1 | <mutation> | |
Run: <run id> · Decision: <winner y_k / another lap / stopped> — <user's words>
Stage 4 — RUN
Goal: the lap's evaluations executed without babysitting.
- Gate: show the final command and the cost shape (
N variants × R repeats = N×R agent runs), get approval.
- Run
axp run lap<N>.yaml --repeat <R> --jobs <J> in the background; variants already parallelize inside one run, so --jobs ≥ variant count.
- While it runs, monitor the newest
.axp/runs/<run_id>/ — per-variant run.json appears as variants finish. Report progress; on setup_check_failed or staging_failed, stop the lap and diagnose rather than letting the matrix burn budget.
Stage 5 — CHOOSE
Goal: a ranked, evidence-backed comparison the user decides on.
- Collect per-variant evidence from
.axp/runs/<run_id>/variants/*/: run.json (status, exit reason, cost/usage fields), tests/application/*.json and tests/introspection/*.json (pass/fail + tails). With platform auth, axp experiment run results get aggregates the same; without it, the artifacts suffice.
- Rank: (a) application-test pass rate across repeats — an unreliable winner is not a winner; (b) playbook introspection metrics; (c) cost/wall-clock as tiebreak. Everything is reported relative to Y₀.
- Present a table: variant, mutation, pass rate, key metric(s), Δ vs Y₀. Call out surprises (Y₀ winning is a real and useful outcome — the hypothesis was wrong).
- Fill the lap's Result column in
LOOP.md, then gate: promote the winner, run another lap, or stop. Record the decision verbatim in LOOP.md.
Stage 6 — LAP or PROMOTE
Another lap: return to Stage 2 with the winner as the new reference point. Either deepen the same axis (y3 → y3′, y3″) or switch to the next playbook axis. New worktrees branch from the winner's worktree commit. Increment the lap number; never overwrite a previous lap's YAML or worktrees.
Promote:
- Create a branch from the baseline, apply the winning worktree's diff (
git diff <baseline>..<winner-worktree-head> → apply, or cherry-pick its commits).
- Gate, then
git push and gh pr create. The PR body is generated from LOOP.md: goal, laps and hypotheses, the winner's metrics table vs Y₀ (with n = repeat count), and a link to the run artifacts.
- Clean up on user confirmation:
git worktree remove the lap worktrees (keep LOOP.md and the lap YAMLs — they are the experiment record).
Stop: fill in LOOP.md with why, so the loop can be resumed later.
Failure handling
staging_failed / MISSING source: the worktree build didn't produce the artifact where the YAML expects it — fix the files: entry or the build, re---dry-run.
setup_check_failed: the sandbox environment is broken for that variant; read setup-checks/<name>.json tails. Fix before re-running; do not compare a lap in which variants failed for environmental reasons.
- Identical results across all variants incl. Y₀: suspect the staging didn't take effect (confirm the staged file inside
workspace/ artifact copy) or the prompt never exercises the mutated surface — fix the experiment, not the artifact.
1---2name: axp-build-loop3description: ALWAYS invoke this skill when the user wants to test, compare, optimize, or iterate on variants of a developer-facing artifact — a CLI, an MCP server, Claude/agent hooks, an install script, docs, or a schema — using axp experiments. Trigger phrases: "test variants of", "which version works better", "optimize my CLI/MCP/docs for agents", "run a build loop", "A/B test my tool descriptions", "iterate on this with axp". Do not hand-roll experiment YAMLs and one-off comparisons for these tasks; drive the full loop with this skill instead.4---56# AXP Build Loop78Drive the experiment build loop end-to-end from a coding-agent chat session:9take a goal or insight, generate N candidate variants of the artifact under10test, implement them in parallel worktrees, evaluate them in isolated axp11sandboxes against a naive agent, rank the results, and either promote the12winner to a PR or run another lap mutating it.1314Six stages, run sequentially: **INTAKE → GENERATE → IMPLEMENT → RUN → CHOOSE → LAP or PROMOTE**.15Complete each stage before the next. Conversation context is working state; the16`LOOP.md` journal (Stage 3) is durable state — keep it current so a fresh17session can resume the loop cold.1819## Requirements2021- `axp` CLI with `files:` staging support (`axp run --help` must list `--file`); Docker running. File staging is **local Docker runs only** — `--remote` and `--runner e2b` reject it.22- `git` ≥ 2.20 (worktrees), `jq`, and `gh` (only needed for the final promote stage).23- Run from the repository of the artifact under test.2425## Command safety2627**Run freely:** `axp validate`, `axp run --dry-run`, `axp schema`, `git worktree list`, `git status`, `git log`, `git diff`, reading any file under `.axp/`.2829**Gate behind explicit user approval (use AskUserQuestion):**3031- `axp run` (real run — spends tokens and time; show variant count × repeat and the estimated agent invocations)32- `git worktree add` / branch creation33- `git push`, `gh pr create` (Stage 6 — outward-facing)3435Never write to the user's working tree outside `.axp/loops/` and the variant worktrees.3637## Hard conventions (apply to every experiment this skill authors)38391. **Naive-user prompt.** `flow.prompt` is a realistic end-user task ("set up logging with this CLI"), never test-aware ("call the foo tool"). A test-aware prompt silently invalidates the experiment.402. **Y₀ control.** Every matrix includes an unmodified-baseline variant. All claims are relative to Y₀.413. **Repeat by default.** Agent behavior is stochastic. Default `--repeat 5` (ask the user to lower it only for cost reasons) and report rates ("tool fired 4/5"), not booleans.424. **Identical mocks below the varied layer.** The surface the test agent touches (help text, errors, tool descriptions, docs, schema messages) must be real; anything beneath it (network, databases) may be stubbed — but with the *same* stub in every variant, with realistic exit codes and error text.435. **One axis per lap.** Each lap varies one mutation axis from the playbook; the hypothesis is written down before the run.4445## Per-artifact playbook4647Branch on the artifact type at intake. Each variant's `setup` installs its build; mutation axes and metrics guide GENERATE and CHOOSE.4849| Artifact | Mutation axes (pick ONE per lap) | Key introspection metrics |50|---|---|---|51| CLI | `--help` text; error-message wording; flag names; JSON vs text output | wrong-flag attempts, retries after error, help re-reads |52| MCP server | tool names; tool descriptions; granularity (one fat tool vs several thin) | activation rate (did the agent pick the tool at all), schema-validation failures, fallback to bash |53| Agent hooks | trigger condition; injected message wording; hook event choice | behavior delta post-hook, false-trigger rate |54| Install script | error handling; preflight checks; idempotency; verbosity | exit codes on fresh sandboxes, agent's recovery path on failure |55| Docs | structure (task-oriented vs reference); placement (README / llms.txt / agent config); length | task success with docs only, resort-to-reading-source, time to first correct call |56| Schema | field names; defaults; error message quality | validate-failure count before first valid instance, hallucinated fields |5758---5960## Stage 1 — INTAKE6162Goal: pin down what is under test and anchor the baseline.63641. Identify the artifact type (table above) from the user's goal; ask if ambiguous.652. Fingerprint the cwd against it: binary name in `Cargo.toml` / `package.json` `bin`, MCP server entrypoint, docs dir. If the repo doesn't contain the stated artifact, stop and ask.663. Capture the baseline: `git rev-parse --short HEAD`, dirty-tree status. A dirty tree needs an explicit answer: is the dirty state or the committed state the baseline?674. Pick a loop name (kebab-case, e.g. `mycli-help-text`) and create the loop home: `.axp/loops/<name>/` (add it to `.gitignore` if not covered).685. Confirm in one gate (AskUserQuestion): artifact, baseline commit, loop name, run budget (variants × repeat).6970## Stage 2 — GENERATE7172Goal: a falsifiable variant plan, approved before any implementation.73741. Pick ONE mutation axis from the playbook for this lap. State the hypothesis in one sentence ("more example-driven --help reduces wrong-flag attempts").752. Propose Y₁–Y₄ concrete mutations along that axis, plus Y₀ control. 3–5 candidates total is the sweet spot; more dilutes the repeat budget.763. Design the evaluation:77 - `flow.prompt`: the naive-user task (convention 1).78 - Application tests: did the task outcome land (files exist, command exits 0, endpoint answers).79 - Introspection tests: read `$AXP_TRACE_PATH` with `jq` for the playbook metrics — e.g. `[ "$(jq '.tool_calls | length' "$AXP_TRACE_PATH")" -lt 30 ]`. Inspect the trace from a previous run (or lap 1's Y₀) to learn its exact fields before writing fine-grained assertions; do not invent fields.804. Present the plan as a table (variant id, mutation, predicted effect) and gate on user OK.8182## Stage 3 — IMPLEMENT8384Goal: each variant's artifact built and staged; experiment YAML validated.85861. For each Yᵢ (i ≥ 1): `git worktree add .axp/loops/<name>/worktrees/y<i> <baseline>` (one approval covers all worktrees of the lap), implement the mutation there, and build the artifact. Independent mutations may be implemented by parallel subagents — one per worktree.87 - **The staged build must be usable inside the sandbox (Linux container).** Interpreted artifacts (Node/Python scripts, docs, YAML/JSON) are typically portable as-is. Compiled binaries must target Linux on the sandbox's architecture — a host macOS binary will fail at `setup` with exec-format/not-found errors. Either cross-compile (e.g. `cargo build --target x86_64-unknown-linux-gnu`, `GOOS=linux go build`) or skip host builds entirely: stage the worktree *source* and compile in that variant's `setup` (slower per variant, but always matches the sandbox). Verify the toolchain assumption early — Y₀'s `setup` failing on the baseline build means every variant will fail the same way.882. Author `.axp/loops/<name>/lap<N>.yaml`. If an experiment-authoring skill is available in the host environment (e.g. `axp-create-experiment`), use it; otherwise follow `axp schema` and the docs at https://docs.514.ai. Per variant:89 - `overrides.files` staging the variant's build, with `source` **relative to the YAML's directory** (e.g. `./worktrees/y1/target/release/mycli` → `dest: tools/mycli`); Y₀ stages the baseline build the same way.90 - `overrides.setup` for any in-sandbox install steps. Files land **before** `setup`, so setup may reference them.91 - Add an `.axpignore` (gitignore syntax) inside any staged *directory* to keep junk out; `.git/` is excluded automatically.923. `axp validate lap<N>.yaml`, then `axp run --dry-run` and check the staging table for `MISSING`/`UNBOUND` rows.934. Write or update `LOOP.md` (format below) with the lap's hypothesis and variant table.9495### LOOP.md format9697```markdown98# Loop: <name>99Goal: <the user's goal / originating insight>100Baseline: <commit> (<clean|dirty: which>)101102## Lap <N> — axis: <mutation axis>103Hypothesis: <one sentence>104| Variant | Mutation | Result (filled after CHOOSE) |105|---|---|---|106| y0 | baseline control | |107| y1 | <mutation> | |108Run: <run id> · Decision: <winner y_k / another lap / stopped> — <user's words>109```110111## Stage 4 — RUN112113Goal: the lap's evaluations executed without babysitting.1141151. Gate: show the final command and the cost shape (`N variants × R repeats = N×R agent runs`), get approval.1162. Run `axp run lap<N>.yaml --repeat <R> --jobs <J>` **in the background**; variants already parallelize inside one run, so `--jobs` ≥ variant count.1173. While it runs, monitor the newest `.axp/runs/<run_id>/` — per-variant `run.json` appears as variants finish. Report progress; on `setup_check_failed` or `staging_failed`, stop the lap and diagnose rather than letting the matrix burn budget.118119## Stage 5 — CHOOSE120121Goal: a ranked, evidence-backed comparison the user decides on.1221231. Collect per-variant evidence from `.axp/runs/<run_id>/variants/*/`: `run.json` (status, exit reason, cost/usage fields), `tests/application/*.json` and `tests/introspection/*.json` (pass/fail + tails). With platform auth, `axp experiment run results get` aggregates the same; without it, the artifacts suffice.1242. Rank: (a) application-test pass **rate** across repeats — an unreliable winner is not a winner; (b) playbook introspection metrics; (c) cost/wall-clock as tiebreak. Everything is reported **relative to Y₀**.1253. Present a table: variant, mutation, pass rate, key metric(s), Δ vs Y₀. Call out surprises (Y₀ winning is a real and useful outcome — the hypothesis was wrong).1264. Fill the lap's Result column in `LOOP.md`, then gate: **promote the winner**, **run another lap**, or **stop**. Record the decision verbatim in `LOOP.md`.127128## Stage 6 — LAP or PROMOTE129130**Another lap:** return to Stage 2 with the winner as the new reference point. Either deepen the same axis (y3 → y3′, y3″) or switch to the next playbook axis. New worktrees branch from the winner's worktree commit. Increment the lap number; never overwrite a previous lap's YAML or worktrees.131132**Promote:**1331. Create a branch from the baseline, apply the winning worktree's diff (`git diff <baseline>..<winner-worktree-head>` → apply, or cherry-pick its commits).1342. Gate, then `git push` and `gh pr create`. The PR body is generated from `LOOP.md`: goal, laps and hypotheses, the winner's metrics table vs Y₀ (with n = repeat count), and a link to the run artifacts.1353. Clean up on user confirmation: `git worktree remove` the lap worktrees (keep `LOOP.md` and the lap YAMLs — they are the experiment record).136137**Stop:** fill in `LOOP.md` with why, so the loop can be resumed later.138139## Failure handling140141- `staging_failed` / `MISSING` source: the worktree build didn't produce the artifact where the YAML expects it — fix the `files:` entry or the build, re-`--dry-run`.142- `setup_check_failed`: the sandbox environment is broken for that variant; read `setup-checks/<name>.json` tails. Fix before re-running; do not compare a lap in which variants failed for environmental reasons.143- Identical results across all variants incl. Y₀: suspect the staging didn't take effect (confirm the staged file inside `workspace/` artifact copy) or the prompt never exercises the mutated surface — fix the experiment, not the artifact.