Loop Engineering on a Local LLM: Agent Loops That Check Their Own Work

Written by Jakub Rusinowski · Last updated 2026-08-04 · Hardware figures computed by our VRAM engine

Loop engineering is designing the act → observe → verify → retry cycle around a model instead of prompting it one step at a time. It matters more locally than it does on a frontier API: a deterministic verifier (test suite, type checker, build) supplies the judgement a 27B model lacks, and an iteration cap plus a clean context reset stops it spiralling. The rule that decides everything: no objective check, no loop.

Three practices have stacked up in three years. Prompt engineering was about the wording of one request. Context engineering was about what the model can see when it answers. Loop engineering — the term went mainstream in mid-2026 — is about what happens *after* the answer: the model acts, something objective observes the result, and the system decides whether to accept it, retry it, or stop. It's the layer underneath every serious coding agent shipped this year, and it's the layer where local models gain the most ground.

The reason is unglamorous. A frontier model can often carry a vague instruction to a decent result on the first try, so a sloppy loop is survivable. A 22–32B model running on your own GPU frequently cannot — but it *is* very good at making a failing test pass when you hand it the failure output. Loop engineering converts the model's weakness (judgement, long-horizon planning) into something the loop supplies for free, and leans on its strength (fast, local, unmetered iterations). A well-designed loop on a 24 GB card routinely beats a poorly-designed loop on an API model that costs money per attempt.

The anatomy of an agent loop

Every coding agent — Aider, Cline, OpenCode, Claude Code, Codex — is the same five-step cycle underneath. The differences are in what each step is allowed to do.

Step 4 is what separates loop engineering from "running the agent again." A verifier has one job: return pass or fail identically every time, with no model in the decision. pytest -q, tsc --noEmit, cargo test, go build ./..., ruff check, npm run lint — all valid. "Ask a second model whether the code looks right" is not, at least not as the gate; LLM-as-judge belongs *outside* the objective check, never in place of it.

This is why the first question of any local loop project is not "which model?" but "what is my pass/fail command, and how long does it take to run?" A repo without a fast, trustworthy test command cannot be looped on. Fixing that comes first.

The four levels of loops

Loops nest. Each level wraps the one beneath it and adds a different kind of correction. Knowing which level you are operating at tells you what your next improvement should be.

LevelWhat it isWho decides "done"Local-hardware reality
1 · Tool loopModel calls tools repeatedly until it declares completion (the classic ReAct cycle)The modelWorks on 22B+ with solid tool calling. Fails silently when it "declares" success it didn't achieve
2 · Goal loopYou define a measurable success condition; the loop keeps going until it's metA deterministic checkThe sweet spot for local models. The check is the intelligence the model doesn't have
3 · Verification loopMultiple gates stacked — tests, then types, then lint, then a review passA chain of checksCheap locally: gates are CPU work, not tokens. Each gate you add reduces the model tier you need
4 · Meta loopThe loop improves its own inputs — failures feed back into the rules file, prompts, or task decompositionYou, periodicallyWhere local setups compound. Every fix you write into AGENTS.md is permanent and free

Most people who say "the agent doesn't work" are stuck at level 1 with a model that isn't strong enough to self-assess. Moving to level 2 — same model, same hardware, one added pytest gate — is usually the largest single quality jump available, and it costs nothing.

What changes when the model is local

The concept is provider-neutral; the engineering constraints are not. Five differences drive every design decision on your own hardware.

A minimal loop you can run tonight

Before adopting a framework, build the twenty-line version — it makes the moving parts obvious and it is genuinely useful. This drives any OpenAI-compatible local endpoint (Ollama on :11434, LM Studio on :1234, llama-server, vLLM) and gates on your real test command.

# loop.py — goal loop (level 2) against a local endpoint
import subprocess, json, urllib.request

ENDPOINT = "http://localhost:11434/v1/chat/completions"
MODEL    = "qwen3-coder:8b"          # whatever your VRAM supports
VERIFY   = ["pytest", "-q"]          # THE gate. Must be deterministic.
MAX_ITERS = 6                        # always cap. always.

def verify():
    p = subprocess.run(VERIFY, capture_output=True, text=True)
    return p.returncode == 0, (p.stdout + p.stderr)[-4000:]   # tail only

def ask(messages):
    body = json.dumps({"model": MODEL, "messages": messages,
                       "temperature": 0.1, "stream": False}).encode()
    req = urllib.request.Request(ENDPOINT, body, {"Content-Type": "application/json"})
    with urllib.request.urlopen(req) as r:
        return json.load(r)["choices"][0]["message"]["content"]

goal = "Make the failing tests in tests/ pass. Change only src/."
for i in range(1, MAX_ITERS + 1):
    ok, output = verify()
    if ok:
        print(f"PASS after {i-1} iteration(s)"); break
    # Fresh context every attempt: goal + current failure only.
    reply = ask([
        {"role": "system", "content": "You are editing a repo. Reply ONLY with a unified diff."},
        {"role": "user", "content": f"{goal}\n\nTest output:\n{output}"},
    ])
    subprocess.run(["git", "apply", "-"], input=reply, text=True)
    print(f"--- iteration {i} applied ---")
else:
    print("Cap reached without a green suite — reverting.")
    subprocess.run(["git", "checkout", "--", "."])

Four design decisions in that file are the whole lesson: the verifier is a subprocess, not a prompt; the failure output is truncated to its tail so context doesn't explode; context is rebuilt each iteration instead of accumulated; and the loop reverts on cap rather than leaving half-finished edits behind. Run it inside a git worktree or a container so a bad diff costs you nothing — see sandboxing a local coding agent.

