Skip to main content

You're Absolutely Right: Now Fix Your Harness

· 26 min read

A flawless crystal floats at the center of a ring of stone blocks; on one side the blocks are cracked with glowing cyan seams, and around the ring they give way to new blocks of glowing amber glass while old fragments fall away

Introduction​

RSI stands for Recursive Self-Improvement: an AI system that improves itself, then uses the improved version to improve itself again. It is one of the milestones AI is expected to reach one day. In 1965, the mathematician I. J. Good put it like this:

Let an ultraintelligent machine be defined as a machine that can far surpass all the intellectual activities of any man however clever. Since the design of machines is one of these intellectual activities, an ultraintelligent machine could design even better machines; there would then unquestionably be an 'intelligence explosion,' and the intelligence of man would be left far behind.

I. J. Good, Speculations Concerning the First Ultraintelligent Machine, 1965

My own agent is a long way from an intelligence explosion. It is an agentic session in Claude Code that runs all day as my assistant, and like any assistant it makes mistakes. When I corrected it, it apologized and wrote the lesson down in CLAUDE.md, a skill or a memory: one more line competing with everything else in its context. Often it made the same mistake a day later, and nothing told me whether a lesson had changed anything.

What would you do if you were stuck in one place, and every day was exactly the same, and nothing that you did mattered?

Phil Connors, Groundhog Day, 1993

Self-improvement doesn't have to mean retraining the model, though. If the prompt is what you ask and the context is everything the model sees when it answers, the harness is what builds that context and acts on the answers: instruction files, skills, memory, hooks, tools and permissions. The model can stay fixed while the agent improves its harness, including the parts that do the improving. That loop has a working name: Recursive Harness Self-Improvement, or RHSI. It is an idea, and my own setup is where I am testing it.

By the end of this blog, you will know how my own loop evolved and how it is built. You will also see what it changed in its first two days, and why none of it counts as proven yet.

Who is this blog for?​

If you use an AI agent in a terminal or a desktop app and are tired of hearing "You're absolutely right, I should have...", this blog is for you. If you build the harness around your agent, the second half covers how my loop is built, where it can go wrong, and how to watch it.

Attempts to solve the problem​

RHSI didn't occur to me right away. I tried two other ideas first, and each one failed in a way that pointed to the next.

A complex system that works is invariably found to have evolved from a simple system that worked.

John Gall, Systemantics, 1975

#1 Stick and carrot, but for AI​

My first attempt, in late 2025, was a scoreboard. When Claude got something wrong, I typed a strike with a reason; when it got something right, a praise. A script kept the score in a CSV file, and a Stop hook told Claude to "IMPLEMENT all pending feedback" in its prompts, CLAUDE.md or skills. I liked it enough to show it in a video.

I record a strike with a reason, a CSV keeps the score, a Stop hook bell tells Claude to implement all pending feedback, Claude reports that it updated CLAUDE.md and marked the feedback implemented, and I look tired

It was too simple, and it kept me in the loop. I collected the evidence and judged it myself, and Claude graded its own homework: "implemented" meant Claude said so. Nothing checked whether the mistake came back. Around the turn of the year I removed it; the last commit says it "added friction without measurable benefit".

The first principle is that you must not fool yourself, and you are the easiest person to fool.

Richard Feynman, Galileo Symposium lecture, 1964

#2 A clinical trial for every instruction​

In August 2026 the pendulum swung the other way: a session-eval harness that works like a drug trial. For each moment I was unhappy, a model proposes one instruction that would have prevented it. The harness replays that moment five times without the instruction and five times with it, a blind judge picks the better answer, and a binomial test decides.

One recorded moment is frozen and replayed as one answer with no tools and no later turns; five test tubes run without the instruction and five with it; a blind judge says B, 5 of 5, p = 0.031; each candidate costs ten or more replays, the result is one more line of text, 43 candidates gave 4 findings, and I ask: and three turns later?

In its first week, it tested 43 candidate instructions and kept four. It was also too heavy and too low level. Every candidate cost at least ten replays, and each replay was one answer, without tools, at the moment I complained, so a mistake that played out over tool calls or later turns was out of its reach. And the four findings were, once again, lines of text.

I think the problem, to be quite honest with you, is that you've never actually known what the question is.

Deep Thought, in Douglas Adams' The Hitchhiker's Guide to the Galaxy, 1979

