Agent Loop Engineering Is Context Engineering in Disguise

Five of the six building blocks of an agent loop are the same move. The seventh never made the list.

Agent Loop Engineering Is Context Engineering in Disguise

You set a loop on your issue backlog before bed. By morning it has opened nine pull requests. Three are good. Six are confidently wrong in ways you would have caught in ten seconds from the chair, and the whole point was that you weren’t in the chair. The loop didn’t fail because the model got weaker overnight. At 2am it didn’t know something you know, and there was nobody awake to hand it over.

That is the failure the new advice sells a fix for. Addy Osmani’s June 2026 writeup popularized the name, loop engineering, and shipped the parts list everyone now quotes: automations, worktrees, skills, connectors, sub-agents, and a state file for memory. The same week, Anthropic (“Getting Started With Loops”) and OpenAI (“Unrolling the Codex Agent Loop”) published loop guides. Two labs, one blogger, one week. This is doctrine now, and doctrine comes with a parts list.

But the list is organized around the wrong question. It tells you what to assemble, never the question that decides whether the assembled thing works. To ask it you need the loop’s unit, the tick: one pass of the loop body, one agent run with its own fresh window. The question is, on each tick, what does the agent actually know? A loop is two halves bolted together. One half is a timer, and the timer was solved decades ago; cron has shipped with Unix since the 1970s. The other half is a model that wakes with no memory of any earlier tick, reads its window, and decides. The quality of that decision was settled before the model was asked, by what text reached the window.

This site has pulled that question through the parts list one block at a time, in six deep dives: the state file, the subagent boundary, the stop condition, the overnight build, the blast radius, the walls that earn headless mode. This piece is the checkpoint. Replayed on one map, two things fall out that no single-block view could see. First: ask each block one question, if I deleted this, would the text some window reads change, or only the timing? Five of the six come back context, one comes back schedule. Second: the failure mode the whole genre treats as its signature bug, the loop that declares victory on a half-done job, is fixed by none of the six. Its fix is a seventh block that was never on the list.

One minute on the loop you already run, or the one on the whiteboard, settles whether the rest of this is for you.

Take each part of that loop and ask the deletion question: if I removed this tonight, would tomorrow’s tick read different text, or just fire at a different time? Then one more: which part decides whether a tick’s finished work counts as done?

If you can place every part in under a minute and name the gate without hesitating, you have already done this synthesis; the table at the end will only confirm it. Close the tab. If any part takes a beat to place, or the gate question has no answer, that gap is what the next sections close.

The parts list tells you what to assemble. The deletion question tells you what each part is actually for.

One loop, fixed for the rest of this piece

Section titled “One loop, fixed for the rest of this piece”

Fix one concrete loop now, and keep it for everything that follows. A nightly triage loop for a repo with 6 open issues labeled agent-queue. (Not the two clocks of Two loops, which splits an eval suite; that piece shares a word and nothing else.) The loop has six parts, one per block on the canonical list:

Here is the whole driver, annotated by block. The next_issue and open_draft_pr helpers stand in for the connector’s MCP calls:

#!/usr/bin/env bash
MAX=12 # AUTOMATION (schedule): the cap. Worst case, 6 issues x 2 ticks.
for ((i=1; i<=MAX; i++)); do
issue=$(next_issue agent-queue) # CONNECTOR (deliver): the tracker's live state, via MCP
wt="wt-$(( (i % 2) + 1 ))" # WORKTREE (isolate): lane 1 or lane 2, never both
skill=$(cat ./triage-skill.md) # SKILL (deliver): 400 words of how we do things here
memory=$(tail -30 ./STATE.md) # STATE (deliver): what happened on ticks you will never see
prompt="## How we do things here
$skill
## What earlier ticks did - your only memory, read it
$memory
## Your job this tick
Fix issue #$issue in ./$wt. One issue, then stop."
agent -p "$prompt" --cwd "$wt" \
--mcp github --allowedTools "Edit,Bash(npm test:*)"
# AUTOMATION fires the tick; the allowlist is blast radius
agent -p "Grade the diff in $wt for issue #$issue against its
acceptance test. Verdict: PASS or FAIL, then one line why."
# SUBAGENT (isolate): a fresh opinion, advisory only
if ./verify.sh "$issue"; then # GATE: deterministic pass/fail. This one counts.
open_draft_pr "fix #$issue" # CONNECTOR (deliver) again: the loop reaches back out
echo "tick $i: #$issue closed" >> ./STATE.md
else
echo "tick $i: #$issue failed, re-queued" >> ./STATE.md
fi
[ -z "$(next_issue agent-queue)" ] && break # AUTOMATION (schedule): the done-sentinel
done

