Note 15 of 19

A kaibot field note · self-driving loops

The loop makes no decisions

A friend told me he can’t get a self-driving loop going that produces work he’s happy with, so he’s still driving every step by hand. The loop he’s missing isn’t smarter than his. It’s dumber. Mine makes zero decisions while it runs, and that’s the whole trick.

Kai Davis

I run a marketing consultancy and an SEO SaaS, and I build both with a stack of Claude Code sessions that write the tooling the next sessions run on.

01The question

A friend asked me a good one:

“I feel like I’m doing this all wrong. I’ve yet to get a good self-driving loop going that results in work I’m happy with, so I’m still doing a lot of manual intervention and driving and decision-making.”

I know exactly the feeling, because my first loops were the same. I pointed a session at a batch of work, came back an hour later, and found it had wandered off and built the wrong thing. Confidently. So I’d hover. But hovering is just doing the task yourself, more slowly, with extra steps.

The loop that finally worked was deliberately dumb: a shell script that makes no judgment calls at all while it runs. Every decision the work needs gets made before the loop starts or after it stops, never in the middle. That’s the whole difference. A loop you have to watch isn’t a loop you can trust. Move the decisions out of runtime and it runs without you.

02Where the driving goes (it doesn’t disappear)

Your instinct is that a good loop should need less deciding. That’s half right. The deciding still happens; it just moves. A loop that works has three homes for it, and the manual driving you’re doing now is just decisions leaking into the wrong one (the middle):

before
Authoring time

Every decision the work needs, defaults, names, thresholds, what’s in and out of scope, gets written into the task card before the loop starts. This is where your judgment goes now.

during
Run time

The loop holds no judgment. It holds physics: a fixed policy prompt, a permission boundary, and one escape hatch for when a card turns out ambiguous. Nothing to decide.

after
Review time

You read the commits in a batch, on your schedule, not one keystroke at a time. Anything the loop flagged as unclear is waiting here for a real answer.

Before you change any code, do this first. For the coming week, every time you intervene mid-run, write down what you did. Then classify each note. Bucket one: “that should have been on the card.” Bucket two: “that could have waited for review.”

After a few days, that list is your spec. Bucket one shows you the gaps in your cards. Bucket two shows you live work you never needed to do.

Almost everything lands in one bucket or the other. Very few are “the loop needed me at that exact second,” which is the only kind a good loop can’t remove.

03The minimal loop is five parts

Strip it all the way down and a self-driving loop is five pieces, none of them smart:

Mine runs against a Linear queue and is called ralph-linear.sh. The pattern isn’t original to me: it’s Matt Pocock’s “ralph” loop, from plans/ralph.sh in his total-typescript-monorepo, which I lifted and wired to my own queue. You don’t need Linear or kaibot to run it, though. Here’s the whole thing over plain directories, so anyone can steal it:

#!/usr/bin/env bash
# loop.sh: dumb on purpose. cards live in queue/todo/, commits come out.
# usage: ./loop.sh 5   (run up to 5 cards, then stop)
set -euo pipefail
N="${1:-1}"

