Principles of Loop Engineering
Derrick Bozkurt · SRE Manager, RingCentral
The prompt was the easy part.
The prompt doesn’t decide
So what controls the system?
Agent proposes.
Loop decides.
Intelligence generates the work. The control system decides whether it counts.
Building the control system around the model, not just the prompt inside it.
A goal is one sentence naming done: not a plan, not a task list — the state the run must reach.
Every tick rebuilds the prompt around it: the context resets, the goal does not.
The rest of the loop points back at it: stops bound it, review judges it, done measures it.
The loop cannot tell finished from stopped. It ends when the agent runs out of things to say.
Anchors are ordinary files: the task, the conventions, and the plan, versioned with the code.
The loop rebuilds the prompt every tick: goal, anchors, last feedback, rules. No history.
Assume the session is lost: a cold agent must resume from the files on disk alone.
State lives in the conversation. Nothing survives a restart, and what tick 3 learned is gone by tick 4.
A tick is the unit of work: one prompt, one attempt, one verdict, counted and capped.
Give one bounded objective: a small testable step makes progress and regression both visible.
A fresh context beats a long one: the loop discards history, so quality does not decay.
An unbounded tick gives you nothing to count, cap, retry, or resume.
The agent is a subroutine, not the system: the loop around it decides whether a change counts.
The agent’s own “done” is not a signal: a finished turn says nothing about a finished task.
Check the agent can act before you judge it: a blocked agent looks like a failing one.
The agent’s own claim of “done” means its turn ended, not that the work met the goal.
Preflight runs before tick one: the loop refuses a dirty tree or a forbidden branch.
The tick guard sits between agent and commit: nothing unscreened ever reaches git history.
It reads the diff, not the agent’s summary: what changed on disk is the only evidence.
Nothing screens the diff, so a bad tick commits exactly like a good one.
It fires before the commit: the loop reverts the tick, and nothing lands.
This is policy, not quality: there is no better attempt to ask for, so a person must look.
Protect anything you would not merge unreviewed: tests, CI config, migrations, and lockfiles.
A dangerous diff has no terminal state, so it commits like any other change.
Commit every tick: a crash then costs one tick, not the run, and the loop resumes from HEAD.
Commit only what cleared the guard: the loop checks safety before it makes progress durable.
A commit is not done: it preserves progress, and the gate decides completion.
A crash with no per-tick commit loses everything the run has done.
Run it every tick: cheap enough to afford always, specific enough to guide the next attempt.
Feed back the failure text, not just the verdict: the diagnostics become the next prompt.
Prove the gate is stable first: two runs on one tree must agree, or every stop is corrupt.
With no error signal, the loop repeats the same attempt, quickly and without correction.
Review what tests cannot express: policy, structure, and intent live outside assertions.
Never let the author grade the work: independence comes from a fresh context, not a new model.
A broken judge halts the run: it is an infrastructure failure, never a rejection.
Log whether the check ran: a step that defaults to off never runs at all.
Anything a test cannot express goes unenforced. That is where the loop drifts first.
It comes from the failure you already have: the bug report, the ticket, the incident.
The iteration gate steers, the held-out gate decides: two checks with two different jobs.
The agent fits any check it can rerun: passing is not the same as solving.
Feed back the result, not the checks: it learns what failed, never how it is graded.
Every pass looks the same. You cannot tell a solved problem from a fitted one.
Stops need an order, not a set: budget first, because spent money cannot be unspent.
Catch stuck early, not at the cap: an unchanged tree for N ticks is a spinning loop.
An agent that keeps editing never looks stuck: count rejected reviews, not file edits.
Record which stop fired: done, halted, and exhausted each need a different answer.
A wedged loop spends the whole budget reaching the cap, and giving up reads like success.
It comes from the failure you already have: the bug report, the ticket, the incident.
The iteration gate steers, the held-out gate decides: two checks with two different jobs.
The agent fits any check it can rerun: passing is not the same as solving.
Feed back the result, not the checks: it learns what failed, never how it is graded.
Every pass looks the same. You cannot tell a solved problem from a fitted one.
Done is a verdict, not a feeling: the held-out gate passed, and that is the entire claim.
Verify the claim once, by hand: read the diff and run the held-out gate yourself.
A passing gate is not a shipped change: check the branch and the PR, not the exit code.
If the loop decides its own completion, nothing outside it confirms whether the work is actually done.
One history, many working directories: each checks out its own branch from one .git.
One task, one branch, one directory: two agents in one tree corrupt each other.
An isolated workspace holds only tracked files: you must reproduce everything else.
Parallelism without isolation adds conflicting edits, not throughput. A human still merges them.
Batch by file overlap: tasks that share no files start in the same wave.
A shared file means waiting: task-4 touches auth.py, so it runs after task-1 lands.
Scale only what you can isolate and verify: supervision cost grows with every loop.
Concurrency multiplies whatever one loop already does, including any gaps in its gates or guards.
These aren’t new systems. They’re new ways of applying the same loop.
Settle these before tick one: the goal, the anchors, the gates, and the bounds.
Every outcome is evidence: a pass advances, a failure feeds the next prompt, a violation halts.
Add them in order: goal and gates first, then the stops, before the first unattended run.
The system around the model is the part you actually write.
The future isn't better prompts.
It's better control systems.
github.com/debozkurt/loop-kit
Derrick Bozkurt
RingCentral · debozkurt@gmail.com