The six blocks feed ticks one at a time; the cap decides how many there are.

Every constant here is a toy value, sized so you can check the later arithmetic with a pencil; nothing was measured. One number is planted and paid off exactly: MAX=12, the worst-case tick budget. Where the 12 comes from, and why the 8 most teams would eyeball is wrong, gets its own section.

Fix the loop once, and every later question about “loops in general” becomes a checkable question about these six parts.

Walk the canonical list one more time, as capabilities you already own from the six deep dives. Each line is what the block lets you do that you could not do before.

Skills and rules are persistent context, written once. In an interactive session you would tell the agent the convention the moment it went wrong. In a loop there is nobody to tell, so the telling lives on disk where every tick re-reads it cold. The capability: you can price any line in a skills or rules file, because every line loads into every tick whether it earns its keep. (For organizing the rules file itself, see Deterministic context.)

The state file is context across ticks. The model forgets between ticks; the repo does not. It is the only thing that turns a month of independent ticks into one project instead of a month of fresh starts. The deep mechanic, the git log as the loop’s only memory and the contract each entry must satisfy, is The loop that re-reads its diary’s subject. Here you only need the capability: the constraint you write down at 6pm is still in the window at 3am, because a file re-delivers it every tick. (A tick is a different unit from the relay phase in The relay, not the window and Treat the plan file as external memory: those two carry one build’s context across turns, and this carries a loop’s memory across scheduled ticks.)

Sub-agents are context isolation. The reason to split the maker from the checker is mechanical: a verifier is only honest if it holds a context the maker cannot see, because the window that holds the flawed reasoning will also hold the grade. The agent loop you already have makes the full case. The capability it leaves: you can manufacture a second opinion that has not been contaminated by the reasoning it is judging.

Connectors are external context reach. The GitHub MCP server pulls the tracker’s live state into the window, so the next decision is made against reality instead of a stale guess. What a connector may touch while nobody watches has its own arithmetic, worked in Full autonomy is a small blast radius; this piece treats connectors purely as delivery and links the reach ledger.

Worktrees are isolation one floor down from the window: two ticks, two checkouts, no collisions. This is the one block with no deep-dive sibling yet; the table marks it and the close hands it forward.

Automation is the timer half. The cron line, the for-loop, the iteration cap, the done-sentinel. Teaching the loop to stop derives the two brakes an unattended loop needs before you run it overnight, and An overnight build loop shows the failure they guard against: one unbounded task sprawling into a night of scope creep.

Now count the moves. Three blocks put text into a window: the skill, the state tail, the connector’s live reads. Two wall text off: the subagent’s clean room, the worktree’s second checkout. Exactly one block touches no text anywhere: the automation, which only decides when a window opens and how many times. Five context moves, one schedule.

That 5:1 ratio is the tell, and the next two sections pay it off exactly.

Ground truth first, the obvious way. Don’t classify by feel. Delete each block from the nightly loop, one at a time, and watch what actually changes the next night.

Block you deleteWhat changesMove
triage-skill.mdThe tick’s prompt loses 400 words of conventions, and the tick guesses themdeliver
STATE.md tailThe prompt loses what earlier ticks did; the next tick restarts from zerodeliver
Verify subagentThe maker’s prompt is unchanged; the uncontaminated second window never existsisolate
GitHub connectorThe prompt loses the tracker’s live state; fixes aim at a stale guessdeliver
Second worktreeNo prompt changes; two ticks now write into one checkout and corrupt each otherisolate
For-loop and capEvery prompt stays identical; nothing fires at 2am at allschedule

The three moves, defined by the table rather than by taste. A deliver move puts text into a window. An isolate move walls text off from a window; deleting it doesn’t change the maker’s prompt, it merges windows that were separate, and the merging is the damage. A schedule move changes only when or whether a window opens.

One objection worth answering in place: isn’t calling a worktree a context move a stretch, since a worktree never says anything to the model? The table’s fifth row answers. Delete it, and the two lanes write into one checkout, and the corruption ends up in the files the next tick reads. That is text reaching a window, by accident. Isolation keeps accidental text out, which makes it a context move with the sign flipped.

Tally the right-hand column: three delivers, two isolates, five context moves, one schedule. That is the 5:1 planted one section back, unchanged by the deletion test. Run the same table against your own loop on paper; the tally is exact, not estimated.

Five of the six blocks change what some window reads. The sixth changes only when the reading happens.

