# The loop — autonomous iteration protocol How an AI agent works on Nomarchy unattended. One **iteration** = pick one task, do it, verify it, commit it, record it. The protocol is runner-agnostic; the same iteration works under any of: - **Interactive `/loop`** in a Claude Code session in this repo — the agent self-paces iterations until stopped. - **Headless** (`claude -p`, cron/systemd-timer) — one invocation runs one iteration (or a small fixed number) and exits. - **A fresh manual session** — a human says "do a loop iteration"; the files below carry all the state, so any session can pick up where the last left off. All loop state lives in this directory, git-tracked. There is no state outside the checkout (the distro's own philosophy, applied to its agents). ## The files | File | Role | Who writes it | |---|---|---| | `GOALS.md` | North star + quality bars + non-goals | Human (agents propose edits) | | `BACKLOG.md` | Prioritized task queue (NOW/NEXT/LATER/PROPOSED/DECISIONS) | Both — see its header rules | | `JOURNAL.md` | Append-only iteration log | Agents | | `MEMORY.md` | Curated durable lessons/gotchas | Agents (curated, not append-only) | | `HARDWARE-QUEUE.md` | Pending on-hardware checks only Bernardo can run | Agents append, human checks off | | `CONVENTIONS.md` | Repo/design conventions to follow while coding | Human (agents propose edits) | ## One iteration, step by step ### 0. Orient 1. Read `GOALS.md`, `CONVENTIONS.md`, `MEMORY.md`, the **last 3–5 entries** of `JOURNAL.md`, and `BACKLOG.md`. 2. `git pull --ff-only` (skip silently if offline). Confirm you are on `main` with a clean tree. **A dirty tree you didn't create → stop and report; never stash or discard someone else's work.** 3. Sanity baseline: if the last journal entry reports a red `nix flake check`, or you have any reason to suspect breakage, run `nix flake check --no-build` first. **A red baseline preempts the backlog — fixing it *is* this iteration's task.** ### 1. Pick exactly one task - Take the **topmost actionable** item: NOW before NEXT; never LATER unless NOW and NEXT are empty or all blocked. - *Actionable* means: not `[blocked:hw]` (those wait in `HARDWARE-QUEUE.md`), not `[human]` (decisions), and small enough to finish + verify in one iteration. If the top item is too big, **split it in BACKLOG.md** (that edit is part of the iteration) and take the first slice. - Never implement anything from **PROPOSED** — those await human triage. - If nothing is actionable, do a **QA sweep** instead: run the full check suite, hunt drift (README option tables vs the live `nomarchy.*` surface, template drift, dead code), deepen a VM test, or research and write up a PROPOSED item. An iteration that only improves the backlog is a valid iteration. If even that yields nothing, journal it and stop — do not manufacture churn. ### 2. Work - Keep the diff focused on the task. Unrelated fixes you trip over become PROPOSED/NOW entries, not scope creep. - Follow `CONVENTIONS.md`. Match the surrounding hand-formatting; never run a formatter. - New gotcha discovered the hard way → one line in `MEMORY.md` now, while it's fresh. ### 3. Verify — the ladder Climb as high as the change warrants and your environment allows; **record the tier reached** in the commit body and journal entry. | Tier | What | When required | |---|---|---| | **V0** | `nix flake check --no-build` (+ `bash -n` / `py_compile` for scripts) | Every change, no exceptions | | **V1** | Build the touched output: `system.build.toplevel`, the HM generation, the ISO, or the package | Anything beyond docs/comments | | **V2** | VM: a `checks.*` runNixOSTest (add one if the change is guardable), or boot `tools/test-live-iso.sh` / `tools/test-install.sh` | Behavioral changes — services, boot, installer, session | | **V3** | Real hardware | Cannot be done by the agent → append to `HARDWARE-QUEUE.md` with exact test steps | The honesty rule governs: a visual/interactive change verified only to V1 is **not done** — it ships as "V1-verified, V2/V3 pending" with the pending check queued. Prefer *adding a permanent `checks.*` test* over a one-off manual VM poke when the behavior is testable headlessly (see MEMORY.md for the reusable recipes). ### 4. Commit + push - Style: match the log — `feat(scope): …`, `fix(scope): …`, `test(scope): …`, `docs(scope): …`. Body explains what/why + the verification tier reached and what remains. - Include the `agent/` bookkeeping updates (backlog/journal/memory/queue) **in the same commit** as the change they describe. - Commit directly on `main` and `git push` (Bernardo's standing workflow). - **Never:** force-push; touch the `v1` branch or any branch/tag you didn't create; commit secrets or binaries; run `nix flake update` unless the task is explicitly a lock bump; delete themes, wallpapers, or user-facing assets without the backlog saying so. ### 5. Record 1. Mark the task in `BACKLOG.md` (move to its ✓ line or delete, per that file's rules). 2. Append a `JOURNAL.md` entry (template in that file). 3. Queue any V3 checks in `HARDWARE-QUEUE.md`. ### 6. Pace (self-paced runners only) Under `/loop`, continue to the next iteration while tasks remain actionable and checks stay green. Stop the loop when: nothing is actionable, the same task has failed twice (journal the failure analysis and mark the item `[stuck]`), or a `[human]` decision blocks everything remaining. ## Stop-and-escalate conditions (any runner) Write a journal entry + a BACKLOG note, then stop, when: - A fix would require touching `v1`, force-pushing, or a nixpkgs release jump. - The working tree contains uncommitted work you didn't create. - A task turns out to need a design decision Bernardo hasn't made → move it to **Decisions** in BACKLOG.md with the options laid out. - Two consecutive iterations failed on the same task (`[stuck]`). - Anything would delete or rewrite user data, git history, or the state file schema in a non-backward-compatible way.