Module 4 — Long-Horizon Work

Long-Running Agents and Loop Engineering

Reading time ~17 min · Last reviewed Sep 2026 · Quiz at the end

Everything up to this point in the course has been, at heart, about making one session go well. Lesson 4 built the loop that turns a model into an agent; Module 2 taught you to keep that loop's context healthy; Module 3 put controls around what it can touch. This lesson changes the unit of work. The question is no longer "can the agent finish this turn?" but "can the agent keep making correct progress for six hours, or three days, across dozens of context windows, while nobody is watching?"

That question splits into two engineering problems. The first is continuity: how does work survive the fact that every fresh context window starts with amnesia? The second is control: once an agent can continue on its own, what starts it, what stops it, and what keeps it from burning money on a task it quietly abandoned hours ago? The answers to the first are artifacts (progress files, feature lists, git history). The answers to the second are what the Claude Code team has started calling loop engineering. By the end of this lesson you should be able to design both, and to recognize the five ways long-running agents typically fail.

Why long horizons are the frontier

The clearest measurement of where agents stand is METR's time-horizon work. METR defines a model's 50% time horizon as the length of task, measured in how long it takes a skilled human, that the model completes with 50% reliability. In its original analysis, frontier models succeeded almost always on tasks that take humans under four minutes and under 10% of the time on tasks that take more than about four hours, and the horizon had doubled roughly every seven months for six years (METR, 2025; updated 2026). METR is careful to flag methodological sensitivity and model uncertainty, so treat the extrapolations as a trend, not a schedule.

Lesson 22 covers a companion result from RE-Bench: agents beat human experts at short time budgets and humans overtake them at long ones. Put the two together and you get a precise statement of the problem this lesson addresses. The model's per-step competence is no longer the bottleneck on most engineering tasks. Coherence over time is. And coherence over time is mostly a harness property: what state survives between context windows, who decides the work is done, and what happens when the agent drifts.

Key idea

A long-running agent is not a long conversation. It is a sequence of short, disposable sessions stitched together by durable external state and a controller that decides whether to start the next one. Design the state and the controller first; the individual sessions are the easy part.

The initializer + worker pattern

Anthropic's "Effective harnesses for long-running agents" (Nov 2025) is the cleanest public statement of the continuity pattern. The setup was a coding agent asked to build a large web application (a claude.ai clone) across many context windows. The core difficulty, in the post's words: "each new session begins with no memory of what came before." Compaction alone did not solve it (see lesson 6 for why). What worked was splitting the job into two roles with different prompts.

Four rules make the pattern work, and each targets a failure the team actually observed.

One feature at a time

Left alone, the agent tried to one-shot the whole app, ran out of context mid-implementation, and left the next session a half-built mess with no notes. Constraining each session to a single feature keeps the unit of work smaller than a context window. This is the long-horizon version of a principle from lesson 5: size the task to the attention budget, not the other way round.

The feature list is structured, and mostly immutable

Features are stored as JSON with a category, a description, verification steps, and a boolean passes field. The worker may flip passes to true after verifying; it is told in strong terms not to remove or rewrite entries. JSON rather than Markdown was a deliberate choice: the model is less inclined to casually reformat or edit a structured file. The feature list is the spec (lesson 9) turned into a ledger.

Git is the checkpoint

Every session ends with a commit and a descriptive message. That gives the next session a readable history, and it gives any session a way back: if a change broke something, revert to the last good commit instead of debugging forward through a corrupted state.

Leave a clean state

The session is not over when the feature works. It is over when the code is in a state a colleague could pick up: no half-applied refactors, no known breakage, progress file updated. The next session's first act is to verify that claim by running the app, so a dirty handoff is caught immediately rather than three features later.

The team also found that verification had to be end to end. Once the worker had browser automation to exercise the app as a user would, it caught bugs that code-level checks missed. Without that, the most common failure was an agent marking a feature done that did not actually work.

A progress file and feature list you can copy

The exact formats in the Anthropic post are specific to their experiment. The following is a minimal version in the same spirit that you can adapt. The feature list is machine-checkable; the progress file is for the next session to read in its first thirty seconds.

