There are many ways to build agentic loops. This is a simplified version of what I do. The tools are moving fast, and some of this may even be outdated by the time you finish reading this deck!
Take the principles, not the syntax. The shape of the loop matters more than which file does what.
The app is deliberately trivial, so that the loop is the only variable.
What’s under test
The loop — spec, build, verify, document, state in files. Not the app it happens to produce.
Why it’s small
You strip a system to the smallest thing that still shows the behaviour. Standard practice. Nobody answers a repro with “too small, so the bug isn’t real.”
What scale adds
More context to route, more invariants to protect, more ways to be wrong. That changes the anchors — not the shape of the loop.
What it can’t prove
That your worst-case bug is as cheap as mine. That one is real. It gets its own slide in Part 4.
Judge the mechanism first. Then argue about blast radius — I’ll make that argument for you.
We're shifting the burden from the model to specification & scaffolding.
Model
The engine
Raw capability. Necessary, but the part everyone talks about.
+
Files
The judgment
Standards, specs, and memory. Where your engineering lives.
+
Loop
The discipline
Build, verify, document (repeat until done).
Take any one away and it stops working. And most of what makes it good isn't the model. It's the files (and what we'll cover in the rest of this deck.)
Only one of these can hallucinate. Knowing which is what makes the loop trustworthy.
Generatecan hallucinate
Text from weights
The model writes the most plausible next tokens from what it learned. Brilliant for synthesis and code — and the only place a guess can sneak in.
prompt→model→plausible text
Callreturns a fact
A tool runs, for real
The model emits a structured request; a deterministic system runs it — code, a search, a test — and hands back ground truth the model then reads.
request→tool runs→fact→model reads
In a loop, most of the work is tool calls returning ground truth not the model free-associating. run-qa’s Playwright test actually ran; the model didn’t decide it passed.A call is an anchor — a result that can’t be argued with.
The loop you just met, relabeled in graph vocabulary. It was a graph the whole time.
Nodes
Build · Verify · Document — one bounded job each.
Conditional edges
The GREEN / RED verdict routes the flow. RED cycles back — bounded at 3.
Checkpointed state
roadmap.md · PROGRESS.md · CLAUDE.md— the run survives any session.
Traversal engine
run.sh — every graph framework runs a loop underneath.
An edge is real only if data moves across it — and every arrow here carries data. By the graph crowd’s own rule, “a loop is fine.” Loops buy depth; graphs buy breadth — Jet is this, fanned wide.
Executes the spec for a single feature / sprint. Then done.
The Builder is just a Claude Code session — no special framework, no persistent agent. Spun up fresh for one sprint, then thrown away. Its power is that nothing carries over.
Stateless
Clean brain every turn — no context rot from the last sprint.
Self-checking
It calls run-qa on its own work before it ever declares done.
Hands off
Writes its memory to PROGRESS.md so the next session can pick up.
One call — run-qa — runs all three QA agents and returns a single verdict: GREEN or RED. RED three times and it stops for a human.
run-qa
→
Lint
→
Review
→
Test
→
GREEN
↺ RED → read failures → fix → re-run max 3 attempts, then mark blocked & stop
.claude/skills/run-qa/SKILL.md
---
name: run-qa
description: Full QA pass — lint, review,
e2e. Returns one GREEN/RED verdict.
---
# run-qa
Run these in order, each as a subagent:
1. Delegate to linter.
2. Delegate to code-reviewer.
3. Delegate to playwright-tester.
Verdict: any lint error, CRITICAL
finding, or failed spec → RED.
Otherwise → GREEN.
If RED: fix, re-run from step 1.
Stop after 3 attempts; report to human.
.claude/agents/They check, they don't build — cheaper, faster models.
This is the scaffolding that earns the trust — the answer to the doubt loop from Part 1.
Every verdict is a contract — GREEN / RED · CRITICAL / WARN / NIT — a shape the next step consumes, never prose.
Judgment · latentvsVerification · anchoredTwo anchors and one opinion — lint and tests are deterministic; the reviewer is a fresh-context judgment, and the anchors outrank it.
RED doesn't stop the loop — it routes back to the Builder.
On RED, the verdict carries the actual failures — the Builder reads them, patches, and re-runs. Three attempts, then it stops. RED isn't an opinion — it's a failed assertion; the fix targets a fact, not a feeling.
RED → read failures · fix · re-run
The Builder
Write & fix
Builds the feature. On a RED return, reads the failures and patches the code.
→
run-qa
Verify
Lint · review · test. Returns one verdict — and the failures with it.
→
REDloops back ↑
GREENexits → Document
Three attempts, then it stops. Still RED after the third try → mark the sprint [!] blocked and hand back to a human. Autonomous, never infinite.
## Sprint 3
scaled hire cost.
spend bug → fixed
(guard the spend)
03CLAUDE.md
Promote durable learnings to permanent context.
Learning
## Gotchas
+ score must never go
+ negative — guard spend
A deliberate simplification. In production this handoff is a context-management system writing to several stores — here it’s one file per job, so you can read the whole mechanism in one sitting.
$ DEMO_MODE=planted ./run.sh # guaranteed Sprint-3 RED → GREEN
A fresh session spins up for each sprint — it builds the feature, runs the QA gate, ticks the roadmap, and hands off. Then the next sprint starts with a clean brain. You write specs; the loop writes code.
A full run takes up to ten minutes. Watch the console scroll by if you like, or step away, stretch, refill your water, and let the loop cook. It doesn't need you for this part.
Autonomy is priced by what a missed mistake costs.
Not by your seniority, and not by how complex the system is. By what happens when the loop is wrong and nobody notices.
Cheap to missrun unattended
Something below you holds the line
Types, the framework, the database, the platform enforce your invariants for you. The worst realistic bug is transient — restart, resync, gone.
mistake→caught by the floor→restart
Expensive to missgate the diff
You are the invariant
Schema migrations, money movement, concurrency — anything that writes durable state. A missed mistake is silent and permanent, and no restart repairs it.
mistake→durable→no undo
I work high in the stack, where the floor catches me — that is the fair version of the “toy” critique, and it was never that the example was small. The loop still runs down there. The gate just moves: a human on the diff, not on the keystrokes.
“I’m skeptical of loops.” You’re already running one.
There are only three places a loop can live. Declining to build one doesn’t delete it — it leaves it in the most expensive place.
03 · Driving · most teams
In your head
04 · Helicopter · you are here
In files
06 · Spaceship · the sequel
In the system
Who runs it
You, in real time
run.sh, from files
The system, from compiled rules
When you stop
It stops
It keeps going
It never needed you
Scales with
Your attention
Your specs
Your corrections
Where the gate goes
Nowhere — you are the gate
On the diff, every sprint
On the equivalence check
“Offloading a full task to an agent” is a loop too — someone else’s, with the control surface hidden.The one you can see is the one you can put a gate in.
The advantage compounds. The gap between Driving and Helicopter isn't closing — it's widening.In public: the Bun runtime port — ≈535K lines of Zig → 1M+ lines of Rust in 11 days, ≈$165K of usage against roughly a year of engineering. (The review-burden critique is real — and earned.)
You’re not buying tokens. You’re buying the headcount you didn’t add.
“An expensive way to spend tokens” prices the loop against zero. Nothing is priced against zero — price it against the person you’d have to hire.
The real baselineloaded cost
A day of senior attention: $1,500–2,500
Salary, benefits, overhead — before a single meeting eats it. The QA pass and the PM writing the ticket are the same currency.
task→headcount→$$$$
My actual billmeasured, not modelled
A month of heavy use: $500–1,000
Hundreds of hours in Claude Code across a month of real work. Less than one engineer-day — at every corner of both ranges.
spec→loop→verified sprint→$
Cap first: one repo, twenty items → read the usage report → earn the scale, then double.
The honest limit: cost rides usage — at this altitude you pay full price on every run. That isn’t a rebuttal, it’s why Part 2 exists: compiling the stable paths until they run free.
A skill file holds a procedure, not facts — how to do a recurring job: run the three checks, return one verdict. The method becomes reusable and versioned, kept separate from any single task it runs on.
Each subagent gets its own context window and exactly one narrow job. The reviewer never sees the builder’s scratch work; the tester starts fresh. Narrow scope means a cheaper model and no cross-contamination.
A model’s context is finite and degrades as it fills. So every sprint launches a new session with an empty window — the roadmap and PROGRESS files carry state forward, not the model’s fading memory.
that’s the clean brain per sprint→roadmap.md + PROGRESS.md
When a session learns something durable — a gotcha, a convention — it writes it back into the broad context. The next session starts smarter. The system quietly edits its own instructions.
Generation is fuzzy by nature — synthesis, design, judgment. Verification isn’t: the assertion passes or it doesn’t. You never trust the model’s opinion of its own work; you run a tool that returns a fact. The gate is anchored: lint and tests are deterministic; the reviewer adds a fresh-context opinion — and anchors outrank opinions.
# Click Farm
## Identity & Mission
You maintain the Click Farm game. Done = the feature
works, QA is GREEN, nothing else broke.
## Map
One index.html (inline CSS + JS). State lives in
localStorage. Specs are in specs/.
State: localStorage key clickfarm = { score, farmhands }.
Stable ids: #score, #harvest, #hire, #farmhands, #cost.
## Conventions
Vanilla HTML/CSS/JS — no frameworks, no build step.
Mobile-first, system fonts, one accent color.
## Running it
Open index.html in a browser to play. run-qa to check.
No package manager.
## Guardrails
Never add dependencies or a build tool. Don't edit
other sprints' specs. Ask before changing the schema.
## Definition of Done
Not done until run-qa returns GREEN.
If RED, read it, fix it, re-run. Never ship on RED.
# Sprint Turn
Read roadmap.md and pick the first sprint marked [ ].
Read that sprint's spec and PROGRESS.md for context.
Build the feature per the spec.
Then run the run-qa skill until GREEN (max 3 attempts).
When GREEN:
- Mark the sprint [x] in roadmap.md.
- Append a handoff to PROGRESS.md: what you built,
key decisions, gotchas, what the next sprint needs.
If you can't reach GREEN after 3 attempts:
- Mark the sprint [!] in roadmap.md, then stop.
Updating the status is your final action — never skip it.
# Sprint 2: Farmhands (auto-harvest)
## What to build
A "Hire Farmhand" button (cost 10). Hiring deducts
10 and adds a farmhand. Each farmhand earns
+1 score/sec automatically.
## Acceptance Criteria
- #hire with score ≥ 10 deducts 10 and adds a #farmhands
- With ≥1 farmhand, #score rises ~1/sec on its own
- #cost shows the hire cost (10)
## Technical Notes
- New ids: #hire, #farmhands, #cost
- setInterval 1000ms; persist state on each tick
# Sprint 3: Scaling Cost
## What to build
Farmhands get more expensive the more you own.
cost = floor(10 * 1.15 ^ farmhands). #cost
shows the NEXT price, updates after each hire.
## Acceptance Criteria
- #cost increases after each hire (per the formula)
- When score < cost, clicking #hire does nothing
(score & farmhands unchanged)
- Score never goes negative; hiring at score == cost succeeds
## Technical Notes
- Integer math, Math.floor
- Guard lives in the acceptance test; the bug is
planted via the fixture swap, not the spec
# Sprint 4: Polish
## What to build
Number formatting + click feedback.
## Acceptance Criteria
- Score ≥ 1000 shows abbreviated (1.2K / 3.4M / 5.6B);
below 1000 shows the integer
- Same formatting on #cost
- Underlying state stays an exact integer (display-only)
- #harvest pulses briefly on click
## Technical Notes
- Format on display only — never round the stored value
---
name: run-qa
description: Full QA pass — lint, review, e2e.
Returns one GREEN/RED verdict.
---
# run-qa
Run these in order. Each is a subagent; use its
returned message as the result.
1. Delegate to linter.
2. Delegate to code-reviewer.
3. Delegate to playwright-tester.
Verdict:
- Any lint error, any CRITICAL finding, or any
failed spec → RED. List what failed.
- Otherwise → GREEN.
If RED: fix the issues, re-run from step 1.
Stop after 3 attempts; then report to the human.
---
name: linter
description: Validates HTML structure and JS errors.
tools: Read, Edit, Bash model: haiku
---
Run validation on index.html:
1. Check HTML is well-formed (tags, nesting)
2. Run node --check on inline script content
3. Check for unclosed strings, missing semicolons,
undefined variables
Auto-fix trivial violations (formatting, whitespace).
For judgment calls, leave it and report it.
Return "CLEAN" if no errors remain, else the list
of remaining errors with location.
---
name: playwright-tester
description: Runs behavioral tests against the app.
tools: Read, Write, Bash model: sonnet
---
Write and run a Playwright test for the sprint's
acceptance criteria.
1. Create tests/click-farm.spec.js from the sprint spec
2. Open index.html via file:// or a local server
3. Test each acceptance criterion
Key scenarios for Sprint 3:
- Click → score increments
- Hire with enough score → cost deducted, +1 farmhand
- Hire when you can't afford it → blocked, no change
- Hire at score == cost → succeeds, score ≥ 0
- Score never goes below 0
- State persists across refresh
Return "GREEN" if all pass, else "RED" plus, per
failure: test name, failed assertion, actual result.
“Fine for toys — our system is much more complicated.”
Click Farm is a pendulum, not a bridge. Nobody says gravity only applies to pendulums.
What scales unchanged
The invariants
Thick specs make building trivial. Anchored gates catch what generation hallucinates. State survives in files. None of this cares how big your system is.
What your complexity becomes
The specs and the gates
Edge cases, tribal scar tissue, the rules an optimizer would bend — that content comes from senior engineers and nowhere else. The complexity objection is the job description.
Receipts, if pressed: the Bun port — a year of work in eleven days, in public · the fan-out-and-verify diamond running in production inside Claude’s research feature · three communities converging on file-based state and deterministic gates without ever meeting.