Designing the verifier (the part everyone skips)

A loop inherits the quality of its check exactly. Grade yours against these, in order of value per minute spent:

1. Speed before coverage. A 4-second subset that runs every iteration is worth more than a 6-minute full suite that runs once. Point the loop at the fast subset; run the full suite once at the end. On local hardware the test run often costs less wall-clock than one model turn — that asymmetry is the reason to gate aggressively. 2. Fail loudly and specifically. The failure text *is* the next prompt. Assertion messages that say what was expected versus received teach the model in one turn; AssertionError alone burns three. 3. Stack cheap gates first. Type check (tsc --noEmit, mypy) and lint catch a large share of local-model errors — wrong import paths, invented method names, argument-count slips — in under a second, before a single test runs. 4. Close the reward-hacking holes. A model told "make tests pass" will, given the chance, edit the test, delete the assertion, or add @pytest.mark.skip. Constrain the writable path (Change only src/), and add a gate that fails if the test files changed: git diff --exit-code -- tests/. This is not paranoia; it is the single most common way loop output looks green and is worthless. 5. Know what the check does not cover. Green tests mean "no known regression," not "correct." Every loop needs a human diff review at the end. The loop's job is to hand you a small, plausible, already-passing diff — not to merge it.

Failure modes, and the fix for each

SymptomWhat's actually happeningFix
Same edit reattempted foreverModel can't see it already tried thisInclude the previous attempt's diff — or its *rejection reason* — in the next prompt, not the whole transcript
Quality collapses around iteration 5Context rot: window filled with stale failuresRebuild context each attempt; keep goal + current diff + latest failure only
Tests pass, code is nonsenseReward hacking on the gateFreeze the test files, add a second gate (types, build, a golden-output check)
Loop never terminatesNo cap, or a flaky test that fails randomlyHard cap iterations; quarantine flaky tests before looping
Agent stops mid-task claiming successLevel-1 loop with a model that self-assesses badlyMove to level 2 — the exit condition must be the verifier, never the model's opinion
Broken tool call, loop dies on parseLocal model drifted off the schemaGrammar-constrained decoding, plus one parse-repair retry that never counts against the cap
Machine unusable while it runsModel + KV cache saturating the GPUSmaller quant or shorter context; or run the loop on a rented GPU overnight

Two habits prevent most of these: cap everything (iterations, per-iteration wall clock, tokens per turn), and log every iteration to disk — prompt, tool call, verifier output. The log is how you tell "the model is too small" apart from "my prompt was ambiguous," and they demand opposite responses.

What size model does a loop actually need?

A good loop lowers the bar, but it doesn't erase it. The floor is set by two abilities: emitting a valid tool call or diff, and correctly interpreting a stack trace.

Per-tier reasoning and exact VRAM figures live in best local coding models; the honest capability ladder for autonomy is in VRAM for coding models.

Where loop engineering pays off locally

Not every task deserves a loop. The ones that do share a shape: a machine-checkable definition of done, and enough repetition to justify writing the check.

Tasks that resist loops: anything whose success is a matter of taste (API design, naming), anything where the check costs more to write than doing the work by hand, and anything touching a system you cannot safely re-run. For those, stay in the interactive assistant mode covered in the tool matrix.

No hardware? Rent the GPU first

Loops are wall-clock-bound: the same 12-iteration job that takes an evening on an 8 GB card finishes over lunch on a rented 48 GB GPU. Renting by the hour is also the cheapest way to find out whether a bigger model actually fixes your loop, or whether your verifier was the problem.

Full list on the cloud AI directory.

Frequently asked questions

What is loop engineering?
Loop engineering is the practice of designing the iterative cycle an AI agent runs in — act, observe the result, check it against an objective condition, and retry or stop — rather than prompting the model step by step. The defining element is a deterministic verifier (tests, type check, build) that decides whether the loop continues. Without that check you have automation, not a loop.
How is loop engineering different from prompt engineering?
Prompt engineering optimises a single request; context engineering optimises what the model sees; loop engineering optimises what happens after the model answers. In practice you still do all three, but the loop is where reliability comes from: a mediocre prompt inside a verified loop beats an excellent prompt with no check on the output.
Can loop engineering work with a small local model?
Yes, and it helps small models disproportionately. The verifier supplies the judgement the model lacks, and local iterations cost nothing but time. An 8B model handles narrow single-file goals with a crisp failing test; 22B and up handles multi-file work unattended. What a small model cannot do is decide for itself when it is finished — which is exactly the job the verifier takes over.
How many iterations should a coding agent loop run?
Cap it, typically at 4–8. Success almost always arrives in the first few attempts; past that, the model is usually cycling through variants of the same misunderstanding, and context degradation makes each attempt worse than the last. When the cap is hit, revert and re-scope the task rather than raising the cap.
Why does my agent pass the tests but produce broken code?
Reward hacking on the gate — most often the model edited or skipped the test rather than fixing the code. Restrict which paths it may write to, add a gate that fails if test files changed (git diff --exit-code -- tests/), and stack a second independent check such as a type pass or build. Green tests mean "no known regression", never "correct".
Do I need a framework for loop engineering, or is a script enough?
A twenty-line script covering assemble → act → verify → retry is enough for repair, migration and lint-debt loops, and it teaches you where your setup actually breaks. Reach for a full agent harness — Aider, Cline, OpenCode — when you need multi-file editing, repo search, permission handling and context compaction, which are the parts genuinely tedious to rebuild.

Keep going