The script’s cap has to be set to something, and this is where teams guess. Six issues are open, so the reasonable-looking answer is eight: the backlog plus slack. The worst case is twelve ticks, and the cap has to cover the worst case. Remember 12.

Count it on your fingers. Best case, every fix passes its gate on the first try: six issues, six ticks. Worst case, every issue fails once and takes its one allowed retry: six issues times two ticks each, twelve. The retry budget is per issue, so it multiplies with the backlog. Slack added once to a total covers none of it: a cap of 8 allows two retries across the whole night, and the worst case allows six. The correct MAX is 12, and it doubles as the spend brake: whatever one tick costs, maker plus checker, the night costs at most twelve tick-costs, and the automation block holds that number.

Now the worktree arithmetic, the payoff. Two worktrees mean two ticks in flight at once. Twelve ticks across two lanes is six wall-clock tick-slots: the night is half as long. And nothing else moves. Tick count before worktrees: 12. After: 12. Any single tick’s prompt before: word-for-word identical to after. The schedule block owns the count, being the cap; the worktree left the count alone, added no text to any window, and only kept two simultaneous windows from writing into one checkout.

That is the arithmetic signature of an isolate move: a deliver block would have shown up as added text somewhere, a schedule block would have changed the count, and the worktree did neither. The deletion table puts it where it does for that reason.

Twelve ticks of cost, six slots of clock: worktrees buy parallelism, and the bill per tick does not move.

Four failure modes, and the hole in the list

Section titled “Four failure modes, and the hole in the list”

Loop writeups keep a short list of famous failure modes. Run each one against the six blocks and ask which block fixes it.

Goal drift. The constraint you set at 6pm, don’t touch the billing module, is gone by night two, because every compression of the window is lossy and nobody re-injected it. The state file fixes it: STATE.md re-delivers the constraint into the window on every tick. Mapped to a listed block; the diary post works the full mechanic.

Runaway spend. No cap and no sentinel: a stuck tick re-fixes a bug it already fixed, all night, pass after pass. The automation block fixes it with the two brakes: the cap sized from the backlog, which is the 12-not-8 arithmetic above, and the done-sentinel that breaks the loop when the queue empties. Teaching the loop to stop derives the pair. Note what those brakes do not fix.

The premature “done.” The genre’s signature bug has a name and a date. The Ralph loop comes from Geoffrey Huntley’s Ralph technique (July 2025): a bash for-loop that re-runs the agent until the agent says it is finished. The failure everyone cites: the exit condition is the agent’s own say-so, so the loop halts on a half-done job and reports victory. Which of the six blocks fixes that?

Walk them. The skill informs the tick; it does not test the result. The state file records what happened; it does not grade it. The verify subagent is closest, a fresh window and an uncontaminated opinion, but an opinion is still a model’s say-so, and the maker’s confidence is what produced the false “done”. The connector reaches the tracker; it has no view of correctness. The worktree is filesystems. The automation’s two brakes stop a loop that runs too long, and the Ralph loop’s failure is the opposite: it stopped too early and lied about why. A cap cannot fail a tick that claims success.

None of the six. Hold that; it is the sharpest fact in this piece.

The fix that works is a deterministic pass/fail gate that fires the moment the agent claims done: run the acceptance test, fail the tick if it fails. Not a second agent with an opinion; a check that lives outside every window and can say no with no model involved. This site has documented it twice, in the hooks chapter and in Earn the automation, where the two-tooth completion gate is worked in full. Call it the seventh block: the primitive that decides whether a tick’s claimed result is allowed to count as done.

Why did the June 2026 list miss it? Because the list runs on one axis, what reaches the window, and the gate lives on a second axis the list has no column for: what is allowed to count. The six blocks sort text into windows. The seventh grades the output. In the bash drivers, this script included, the gate sits inside the loop wearing a bash costume, so it reads as part of “automation” and never gets counted.

Comprehension debt. The fourth failure mode also maps to none of the six, for a different reason: it is not a loop-design failure at all. The gap between what the repo now contains and what any human has actually read grows with every merged PR, and no block shrinks it. Assemble all seven primitives correctly and the loop still writes code you do not understand. The mitigation is reading the diffs, and the honesty section comes back to it.

Tally: four famous failure modes. Two are fixed by blocks on the list, state and automation. One by a block never listed, the hook. One by no block, because it belongs to you. Two, one, one.

Two of the four famous failure modes are fixed by blocks on the list. The most famous one is fixed by a block that isn’t.

Everything above, on one grid. Read it as the canonical list re-sorted by the deletion question, plus one row that was never counted.

