Post 12

Loop Engineering: The System That Prompts The Agent

We started with one tiny message loop. We will close the lopp (pun intended!) by designing the larger loop around it: the system that finds work, prompts, checks results, records state, and decides what should happen next.

Hand-drawn diagram of loop engineering as the team car behind several bicycles.

We started with one tiny message loop: send messages to a model, run requested tools, append the results, and repeat.

agent loop:
  - receive task
  - call model
  - run tool calls
  - append tool results
  - repeat until done

Then we added tools, a harness, a workspace, permissions, a CLI, project context, memory, compression, tests, and evals.

The loop around the loop

A single agent run still depends on a user to drive it. You notice work, write the prompt, watch the output, decide what to do next, and remember what happened.

Loop engineering moves this one level up. Instead of only prompting an agent, you design the loop that prompts agents.

Just like the team car from the image that carries supplies, watches the situation, coordinates the next move, and decides when the rider needs help. Loop engineering is that support system around the agent.

outer loop:
  - discover work
  - choose what is worth doing
  - prepare context
  - start an isolated agent run
  - verify the result
  - record what happened
  - decide the next action

The loop has inputs, state, policies, execution paths, checks, and stopping conditions.

Addy Osmani describes loop engineering as the shift from being the person who keeps prompting the agent to designing the system that does that work. To do a complete system we do need both. Automation and user driven control.

Start with the inner loop

Earlier, we treated the harness as the code that wraps one agent run.

harness responsibilities:
  - call the model
  - expose tools
  - execute tool calls
  - enforce permissions
  - manage workspace boundaries
  - build context
  - store memory
  - compact long runs
  - emit traces

The outer loop decides when to run the harness, what task to give it, which workspace it should use, which checks matter, and what should happen after the run finishes.

loop engineering:
  harness + scheduler + state + verification + routing

Where does the work come from?

The outer loop needs a source of work. For a coding agent we already have artifacts that fit very well.

possible work sources:
  - Jira or Linear issues
  - failing CI
  - stale pull requests
  - TODO comments
  - dependency updates
  - flaky tests
  - user requests
  - your system observability
  - customer success requests or feedback
  - regression reports

Our first version does not need to be autonomous. We will build it as a command you can run manually:

.venv/bin/python loop.py triage
.venv/bin/python loop.py work-on issue-42
.venv/bin/python loop.py eval

Later, the same logic can run on a schedule. The important thing is that work discovery becomes a structured input, not a vague prompt.

Your brain is not the database

An outer loop needs to remember what happened between runs as we know The Model itself will not remember. The Terminal will not remember. So your brain should not be the database, neither.

.loop/
  state.json
  runs/
    2026-07-15-issue-42.json
  notes.md
  eval-results.json

Our state can be a simple markdown file, JSON file, issue tracker, Linear board, or simple database. What matters is that the state lives outside the single conversation.

{
  "task": "Fix issue 42",
  "status": "needs_review",
  "workspace": "../worktrees/issue-42",
  "last_run": "2026-07-15T10:30:00Z",
  "checks": {
    "tests": "passed",
    "lint": "passed",
    "eval": "failed: missed edge case"
  },
  "next_action": "ask human to review failing eval trace"
}

This is where memory and compression become practical. Worth re-reading if you missed it earlier.

Give each agent its own lane

Once you run more than one agent, the workspace becomes a coordination problem. Two agents editing the same checkout can corrupt each other's work. A worktree gives each agent its own copy of the repo state to mutate.

main repo:
  clean, reviewable, human-owned

agent worktree:
  - isolated branch
  - task-specific changes
  - disposable if the run fails

After that, the outer loop can run several small tasks at once.

Not every agent has the same job

A loop does not need one agent that does everything. It can route different parts of the work to different roles.

roles:
  explorer:
    reads the codebase and explains the problem

  maker:
    edits files and runs tests

  checker:
    reviews the diff against the task and failure history

  summarizer:
    compresses the run into durable state

This separation of concerns is important as the agent that made the change is often too eager to call it done. A checker with a different prompt and stricter criteria gives the loop a better chance of noticing mistakes before you do.

