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
| detector | rule | confidence | fires when |
|---|---|---|---|
graph-first | graph-first | deterministic | a 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-gate | openspec-gate | heuristic | two or more source files edited, nothing in the session touched openspec/changes, and no active change directory exists |
scope-warning | scope | deterministic | warn-scope.py printed its warning |
generated-file-block | generated-files | deterministic | block-generated.py blocked an edit |
build-retry | none | deterministic | the same shell command ran three or more times with at least two failures |
user-correction | user-correction | heuristic | a 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 |
defect | defect | deterministic | a 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-dependency | no-new-deps | deterministic | npm install <pkg>, pip install <pkg>, uv add, cargo add, go get, and friends, or an edit to a dependency manifest |
session | none | deterministic | always: 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.
| Tool | transcript detectors | hook-emitted | session-start line | feature-end prompt |
|---|---|---|---|---|
| Claude Code / Cowork plugin | all | scope, generated-files | yes | yes |
| Codex CLI | none | none (Bash audit log only) | no | yes |
| OpenCode | none | none (Bash audit log only) | no | yes |
| Cursor, Devin Desktop, Aider, Cline | none | none | no | yes |
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:
- edit rule: reword an existing rule when the evidence shows ambiguity
- add rule: a recurring pattern no rule covers
- new instruction file: a
.github/instructions/<name>.instructions.mdscoped withapplyToto the directory where the violations cluster - new hook: a prose rule that is still violated becomes a warning hook; a warning hook that is still violated becomes a blocking one
- prune rule: a non-hard prose rule nobody has tripped in ten sessions
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
- A prose rule that is still being violated is escalated to a hook. It is never reworded louder; the data says that does not work.
- Heuristic signals are never sole evidence.
- Over the instruction token budget, every added rule is paired with a pruned one.
- Retro may target its own skill, its detectors, and its hooks, but cannot change thresholds or disable a detector without an explicit
Loosen:task in the proposal. - Every generated hook carries a provenance header. Every retro proposal ends with a rollback task.
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.
| threshold | default | meaning |
|---|---|---|
deterministic_events / deterministic_sessions | 3 / 2 | a deterministic rule is over threshold at this many events across this many episodes |
heuristic_events / heuristic_sessions | 5 / 3 | a heuristic rule becomes supporting-only evidence |
prune_sessions | 10 | a non-hard prose rule with zero events across this many episodes is a prune candidate |
defect_lookback_days | 14 | how far back the defect detector looks for the change a fix landed on |
instruction_token_budget | 8000 | above this, every added rule must be paired with a prune |
retro_prompt_min_sessions | 3 | episodes since the last retro before the workflow offers one |
bootstrap_min_archives | 10 | archived 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.