Closing the Loop
Twelve essays argued that a coding agent's harness should learn from its own runs. This is the lab report: four cycles on a frozen 24-task suite, learning as gated external memory, a local 7B, $0 — everything published, including the rejection.
§1 · The bill, paid down a little
This series has told you twice what to do about amnesic coding agents. [← 4] mapped how a deployed agent can learn on the job and argued that the system-space half of the loop — capture episodes, distill them into memory, inject the memory next run — ships today, on a frozen model, without touching a weight. [← 12] closed the series with the guardrails for any loop that improves itself: a gate it cannot edit, a rollback it cannot disable, a budget it cannot raise. Both essays were surveys. They cited systems that ran; they did not run one.
This essay runs one, small and honest. Four cycles of one coding agent against one frozen suite of 24 FastAPI tasks: every act of learning deposited in external memory, every attempt logged append-only, a promotion gate deciding which memory survives. Small on purpose — a 7B model served locally, ~10 seconds median per task, ~10 minutes per cycle on a laptop, $0 of API spend. Honest on purpose — the suite hashed and frozen before cycle 1, verifiers hidden from every workspace, the full record published: episodes, patches, reports, the suite itself, hidden tests included.
The headline: completion went 58% → 67% → 67% → 62% while mean graded reward went +0.56 → +0.59 → +0.65 → +0.62; three tasks converted durably, including the hardest in the suite; and the gate rejected one memory promotion for cause. Stated plainly enough to be wrong: a thin run → store → distill → gate loop, with zero LLM calls in its learning step, measurably improved a small frozen coding agent within a fixed suite — and the same machinery reported, without flattery, exactly where it did not.
The series' first lab report: four cycles of run → store → distill → gate → re-run on a frozen 24-task suite, learning only in external memory, on a local 7B at $0. Completion 58% → 67% → 67% → 62%; graded reward +0.56 → +0.65 → +0.62. The numbers are modest by design — the point is that the loop, the anti-cheating machinery, and the full data ship together.
§2 · The loop, made mechanical
The loop is the one [← 4] drew in the abstract, made mechanical (see Figure 2). Run: a pinned harness — aider 0.86.2 — drives a local model, qwen2.5-coder:7b under ollama, through all 24 tasks; the adapter's run call is run → git-snapshot capture → episode. The harness was picked by the code, not by preference: the aider adapter is the only one in the cl-agent repo that can drive a live run — the OpenHands live mode is an explicit NotImplementedError stub. Store: every attempt lands as an append-only JSONL episode, mirrored into a memory service. Distill: a rule-based layer compresses the cycle's episodes into a candidate bundle — PROGRAM.md and SKILLS.md, skills plus failure warnings. Gate: the candidate runs against the current bundle on a held-out 6-task validation split and is promoted only if it is no worse on completion and breaks nothing that previously passed. Re-run: the next cycle injects whatever memory survived, plus per-task reflections, and the suite runs again.
Cycle 1 runs bare — baseline mode, no memory — to pin the floor; cycles 2 through 4 run integrated. Every stage is resumable and every service write idempotent.
Σ: a gated PROGRAM.md/SKILLS.md bundle plus per-task reflections. The gate (amber) has two exits, and the dashed one — reject, keep the current memory — is the one §7 is about. Why it matters: this is [← 4]'s bottom-row loop and [← 12]'s gated production discipline in one running system.§3 · What "learning" means here
The model never changes. No fine-tuning, no LoRA, no gradient anywhere in the pipeline: θ is frozen, and every bit of adaptation lands in Σ, the external store, exactly as the system-space row of [← 4]'s map prescribed. Σ here has two levers, ranked in the order we expected them to matter — disclosed up front, because §9 will have to own the fact that the headline number cannot fully separate them.
cl_layer). Zero LLM calls: it can select and compress, never invent.The first lever is episodic memory in the Reflexion lineage (Shinn et al., 2023): from cycle 2 on, each attempt's prompt carries a reflection block for this task — which verifier steps failed on previous attempts, how many checks passed, whether previously-passing tests broke. Reflections are rendered from the canonical episode log at injection time, not stored as prose, so they cannot drift from what actually happened. The second lever is semantic: the distilled, suite-wide bundle of skills and warnings. And the property that keeps the learning step auditable: distillation is 100% rule-based. The loop that writes memory cannot hallucinate a lesson; it can only compress what the verifier already said. Both levers are markdown files — inspectable, diffable, revertible — precisely the low-blast-radius corner [← 12] said self-improvement should start in.
§4 · A suite that cannot flatter the agent
A positive result on a suite you can quietly bend is worth nothing, so most of the engineering went into making this one unbendable. 24 tasks against a small FastAPI shop API — 14 feature implementations, 10 bug repairs — drawn from 32 authored candidates, each admitted only after a mechanical check: the start state must fail its hidden tests, the reference solution must pass them, protected paths never ship. Calibration over the full pool scored 69% (22/32), so selection was biased toward non-passing tasks to freeze a 58% baseline — headroom by construction. The suite was frozen before cycle 1 at tag suite-freeze-v1, and its hash — 209e33b4…, over tasks, fixture, overlays, and hidden tests — is embedded in every report. Had the suite drifted mid-experiment, the reports would say so.
Four mechanisms keep the agent honest, and they are worth listing because each one closes a specific cheat:
- Hidden verifiers. Acceptance tests never enter any workspace. Verification runs in a disposable copy of the workspace with
.gitand aider state stripped and the hidden tests freshly overlaid. - Tamper detection. Protected paths (
tests/,conftest.py) are reset from pristine before verification; modifying or deleting an existing protected file marks the runtampered— reward −1, task failed. Test files the agent merely added are dropped without penalty. - The feedback firewall. Everything the agent ever learns about verification — in prompts, reflections, or warnings — is step names and pass/fail counts, never verifier stdout or stderr. Assertion output contains expected values; leak it once and "learning" degenerates into transcribing the answer key. [← 12]'s rule — a gate the loop can read becomes a target (Wu and Raghunathan, 2026) — applied to every byte of feedback.
- Quarantine. Gate runs and calibration episodes are excluded from distillation and reflections, under separate generation ids. The memory never trains on its own exam.
Credibility is machinery, not intention: a suite frozen and hashed before cycle 1 (suite-freeze-v1, 58% calibrated baseline), verifiers that never enter a workspace, tamper detection on protected paths (reward −1), a feedback firewall that passes step names and counts but never verifier text, and gate episodes quarantined from the memory. Each mechanism closes one specific way the result could have been fake.
§5 · Four cycles, measured
The scoreboard is machine-generated — verifier outcomes flow into an eval store and come back as a table (report_cycle.py → ferrum-evals /eval/cl-report) — and is reproduced verbatim below. Recurrence is the share of the previous cycle's failures that failed again; latency and patch lines are per-task means for the cycle.
| Cycle | Artifacts | Mode | Completion | Feature | Repair | Valid | Mean reward | Recurrence | Latency (s) | Patch lines | Gate verdict |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | none | baseline | 14/24 (58%) | 50% | 75% | 33% | +0.56 | — | 24.0 | 11.8 | promote v1 |
| 2 | v1 | integrated | 16/24 (67%) | 56% | 88% | 67% | +0.59 | 70% | 16.2 | 11.3 | reject v2 |
| 3 | v1 | integrated | 16/24 (67%) | 56% | 88% | 67% | +0.65 | 88% | 16.3 | 10.1 | promote v3 |
| 4 | v3 | integrated | 15/24 (62%) | 56% | 75% | 50% | +0.62 | 75% | 26.8 | 11.3 | promote v4 |
209e33b4… embedded). "Artifacts" is the memory bundle the cycle ran under; the gate verdict decides what the next cycle runs under. Why it matters: every number in this essay traces back to this table, and this table traces back to verifier outcomes, not to anyone's notebook.Cycle 2 is the clean win: two tasks up (58% → 67%), repair 75% → 88%, the validation split 33% → 67%, and 3 of the 10 cycle-1 failures converted. Cycle 3 is the more instructive one: the headline froze at 67% while mean graded reward rose +0.59 → +0.65 — partial scores improving on tasks that still failed — and the mean patch shrank from 11.3 to 10.1 lines. Same pass count, better attempts, smaller diffs. Cycle 4 gave one net task back: 62%, churn dissected in §8. Fitted across cycles, the reward slope is +0.026/cycle and plasticity — cycle-4 pass rate over cycle-1's — is 1.07 (see Figure 4). This is Continual Learning Bench's question (Asawa et al., 2026) — better across sessions, or merely busy? — at the scale of one agent, one suite, one afternoon.
Which is why the honest unit of analysis is not the rate but the paired per-task flip: same task, cycle 1 versus cycle 4 (see Figure 5). Three tasks converted and held. Two regressed at cycle 4. Eleven passed every cycle; five never passed at all; three flickered. Net paired flips c1 → c4: +3/−2; at the cycle-3 peak, +2/−0.
Read the experiment at three altitudes: completion (58 → 67 → 67 → 62), graded reward (+0.56 → +0.59 → +0.65 → +0.62, slope +0.026/cycle, plasticity 1.07), and paired per-task flips (+3/−2 net; +2/−0 at the cycle-3 peak). The graded and paired views are the trustworthy ones — cycle 3's "flat" 67% hid better partial scores and smaller patches.
§6 · Three cycles of "existing tests BROKE"
The best single story in the data is feat-request-id-header, the suite's hardest task: add a request-ID header to every response via middleware. It scored 0.00 in calibration and in cycles 1–3 — not a near miss, a demolition: every attempt broke the app's middleware stack and took previously-passing checks down with it. From cycle 2, its reflection block accumulated the same two facts, attempt after attempt: exactly which checks failed, and the flag that existing tests BROKE. Step names and counts only — the firewall never let a line of verifier output through. At cycle 4 it passed: the suite's only 0.00-to-pass conversion, on a frozen model whose only new input was a description of its own past failures.
One task is an anecdote, not a mechanism; §9 says so in bold. But the pattern leans the same way: the other two durable conversions — feat-items-bulk-create and feat-items-validate-price — both arrived at cycle 2, the first cycle in which reflections existed. Reflections are the dominant lever, as we expected going in. The gated bundle is the weak-but-cheap one: cycle 1's distillation yielded just 4 skills and 2 warnings, yet at the cycle-1 gate — before any reflection existed — that bundle alone flipped two repair tasks on the validation split, 33% → 67%: the one clean isolation of the artifact lever in the experiment. Small, real, nearly free. The precise claim, and the only one the data supports: the harness turned verifier feedback into durable, promoted memory. The model got nothing smarter.
§7 · The rejection is the receipt
If you audit one artifact from this experiment, audit the gate's one refusal. After cycle 2 the distiller emitted candidate bundle v2. On the validation split it matched the incumbent v1 on completion exactly — 67% versus 67%. Under the rule most dashboards enforce — did the number go down? — v2 ships. The gate checks a second clause: nothing that previously passed may break. v2 broke repair-users-duplicate-by-email, a validation task that passed under v1 — a 25% legacy regression on the split — so v2 was rejected and cycle 3 ran on v1 unchanged. Cycle 3 vindicated the refusal: same 67% completion, reward up +0.59 → +0.65, patches down 11.3 → 10.1 lines. The loop lost nothing by refusing the "equal" candidate.
For the record, the other three verdicts: v1 promoted (67% vs 33%), v3 promoted (67% vs 67%, zero regression), v4 promoted (83% vs 50%). One rejection in four decisions is a gate that is alive, not a rubber stamp — and it is [← 12]'s contract at toy scale: the gate reads a split the distiller never sees, on episodes quarantined from the memory — a gate the loop cannot edit and cannot train against. A rejected promotion is the mechanism working, and the part of this experiment most worth copying.
The gate's value showed up as a refusal: candidate v2 equaled the incumbent on completion but broke one previously-passing validation task, and was rejected for it. The next cycle matched the candidate's headline anyway — with better graded scores and smaller patches. "No worse on average" is not the bar; "breaks nothing that worked" is.
§8 · The failure gallery
Five tasks never passed in any cycle: pagination, a PATCH endpoint, a stats rollup, a deactivate flow, and search — all feature implementations. Pagination is the starkest: its best graded score across every attempt was 0.07, a near-total miss rather than a near miss. What the five share is shape: multi-clause specs — an envelope and a default and two edge cases, several independent requirements that must all land in one patch — more spec than a 7B holds together at once. Four cycles of memory moved none of them. That is the method's boundary drawn in data: external memory improves what the model can nearly do; it does not add what the model cannot do. Reminding is not teaching.
The two cycle-4 regressions — feat-orders-note-field and feat-users-email-validation, both passed cycles 1–3 — read differently. This is churn, not forgetting: the loop only ever adds context, so nothing it wrote could delete whatever made those tasks pass. The evidence is the rehearsal runs, where a task flipped between cycles with no change in memory at all — 7/8 after 8/8 on an identical configuration. A 7B sampling code is a noisy instrument; three further tasks flickered the same way. That noise is a term in every headline number here — exactly why §5 leaned on paired flips and graded scores rather than the rate alone.
§9 · What four cycles cannot tell you
The limitations are not fine print; they are the boundary of the claim, and each one is a measured fact about this experiment rather than a hedge.
- Within-suite is not generalization. Re-running a frozen suite measures adaptation to those 24 tasks. It says nothing about task 25. The freeze is simultaneously the credibility device and the scope limit — the same object, seen from both sides.
- Churn is a real error bar. One task is ±4 points of headline; rehearsal showed a flip with zero memory change. Any single-cycle delta smaller than that is weather, not climate.
- The validation split is six tasks. Every gate verdict rests on n=6, where one flip is 17 points. The gate was directionally sound — its one rejection was vindicated — but it is a coarse instrument, and its percentages should be read as counts.
- The levers are entangled. No ablation cycles were run; at the headline level, reflections and the gated bundle cannot be separated. The cycle-1 gate isolates the bundle once (two valid-split flips, pre-reflection); reflections' dominance is expected and consistent with the flip timing, not proven.
- One 7B model, one harness, one repo. Nothing here shows the levers transfer up the model scale — a frontier model might saturate this suite before it had anything to learn — or across harnesses and domains.
So the defensible sentence, the one the data will sign: the harness turned verifier feedback into durable, promoted memory, and that memory paid for itself within the suite. Not "the model got smarter." Not "the agent generalizes." Those experiments have not been run — and unlike most such sentences, this one comes with the artifact that would let you run them.
Five limits, owned: within-suite improvement only; ±1-task nondeterministic churn (a memory-free rehearsal flip proves it); an n=6 validation split behind every gate verdict; reflections and artifacts entangled at the headline level; one small local model. The claim that survives all five: feedback became durable, promoted memory that paid off inside the suite.
§10 · Run it yourself — the artifact
Every prior artifact in this series was a table or a playbook. This one is a reproduction package. The repo is github.com/dattgoswami/cl-agent; the experiment lives under experiments/v1/ as a runbook of small scripts: set up the pinned harness, bring up the services (qdrant, redis, the memory and eval stores), validate the pool mechanically, calibrate, freeze, run cycles 1–4, publish. The release tarball is the complete record — every episode, capture, patch, and report, plus the frozen suite including the hidden tests — and cycle 3 was recorded end to end as an asciinema video, below. Everything ran locally on one machine: no API key, no cloud, $0. At roughly ten minutes per cycle, an afternoon reproduces the whole of §5.
.cast and .mp4 ship as assets on the cl-exp-v1 release.One disclosure that belongs here rather than in a footnote: running the loop for real surfaced two genuine bugs in the eval tooling itself — a crash in the report endpoint and a dependency-wiring fault in the services — fixed with a one-line patch and external launcher wrappers. Closing a loop through your own infrastructure debugs the infrastructure. That, too, is what "deployable" means.
The artifact is the reproduction: experiments/v1/ in the cl-agent repo — runbook, frozen suite (tag suite-freeze-v1, hash 209e33b4…), full release tarball (episodes, captures, patches, reports, hidden tests), and the recorded cycle. If a claim in this essay looks too convenient, the data to check it against is published. That standard — publish the loop, not just the curve — is the one this series asks of everyone else.
What comes next
[← 12] ended the series with the engineer stepping out of the loop; this epilogue reported the smallest honest version of that loop actually turning — four cycles, one refusal, +3/−2. The next experiments write themselves, each one flag away in the same runbook: unfreeze (hold the memory, present task 25 — the generalization question §9 could not answer); ablate (reflections-only and bundle-only cycles, to untangle the levers); scale (the same machinery under a model that does not lose to pagination); lengthen (does +0.026/cycle survive cycle 10, or was this the easy headroom?).
[→ C6] The Continual Agent — the long-bet version of this loop: what it would mean, over a decade rather than an afternoon, for the agent that never stops learning to be the default. (The Long Bet, C-series.)
- The loop from B4 and B12 runs end to end at laptop scale: run → store → distill → gate → re-run, learning only in external memory, on a frozen local 7B, at $0 — with the full record published.
- Measured honestly — frozen hashed suite, hidden verifiers, tamper detection, a feedback firewall — it still improved: reward slope +0.026/cycle, plasticity 1.07, +3/−2 net paired flips, one 0.00-to-pass conversion after three cycles of reflections.
- The gate mattered as a refusal: an equal-on-average candidate that broke one passing task was rejected, and the next cycle matched its headline anyway with better scores.
- The boundary is visible in the same data: five multi-clause tasks never moved (memory reminds, it does not teach), churn is ±1 task, and nothing here claims generalization — the package to test that is the artifact.
References
- Goswami, D. (2026). cl-agent: A Continual-Learning Substrate for Coding Agents — Episode Capture, Replay, and Rule-Based Distillation for Cross-Session Improvement Without Fine-Tuning. Independent Research, Preprint. PDF · github.com/dattgoswami/cl-agent
- Goswami, D. (2026). cl-agent v1 experiment — four-cycle continual-learning loop on a frozen FastAPI suite. Runbook, machine-generated scoreboard, and full release (episodes, captures, patches, reports, suite incl. hidden tests; recorded cycle). github.com/dattgoswami/cl-agent · experiments/v1
- Shinn, N., Cassano, F., Berman, E., Gopinath, A., Narasimhan, K., & Yao, S. (2023). Reflexion: Language Agents with Verbal Reinforcement Learning. Advances in Neural Information Processing Systems (NeurIPS), 2023. arXiv:2303.11366.
- Asawa, P., Glaze, C. M., Orlanski, G., Ramakrishnan, R., Xu, B., Biswal, A., Chen, V. S., Sala, F., Zaharia, M., & Gonzalez, J. E. (2026). Continual Learning Bench: Evaluating Frontier AI Systems in Real-World Stateful Environments. Preprint. arXiv:2606.05661.
- Wu, C. H., & Raghunathan, A. (2026). Self-Trained Verification for Training- and Test-Time Self-Improvement. Preprint. arXiv:2605.30290.