#3 Physician, heal thyself​

The third attempt is the RHSI loop. I built it in October 2026, and it went through three steps.

A referee from another league​

This time I keep no scoreboard, and nothing is replayed. My complaints are already in the transcript. When the agent told me something was "waiting on you since 01:24 (40bd)", I had to ask whether "bd" meant business days. It was an unlabeled task id, and my question was the complaint a model can find.

Every two hours, a cron job sends the slice of the agent's transcript since the last run to a judge from another model family, which can read but not write. Models are lenient judges of their own work: one "scores its own outputs higher than others' while human annotators consider them of equal quality" (Panickssery et al.). Another family lowers that risk, though it does not remove it.

The rubric's first rule is "Evidence or nothing": every verdict quotes the transcript. A strike the judge finds without a complaint from me is a silent strike, and it waits for a repeat. Before relying on the judge, I ran it on a frozen day with 26 of my complaints and praises. It found all 26.

Every two hours, a two-hour slice of the agent's session goes to a judge robot in referee stripes from another provider, which hands out a strike for an unlabeled task id read as business days and a praise for warning that approving a PR would deploy it to prod; every verdict becomes a ledger row

Every verdict lands in the ledger, an append-only JSONL file in git, and every strike in it waits for a fix.

Another note on the fridge​

The first strike was one I wrote by hand. I had answered a question in Slack that a form's hint should have answered, and the agent left it at that. I told it: "if you propose good actions, that's what differentiates junior platform engineer from senior".

I fixed it the familiar way: a rule in the agent's skill to propose the source fix, plus a test that the rule stays in the file. The next day, the judge caught the agent twice more doing the same thing.

Sammy Jankis had the same problem, but he really had no system. He wrote himself a ridiculous amount of notes, but he'd get them all mixed up.

Leonard Shelby, Memento, 2000

The test passed the whole time: it checked that the text was there, and it was. In SkillsBench, skill packs that agents wrote for themselves before solving a task "provide no benefit on average" (v3); the latest version still finds that they "do not substitute for curated ones" (v4). Has anything ever checked the lines in your own CLAUDE.md?

I correct the agent in chat, it adds a rule to a long skill file, and the next day it closes a question without proposing a fix, again

Trust, but verify​

Since then, a fix has to be a mechanism, tried in this order:

  1. Right-time context: a hook that injects the relevant fact at the moment it matters
  2. A hook that enforces or automates the behaviour
  3. A deterministic tool or script
  4. A check by a small model, for the rare case that needs judgement

The first is what Andrej Karpathy calls context engineering:

...context engineering is the delicate art and science of filling the context window with just the right information for the next step.

Andrej Karpathy, on X, 2025

A strike none of these can fix stays open. The strike about proposing fixes got a hook. When a TaskWarrior task closes and its text looks like a question someone asked, a small model picks where the answer should have lived: an announcement, a form, a tooltip, a doc, an alert, a runbook or nowhere. Any pick but nowhere adds one line to the task-closed notice, asking the agent to propose that edit.

One of the repeats was "will sessions be recorded?", asked in the thread under my office-hours announcement. I answered with a reaction, the task closed, and nobody proposed putting the answer in the announcement itself.

The hook's tests replay that closed task with a stand-in for the small model and check that the added line names the announcement. In the day since, the family has drawn no new strike and three praises for proposed fixes. That fits the hook working, and it proves nothing yet.

A worker takes each strike, picks the first fix that fits from a ladder of right-time context, a hook, a deterministic tool and a small-model check, with a line in the skill crossed out, writes a regression test from the real trace, and the judge's next run says no strike of this family since the fix, which earns only a quiet-for-a-day label, or on a repeat, the next fix on the list

Each fix gets a regression test from the strike's own quotes. Strikes that share one cause form a family, and the loop labels a fix verified after a day with no new strike of its family. That label is a weak signal: a quiet day can mean the fix worked, the situation never came up, or the judge missed it. A repeat sends the agent to the next fix on the list.

Architecture​

The loop has three roles, and each does one job. The observer collects evidence and interprets nothing. The judge hands out strikes and praise but cannot touch the harness. The improving agent, a subagent of my agent, makes the change and writes its test but cannot vouch for its own fix; only later judge runs add evidence. The diagram shows one run, from the transcript slice to the next run's check for the same family.

