Self-Improving Agent Loops: From Traces to a Harness PR

TL;DR

  • A self-improving (hill-climbing) agent loop is the fourth loop layer: loops 1-3 automate the work; this one reads the loop’s own traces and proposes a fix to the harness itself.
  • Two hard rules keep it safe: the analysis agent proposes a diff (it never edits the harness directly), and the change ships through human review (a PR). The loop never rewrites its own rules unattended.
  • Worked example, real: a pass over the gap-log loop’s four runs found the cheap ranker paging a human for already-resolved gaps, and proposed a no-action bucket that auto-closes 2 of 3. You will run the same shape.

📊 Result Proof. After applying the proposed diff, the patched ranker was run against the three real false-open cases on disk. Output: no-action, no-action, open. The two resolved gaps (LevelGrid, Astro loader) now auto-close; the third (LevelHero) stays open by design; the design-action bucket is unchanged, so no regression. Two of three, verified, not three of three.

Part 9 of the Loop Engineering series ended on a warning: a loop ships code you did not write, and comprehension debt is the bill. The sharpest version of that fear is a loop that rewrites its own harness while you sleep. This post is the constructive counterpart: how to get the upside, a harness that improves from its own runs, without the failure mode. A self-improving agent loop is one that reads its accumulated traces and proposes changes to its own ranker, checker, or prompt, gated by a human.

Hold the definition the whole series has used:

A loop is a recursive goal: you define a purpose and a verifiable stop condition, and the system iterates agents against it until the condition holds — without you prompting each turn.

Loops 1-3 automate the work: an agent loop does a task, a verification loop checks it, a schedule loop fires it on a heartbeat. The fourth loop automates the improvement of the harness underneath them. The idea traces to swyx’s “loopcraft” and the four-loop stack LangChain laid out in “The Art of Loop Engineering”. This walkthrough is the Claude Code-native version: git-committed traces, an analysis pass, and a PR, not a vendor trace-analysis engine.

Prerequisites:

  • A loop that has already run for a while and accrued committed traces (a state file, run history). This is rung 5 of the adoption ladder; you cannot hill-climb a loop that has not run.
  • You understand the maker/checker split and that merge stays human. This post adds the layer that improves the harness; it does not re-teach the split.
  • You can read a small Python ranker and a git diff. The worked example tweaks a keyword classifier and ships it as a PR.

What is a self-improving agent loop?

A self-improving agent loop reads its own accumulated traces and proposes changes to its own harness, the ranker’s keywords, the checker’s rubric, the maker’s prompt, gated by human review. It automates improvement, not just work. The inputs it reads are concrete: run history, state-file diffs, and the verdicts a human overrode. Each lower loop produces those traces; the hill-climbing loop consumes them.

The four-loop stack is easiest to hold as a table. The left three rows are familiar from the series; the fourth is this post.

LoopWhat it automatesOur CC-native instrument
1. AgentDoing one taskA subagent with a prompt
2. VerificationChecking the taskA machine-checked stop condition
3. ScheduleFiring on a cadenceA cron heartbeat
4. Hill-climbingImproving the harnessAn analysis pass that opens a PR

Key insight: Loops 1-3 produce traces as a side effect of running. The hill-climbing loop is the only one that treats those traces as its input.

The differentiation matters because the word “self-improving” carries a lot of vendor weight. LangChain instruments loop 4 with LangSmith Engine, a managed service that analyzes traces and tunes config. That is a reasonable product. It is also the exact step you most want to keep in your own hands. The CC-native version keeps every trace in git, runs the analysis as an ordinary agent over files you already have, and emits its conclusion as a diff in a pull request. No platform is required, and nothing changes the harness without passing under a human’s eyes first.

Is your loop even ready? The rung-5 prerequisite

You cannot hill-climb a loop that has not run. A pass needs accrued traces; on a young loop there is nothing to read, and a “self-improving” pass over no data just invents changes. The readiness gate is three questions: has it run enough cycles, are the traces committed and greppable, and is there at least one verdict a human would have called differently? If any answer is no, the loop is not ready.

The worked example clears the gate. The gap-log triage loop, the one built in the first retrofit, ran four weekday heartbeats from June 16 to 19, 2026. Each run triaged a batch of design-system gaps into a committed state file, growing from 8 to 16 to 24 to 26 entries. By the end it held 26 gaps, every one classified by the cheap deterministic brain, never the agent brain.

Field in the traceValue
Gaps triaged26
Heartbeat runs4 (June 16-19)
Bucket: deferred10
Bucket: open7
Bucket: design-action5
Bucket: consumer-workaround4
Brain useddeterministic (all 26)