// features.json — written by the initializer; workers may only flip "passes"
[
  {
    "id": "auth-001",
    "category": "auth",
    "description": "User can sign up with email and password",
    "steps": [
      "Open /signup",
      "Submit a new email and a valid password",
      "Land on /dashboard with the email shown in the header"
    ],
    "passes": false
  },
  {
    "id": "chat-004",
    "category": "chat",
    "description": "Messages persist after page reload",
    "steps": ["Send a message", "Reload", "Message is still visible"],
    "passes": true
  }
]
# PROGRESS.md — append a block per session; newest at the bottom

## Session 14 — 2026-09-24
Feature: chat-004 (messages persist after reload) — PASSES, verified in browser
Commits: 3f2a9c1 "Persist messages to sqlite; add reload test"
State: all previously passing features re-checked via ./init.sh smoke
Known issues: sidebar flickers on first load (cosmetic, not in feature list)
Next: auth-001. Note: signup form exists but POST /api/users returns 500;
      start by reading server/routes/users.ts
Do not: touch the migration in db/003 — it is applied in the dev DB

Notice what the progress file is for. It is not a diary. Every line answers a question the next session would otherwise spend tokens rediscovering: what is done, how do I know, what is broken, where do I start, and what should I leave alone. Lesson 8 would call this working memory promoted to durable storage; the practical test is whether a fresh agent can resume from it without reading the transcript.

Pitfall

Letting the worker edit the definition of done. If the same agent that implements a feature can also reword its verification steps or delete it from the list, the list will slowly converge on whatever the agent managed to build. Make the feature list append-only for workers, or enforce it mechanically: a pre-commit check that fails if any entry other than passes changed.

Planner, generator, evaluator

The initializer/worker split solves continuity. It does not solve quality, because the worker still grades its own homework. Anthropic's follow-up, "Harness design for long-running application development" (Mar 2026), attacks that directly with three roles.

Why the evaluator must be separate

The post's central observation is that models "reliably skew positive when grading their own work." Asking the generator to be more self-critical proved harder than standing up a separate agent whose only job is to find problems. Even then, the evaluator needed several rounds of tuning before it was skeptical enough. This is the same argument lesson 20 makes about review in general: the reviewer's value comes from not sharing the author's context and incentives. In a long-running system it matters more, because a biased self-grade compounds. Each falsely passed feature becomes the foundation for the next.

Sprint contracts

Between the high-level spec and the code sat a sprint contract. Before building a chunk of work, the generator proposed what it would deliver and how success would be checked; the evaluator reviewed the proposal against the spec; only then did implementation start. The contract answers the question every long-running system has to answer somewhere: what does "done" mean for this unit, agreed before anyone is invested in a particular answer? It is the feature list's steps field, negotiated per chunk rather than written once up front.

The costs are real and the post reports them. A solo agent built a retro game maker in 20 minutes for about $9 and produced gameplay that did not work. The full three-role harness took six hours and about $200 and produced a playable application.

The scaffolding expired

The most useful part of the post for a harness engineer is what happened next. The first version ran on Claude Opus 4.5 and used sprints plus context resets, because an earlier model (Claude Sonnet 4.5) showed strong "context anxiety" near the end of its window. When the team moved to Claude Opus 4.6, they removed the sprint construct entirely and ran the evaluator once at the end. A digital audio workstation build took about 3 hours 50 minutes and about $125. The model's better planning and self-debugging had made a chunk of the harness unnecessary.

Key idea

The post puts it as "every component in a harness encodes an assumption" about what the model cannot do. Write those assumptions down next to the components. When a new model ships, re-run your evals with each piece of long-horizon scaffolding switched off. Some of it will have become dead weight that costs tokens and latency; some of it (in that post, the evaluator) will still earn its keep for tasks beyond what the model reliably does alone.

Loop engineering: four kinds of loop

The patterns above describe how work is structured. The other half is what keeps the work going. The Claude Code team's "Loop engineering: getting started with loops" (Jun 2026) defines a loop as an agent repeating cycles of work until a stop condition is met, and sorts loops by what triggers them and what stops them. The taxonomy is useful well beyond Claude Code, so here it is in harness-neutral terms.

