skip to content
coograph 17

Retro

Instruction files grow by accretion. Every rule was added after someone watched an agent do the wrong thing, and nobody measures whether the rule works afterwards. Retro closes that loop. It records when the guardrails get broken, measures what each session costs in tokens, and proposes concrete edits to the instructions, hooks, and skills as an OpenSpec you approve or reject line by line.

Nothing leaves the machine. Nothing is applied automatically.

What it records

A Claude Code hook parses the transcript Claude Code already keeps on disk and appends metadata to .coograph/signals.jsonl (gitignored). It runs at SessionEnd, and also every time the session compacts. That second trigger is what makes a long session legible: a session that runs for a day reaches its end once, late, or never if it is killed, and compaction is the only event it fires repeatedly. Re-capturing is safe, since a session’s records are replaced rather than appended. The warn-scope.py and block-generated.py hooks append a record each time they fire.

Each violation carries the moment it happened, taken from the message that caused it, not the moment the capture ran.

Each record holds tool names, counts, rule ids, repo-relative file paths, a hash and program name for shell commands, timestamps, and token usage. It never holds prompt text, assistant text, tool output, file contents, or full commands. The allow-list is enforced in code, and a sentinel test fails if a marker string planted in every part of a transcript reaches the signals file.

Detectors

detectorruleconfidencefires when
graph-firstgraph-firstdeterministica Grep or Glob call happens before the session’s first graph access (a mcp__code-graph__* tool or a shell command containing both sqlite3 and .code-graph), while .code-graph/graph.db exists. Records whether the graph was reached later (mcp-later, sqlite-later) or never (total-bypass). Subagent calls are ignored.
openspec-gateopenspec-gateheuristictwo or more source files edited, nothing in the session touched openspec/changes, and no active change directory exists
scope-warningscopedeterministicwarn-scope.py printed its warning
generated-file-blockgenerated-filesdeterministicblock-generated.py blocked an edit
build-retrynonedeterministicthe same shell command ran three or more times with at least two failures
user-correctionuser-correctionheuristica user message matched a correction pattern right after a tool call (pattern id only). Repeated corrections on one theme are the clearest evidence that an instruction is missing or unclear, so they cluster against a rule of their own
defectdefectdeterministica fix commit in the session’s window lands on a file a non-fix commit touched in the last 14 days. One signal per fix and origin pair, carrying the file list, the two abbreviated hashes, and nothing else. Scans the project root when it is a repository, otherwise its immediate sub-repositories, so a root holding app/ and admin/ is covered
new-dependencyno-new-depsdeterministicnpm install <pkg>, pip install <pkg>, uv add, cargo add, go get, and friends, or an edit to a dependency manifest
sessionnonedeterministicalways: message count, tools used, edited-file count, skills invoked, whether the graph existed, start and end, and token usage summed per session

Heuristic signals are never sole evidence for a change.

The defect detector is the one that watches for bugs rather than broken rules. A change that type-checks, passes review and ships is invisible to every other detector; git is the only place it surfaces, as a fix landing on something recently changed.

Per-tool support

Only Claude Code exposes transcripts and lifecycle hooks. Other tools get the analyzer and the skill but no capture.

Tooltranscript detectorshook-emittedsession-start linefeature-end prompt
Claude Code / Cowork pluginallscope, generated-filesyesyes
Codex CLInonenone (Bash audit log only)noyes
OpenCodenonenone (Bash audit log only)noyes
Cursor, Devin Desktop, Aider, Clinenonenonenoyes

How you find out

You do not have to remember a step. Every Claude Code session start prints one line, in the same place the code-graph status already appears:

[retro] 12 sessions captured, 3 rules over threshold (graph-first 11x), run /coograph-retro

When nothing crossed a threshold it says so. When Retro is not enabled but the project has ten or more archived OpenSpec changes, it says run /coograph-retro to bootstrap.

coograph-archive and coograph-apply also ask, once, at the end of a change: Run /coograph-retro now? (n sessions since last retro). Default is no. The agent never runs a retro unprompted.

Day one, not day twenty

Claude Code keeps every past transcript under ~/.claude/projects/<slug>/, where <slug> is the absolute project path with every character outside A-Z a-z 0-9 replaced by - (for example C--paul-code-app or -home-paul-app). Init asks whether to read them, so the first report has real sessions in it immediately:

python3 .claude/hooks/capture-signals.py --backfill ~/.claude/projects/<slug> --cwd .

Projects that never enabled Retro but have at least ten archived changes can bootstrap by running /coograph-retro. The skill seeds the registry, asks for the transcript directory, backfills, and continues as a normal run.

The report

python3 .github/retro/retro.py --report

writes .coograph/retro/report.md. It opens with a plain-language paragraph, then tables: every rule against its threshold with an “escalate to” column, path clusters, build retries, tokens per session with a before / after split around the most recent rule change, workflow adherence (editing sessions that also ran a review or verify skill), instruction file sizes against a token budget, and archive statistics. Deterministic Python, standard library only, no model involved.

The proposal

/coograph-retro reads the report and writes openspec/changes/<date>-retro-<n>/ with a proposal, a spec, tasks, and ready patches. Five change types:

Every change opens with three plain sentences, what happened, why it matters, and what changes, followed by an evidence block whose numbers come only from the report. You approve, then /coograph-apply applies it like any other change.

Rules of the loop

Episodes

Thresholds count episodes, not sessions. An episode is one session on one calendar day.

A session id cannot carry a threshold on its own. Someone who works in long sessions produces one id per several days, so a rule needing “three sessions” is unreachable no matter how often it breaks; someone who restarts constantly crosses it on noise. Counting session-days makes the two styles comparable, and means a rule broken on three different days inside one long session reaches its threshold, which is exactly the case the session count used to hide. The report shows both figures, so three short sessions still look different from one long one.

Thresholds

All of them live in .github/retro/rules.json and can be tuned per project. The session-named keys count episodes.

thresholddefaultmeaning
deterministic_events / deterministic_sessions3 / 2a deterministic rule is over threshold at this many events across this many episodes
heuristic_events / heuristic_sessions5 / 3a heuristic rule becomes supporting-only evidence
prune_sessions10a non-hard prose rule with zero events across this many episodes is a prune candidate
defect_lookback_days14how far back the defect detector looks for the change a fix landed on
instruction_token_budget8000above this, every added rule must be paired with a prune
retro_prompt_min_sessions3episodes since the last retro before the workflow offers one
bootstrap_min_archives10archived changes needed to bootstrap in a project that never enabled Retro

What it is not

Retro is not recursive self-improvement in the model sense. No weights change, no model trains itself. It is the instruction-layer version: the system observes its own failures and proposes changes to its own scaffolding, with a human at the gate. Whether rules keep improving across many cycles is unproven; the first retro usually finds most of the value. The full detector reference, registry format, and tests are in the repo at .github/retro/README.md.