The loop
Lap
/pitwall:lapOne round: pick a task, branch, code, prove it, review it, open the PR.
What it does
/pitwall:lap runs one round of the loop. It takes the next task from the queue, does it on a fresh branch, proves it, gets a review and an independent verdict, and opens a pull request. Then it ends, or, when /pitwall:drive started it, schedules the next round. It never merges.
Use it when
- you want to watch one round before you let the loop run on its own
- you changed the profile or a playbook and want to see one task end to end
- one task is urgent, and
/pitwall:drivewould keep going after it
Try it
/pitwall:queue
/pitwall:lap
Run /pitwall:queue first. A lap only takes tasks from the queue, and the queue is where every question for you was asked, up front.
How a round runs
- Sync. Pull the base, tick or untick PR checkboxes whose code changed since they passed, re-verify one older PR, and note which PRs were merged.
- Pick. Take the first task that is not blocked, parked, already open as a PR, or waiting for another merge. Nothing left: report and end.
- Branch. A new branch from the base, named by the profile's pattern.
- Work. Research, a six-part brief, then code. A large task goes to a subagent for its role; the lead reads every diff and reverts any file outside the brief.
- Prove and review.
/pitwall:gaugeruns your checks and drives the page in a browser. The same review prompt goes to every model on the panel, and their findings become one table. - Verdict and PR. A fresh, read-only verifier checks the committed head and pins its verdict to the patch-id. Then the PR opens with all of the evidence.
A task that cannot be finished safely is parked instead of shipped: the work is kept as a local commit that is not pushed, the reason goes on record, and the round moves to the next task. A task that runs past 90 minutes changes its method first; after two changes, or 180 minutes, it parks.
What you’ll see
The examples below come from a made-up repo, acme/shop.
The report at the end of the round
Lap · P2-04 Sort search results by price
PR https://github.com/acme/shop/pull/58 [PARTIAL]
Task plan row P2-04 (tracker: none)
Gauge PARTIAL: every A and B item passed, 1 C item left for a human
Edited src/search/sort.ts · src/search/sort.test.ts · docs/plan/phase-2.md
Parked none
The pull request
What. Sort search results by price, low to high and back. Plan row P2-04, playbook feature.
Changes
- the search page has a price sort, low to high and high to low
- results with the same price keep newest first
Verify
| Item | Level | Command / method | Result |
|---|---|---|---|
| typecheck | A | pnpm typecheck | ✅ 0 errors |
| unit tests | A | pnpm test src/search | ✅ 14 passed |
| search page | B | dev server /search?q=lamp @1366 | ✅ order correct both ways, console 0 errors |
| phone check | C | 1. search “lamp” on a phone 2. sort high → low → order is right | ⏳ |
| Verifier | — | general-purpose sonnet @3f9c2e1 patch 8d41b0c27a9e | ✅ PARTIAL |
Decisions to confirm
- Equal prices sort newest first · other option: by name · change it in
sortResults()
Agents used
- Explore haiku · research → 6 files read
- general-purpose sonnet · coding → done
- review panel, 2 models · reviewing → 2 findings
Review
- act on (both models): an empty result list crashed the sort → fixed
- consider (one model): cache the sorted list → follow-up
Not done yet (before merge)
- C · on a phone: search “lamp”, sort high → low, the order is right
When a task is parked
Parked P2-05 Pay with a saved card
Reason the plan does not say what happens when the saved card has expired
Saved commit "wip: P2-05 parked — …" on agent/p2-05-saved-card (not pushed)
Asked the question is written into the plan's open questions on that branch
Stop or change it
/pitwall:stopfinishes the task in progress, then stops./pitwall:brakestops now and saves the work as a WIP commit./pitwall:driveruns lap after lap until the queue is empty.- The profile,
.claude/agent-loop.md, sets the branch pattern, check commands, review panel, agents per task and dev server.
A lap never merges, never force-pushes, never touches a branch the profile protects, and never asks you a question in the middle of a round.
The playbook the agent followsThe full spec for /pitwall:lap. This page sums it up; when the two differ, the playbook wins.
/pitwall:lap — 1 round of the agent loop
This round does not wait for a human on anything it can decide itself: work → /pitwall:gauge → review → commit → PR → end the round (when coming from /pitwall:drive → it schedules the next round itself, see section 8) · humans merge
Proving is not deciding — a check the agent can run itself must never be handed to a human (pstack: “never hand the human a check you could run”)
⏳ is allowed only in these 3 cases — “the 3 wait cases (⏳)” (the single list — /pitwall:gauge, /pitwall:drive and the playbooks refer here); each needs a specific reason in the PR:
human-only step while the user is away— a step on the profile’shuman-only stepslist (level B: wallet popup, 2FA, payment confirm …) must be done and the user is away — “present” = the user typed a message or ran a command themselves in this session during this round (including running/pitwall:lapthemselves) → announce/pitwall:gauge’s pair run, wait for “go” for at most 10 minutes; no reply = away · the waiting time does not count toward the 90 minutes (section 4)needs the dev env after merge— has to be checked on the dev env after mergeenvironment unusable— the profile’s dev server port is in use by someone else, the browser from the profile won’t connect, the source URL of theportplaybook does not exist / is stuck on a login page
bug-fix tasks: the repro before / after the fix must never be ⏳ — hits case 1 or 3 → park (reason: waiting for pair run / environment)
Read .claude/agent-loop.md before anything else — base branch, branches never to touch, check commands, plan, tracker, agents per task, state path all come from the profile
Tasks come from 2 sources (field source in the queue):
plan= a row in the repo’s plan — read in the order given in the profiletracker= a task in the profile’s tracker — the brief is the description (Why / What / Done when); no plan to tick
Profile tracker ≠ none → every task must have a tracker task (the pitwall:ticket skill) · none → the PR is the record; every tracker step below is skipped
0. State
path in the profile — write it with Write every time it changes
step: research | brief | coding | checking | reviewing | gauging | docs | committing | verifying
Start of round:
- No state file → the profile has a “multi-machine” section → receive state per that section first · still none → tell the user to run
/pitwall:queuefirst, end running:false= single-round mode: do 1 task, then don’t schedule morestopRequested:trueandcurrentTask:null→running:false,ScheduleWakeup stop:true, summary report ofdone/parked, endcurrentTasknot null → resume fromstep(skip section 2):git switch <branch>, hasstash→git stash popthen delete the field
1. Guard + sync
git status --porcelain # not empty (and not a resume) → running:false + stop + report asking the human to commit/stash first
git fetch origin -q && git switch <base> && git pull --ff-only
lint baseline: lintBaseline.sha ≠ git rev-parse origin/<base> → run lint on base, count warnings → lintBaseline = { sha, warnings }
upkeep: the profile names a periodic upkeep step (level B upkeep) and it is due → put it at the front of the queue as a task (the profile names the playbook / task) and record the date in state when queued
PR sync (every round, including rounds with an empty queue):
0. Agent PRs that conflict only in docs / wiki → resolve per /pitwall:merge-order item 2b · PRs still open: items in “Not done yet” with a verified entry of the PR’s current patch-id but not ticked → tick them (/pitwall:gauge “Record results + tick the PR yourself” items 2–4) · ticked boxes whose patch-id is stale → untick (same item 2) · re-verify one agent PR per round, oldest first, among those with no verdict or only verdicts at an older patch-id (a new commit or conflict resolution after the verdict — patch-id rule in /pitwall:gauge “Independent verifier”) — a PR whose latest verdict at its current patch-id is FAIL is skipped, not picked again: git switch <head> + git pull --ff-only, /pitwall:gauge “Independent verifier” (pass → replace the body’s verifier row with gh pr edit · FAIL → recorded per that section + a PR comment with its rows + the report, no fix outside a task), git switch <base> · verified entries of branches whose PRs are all merged → delete
- Every item in
donewhosemergedis not yet true →gh pr view <pr> --json state,mergedAt—MERGED→merged: true(+ tracker sync item 2) ·CLOSED→ put it in the report (+ tracker comment, keepin progress)
Tracker sync — profile tracker = none or the tracker is unavailable → skip all of it:
- assignee: parents in
parentsand every subtask must have me — missing → add me (theticketassignee rule) - Items that became
merged: trueabove →pitwall:ticketskill section 5 “PR merged” - Every parent task in
parents(subtasks may be moved by people or other teams): read the parent with its subtasks (pitwall:ticketskill) — ≥ 1 subtask and all of them atreviewor later, and the parent not yetreview→ parent →review+ comment to QA · a parent already atreviewwith no new subtasks → remove it fromparents
2. Pick a task (from queue only)
Go through queue in order (if firstTask is set, take that one first, then delete the field), skipping any that:
- have
blocked(waiting for an answer from the briefing inqueue.md) - are in
done/parked(except arepeatthat has not reached its target) plan: the row is already[x]on origin/base → remove from the queue ·tracker: status already pastin progress, or it belongs to someone else → remove from the queue- have an open PR for this task (
gh pr list --state open --json headRefNameby the branch pattern in the profile) waitFor/ dependency not merged yet → skip this round (don’t park), put the reason in the report
Nothing left → report “queue empty / waiting for merge: ”; loop →
ScheduleWakeup 1800s prompt "/loop /pitwall:lap" noop:true, end; single round → end
Got a task → playbook: not set by the queue and it’s a bug fix (task [bug]) → bug-fix · tracker ≠ none → pitwall:ticket skill sections 2–4: plan → find/create the phase’s parent task + a subtask for the row (in progress) · tracker → set in progress
→ state: currentTask, trackerId, parentId, playbook, taskStartedAt=now, step=“research”, agents=[] · new parentId → add to parents · this task’s tick-box answers (answers[<id>] from the briefing in queue.md) are requirements of the brief
→ start the task’s decisions.tsv: <state folder>/decisions/<id>.tsv with header time\tstep\tdecision\twhy\tevidence · write a row every time you decide (choose an approach, cut scope, a ladder step, ⏳), not written back at the end
Tracker set but unavailable → work can continue, but write in the PR “not logged in the tracker yet”
3. Branch
git switch -c <name per the pattern in the profile> origin/<base> (English slug, 2–4 words) → state.branch
4. Work (per CLAUDE.md + every repo rule in the profile)
Has a playbook → read <playbook>.md (same folder as this file) and follow it instead of the overlapping steps below
- research:
Explorereads the brief + the context the plan cites + the current code + the reference code (read-only branch in the profile) — finds it’s already done → skip to docs with file:line evidence - brief with 6 sections (task / context / files / requirements / do not touch / done when = DoD + the
/pitwall:gaugerows), written to the scratchpad - an install that fails on a file another program holds (an editor locking
node_modules) → change method: do the task in a separate git worktree with its own dependencies, never fight the lock in the main checkout - coding: task > ~150 lines or > 3 files → send the brief to the profile’s matching
code rolerow (subagent type + model from “agents per task”, a fresh agent per task) and name the task’s domain checklist file in the brief · smaller than that, main may do it itself record every time inagents[]:{ agent, model, step, task, files, result }(main is recorded asmaintoo) · then readgit diffof every file: a file outside the brief →git checkout -- <file> - checking:
/pitwall:gaugeset A — FAIL → re-brief the same agent with the error, 1 time — FAIL again → park - reviewing (adapted from pstack
interrogate, MIT): variety comes from the models, not personas- one prompt: the diff + the brief + the every-task review checklist file from the profile + the extra-review checklist when a profile row matches the change (added to the same prompt) + the task’s
decisions.tsv+ Decisions to confirm, and the direct question “does any default touch the “Always park” / “Never default either” lists inpitwall/SKILL.md” (found =CRITICAL) · a playbook may add its own questions - the prompt says read-only ·
git status --porcelainbefore sending and after the panel returns — a file a reviewer changed →git checkout -- <file> - send the same prompt to each model of the profile’s
review panel, in parallel, one fresh agent per model · record each inagents[]· a reviewer fails / times out → re-run it once with a fresh agent; fails again → build the table from the models that answered and say so in the PR - severities are only
CRITICAL/WARNING/INFO(pitwall-agent“As a reviewer”) — a reviewer that answers with another word (SUGGESTION,NIT…) → count it asINFO, and say so under Review in the PR - consensus table
finding | <one column per review-panel model> | call, written to<state folder>/reviews/<id>.tsv(survives a resume) — merge findings that describe the same issue, keep the higher severity,IMPACTstays in the finding text · call =act on/consider/noted/dismissed· raised by every model = highest signal · raised by one only, or the models contradict each other → the lead reads the code itself and makes the call · every one-model call and every call other thanact onfor aCRITICAL/WARNING→ a row indecisions.tsv CRITICALis alwaysact on, or park — never consider / noted / dismissed · aCRITICALfrom the “Always park” / “Never default either” question → park- act on, in the brief’s files → fix + checking again (the panel is not re-run;
/pitwall:gaugeis) · act on outside the brief’s files, consider, noted → follow-up in the PR · dismissed → only indecisions.tsvwith the reason
- one prompt: the diff + the brief + the every-task review checklist file from the profile + the extra-review checklist when a profile row matches the change (added to the same prompt) + the task’s
- gauging = the gate before opening a PR:
/pitwall:gaugeevery row — A FAIL → park · this is the writer’s check, not the verdict: the independent verifier runs later on the committed head (stepverifying, section 6)- B items that are not among “the 3 wait cases (⏳)” (top of the file) must pass in this round · stuck → change method / build the condition (“states to build” in the profile) · tried 2 methods and still not passing → park, never open the PR
- items that need a human-only step + the user is present →
/pitwall:gauge’s pair run (announcing + waiting for “go” does not count as “asking mid-round”) · user away → ⏳ case 1 + put “waiting for pair run:- ” in the end-of-round report
repeattasks: 1 batch (≤ ~8 files) per round, measure the number before/after and put it in the PR; tick[x]only in the round that reaches the target- a fork with no answer in
answers→ “The 3-step ladder (loop / autopilot)” inpitwall/SKILL.md· its step 3 here = park as a question (below; you may edit the plan yourself only where the playbook allows) - past 90 minutes since taskStartedAt → change method, not park: a
decisions.tsvrow (method dropped → method next, why), then try another approach · build the condition (“states to build” in the profile) · a smaller slice = split the row perport.md(sub-rows + Plan correction; tick only the finished sub-row) · hard stop: at most 2 switches, or 180 minutes since taskStartedAt, or no new method left → park, and the park reason lists the methods tried
park:
- has code →
git add -A && git commit -m "wip: <id> parked — <reason>"(hook fails →git stash push -u -m "agent-loop <id>", record it in parked) - park as a question → add the question to the plan’s open-questions location (profile) on the same branch
- don’t push —
parked+={ id, branch, step, reason, question? },git switch <base>, clear currentTask / branch / step → back to section 2 - Tracker (skip when
none):pending+ comment with the reason / question (ticketsection 5)
5. Docs (step=docs)
tracker → skip · plan → in the phase file on this branch: a queue item with newRow → add that row first (it is not on base yet), then tick [x] (don’t tick if a repeat has not reached its target) · profile tracker = none only → add a Progress log row | <today> | <id> <summary> | agent PR | <short evidence + what still needs level C testing> | — with a tracker, the issue (its PR comment and status) is the progress record, and a row that every PR appends at the same place makes each open sibling PR conflict whenever one merges (GitHub then also runs no CI for them until it is resolved) · first task of the phase → change the status board to in progress · plan disagrees with the actual code → fix the plan at that spot + “Plan correction” in the PR
6. Commit + PR (step=committing)
Follow opening-a-pr.md (commit, push, PR body, Not done yet) · loop-only parts:
- set step=committing in state · after the commit step=verifying (
opening-a-pr.mdsection 2 item 1) · record the verifier inagents[] - hook fails → fix 1 time → fails again → park (section 4 park)
- PR body in the profile’s PR / commit language
7. Close the task
Tracker (ticket section 5 “PR opened”, skip when none): keep in progress + 1-line comment → store prCommentId — never move it to review now (the start-of-round sync moves it when merged)
state: done += { id, pr, trackerId, parentId, prCommentId, merged: false, status: <the verifier's VERIFIED|PARTIAL> }, remove from queue (except a repeat that has not reached its target), clear currentTask / branch / step / taskStartedAt → git switch <base>
agents that used a separate worktree (isolation) → remove the worktree + its local branch as soon as its work is integrated (cherry-picked / merged / pushed as its own PR) · never remove one with uncommitted changes, one you push an open PR from, or a checkout that is not an agent worktree
8. End of round
- single-round mode → report, then end
stopRequested:true→ running:false,ScheduleWakeup stop:true, summary report- otherwise
ScheduleWakeup delaySeconds:60 prompt:"/loop /pitwall:lap" noop:false— the prompt is the whole/loopinput, so the next firing re-enters/loop(a bare/pitwall:lapruns once and the loop ends) - the profile has a “multi-machine” section → send state every round (before scheduling / stopping)
- a round the user did not start (a wakeup) that opened a PR, parked a task, or stopped the loop → also send one push notification, when the harness has one (Claude Code:
PushNotification):<repo>: PR <link> <status>/parked <id>: <reason>/loop stopped: <why>· nothing happened (sync only, queue empty) → no notification - short report every round (in the profile’s user language): task, PR link, task link, gauge status, Edited files table, what was parked with reasons · ≥ 2 PRs waiting for merge → include the output of
/pitwall:merge-order(table + “next” line) instead of describing the order yourself
Never
- never merge a PR, never push / checkout to edit branches the profile forbids, never
--force, never--no-verify - conflict on pull / rebase → park, never resolve it yourself
- never edit
.env*, secrets, CI workflows (unless the task says so directly), never answer the plan’s open questions yourself - never touch other people’s PRs / branches / tasks, never move a task to
reviewbefore its PR merges, and never move it pastreview - never ask the user mid-round — human questions are asked all at once in the briefing of
queue.md· during the run use “the ladder” · the pair run for human-only steps does not count as asking - never open a PR for a bug task that was never reproduced (playbook
bug-fixitem 1) — except the “already correct” case there, which changes no code