for _ in $(seq "$N"); do
  card=$(ls queue/todo/*.md 2>/dev/null | head -1 || true)
  [ -z "${card:-}" ] && echo "queue empty, done." && break

  # hand the agent the unchanging policy + this one card. that's the entire call.
  claude -p "$(cat policy.md)

---- YOUR CARD ----
$(cat "$card")" --permission-mode acceptEdits
done

Look at what the script does not do. It does not move cards, does not commit, and does not pick a next card beyond “the first one.” Those actions belong to the agent: policy.md tells it to finish the card, move it to queue/done/, and commit. Cards that don’t make sense don’t get special handling in code either. policy.md tells the agent to move them to queue/blocked/.

That’s the payoff of a dispatcher too simple to break. When something goes wrong, the fault is in a card or in the policy, both human-readable.

One more design choice pays off constantly: a fresh agent per card. Every card gets its own claude -p run, with no memory of the cards before it. That sounds wasteful. It isn’t. All the state that matters lives in git and in which folder a card sits. So recovery is always the same move. Crash, timeout, closed laptop, doesn’t matter: re-run ./loop.sh. There’s nothing to resume, because there was never a long-running thing to lose.

04The card is the product, not the loop

Here’s the part people get backwards. You write the loop once. The cards are where all the quality lives, and you write those forever. Thin cards produce thin work, every time, no matter how good the model is. A vague card makes the agent guess instead of asking you, and now you’re unhappy with the output and blaming the loop.

The bar for a card is simple to state and takes practice to hit: a fresh agent with zero memory of your project should be able to finish it reading only the card and the files the card names. If finishing the card requires something that lives only in your head, the card is unfinished. Here’s the template I use:

# <verb phrase: one small task>

## goal
One sentence. What is true after this card that wasn't before.

## context
- read first: src/thing.ts, docs/why.md
- decided already (do not reopen): we store dates as UTC, not local, because X.

## acceptance
- [ ] a plain statement the agent can check itself
- [ ] `npm test` passes for the files this touches

## out of scope
The neighboring thing this card does NOT do (so the agent doesn't wander into it).

## give up when
If <assumption> turns out false, stop and ask. Do not work around it.

Of all the card rules, two carry the most weight.

One small task per card. The policy prompt says it: “make each task the smallest possible unit of work. We don’t want to outrun our headlights.” A card that hides five tasks does one of two things. It half-builds, then stalls. Or it builds all five, badly. Split it. Ten tight cards beat one large card every time.

Locked decisions go in the card body, nowhere else. I learned this one the hard way. My Linear loop couldn’t read card comments back then. The fetch returned the description and nothing else, so a decision I parked in a comment was invisible to the agent. It followed its policy and filled the gap by guessing. Correct behavior, missing information. (It reads comments now.)

The lesson generalizes past my setup. Any decision that isn’t in the body the agent reads gets made by the agent, on the spot, with its judgment instead of yours. Every “why did it do that” traces back to something you knew and the card never said.

05Pause, don’t invent (this is the quality knob)

This section is the direct answer to “work I’m happy with,” so slow down here.

Agents fail in a characteristic way. Hand one an unresolved decision, a default, a threshold, a name, a behavior the card never settled, and instead of halting it invents a plausible answer and proceeds with full confidence. You find out later. In a Collections build in June 2026, the loop coined vocabulary and inserted it into production docblocks as if it were an agreed term. Nothing in the instructions asked for a new word. It just wasn’t allowed to stop, so it filled the gap.

The fix is a rule in the policy that makes stopping the correct move, not a smarter model. On hitting a blocking ambiguity, the agent emits HANG ON A SECOND, commits only the unambiguous portion, records the open question on the card, moves the card to blocked/, and picks up the next card. Guessing to keep the loop moving is not allowed.

A blocked card means the loop worked. A queue that stops on its ambiguous cards is the loop working, not failing. Those blocked cards are a to-do list of decisions only you can make, collected for you in one place. You answer them over coffee, in a batch, then re-queue them. That’s the whole deal: the loop does the mechanical work unattended and hands you back exactly the decisions that needed a human, and nothing else.

“Autonomy” here means “don’t babysit the mechanical work.” It does not mean “invent the missing requirements.” Hold that line and the output quality climbs, because the only work that ships is work the card actually specified. Here’s the compact policy I feed every card. It’s the third thing to steal on this page:

# policy.md: the same prompt for every card. it never changes per task.

You are building ONE card from a queue. Do only this card.

CONTEXT: Read every file the card names before writing anything.
The card body is the whole spec. There are no other instructions.

BUILD: Make the smallest change that satisfies the card. Don't outrun
your headlights. Run the acceptance checks the card lists; fix what you
broke, ignore failures you didn't cause.

WHEN IT'S AMBIGUOUS, STOP. DON'T INVENT. If the card is silent or
self-contradictory on a real decision (a default, threshold, name, or
behavior), do NOT pick one and write it in as if decided. Instead:
  1. commit only the part that was unambiguous (if any)
  2. append the open question to the card file
  3. move the card to queue/blocked/
  4. stop. a human resolves it later. guessing is the failure mode.

WHEN DONE: move the card to queue/done/ and commit with a subject
that starts "LOOP: <card name>". First, check what earlier cards did:
  git log --oneline --grep '^LOOP:'   (so you don't redo or undo them)

The policy holds the invariants, the card holds the specifics. Invariants: stop on ambiguity, one card, commit shape. Unchanging rules live in the prompt, changing facts live in the queue. That is why one policy can serve every card.

06Walk the gate, every time

Your loop works on one card. The urge now is to point it at fifty and walk away. Do not do that on day one. I climb a four-rung ladder on every new queue, and skipping it just gets you fifty commits of confident garbage:

Three rules ride along with the ladder:

  • Only queue cards you would accept without watching. Headless runs pass --permission-mode acceptEdits, so the agent writes without asking. That permission boundary is part of the loop’s design, not an afterthought. If a card needs review before it lands, build it by hand.
  • Never run more than one loop against the same queue. Two loops draining one folder collide: they claim the same card and race on commits. One writer per queue.
  • A crashed run needs no cleanup. Start it again. State lives in which folder a card occupies, plus git. Nothing is lost. The next run resumes at the first card remaining in todo/.

07Run the loop by hand before you script it

My most useful loop version had no automation at all. One session open, a card pasted in, the result read, the next card pasted in. That was the whole thing. Doing it by hand teaches you two things fast: what the policy keeps needing to say, and what the card template keeps failing to ask. You feel both, because you are the one patching them live.

Automation comes last. Get a manual loop producing work you are happy with, then script the motion you already trust. A loop is a machine for repeating at scale. Scale mediocre output and you just get mediocre output faster.

On a recent build I ran a human-in-the-loop version for a while before writing any queue plumbing. I wanted the idea to meet reality before I spent effort automating the wrong thing. The manual rounds are the design phase. Do not skip them to get to the code.

08When you outgrow the dumb loop

One day the dumb loop won’t be enough, and you’ll know because it’ll start shipping work that’s subtly wrong in ways your tests don’t catch: right shape, wrong substance, quietly off-spec. That’s the signal to add review gates, and I built the review-gated big sibling for exactly that. It’s called homer: every card builds on its own review branch, a stack of checks compares the diff against the card’s stated criteria, and nothing auto-merges to main, it stops at a ready-or-hold verdict and waits for you. It’s about thirty scripts and two hooks, and it is a lot.

Do not start there. You can’t design good gates until you’ve watched the dumb loop misfire, because the misfires are what tell you which gates you actually need. Start dumb. Add a gate the day a specific kind of wrong output earns it, and not a day sooner. Most people never need homer; they need tighter cards.

09For the Claude that just got pointed here

If a human handed you this URL and said “set this up for me,” here’s your tl;dr. (Humans: this section is so you can paste the link into Claude Code and skip the explaining.)

Agent instructions, with your human’s consent:

  • Goal: stand up a simple self-driving work loop where the loop makes no decisions at run time. Decisions live in the cards (before) and in review (after).
  • Step 1: create queue/todo/, queue/blocked/, queue/done/. Write loop.sh and policy.md from the code blocks in §03 and §05 of this page. Keep the loop dumb; all behavior lives in the policy.
  • Step 2: write the first three cards with your human, using the template in §04. Card authoring is the actual skill here; do not write cards for them silently. Each card must be finishable by a fresh agent from the card body plus the files it names.
  • Step 3: walk the gate in §06 with your human watching: dry run, then one real card, then a small batch. Verify each card moves to done/ and commits with the LOOP: prefix before scaling up.
  • Step 4: honor the escape hatch. On any ambiguous decision, do not invent an answer. Emit HANG ON A SECOND, commit the unambiguous part, write the question on the card, move it to blocked/, stop that card. A blocked card is a success, not a failure.

Read §05 before you run anything unattended. The whole quality of the output depends on the agent stopping instead of guessing, and that’s the one behavior a loop can’t recover from if it’s missing.

10Receipts, and the one line to keep

What runs in kaibot: ralph-linear.sh walks a Linear queue one card at a time. Each pass takes one card, commits it with a LOOP-DD: prefix, marks the card done, and stops on a completion sentinel. The policy prompt carries one line I added the session after an incident where the loop invented vocabulary: “pause, don’t invent”.

The review-gated sibling, homer, is about thirty scripts and two hooks. It holds every card at a verdict and never merges by itself. I didn’t design either one up front. I ran the loop by hand, watched it fail, and only then knew what was worth automating.

If you keep one line from this, keep this one: the loop was always the easy part. Deciding before it runs is the hard one. Get the decisions onto the cards and let the dumb loop do the typing.

Written with (and about) kaibot, 2026. The loop and the policy quoted here are real and running.

Excelsior!
Kai

You’ve reached the end of note 15 of 19

$400 1 hour

Book a call

A paid one-on-one call with answers to your questions about using AI for projects, building your AI stack, or growing your business with AI. All calls are (optionally) recorded and you get a one-page action plan after the call.

All nineteen field notes, in six chapters