Skip to content

Latest commit

 

History

History
159 lines (122 loc) · 7.43 KB

File metadata and controls

159 lines (122 loc) · 7.43 KB

Understudy

Learns how you brief your AI agents, then briefs them for you.

An understudy sits in the wings, watches every performance, learns the blocking, and goes on when the lead can't. This is that, for the way you talk to agents.

Understudy reads the session logs already sitting on your disk — Claude Code, Codex, OpenClaw, and whatever else it finds — keeps only the messages you typed yourself, and distills them into a portrait of how you actually instruct agents: how long your prompts are, when you show evidence instead of describing a problem, when you put on the brakes, and which habits you're missing.

git clone <this repo> && cd understudy
python -m understudy run

No dependencies. Nothing leaves your machine. Nothing you own is modified — every source is opened read-only.

Your corpus is not public data

out/ holds your entire working history: client names, internal hostnames, tokens, private repo paths, unreleased product details. It is in .gitignore and must stay there. Do not commit it, upload it, or paste it into a third-party service. This repository ships code only.


Why bother

role: "user" is not the same as a person said this. Every agent runtime stuffs machine-generated text into the user slot — skill injections, cron heartbeats, replayed history, sub-agent hand-offs, tool output fed back in. On one real machine that noise was the majority of user-role records. A corpus built without removing it is a portrait of the harness, not of you.

Removing it correctly is the whole job, and it has exactly one rule:

Noise is detected by structure, never by writing style.

"You are an agent that…" is something you write. It is precisely the technique this corpus exists to capture. Any filter that guesses from tone deletes your best material. Only envelopes, injected markers, and record-level flags may disqualify a message.

The distillation, end to end

Understudy uses a distiller's vocabulary, because the process is the same shape — you run the whole batch through and keep only the middle fraction:

stage file what it is
heads out/heads.jsonl the foreshots you throw away — machine-injected text, agent-to-agent sessions, protocol boilerplate. Kept, with a reason on each, so the call stays auditable
hearts out/hearts.jsonl the cut you keep — every session, every message you actually sent, full text, never truncated
cut out/cut.md the purest fraction — your prose only, no slash commands, no pasted blocks. This is what you feed a model
proof out/proof.md how pure it came out, and what was removed to get there
understudy discover   # what agents does this machine have?
understudy extract    # read each one, keep only what you typed
understudy merge      # combine, re-judge every message, write the heartcut
understudy run        # all three
understudy proof      # print the purity report

Discovery finds tools nobody told it about

understudy discover runs three passes:

  1. Adapters — each adapter offers candidate paths; the ones that exist and pass detect() get read.
  2. Known, no adapter yet — a catalogue of tools we know where to find but can't parse yet. Finding one prints an invitation to write an adapter.
  3. Blind scan — the catalogue is never complete. Understudy sweeps your home and platform data directories for anything holding chat-shaped records and reports it.

That third pass is not decoration. On the machine this project was built for, it turned up six agents beyond the three it was written for — including one whose transcripts were byte-identical to a format already supported, which became a working adapter in six lines.

Supported agents

tool where it looks native session titles status
Claude Code ~/.claude/projects/**, ~/.claude/history.jsonl yes (ai-title / custom-title) ✅ adapter
Codex CLI / IDE ~/.codex/sessions/**, archived_sessions/, session_index.jsonl yes (thread_name) ✅ adapter
OpenClaw ~/.openclaw*/agents/*/sessions/* no ✅ adapter
Qoder ~/.qoder/projects/** yes ✅ adapter
Gemini CLI, Qwen Code, Grok CLI, Kimi CLI / Code, Cursor CLI, Continue, Goose, opencode, Crush, Amp, Zed, Aider, VS Code–family chat storage see discover.py 🔎 found, adapter wanted

The most valuable thing you can contribute is not code — it's the noise rules you had to work out for your tool. See ADAPTERS.md.

What counts as "something I sent"

Each adapter documents its own rules in its module docstring. The three recurring problems, in every runtime:

  1. A sub-agent's instructions are also role: user. The parent agent talking to its child looks exactly like you talking to the agent. Detected by record flags (isSidechain, sub-agent originators), never by content.
  2. History gets replayed into new sessions. Forks, resumes and sub-agent spawns copy earlier conversations forward — sometimes with fresh timestamps, so time-based dedup silently fails. Understudy dedups by record uuid, or tracks a current-thread cursor while streaming, depending on what the format allows. Without this, one sentence of yours can appear in dozens of files.
  3. Real messages arrive wrapped. Resident agents and chat bridges (Feishu, Slack, QQ…) wrap your sentence in metadata headers, sender prefixes, queue markers and async-completion notices. Judging the wrapped whole throws your sentence out with the envelope, so adapters peel first (unwrap()) and re-test the payload.

Three message kinds survive into the corpus, re-decided uniformly at merge time:

  • prompt — what you wrote. The core material.
  • relay — a long structured block you pasted in (another agent's answer, a log, a spec). Your behaviour, but not your prose.
  • slash — a slash command you typed.

Plus two flags that mark rather than delete: template (an integration's boilerplate where only a title line is yours) and chore: true (continue, push it, a bare number). Nothing is deleted for being short or routine — handing off without re-specifying is itself an interaction trait, and proof.md reports the length distribution both with and without them.

Reading the numbers honestly

Any statistic in proof.md built from keyword matching is a lower bound, not a measurement. "Only ask for falsifiable answers 0.7% of the time" means 0.7% said so in a way a regex could see. Phrase it differently and it isn't counted. To get real proportions, feed out/cut.md to a model for semantic classification — that is the intended next step, and PROMPT.md describes it.

Privacy

  • out/ is gitignored, and so is sources.json — discovery results name your projects.
  • The repository contains no corpus samples. Every example in the docs is invented.
  • Sources are opened read-only; SQLite is opened with mode=ro&immutable=1.
  • Contributing an adapter must never include real captured messages.

What's next

A clean corpus is the foundation, not the goal. The path from here to something that can draft an instruction the way you would — reproducible technique analysis, a semantic labelling pass, outcome pairing, and the projection itself — is laid out in ROADMAP.md.

License

MIT. See LICENSE.