Key insight: The readiness gate is traces accrued, traces committed, and at least one override signal. A pass is reading, not guessing, and reading needs something on disk.

Be honest about scope here. This is the only loop in our register mature enough for a pass. The design-toolkit and fixkit loops have not run long enough to accrue anything worth analyzing, so they are not rung-5 candidates yet. A scheduled heartbeat is what produces the trace depth a pass depends on, which is why rung 5 sits above the schedule loop, not beside it.

How do you walk a real pass and find the blind spot?

A pass starts by reading where the loop’s cheap judgment is weakest. In the gap-log loop, 7 of 26 gaps landed in the open bucket, the human inbox for anything subjective. On inspection, 3 of those 7 were not subjective at all. Their recorded action cell already said the work was intentional, correct, or fixed. The ranker paged a human for gaps that were already resolved.

The open bucket is the ranker’s confession of “I don’t know.” That makes it the right place to look first: it is where the cheap brain admits it has no rule. Reading the three flagged rows against their source cells made the pattern obvious.

GapRecorded bucketWhat the cell actually says
V0.25:6 LevelGridopen”None; display string vs slug distinction is intentional”
V0.25:8 LevelHeroopen”None; prop design is correct”
V0.27:14 Astro glob loaderopen”No action needed; fixed in Sprint 2c”

The pattern has a name worth keeping: the cheapest brain is over-routing to the most expensive resource. A human-review slot is the scarcest thing the loop spends, and the ranker was spending three of them on gaps that needed no work. The verification discipline that governs a stop condition applies just as well to a ranker verdict: a bucket is a claim, and a claim that pages a human for resolved work is a claim that is wrong.

Key insight: The blind spot lives in the catch-all bucket. When the cheapest brain routes resolved work to the most expensive resource, the ranker is missing a category, not failing at judgment.

This is the deliverable of the read phase: not a fix yet, but a named blind spot grounded in real rows. The ranker has no bucket for “resolved, no action needed,” so every already-closed gap falls through to open.

Propose the diff (not the edit): the no-action bucket

Hard rule one: the pass proposes a diff; it never edits the harness directly. The analysis agent’s output is a patch in a file, reviewed like any other patch, not a live mutation of triage.py. Here the proposed diff adds a no-action bucket to verdict_deterministic, checked first, with a deliberately narrow keyword set.

def verdict_deterministic(u):
t = (u["title"] + " " + u["raw"]).lower()
# hill-climbing pass 01 (2026-06-26): close-on-resolution. 3 of 7 'open'
# verdicts in the 2026-06-16..19 run were already-resolved gaps the ranker
# paged a human for. Add a no-action bucket, checked first.
if any(k in t for k in ("no action", "intentional", "by design",
"fixed in", "fixed during", "correct per spec")):
return "no-action", "log + close (resolved / intentional, no work)"
if any(k in t for k in ("design-repo", "design session", "design spec",
"organism", "spec + impl")):
return "design-action", "open design-repo issue (human confirms; never auto)"

The keyword choices are conservative on purpose: intentional, no action, fixed in, by design, correct per spec. Bare correct is excluded. It would match too much and false-close real gaps, which is a far worse error than leaving one open. Checking the bucket first means a resolved gap short-circuits before the subjective fallthrough ever runs.

The verification is the proof. Running the patched ranker against the three real cases returns no-action, no-action, open. The design-action bucket is unchanged, so the tweak adds a category without disturbing the others. The honest result is two of three: LevelGrid and the Astro loader close, but LevelHero stays open because its cell says only “prop design is correct,” and correct is exactly the word the conservative set refuses to match. That miss is deliberate.

Key insight: Check the no-action bucket first, so a resolved gap short-circuits before the subjective fallthrough ever runs. The narrow keyword set closes 2 of 3 and refuses bare correct on purpose.

Try It Now. On your own loop: (1) dump the catch-all bucket from your state file, for example jq '.triaged | to_entries[] | select(.value.bucket=="open")' gaplog-state.json; (2) for each entry, read the recorded action cell rather than the title; (3) list the entries whose cell says the work is already done or intentional; (4) draft one narrow keyword that would catch them, and reject any keyword broad enough to match a real open gap. Stop there. Propose the diff in a branch; do not apply it to the harness.

The trade-off is explicit. A first-checked keyword bucket is the cheapest possible improvement, and it pays for itself the first time it auto-closes a resolved gap. The cost is precision: a keyword cannot read intent, so it will always leave the cases that need judgment. You accept that ceiling knowingly.