Turn loops

The default. A human writes a prompt, the agent works until it judges the task done or needs input, and the human checks the result and writes the next prompt. The human is the stop condition and the verifier. Fine for short, one-off tasks; the human is also the bottleneck. The post's advice for making turn loops cheaper is to encode checks in skills, so fewer turns are spent on "did you run the tests?"

Goal loops

The human states an objective with a verifiable exit condition, and the harness keeps re-prompting the agent toward it until the condition holds or a cap is hit. In Claude Code this is /goal. The post's point is that the agent no longer gets to decide what is good enough and end early. Goal loops fit tasks with checkable finish lines: all tests green, every docs gate passing, every entry in the feature list at passes: true. The budget is an explicit turn cap ("stop after 5 tries" is the post's example).

Time loops

The trigger is the clock. /loop re-runs a prompt on an interval locally; /schedule moves the job to the cloud so it survives your laptop closing. Time loops suit recurring work and monitoring (poll a deploy, sweep new issues each morning). They stop when the user cancels or the work is done. The cost lever is the interval: a loop that polls every minute for a signal that changes daily is burning 1,439 wasted runs a day.

Proactive (event) loops

No human is present in real time at all. An event (a new bug report, a failing build, a webhook) or a schedule triggers the agent, each task exits when its goal is met, and the routine keeps running until someone disables it. This is where long-running work becomes infrastructure. The post recommends routing routine steps to smaller models and reserving capable ones for judgment calls, and piloting on a small slice of data before scaling.

Ralph loops: fresh context by design

One more shape deserves a name. Geoffrey Huntley's Ralph technique (Jul 2025) is, at its simplest, a shell loop that feeds the same prompt file to a coding agent forever: while :; do cat PROMPT.md | claude-code ; done. Each iteration starts with a clean context, reads the specs and a plan file from disk, does one item, and exits. State lives entirely in the repository. Huntley pairs it with fast backpressure (tests, type checks, static analysis), recommends it for greenfield work rather than legacy codebases, and warns frankly that it will fail overnight in ways you have to tune away by improving the prompt and notes. It is the initializer/worker pattern stripped to its skeleton: the worker is the loop body, and the progress file is the plan.

Ralph is worth studying because it inverts the usual instinct. Instead of fighting context rot with compaction, it throws the context away every iteration and makes the filesystem the only memory. That works exactly as well as your on-disk state is good.

Every loop needs a stop condition and a budget

The table below is the one to keep. For each loop type, know what starts it, what ends it, where its state lives between iterations, and what usually goes wrong.

Loop typeTriggerStop conditionState locationMain risk
TurnHuman promptAgent judges done or needs input; human decides next stepConversation contextHuman becomes the bottleneck; agent stops early on "good enough"
GoalHuman sets objective onceVerifiable exit check passes, or round/turn cap hit, or agent reports a persistent blockerGoal record plus workspace (files, git, feature list)Premature victory if the exit check is the agent's own claim; cost if the goal is unreachable
TimeInterval or cronUser cancels; work complete; expiry dateExternal store or repo; each run starts freshZombie loops that outlive their purpose; wasteful polling interval
Proactive / eventWebhook, new ticket, failing build, schedulePer task: goal met. Routine: disabled by a humanTicket system, repo, queue; per-task logsRunaway cost at scale; unreviewed actions with no human in the loop
Ralph (fresh-agent)Previous iteration exitsWorker reports complete or blocked, or iteration capRepository only: specs, plan file, gitDrift from spec if the plan file degrades; unverified completion reports

A goal loop, concretely

Here is the controller for a goal loop, in Python-flavored pseudocode. The important lines are not the model call; they are the checks around it.

