The end goal is in the name: an understudy — something that has watched enough of your performances to go on in your place. Concretely, a component that can draft an instruction to an agent the way you would, with your strengths kept and your blind spots corrected.
Today the project gets you a clean corpus. That is the foundation, not the goal. This file is the path from one to the other.
Works today. Discovery across four supported agents plus a blind scan for
unknown ones; per-tool extraction with structural noise removal; a merged,
deduplicated corpus (hearts.jsonl), a distillation-ready prose file
(cut.md), an auditable reject pile (heads.jsonl), and a purity report
(proof.md).
The gap. proof.md reports volume — how many sessions, how long your
messages are, which language, which slash commands. It reports nothing about
technique: how often you show evidence instead of describing a problem,
how often you limit scope, how often you ask for a falsifiable answer. Those
numbers have been produced by hand, in one-off model sessions, and they live
nowhere in this repository. Anyone who clones this today runs the pipeline and
sees counts, not craft.
Stage A closes that gap. Everything after it depends on A existing.
Make the technique analysis reproducible code.
Right now the interesting findings are artisanal. They should be a command.
Deliverables
understudy/techniques.py— the technique taxonomy as data, not prose: a table of(id, description, patterns_by_language). Contributors extend it by adding rows.understudy analyze→out/techniques.md(report) andout/techniques.jsonl(per-message labels, so later stages can join on it).
Techniques to detect, at minimum
| id | what it looks for |
|---|---|
evidence |
a URL, a device/model name, a file path or class name, a pasted block from another agent or a log |
scope_limit |
"only analyse", "don't change anything", "don't touch X yet" |
falsifiable |
"if there is no problem, say the code is already fine" — a stated condition under which the answer is no |
side_effect |
"will this break the other cases / platforms / callers?" |
orchestration |
instructions that spawn or coordinate other agents |
in_the_loop |
a real device, a real environment, a live check |
artifact_request |
asks for a page, a doc, a plan, a diagram — an object rather than an edit |
chore |
a bare continuation or routine command (already computed in merge.py) |
Also report
- monthly trend: message count, median length, share ≤20 chars, session count
- rhythm: opener length vs follow-up length; median turns per session; share of single-message sessions
- closing move: does a session end with a commit/merge, or with a question?
- per-tool breakdown of every technique — the tools people use are not interchangeable, and the split is itself a finding
Two constraints that are not optional
- Every percentage printed must be labelled a lower bound. Regex sees only the phrasings it was given. Anyone quoting these numbers as measurements is quoting them wrong, and the report should make that impossible to miss.
- Patterns must be language-keyed. The current
RE_CHOREinmerge.pyis Chinese-only. A technique table withzhandenpattern sets — and room for more — is a prerequisite for this project being usable by anyone else. Falling back to English patterns for an unknown language is fine; silently reporting 0% because the user writes in Japanese is not.
Done when a fresh clone can run understudy run && understudy analyze and
get the technique table without a model in the loop.
Replace lower bounds with real proportions.
A regex catches "只分析" and misses "先别动代码,我只想知道为什么". The gap between the two is unknown, and the size of that gap is itself worth knowing.
Deliverables
- stable message ids: add
mid(a hash of source + session + ts + text) inmerge.py, so labels can be joined back to messages across re-runs understudy label --emit— chunkcut.mdinto batches of 200–400 messages and write ready-to-send classification prompts toout/label_batches/, each with the taxonomy from Stage A and a strict output schema ({mid, techniques: [...], confidence})understudy label --ingest <dir>— read the model's JSON back intoout/labels.jsonl- provider-agnostic by design: the batches are plain files, so any model can do the pass. An optional API runner is a convenience, never a requirement.
The report must show the delta, per technique: regex found X%, semantic found Y%. That difference tells you which of your habits you express in ways a pattern can't see — which is a finding about how you write, not just about the tool.
Done when techniques.md can print both columns side by side.
The stage that decides whether this project is useful or merely interesting.
The corpus records what you said. It does not record whether saying it that way worked. Distilling style alone produces a clone — one that inherits the blind spots along with the craft: never asking about side effects, rarely asking for falsifiable answers, leaving 9% of multi-turn sessions hanging. A clone of your bad habits is not an understudy.
The fix does not need new extraction. The outcome signal is already in your own words:
| signal | derived from |
|---|---|
| rework | "still broken", "still not right", "same problem" — you saying it didn't land |
| closing move | last message is a commit/merge vs. a question left open |
| turns-to-resolution | how many messages before the rework signals stop |
| self-correction | "let's revert, my instruction was wrong" — you catching yourself |
| repetition | the same request re-asked in a later session |
Deliverables
understudy outcomes→ per-session labels inhearts.jsonl- join with Stage A/B labels → the table that matters: sessions that used technique X have N% lower rework rate, M fewer turns.
Optional hard signal. Session cwd plus the session's time window can be
matched against git log in that repository: did work actually land after this
conversation? Stronger evidence, much more setup, and it only applies to
sessions that happened inside a repo. Do the soft version first.
Done when techniques can be ranked by effect, not by frequency.
The understudy goes on.
understudy project→out/projection.md: a specification of how you brief agents. Not a summary of statistics — an instruction-writing style guide derived from you: your typical opener structure, what you always supply (device, path, evidence), your scope-limiting phrasing, and — from Stage C — the habits worth keeping versus the gaps to close by default.understudy rehearse "<task>"→ a draft instruction for that task, written in your voice, with your known gaps pre-filled: the falsifiable condition, the side-effect question, the scope limit.
That last command is the actual deliverable of this project. Everything before it exists to make its output trustworthy.
Open question, deliberately unresolved: should rehearse reproduce you
faithfully, or reproduce the better version of you that Stage C identifies?
Probably both, behind a flag. The honest default is arguable and should be
decided with real output in hand, not in advance.
Adapters wanted. Discovery already finds these on real machines but cannot
read them yet — see ADAPTERS.md:
Gemini CLI, Qwen Code, Grok CLI, Kimi CLI, Kimi Code, Cursor CLI, Continue,
Goose, opencode, Crush, Amp, Zed, Aider, and VS Code–family chat storage.
Check first whether a tool's records match a format already supported —
adapters/qoder.py is a complete adapter in six lines for exactly that reason.
Site-specific noise rules belong in config, not in source. Several entries
in merge.py's MACHINE_PREFIXES and in the adapters name particular local
automation systems. They are correct rules, but they are someone's rules
baked into shared code. They should move to an optional
~/.understudy/noise.toml (or --noise-rules <file>), leaving only
genuinely universal patterns in the repository. This also makes the rules
contributable without a pull request.
Tests. A small suite over synthetic fixtures: envelope peeling, replayed-
history dedup, kind_of classification, the technique patterns.
Fixtures must be invented, never captured — no real corpus content in the
repository, including in tests.
Reproducibility. understudy run currently prints a report. It should also
stamp a run manifest (out/run.json: version, timestamp, adapter versions,
counts per stage) so two runs can be compared and a regression in a noise rule
is visible rather than silent.