Ship it through review: the second hard rule

Hard rule two: the proposed change ships through the same human review as any other change, a PR a person approves, never an auto-rewrite. For this pass that is PR #189 on the website repo. A human reads the diff and the pass record together, sees the two-of-three result stated plainly, and decides whether it merges. Merge stays human.

This is where the CC-native version diverges hard from the vendor framing. A managed trace-analysis engine that rewrites config is automating precisely the step that should stay reviewed. The whole risk of a self-improving loop is a harness that changes its own rules faster than anyone can audit them. Routing the change through a PR puts the slowest, most human gate on the one action that most needs it. The independent-evaluation rule the series leans on says the verdict must live in an artifact the maker cannot author, and the taste gate is permanent. A self-improving loop is the strongest test of that rule, because the maker here is proposing to edit the very thing that judges its work.

Key insight: Both hard rules together: the analysis agent proposes a diff (never edits directly), and the diff ships through a human-approved PR. Neither is optional, and the second is where vendor “self-improving” usually quietly disappears.

The provenance is worth tracing. The first retrofit built this loop, and the heartbeat ran it long enough to produce the 26-gap trace this pass reads. The pass adds nothing to how the loop runs day to day. It is a separate, deliberate act: a human points an analysis agent at the accumulated traces, reads what it proposes, and ships or rejects it. One pass, by hand, is the honest description. This is not yet a scheduled rung-5 system that runs itself, and the difference matters: a scheduled self-improver would need its own stop condition and its own review budget before it earned the same trust.

When does keyword hill-climbing stop and a semantic pass begin?

A keyword tweak is cheap and catches the obvious blind spots, but it cannot encode judgment. LevelHero is the proof: “prop design is correct” needs the word correct, and correct over-triggers, so the deterministic pass leaves it open on purpose. That is the exact boundary where keyword hill-climbing stops. The next gap up requires reading meaning, and a keyword set cannot read meaning safely.

The escalation is the loop’s other brain. The same ranker ships a verdict_agent path, a single claude -p turn that classifies the gap by reasoning instead of matching. It would catch LevelHero, because it reads “prop design is correct” as a resolution rather than a string. The catch is cost: that brain draws metered credit, so it is bounded by an explicit N, run on demand, not on every heartbeat. In the four runs that produced this trace, the agent brain was never invoked; all 26 verdicts came from the free deterministic path.

So the trade-off is a tiered one. Cheap deterministic hill-climbing handles the obvious, mechanical blind spots for zero marginal cost. A metered agent pass handles the semantic cases that a keyword cannot reach, at a price you cap deliberately. You do not reach for the expensive brain until the cheap one has provably run out of road, and you bound its spend the way you would bound any metered call. For the mechanics of that ceiling, see bounding agent spend.

Key insight: Deterministic for the obvious, metered agent for the semantic. The deterministic pass tells you exactly where it gave up (the cases it left open), which is the signal for when the agent pass is worth its cost.

This is also the cleanest answer to the Part 9 warning. A self-improving harness is the sharpest case of the loop shipping code you did not write, and that is exactly why both hard rules are non-negotiable. The loop proposes; you read; you merge. The moment any of those three steps becomes automatic, you have traded the comprehension you were trying to protect.

FAQ

What is a self-improving agent loop?

It is the fourth loop layer. Where an agent loop does a task, a verification loop checks it, and a schedule loop fires it, a self-improving (hill-climbing) loop reads the accumulated traces those loops produce and proposes changes to the harness itself: the ranker’s keywords, the checker’s rubric, or the maker’s prompt. The proposal is gated by human review, so it improves the system without rewriting its own rules unattended.

How is hill-climbing different from letting the agent rewrite its own harness?

Two hard rules. First, the analysis agent proposes a diff in a file; it never edits the running harness directly. Second, that diff ships through the same human-approved PR as any other change. An agent that rewrites its own harness skips both gates, which is the failure mode hill-climbing is designed to avoid. The loop never changes its rules without a person reading the change first.

When is a loop ready for a hill-climbing pass?

Only after it has run long enough to accrue committed traces: real run history, state-file diffs, and ideally at least one verdict a human would have called differently. The gap-log loop qualified after four heartbeat runs and 26 triaged gaps. A pass over a young loop has nothing to read, so it invents changes instead of finding them. No traces, no pass.

This is a spin-off of the Loop Engineering series, not a numbered part. The nine-part arc is complete; this is the hill-climbing layer on top.