From corrections to rules: teaching coding agents without more prompts

Correcting your coding agent the same way three times is a failure of your workflow, not the model. Every session starts blank by default, and the corrections you typed yesterday are gone. Promoting a repeated correction to a real rule, with evidence attached, costs about two minutes and stops the bleed.

The tell: you've typed the same correction twice

Mine was bun, not npm. Four sessions in a row, Codex opened a fresh shell and reached for npm install. Four times I typed some version of "we use bun here, check the lockfile". On the fifth session I caught myself mid-correction and realised I was the bottleneck.

The pattern is always the same. A one-off correction in session one feels fine. Typing it again in session two feels like the agent is being dense. By session three you're annoyed, and by session four you've spent more time correcting than the rule would have taken to write down. That's the tell. It's a missing rule, not a bad model.

Other versions I've seen in my own logs: "stop rewriting my import order", "the test command is pnpm test --run, not pnpm test", "use NZ English spelling", "don't add JSDoc to internal helpers". If you can complete the sentence "I keep telling it to...", you already have a rule waiting to be written.

Why just adding to CLAUDE.md is wrong

The obvious fix is to dump the correction into CLAUDE.md or .cursorrules. It works, sort of, until the file hits 600 lines. Then 1200. Then 2000. At that point the agent is skimming a wall of bullet points, and so are you when you try to audit what's in there.

Three real problems with the static-prefix approach:

  • No evidence. A line says "always use tabs" but there's no record of when that was decided, or by whom, or whether it's still true six months later.
  • No threshold. Every gripe graduates to policy the moment you type it, so signal and noise sit next to each other.
  • No cleanup. Rules that contradict each other pile up, and nobody wants to do the archaeology to figure out which one won.

Claude Code's built-in auto memory has the opposite failure mode. It writes almost every correction-shaped utterance into memory with no threshold, so you end up with a memory file full of one-off frustrations that should never have been promoted.

The promotion threshold that matters

The mental model I use, and the one Munin is built around, has three levels: observation, claim, rule.

Observation

Something happened once. You corrected the agent. It's logged as an event with the agent name, the correction text, and a timestamp. Nothing loads into future sessions yet. Most observations die here, and that's correct behaviour.

Claim

The same correction has now shown up N times across sessions (I use N=3) and nothing has contradicted it. It's a candidate. It surfaces in munin friction reports so you can see the pattern, but it's still not being enforced. You have to look at it and decide.

Rule

You've promoted the claim. It's scoped to a specific agent (so a rule about Codex's prettier behaviour doesn't leak into Claude's context), it has the original evidence attached, and it loads into the startup brief for every new session in that scope. This is the only level that changes agent behaviour on its own.

The threshold matters because one-off frustration is not policy. If you promote every correction the moment it happens, you rebuild the 2000-line CLAUDE.md problem with extra steps.

What freshness, stability, and confidence mean

Each promotion step checks three numbers. They're simple:

  • Freshness: how recently the rule was reconfirmed. A correction from 90 days ago that hasn't come up since is probably stale.
  • Stability: how consistent the correction is across sessions. If three sessions say "use bun" and one says "use pnpm for this package", stability is low and the rule shouldn't fire yet.
  • Confidence: a derived score from the first two, plus how many independent observations back it. High confidence means the rule has earned a spot in the startup brief.

You don't have to tune these yourself for the normal case. You just need to know they exist, so you can read munin friction output without guessing.

A worked example

Last fortnight I corrected Codex three times about prettier config. It kept adding a .prettierrc to subpackages even though the repo uses a single root config. Here's what munin friction --agent codex --last 30d showed:

  • Observation, 2026-04-02: "don't add .prettierrc to packages/ui, use the root config"
  • Observation, 2026-04-07: "remove the prettierrc you just added, root config only"
  • Observation, 2026-04-11: "root prettier config, stop writing package-level ones"

Three observations, same shape, no contradictions. Friction score high enough to flag it as a claim. I ran:

munin promote "Codex: single root .prettierrc only. Never write package-level prettier configs in this repo."

The three observations attach as evidence. Next time I started a Codex session, munin resume --format prompt included that rule in the startup brief. Codex opened a new feature branch, added some UI code, and did not touch prettier config. First time in a month.

When NOT to promote

Equally important, because over-promoting is how you end up back at the wall-of-text problem.

  • One-off corrections. A weird edge case in one file is not a rule. Leave it at observation and let it decay.
  • Contradicted by later work. If you said "no JSDoc on helpers" in January and then added JSDoc yourself in March, the old observation is stale. Don't promote it, retire it.
  • Stylistic preferences that drift. Import ordering, naming conventions that you're still making up your mind about. Wait until your own behaviour is stable before asking the agent to match it.
  • Project-specific things that don't generalise. If the correction only applies to one file in one repo, an inline comment or a repo-local note is the right surface, not a global rule.

A rule should feel boring and obvious by the time you promote it. If you're arguing with yourself about whether it's really a rule, it isn't yet.

Do this today

Open a terminal in a project where you've been working with a coding agent for at least a few weeks. Run:

munin friction --agent codex --last 30d

Read the output. Find the claim with the highest friction score, the one you've been typing over and over. Promote it:

munin promote "your rule text here"

Start a fresh session tomorrow morning. Watch the agent do the right thing on the first try, with no correction from you. Then pick the next one. That's the whole loop.

Try Munin.

A local Rust binary. Four commands. No SaaS, no account, no telemetry.