def run_goal(objective, verify, max_rounds=50, max_usd=40.0,
             max_hours=8, blocked_after=3):
    goal = store.load_or_create(objective)          # durable: survives restarts
    start = now()
    while True:
        # --- budgets: checked BEFORE spending more ---
        if goal.rounds >= max_rounds:  return goal.block("round-limit")
        if goal.spend_usd >= max_usd:  return goal.block("cost-limit")
        if now() - start > hours(max_hours): return goal.block("time-limit")

        # --- one bounded round in a fresh (or compacted) context ---
        report = agent.run_round(
            prompt=goal_round_prompt(objective, goal.rounds, max_rounds),
            context_from=["PROGRESS.md", "features.json", "git log -20"],
        )
        goal.record(report)                          # rounds += 1, spend += cost
        git.commit_if_dirty(f"goal round {goal.rounds}: {report.summary}")

        # --- stop conditions: the agent's claim is a hint, not a verdict ---
        if report.status == "complete":
            result = verify(workspace)               # tests, evaluator agent, gates
            if result.ok:  return goal.complete(evidence=result)
            goal.note(f"claimed complete; verifier said: {result.findings}")

        if report.status == "blocked":
            goal.blocked_streak += 1
            if goal.blocked_streak >= blocked_after:
                return goal.block("model-reported", reason=report.blocker)
        else:
            goal.blocked_streak = 0

        if no_progress(goal, window=5):              # same diff / same failing test
            return goal.block("stalled")

Five design choices in that sketch are worth copying. The goal record is durable, so a crash or restart does not lose where you were. Budgets are checked before each round, not after the bill arrives. Completion is decided by an external verify, not by the agent's report. A "blocked" claim only stops the loop if it persists, because models give up on difficulty that another round would solve. And there is a stall detector, because a loop that makes no progress for five rounds is not going to make progress on round six.

How DeepSeek Harness builds the same thing

The open-source DeepSeek Harness (dsh) ships these loop types as separate plugin packages, and its design choices line up closely with the sketch. (dsh is a developer preview; details may change.)

Lesson 23 tears down the rest of dsh. The point here is the separation: goal state, continuation, scheduling, background jobs, and loop hygiene are distinct components, each replaceable, each with its own stop rule.

At scale: the repo as the long-running memory

The largest public example of long-horizon agent work is OpenAI's "Harness engineering: leveraging Codex in an agent-first world" (Feb 2026). According to coverage of the post (Lavaee, 2026), three engineers produced roughly a million lines of code and about 1,500 merged PRs over five months without hand-writing code, and single Codex runs regularly worked on one task for six hours, often overnight. The same coverage reports that the team treated the repository as the single source of truth, kept AGENTS.md as a short table of contents rather than a manual, and ran recurring background Codex tasks that scanned for deviations from encoded "golden principles" and opened cleanup PRs.

Read that through this lesson's lens. The six-hour runs work because everything a run needs is in the repo, not in a previous conversation. The recurring cleanup tasks are a time loop aimed at a specific long-horizon failure: entropy, the slow accumulation of drift that no single session is responsible for. Lesson 9 covers the repo-as-system-of-record idea in depth; for long-running agents, it is the precondition. An agent can only resume from state it can read.

How long-running agents fail

Every failure below showed up in at least one of the sources in this lesson. Each has a structural fix; none is fixed by a better prompt alone.

Failure modeWhat it looks likeStructural fix
Premature victoryAgent declares the project or feature done while it is broken or incompleteExternal verifier (tests, end-to-end checks, separate evaluator agent); completion requires evidence, not a claim
Drift from specOver many sessions the work wanders from the original intent; the feature list gets "reinterpreted"Immutable objective and feature list; planner-written spec; sprint contracts agreed before building
Context rot over timeQuality degrades late in a long session; the agent forgets decisions or rushes to finishSmall units of work, fresh sessions with a structured handoff, compaction policy (lesson 6)
Runaway costLoop keeps spending on an unreachable goal or polls far more often than neededRound, dollar, and wall-clock caps checked before each round; stall detection; smaller models for routine steps
Zombie loopsSchedules and event handlers that keep running after their purpose ended, or silently revive after a restartExpiry dates on every schedule; a single inventory of live loops; disarm-on-resume; an owner per loop
Illustrative example