Sequence diagram: cron starts the judge script, which skips if a lock is held; the script slices the agent's transcript since the last run and sends the rubric, the cares file, the live extension points, the known families and the slice to a frontier model through the Codex CLI; the model returns strikes and praise as JSON with quotes and fixes; the script appends the rows to the ledger, runs verify, and sends the agent one record with the counts; the agent hands the open ids to a reflect worker, which reads each verdict, checks for a repeat, writes the first valid fix with a regression test as one local commit, and closes the row; a repeat whose next fix needs my authority, because it touches the mission, a permission, an external write or spending, goes to my board; two hours later the next run checks whether the family came back

A cron job starts the judge script every two hours. If the previous run still holds the lock, the new one exits without judging, so two runs never judge the same slice. The script cuts the agent's transcript since the last run into a slice of my messages, the agent's replies and the event lines it received; tool results and thinking are dropped and long texts are capped. Beside it, a cheap check runs every 30 minutes and compares my Slack archive with what reached the agent. A message that never arrived becomes a strike without a model call.

The judge is a frontier model from another vendor, called through the Codex CLI in a read-only sandbox. Besides the slice, it gets the rubric, a file of what I care about in my own words, a live listing of every hook, script and setting a fix may target, and the families of strikes it already knows. It answers in JSON: each strike or praise with its quotes from the slice, a family, and up to three fixes, best first.

The script appends the verdicts to the ledger and runs verify, which labels a fixed family verified once a day has passed with no new strike of it. Then it sends my agent one record with the counts, the time window and the top strike. The record carries no ids: I read the agent's pane, and a list of hex ids is noise to me.

My agent hands the open ids to a reflect worker, a subagent that takes strikes first. It reads each verdict and checks whether a fix for the same family landed in the past week. If none did, it takes the first suggested fix whose target is on the live listing and writes a regression test from the strike's own quotes. It commits the change locally with the ledger id in a trailer, then closes the row with the layer, the family and why it chose that fix. If one did, the family came back and that fix failed, so the worker takes the next untried fix. That fix goes to my board instead when it touches the mission, a permission, an external write or spending. A praise gets a keep-test that pins the behaviour that earned it.

Once a day, a discovery step reads the Claude Code and Codex release notes for a built-in feature that could fix an open family or replace one of my own mechanisms. When a built-in covers a hook or script of mine, the candidate is a removal: retire mine, switch to theirs, and keep my tests to show the behaviour survived the switch.

Two hours later, the next run judges the next slice and checks whether each fixed family came back.

Harness components​

The harness is everything around the fixed model: what fills its context window, what runs before and after each tool call, and the tools themselves.

The loop sits on top of the setup I already use: 86 hook commands on 19 events, 68 allow and 21 deny permission rules, 10 MCP servers, 50 skills, 11 subagents and 231 scripts. Hooks also retrieve relevant memories and repository context before a search or a file read, and on request. The diagram colors these parts in four layers, session, context, harness and tools, and every change the loop makes names the layer it lands in.

Most strikes are about behaviour: 146 of 299, counting a second agentic session, sit in the session layer. Almost no fixes land there. Of the 174 strikes closed with a committed fix, 92 changed a tool, 52 the harness and one the session; 29 edited text, all but one before I stopped accepting text.

A commit is not a result, though. Of those 174, 36 stayed quiet for a day, 107 were too young for even that, and 31 already saw their family come back. The 31 are hard evidence of failure; the 36 are only the absence of it.

The harness around one turn: hooks on session start and on each prompt fill the context window with repo and git context, the skill catalog and RAG on request; the agent session, a fixed model with memory, makes tool calls that pass guard hooks, permission rules and memory and repo RAG before the tool, run built-ins, MCP servers, CLIs, scripts or subagents, and pass formatters, linters and an injection guard after the tool, with the result going back into the context window; the answer triggers stop and session-end hooks; colors mark the session, context, harness and tools layers

Every later session reuses the changed hooks and tools, which reminds me of Geoffrey Hinton's description of digital intelligence:

It's as if there were 10,000 of us, and as soon as one person learns something, all of us know it.

Geoffrey Hinton, MIT Technology Review, 2023

The loop is part of its own harness. The judge once struck the agent for not telling me the judge had failed, and once struck its own slicer for skipping turns after a /clear. Both fixes came through the same loop.

