Drives Google Antigravity CLI (agy -p, the non-interactive print-mode
agent at /Users/__blitzzz/.local/bin/agy) to iteratively improve a piece
of work, with the agent rewriting both the artifact AND the next goal based
on what it learned.
The full design lives in DESIGN.md. Read that first if
you want to understand the loop's semantics. This README is the
operating manual.
You run ./loop.sh, it calls runner.sh up to 5 times (default), AGY
gets a fresh prompt each iteration that includes:
- the current goal (
tracks/<id>/current.md) - the next goal (
tracks/<id>/next.md) which AGY itself rewrites - the previous iteration's reflection (
tracks/<id>/reflection.md) - the three criteria — reflection / validation / completion — visible to AGY (not hidden)
- an explicit instruction to write/update
tracks/<id>/artifacts/v<N>.mdand rewritetracks/<id>/next.mdbased on what it learned
After each iteration the runner:
- persists the prompt + AGY stdout/stderr to
tracks/<id>/history/iter-NNN.md - extracts AGY's
## Reflectionsection intotracks/<id>/reflection.md(so the next iteration can read it without scanning history) - increments
meta.yaml.iteration - decides whether to stop (track done / max iters / stuck / error)
Success = at least one completed iteration that landed artifacts/v1.md
plus a history/iter-001.md audit trail.
# 1. Preview what AGY would see on iteration 1, no AGY call.
./loop.sh --dry-run
# 2. Run the loop for real. Stops on completion / max iters / stuck / error.
./loop.sh
# 3. Run with a different cap (default is 5 attempts).
./loop.sh --max-iterations 3
# 4. Run a single iteration manually (you handle state yourself).
./runner.sh --dry-run # preview
./runner.sh # actually invoke AGY (not exercised by build-demo)The runner is non-git by design (see DESIGN.md §11 open question #1). You commit between iterations yourself with whatever squash/reorder story you want.
-
Create the folder:
mkdir -p tracks/<your-id>/artifacts tracks/<your-id>/history touch tracks/<your-id>/artifacts/.gitkeep tracks/<your-id>/history/.gitkeep
-
Write
tracks/<your-id>/meta.yaml:id: your-id topic: "One-line topic statement" status: active created_at: 2026-06-19T00:00:00+00:00 updated_at: 2026-06-19T00:00:00+00:00 iteration: 0 termination_reason: "" max_iterations_override: null model_override: null criteria: reflection: [] # empty = use tracks.yaml default_criteria.reflection validation: [] completion: []
-
Seed
current.md(this iteration's goal),next.md(pivot target for the next iteration), andcompletion.md(track-done criteria).reflection.mdmay be empty or missing. -
Add the track to
tracks.yamlundertracks:, then flipactive_track:to point at it. Validate:./loop.sh --dry-run
-
Run the loop:
./loop.sh
# All iterations for the active track
ls tracks/<id>/history/
# Most recent iteration's full audit log
ls -t tracks/<id>/history/iter-*.md | head -1 | xargs cat
# Just the reflection handoff
cat tracks/<id>/reflection.md
# Just the latest artifact
ls -t tracks/<id>/artifacts/v*.md | head -1 | xargs cathistory/iter-NNN.md contains:
- header (timestamp, iteration, model, exit code, elapsed seconds)
- the verbatim prompt that was sent to AGY
- AGY's full stdout + stderr
- extracted
## Reflectionand## Validationsections
We do track:
tracks.yaml,runner.sh,loop.sh,README.md,DESIGN.mdtracks/<id>/meta.yaml,current.md,next.md,completion.md,reflection.md(the human-curated and machine-extracted state)tracks/<id>/artifacts/.gitkeepandhistory/.gitkeep(so git keeps the empty directories)
We do not track:
tracks/<id>/artifacts/v*.md— these are AGY's working drafts, regenerated every iteration; commit them manually when you want a snapshot.tracks/<id>/history/iter-*.md— append-only audit logs, can get large. The runner writes them; you commit them when you want a snapshot.
This split lets you keep the demo repo small (no AGY drafts in git status unless you add them) while still being able to commit snapshots
of the artifact or full history at any point. See .gitignore.
| Code | Source | Meaning | What loop.sh does |
|---|---|---|---|
| 0 | runner | iteration done; more may follow | call runner.sh again |
| 1 | runner | config error | stop immediately |
| 2 | runner | max iterations reached | stop; mark done |
| 3 | runner | stuck (no progress for 2 iters) | stop; mark stuck |
| 4 | runner | AGY exited non-zero | stop; mark error |
| 5 | runner | AGY produced no usable output | stop; mark error |
| 6 | runner | timeout | stop; mark error |
loop.sh propagates non-zero exit codes from runner.sh so a CI step or
human caller can tell apart "track done" (exit 0) from "track stuck"
(exit 3) from "AGY crashed" (exit 4).
- bash (macOS-shipped
/bin/bashis fine; we avoid bash 4+ features likemapfile) - python3 (preinstalled on every macOS) — only used for YAML parsing and prompt composition
- agy at
/Users/__blitzzz/.local/bin/agy— invoked only by the non-dry-run path
No yq, jq, perl, timeout, or any Linux-only tool. The
.gitignore does not require anything special.
| Symptom | Where to look |
|---|---|
| Runner exits 1 with "tracks.yaml not found" | tracks.yaml is missing from repo root |
| Runner exits 1 with "malformed YAML" | Re-run python3 -c "import yaml; yaml.safe_load(open('tracks.yaml'))" |
| Runner exits 1 with "active_track not in tracks registry" | tracks.yaml:active_track doesn't match any tracks[].id |
| AGY exited non-zero | tracks/<id>/history/iter-NNN-fail.md has stderr |
| AGY output empty | tracks/<id>/history/iter-NNN-fail.md has the prompt + empty stdout |
| Loop is stuck | tracks/<id>/meta.yaml has status: stuck, history/iter-NNN.md shows byte-identical artifacts |