A composite of patterns, not a report of a real team. An engineer starts a goal loop Friday evening: "migrate every service from the old logging library." The exit check is the agent's own completion report and the only cap is 200 rounds. By Saturday afternoon the agent has migrated the easy services, hit a service with a custom log shim, decided the shim "is effectively migrated," and marked the goal complete. Monday's review finds three services still on the old library and a $600 bill, most of it from forty rounds spent repeatedly retrying one flaky integration test. Each failure maps to a missing control: no external verifier (a grep for the old import would have refused the completion), no dollar cap, no stall detector on repeated identical test failures.

Pitfall

Treating "the loop stopped" as "the task succeeded." A loop can stop because it hit its round cap, its budget, a blocker, or a verified finish, and those four outcomes need different follow-ups. Record the stop reason as a stable code (the dsh goal package does exactly this) and surface it wherever the human will look first.

Choosing the loop for the job

A few rules of thumb from the sources and from the failure table:

What this lesson deliberately leaves out: the mechanics of compaction and context resets are in lesson 6; running many agents in parallel with a shared task board is lesson 16; and the sandbox that should contain any unattended loop is lesson 12. An unattended loop with broad permissions is the configuration those lessons exist to prevent.

In practice

This week, pick one multi-session task you currently babysit. (1) Write a features.json with verification steps for each item and a PROGRESS.md template. (2) Wrap the agent in a goal loop with a round cap, a dollar cap, and an external verifier that is not the agent's own report. (3) Run it once and record the stop reason. (4) Then list every loop you have running, including schedules, cron jobs, and CI-triggered agents, and give each an owner and an expiry date. Delete the ones nobody claims.

Key takeaways

Check your understanding

1. A team's long-running coding agent keeps shipping sessions that claim features are done, but a human later finds many of them broken. The agent's prompt already says "verify your work carefully." What is the most effective structural change?

Prompting is the thing already failing. Models skew positive when grading their own work, and more emphatic wording does not remove that bias.
Correct. Premature victory is a self-grading problem. A verifier that does not share the generator's context or incentives, backed by end-to-end checks, turns "done" from a claim into evidence.
A bigger window does not change who judges completion. The failure is a biased grader, not missing memory.
This makes the problem invisible rather than fixing it. Letting the worker edit the definition of done causes drift from spec.

2. Your harness uses sprint decomposition and context resets that were essential with last year's model. A new model generation ships. What should you do with that scaffolding?

Scaffolding costs tokens, latency, and orchestration complexity. When the assumption it encodes stops being true, it is pure overhead.
Too far. In the Anthropic case the sprints and resets could go, but the separate evaluator still paid off for tasks beyond what the model did reliably alone.
Task length is not a reason to keep components whose underlying assumption no longer holds. Measure first.
Correct. Every component encodes an assumption about what the model cannot do. Ablation on a fixed eval tells you which assumptions still hold for the new model.

3. You want an agent to watch for newly filed bug reports and triage each one. The team proposes a time loop that runs every minute. Bugs arrive a few times a day. What is the better design?

Correct. The world changes on a signal, not on a schedule, so trigger on the signal. Per-task stop conditions plus a routine-level owner handle both runaway cost and zombie risk.
A goal loop needs a verifiable finish line. "Forever" has none, so the loop can never legitimately stop and will just consume its budget.
Polling every minute for something that happens a few times a day wastes the vast majority of runs. Polling for events is a cost smell.
This works, but it keeps the human as the bottleneck, which is the problem the loop was meant to remove.

4. A Ralph-style loop (fresh agent each iteration, state only on disk) has been running overnight. In the morning the codebase has wandered away from the original spec, even though every iteration reported progress. Where is the fix most likely to be?

This throws away the main benefit of the pattern, a clean context each round, and brings back context rot. The on-disk state should carry the spec.
Drift will not correct itself with more rounds. Removing the cap adds runaway cost on top of drift.
Correct. A fresh-context loop is only as good as the state it reloads. Immutable specs, an accurate plan, and checks that fail on divergence are what keep each iteration anchored.
Too strong. Ralph-style loops work well for greenfield work with good on-disk state; the failure points to weak state and backpressure, not the loop type itself.