---
title: Loop engineering
description: "Every agent already runs a loop inside one session. Loop engineering is the layer above: designing the goal, the checks, the stop conditions and the budget so an agent keeps working toward a verified result across many steps or many runs, without a human prompting each one. The term emerged in 2026 and is not standardized yet, but the practices behind it are solid."
author: Asem Abdo
course: Agentic Engineering
lesson: Loop engineering
url: https://openplanr.dev/learn/agentic-engineering/loop-engineering
updated: 2026-10-17
related:
  - https://openplanr.dev/learn/agentic-engineering/primitives.md
  - https://openplanr.dev/learn/agentic-engineering/multi-agent.md
---

# Loop engineering

By [Asem Abdo](https://www.linkedin.com/in/asemabdo/) · Updated 2026-10-17

*Work that keeps going without you prompting*

Every agent already runs a loop inside one session. Loop engineering is the layer above: designing the goal, the checks, the stop conditions and the budget so an agent keeps working toward a verified result across many steps or many runs, without a human prompting each one. The term emerged in 2026 and is not standardized yet, but the practices behind it are solid.

## Anatomy of a durable loop

Level 1 · The rules

The shift is from being the person who prompts the agent to being the person who designs the system that prompts it.

1. **Goal** (Goal): A result a program can verify: "all tests pass", "the build is green", "every acceptance criterion maps to a test".
1. **Work** (model): One agent run that tries to move toward the goal.
1. **Check** (harness): A sensor measures progress: tests, linters, an eval, a judge.
1. **State** (context): Progress written outside the window: a notes file, a task list, git history.
1. **Stop** (stop): Success, or a limit: iterations, money, time, or no progress.

Loop: work, check, write state, until a stop fires.

```python check=run title="A goal loop with a verifiable exit and two limits" harness="10-12: check: goal verified by tests" stop="2-3, 13-15: limits on rounds and money"
failing_tests = [5, 3, 1, 0]
max_rounds = 10
budget_usd = 3.00
cost_per_round = 0.40

spent = 0.0
for round_no, failing in enumerate(failing_tests, start=1):
    spent += cost_per_round
    print(f"round {round_no}: {failing} failing, ${spent:.2f} spent")
    if failing == 0:
        print("goal met: verified by the test run, not by the agent's word")
        break
    if round_no >= max_rounds or spent >= budget_usd:
        print("stopped by a limit")
        break
```

- Lines 10-12 (harness): check: goal verified by tests
- Lines 2-3, 13-15 (stop): limits on rounds and money

```text title="Output (Python 3.14.8)"
round 1: 5 failing, $0.40 spent
round 2: 3 failing, $0.80 spent
round 3: 1 failing, $1.20 spent
round 4: 0 failing, $1.60 spent
goal met: verified by the test run, not by the agent's word
```

## "Done" is a check, not a claim

Level 1 · The rules

An agent that decides for itself when it is finished will sometimes stop early with a confident summary. A loop needs an exit condition that something other than the agent verifies.

```python check=run title="Agent claim versus verified exit"
def agent_says_done(summary):
    return "complete" in summary.lower()

def tests_say_done(failing):
    return failing == 0

summary = "Implementation complete. All edge cases handled."
failing = 2
print("agent claims done:", agent_says_done(summary))
print("tests confirm done:", tests_say_done(failing))
```

```text title="Output (Python 3.14.8)"
agent claims done: True
tests confirm done: False
```

> **Watch out:** **No verifiable goal, no loop.** If you cannot write a check that tells success from failure, an autonomous loop will burn budget and stop wherever the model feels finished. Keep a human in the loop for that work instead.

## Detect when the loop is stuck

Level 2 · Why it works

Iteration and money caps stop a runaway loop eventually. A no-progress check stops it early, when the same failure repeats.

```python check=err:NoProgressError title="A loop that notices it is going in circles"
class NoProgressError(Exception):
    pass

def run(failing_per_round, patience=2):
    best, stale = None, 0
    for failing in failing_per_round:
        if best is None or failing < best:
            best, stale = failing, 0
        else:
            stale += 1
        if stale >= patience:
            raise NoProgressError(f"stuck at {best} failing for {stale} rounds")
        if failing == 0:
            return "done"

run([4, 2, 2, 2, 2])
```

```text title="Traceback (Python 3.14.8)"
Traceback (most recent call last):
  File "main.py", line 16, in <module>
    run([4, 2, 2, 2, 2])
    ~~~^^^^^^^^^^^^^^^^^
  File "main.py", line 12, in run
    raise NoProgressError(f"stuck at {best} failing for {stale} rounds")
NoProgressError: stuck at 2 failing for 2 rounds
```

| Loop shape | What repeats | Example |
| --- | --- | --- |
| Inner agent loop | Model step, tool call, result, inside one session | Any coding agent working on one request |
| Goal loop | Whole runs until a check passes | "Fix until the test suite is green, max 10 rounds" |
| Scheduled loop | A run on a timer or an event | Nightly dependency check, triage new issues every hour |
| Pipeline of loops | Stages that each loop until their own check passes | Plan until reviewed, build until green, verify until the eval holds |

## Guardrails for loops that run without you

Level 3 · Mastery

A loop multiplies whatever the harness allows. If one run can do damage, a hundred unattended runs will. These controls are not optional:

- **Hard limits:** Maximum iterations, maximum spend and a timeout, enforced in code outside the model.
- **Isolation:** Each loop works in its own branch, worktree or sandbox, so a bad run is thrown away, not merged.
- **Gates on irreversible steps:** Merges, deploys, payments and emails wait for a human or a stronger check.
- **Untrusted input:** Every page, issue or file the loop reads can carry injected instructions. Limit what tools a loop holds while it reads them.
- **A trace per run:** Time, tokens, cost and outcome for every iteration, so you can see where loops stall or overspend.
- **Know when not to loop:** One-shot tasks, work without a verifiable signal, and anything irreversible are better run once with review.

## Predict the output

### When does the loop stop?

Level 3.

```python
history = [6, 4, 4, 4, 3, 0]
best, stale = None, 0
for round_no, failing in enumerate(history, start=1):
    if best is None or failing < best:
        best, stale = failing, 0
    else:
        stale += 1
    if stale >= 2:
        print("stuck after round", round_no)
        break
    if failing == 0:
        print("done in round", round_no)
        break
```

1. stuck after round 4
2. done in round 6
3. stuck after round 3
4. done in round 5

**Answer:** option 1.

```text title="Output (Python 3.14.8)"
stuck after round 4
```

**Why:** The count stays at 4 in rounds 3 and 4, so the patience of two stale rounds runs out at round 4, before the later improvement. Tune patience to how noisy your progress signal is.

## Check your understanding

Levels 1 to 3.

**Q:** How is loop engineering different from the agent loop?

**A:** The agent loop is what happens inside one session: model, tool, result, repeat. Loop engineering designs the system around many such runs: a verifiable goal, checks, stop conditions, state that persists, and limits.

**Q:** Why is "the agent said it's done" a bad exit condition?

**A:** Agents sometimes stop early with a confident summary. The exit must be verified by something independent of the agent, such as a test run or an eval.

**Q:** What three limits should every unattended loop have?

**A:** An iteration cap, a spend cap and a no-progress check (plus a timeout). The first two stop runaway loops; the third stops stuck loops early.

**Q:** Where does loop state live between runs?

**A:** Outside the context window: a progress file, a task list, git history. Each new run rereads it, so a fresh session can continue where the last one stopped.