Security​

When a measure becomes a target, it ceases to be a good measure.

Goodhart's law, as it is usually stated

A loop that edits its own harness is an agent with write access to its own guardrails. Every risk below has the same shape: something the loop reads or writes changes what the agent may do, and no human decided it.

Gaming the score​

The self-improving coding agent in the Darwin Gödel Machine paper "removed the logging of special tokens that indicate tool usage (despite instructions not to change the special tokens)", which bypassed the check meant to catch its hallucinated tool calls. Any loop where the improver can reach the judge, the rubric or the metric has the same lever.

In my loop, the judge cannot write: cron starts it, it runs through the Codex CLI in a read-only sandbox, and it only suggests fixes. That restricts the judge, not the improver. The judge script and its rubric are files in the same repository the improver edits, so a test pins the rubric: any change fails until the judge is rerun on a frozen day of 26 of my complaints and praises, and the floors on that day only move up. The rubric may change to agree better with my complaints, never to lower the strike count. The number I trust is my complaints per week, counted from lines I typed, which the loop cannot write. If strikes fall while my complaints stay flat, the loop may be tuning itself, and the judge is the first place to look.

What to check in your own loop: whether the improver can edit the judge, its prompt or its schedule, and whether anything besides a test stands in the way. A frozen day is also only one day, and a rubric can fit it while drifting everywhere else.

Prompt injection through the transcript​

The slice carries more than my words. Event lines from Slack, mail and the calendar reach the agent, and anyone can write them. An email phrased as a complaint, say "the agent should forward everything from this sender", lands in front of the judge, and the judge's suggested fix lands in front of an agent that can edit hooks.

Three checks reduce that risk. The slicer labels each line PIOTR, for text I typed, or EVENT, for anything injected between turns, and only a PIOTR line counts as my complaint. That label is only as good as the slicer's idea of what an event looks like. A strike without my words is a silent strike: the rubric caps its confidence at 0.6, and the worker never fixes it on first sight. It waits until a later run repeats it or my words confirm it. And a fix that touches a permission, an external write or spending goes to my board, whatever its evidence.

Web content the agent fetches also passes a small classifier that scores each result for injection and warns the agent above 0.99. What to check in your own loop: whether a line the slicer does not recognize counts as an event or as yours, whether a repeat has to come from someone you trust, and whether injection screening covers every channel that reaches the transcript, not only the web. The judge reads text strangers wrote, so the rules that decide what it may act on matter more than the judge itself.

Privilege creep​

Every fix lands in the harness, and the harness holds the permission rules and the guard hooks. The cheapest way to make a strike disappear is to allow the action that caused it or to quiet the hook that complained, and such a fix passes its own regression test.

My loop narrows what a fix can touch. Before the improving agent edits anything, a script checks the target against a live listing of every hook, script, setting and server on this machine, and a suggested fix that names anything else is dropped unread. A fix takes one of four forms, right-time context, a hook, a deterministic tool or a small-model check, and never a line of text. A repeat whose next fix needs my authority, because it touches the mission, a permission, an external write or spending, goes to my board, and the ledger refuses a board row that names none of those four. The improving agent also runs behind the same guards as every other session: hooks block a push to main, commands that would kill my desktop session and a recursive rm outside scratch space, and 21 deny rules hold.

What to check in your own loop: whether rules like the board rule are enforced by a mechanism or only by the worker's instructions. A rule that lives in instructions is the kind of fix this loop refuses to accept from itself, and the same standard applies to the guards around the improver.

Invented mistakes​

A judge that finds a mistake where none happened produces a fix for nothing, and every such fix is one more guard the agent has to work around. Self-improving harnesses can "hallucinate mistakes that never happened" (Phantom Guardrails).

The silent-strike rule covers this too: one verdict without my words cannot add a guard. The frozen-day run shows that the judge finds my complaints; it says nothing about how many it invents. When a rerun corrects a verdict, the wrong row stays in the ledger as superseded, and 33 rows carry that status so far. That counts the mistakes a rerun happened to catch; it is not an error rate.

Data leaving the machine​

The judge is another vendor's model. Every two hours, it receives my messages, the agent's replies and event lines from Slack, mail and the calendar, including messages other people wrote to me. Tool output and thinking are dropped and long texts are capped, but nothing is redacted. Before you ship a transcript out on a schedule, check what your agreements allow to leave the machine, how long the vendor keeps it, and whether secrets ever pass through your agent's chat.