This is the same idea we used in evals: separate the thing that produces the answer from the thing that judges it.

Done is not what the agent says

The outer loop should not treat "the agent said it is done" as done.

weak stop condition:
  assistant says "done"

better stop condition:
  - tests pass
  - diff is scoped
  - forbidden files are untouched
  - eval case passes
  - checker found no blockers
  - human accepted the change

In implementation, these checks are not only final labels. They can become feedback loops. If tests fail, the harness can send the failing output back into the agent and let it try again. If the diff touches a forbidden file, the harness can stop immediately. If the checker is unsure, the loop can ask a human.

check result:
  pass:
    continue or finish

  recoverable failure:
    add feedback to context
    let the agent try again

  policy failure:
    stop the run

  uncertain:
    ask a human

This is why the testing and eval posts come right before this one. A loop that cannot verify work is just an automatic mistake generator.

The loop can prepare a branch, run checks, summarize the result, and ask for review. Shipping can remain a user action.

The smallest outer loop

Here is the simplest shape of a loop controller.

def run_outer_loop():
    work_items = discover_work()
    state = load_state()

    for item in work_items:
        if not should_attempt(item, state):
            continue

        workspace = create_isolated_workspace(item)
        context = build_context(item, state, workspace)

        run = run_agent(
            task=item.prompt,
            workspace=workspace,
            context=context,
            permission_mode="ask",
        )

        checks = verify_run(run, workspace)
        record_result(state, item, run, checks)

        if checks.can_merge:
            create_pull_request(workspace, item, run)
        elif checks.needs_human:
            notify_human(item, run, checks)

    save_state(state)
discover_work       -> where tasks come from
should_attempt      -> priority and safety policy
create_workspace    -> isolation
build_context       -> context management
run_agent           -> harness
verify_run          -> tests and evals
record_result       -> memory
notify_human        -> product surface

That is the main lesson of the series. The model is powerful, but the product is the loop around it.

Autonomy is a permission level

Where to draw a line for autonomy is another product design decision worth exploring further.

safe autonomy:
  - inspect
  - summarize
  - propose
  - run read-only checks
  - prepare branches
  - draft pull requests

risky autonomy:
  - merge
  - deploy
  - delete data
  - change permissions
  - spend money
  - notify customers

The loop can become more autonomous where verification is strong and the blast radius is small. It should stay cautious where mistakes are expensive.

This is why permissions are product design. The user experience of "what can the agent do by itself?" is also the safety model.

Every failure should leave a mark

A good loop will also improve because every failure leaves a mark.

agent failed because it edited generated files
  -> add policy check
  -> add test
  -> update project instructions

agent failed because it lost the task goal
  -> improve context summary
  -> add state field
  -> add eval case

agent failed because it called the wrong tool
  -> rename tool
  -> tighten schema
  -> improve description

This is harness engineering becoming loop engineering. The single run produces evidence. The outer loop turns that evidence into better instructions, better checks, better permissions, better evals, and better state.

The system gets better when mistakes are structured.

You are still in the loop

Where do we see the user in the loop?

bad loop:
  - agent acts
  - agent claims success
  - human rubber-stamps

better loop:
  - agent acts
  - harness records evidence
  - checks run
  - evaluator reviews behavior
  - human reviews the important decision

What we built together

It's a wrap folks! The whole series is now complete.

01 loop:
  model + messages + repeat

02 tools:
  model proposes actions

03 harness:
  software decides what happens

04 workspace:
  agent gets a world to act in

05 permissions:
  product decides what is allowed

06 CLI:
  engineer gets a usable surface

07 context:
  project knowledge becomes an input

08 memory:
  useful state survives the run

09 compression:
  long work becomes resumable

10 tests:
  harness behavior becomes reliable

11 evals:
  agent behavior becomes measurable

12 loop engineering:
  the system starts prompting agents

Thank you for reading

Thank you for reading the series. If it helped you understand coding agents from first principles, you can buy me a beer!


Buy me a beer

Curious how much that buys in Finland? Check the beer price table.