BlockMoveIn the tick’s window, or walled off from it, because of itFailure mode it fixesDeep-dive
Skills / rulesdeliverProject conventions, loaded cold every tick-Skills
State filedeliverWhat already happened on ticks you will never seeGoal driftThe loop that re-reads its diary
Sub-agentsisolateA second opinion the maker’s window cannot reach-The agent loop you already have
Connectors (MCP)deliverThe live state of your real systems-Full autonomy is a small blast radius
WorktreesisolateOne tick’s filesystem, walled off from another’s-none yet, handed forward
AutomationscheduleNothing; it only decides when the tick firesRunaway spendTeaching the loop to stop
HooksgateWhether this tick’s claimed result counts as doneThe Ralph loop’s premature “done”Earn the automation, hooks

The footer is the point of the piece: 5 context : 1 schedule : 1 gate. The gate row sits outside the original six-block count, and that outside seat is the finding: the taxonomy had no column for what is allowed to count, so the block that guards it went uncounted.

Six blocks classified by one question, one gap found and filled: that is the whole map.

Worktrees get a row, not a deep-dive. Five of the six blocks have a sibling post tracing their mechanic to the bottom. Worktrees have this piece’s tick-slot arithmetic and nothing else. The full mechanic, when a second lane is worth its checkout, what cleanup keeps stale worktrees from rotting, how the state file logs two interleaved lanes, is the next open door.

Comprehension debt has no block-shaped fix. Worth saying twice: seven primitives, assembled correctly, still do not make you understand code you did not write. The loop compounds your intent and your ignorance at the same speed, and reading the diffs is the only payment that slows the second one.

The trigger taxonomy is a different axis, and this piece does not adopt it. Current writeups also sort loops by how they start: heartbeat, cron, hook-triggered, goal-driven. That is a real axis, when a loop fires. This piece’s axis is what a tick knows once it fires. One disambiguation matters: a hook-triggered loop is an external event that starts the loop. The seventh block is a gate on one tick’s completion. Same word, two mechanisms; the shared name should not suggest the table forgot a trigger.

The composed loop creates maintenance it does not pay for. Who prunes STATE.md when the 30-line tail becomes the whole file? Who re-earns triage-skill.md’s accuracy six months in, when the conventions it froze have moved? Every block on the map is now a file or a boundary someone owns. That question is handed forward with the worktree piece.

Getting the seven blocks right is a solved problem. Keeping them right is the open one.

Replay the table row by row, with nothing left as a dash. Three blocks deliver text into the tick’s window: skills and rules, the state file, the connectors. Two wall text off: the subagent’s clean window, the worktree’s separate checkout. One decides only when: the automation, the cron half, holding the cap that stops runaway spend. And one row sits outside the count: the gate that decides whether a tick’s work counts as done, the hook documented on this site before the list that needed it existed.

That is the 5:1 from the top of the piece, reconciled. Loop engineering turns out to be one discipline, context engineering, the same one this site teaches everywhere else, plus a timer, plus a gate.

Two things get handed forward. The worktree row is the open door: no post on this site has traced the collision mechanic the way the other five are traced. And the maintenance question is the bill this loop quietly creates: state files grow, skill files go stale, nothing in the seven blocks prunes itself.

So when the next framework arrives selling an eighth building block, you have a four-part test. Does it put something in the window? Take something out of it? Decide when the window opens? Decide whether the window’s output is allowed to count? If it does none of those four, it isn’t a loop primitive. It’s marketing.

The timer is cron. The gate is a hook. Everything else is context.


About the numbers. The running example’s constants, six issues, one retry per issue in the worst case, two worktrees, the 400-word skill, the 30-line state tail, and the nine-PRs-three-good opening, are toy values, invented for traceability and sized to be plausible, not measured. Every other number in this piece, the 12-tick worst case, the 6-tick best case, the undersized cap of 8, the retry budgets of 2 and 6 on either side of it, the 6 wall-clock slots, and the 5:1:1 tally, is exact arithmetic from those constants, checked against an independent script before publishing. The dated facts are quoted, not derived: Addy Osmani’s loop engineering writeup, June 2026; Geoffrey Huntley’s Ralph technique, July 2025; Anthropic’s “Getting Started With Loops” and OpenAI’s “Unrolling the Codex Agent Loop”, the same week of June 2026. No measured dollar figure, benchmark, or transcript appears in this piece.

For the per-tool mechanics, see Skills for the persistent knowledge a tick re-reads cold, Subagents for the isolated verifying window, MCP servers for the external reach, and Hooks for the seventh block itself.