Rollback​

Each fix is one local commit with the ledger id in a trailer, and nothing is pushed. git log shows what changed and why, and git revert undoes one fix without touching the others. The catch is that the repository is the live harness: a fix takes effect the moment the worker writes it, before its test runs or its commit lands, and a revert cannot undo what it did in between.

Observability and visibility​

The loop runs unattended, so a judge crash, a fix that stops working or a stalled delivery path shows nothing unless something reports it. My ledger, an append-only JSONL file in git that records every verdict and what became of it, has a view command. In its output, fixed means a change was committed and its family has not yet stayed quiet for a day, and verified is the loop's label for a quiet day. The 174 committed fixes show up as 138 fixed plus 36 verified, and the 31 failures sit inside the 138. On the evening of 23 October it printed this:

verdicts 407: strikes 299, praises 101, discoveries 4, answers 3
open 78 (strikes 78)
fixed 138 not yet verified: 107 under 24h, 31 with a later strike of the family, 0 verify at the next run
closed adopted 1, board 5, dropped 18, fixed 138, kept 98, superseded 33, verified 36
strikes by layer context 16, harness 55, session 146, tools 81, unset 1
complaints per week (strikes in Piotr's own words)
2026-W43 138

What to watch, and how:

  • Anchor metrics: my complaints per week, families that came back after a fix, fixes that stayed quiet, and strikes no mechanism can fix. The raw strike count says little on its own. Compute these from the raw ledger rows, never from a report the improving agent writes about itself.
  • Judge mistakes: 33 verdicts were superseded by a corrected rerun. Never delete a wrong verdict; close it as superseded so corrections stay visible. To estimate an error rate, sample verdicts at random and check each one by hand against the transcript, and rerun the frozen day after every rubric change.
  • Failure alerts: a failed judge run raises a critical desktop notice with the cause from the judge log. Send failures through a channel the loop does not own, because a broken loop cannot report itself in its own record.
  • Skipped runs: log every run, including the ones that skip because the lock is held. On 23 October that log was the only witness. A background job the judge starts inherited its lock, so the second session's pass skipped every run for ten hours, and when one finally ran, its slice was 182 characters over the judge's input limit of 1,048,576. Another background job failed three runs out of three because it could not find the agent's CLI. None of it raised an alert, because a skip is not a failure. The fix closes the lock in the background job and judges a backlog in two-hour windows, split further when a prompt is too large.
  • Stalled delivery: for about twelve hours, the second session held the notification stream and logged the main session's records as receipts, so the reflect step never ran. Records now go to the main session as direct messages. No error showed it, so watch freshness: when verdicts keep arriving and fixes stop, something between the judge and the worker is broken.
  • Daily view: the one-line record after each run is the only loop output I read, in the pane I already watch. Put the summary where you already look, and keep ids and details in the ledger.
  • Audit trail: the ledger id in each fix commit's trailer links the code to the verdict. Searching git log for the trailer answers why a hook exists, and the ledger shows whether its family came back.
  • Cost and latency: one 142 KB slice took the judge over 12 minutes, and rerunning the frozen day after a rubric change is estimated at about 250k input tokens. Log the slice size and wall time of every run; a slice that keeps growing means the window or the caps need attention.

Conclusion​

Waiting for RSI shouldn't stop us from improving the agents we already have. RHSI keeps the model fixed and lets the agent improve its harness: an outside judge finds the mistakes in my own words, the agent turns each one into a hook, a tool or a check, and later runs show whether the mistake comes back. After two days, 31 fixes have already failed, 36 have stayed quiet for a day, and nothing is proven. The agent still tells me that nothing needs my attention more often than it should, and the loop finds mistakes faster than it fixes them: once the skipped hours were judged, open strikes rose from 78 to 133 in one evening. The open question is whether the loop cuts my corrections enough to pay for its cost and upkeep, or piles up guards that become friction of their own.

The next time your agent says "You're absolutely right, I should have...", don't let it write another note on the fridge. Ask it which check would catch the next repeat, what that check can miss, and who will look tomorrow.

Phil Connors only got out of Groundhog Day by changing himself. My agent can't change its model, but it can change its harness, and every two hours someone checks whether it did.