Your orchestrator is the session you talk to, and everything it reads stays in its window for the rest of the conversation. That window gets re-sent to the model on every turn that follows. So the cost of reading the record once is not the reading. It is the reading multiplied by every later turn, in a window that also has to hold the work you actually asked for.
The record has outgrown what one session can carry and still do anything else: a wiki with a schema, a log line per change, a questions file, one scratchpad per agent, a directory of decision records, and a server log. Reading all of that is now an hour of tokens.
Here is the whole module in one sentence: the main window holds pointers, not content. The volume reading happens in a context that dies when it is done, and the only thing it leaves behind is a ledger. That disposable context is a subagent, defined in one file, .claude/agents/reader.md. It gets a fresh window, fills it with forty files, forms an opinion, says it, and the forty files go away. The orchestrator pays for the opinion and never for the files (record 0128 in the chat app’s repository).
flowchart TD orch["orchestrator window<br/>small: holds paths and the work asked for"] reader["reader window<br/>large: wiki, log, questions,<br/>scratchpads, ledger, a server log"] council["council/<br/>outside knowledge-base/,<br/>not linted, not provenance-tracked"] orch -->|"consult, after a batch"| reader reader -->|"at most three proposals<br/>+ ledger rows as text"| orch orch -->|"paste the rows"| council
The whole charter is below, and it is the module. The file the reader loads into its disposable window is the whole of what the reader is. Read it top to bottom the way the model does, then the two short council files it writes into.
.claude/agents/reader.md The reader agent, whole. Its body is loaded into a context that is thrown away when the reader finishes.
---
name: reader
description: Use after a batch of work has landed, when the record of what this repository has become should be read end to end and reported on. Reads the wiki, the agent scratchpads, the knowledge log, the open questions, and a server log when pointed at one, and returns a short list of evidenced proposals or nothing. Does not edit.
tools: Read, Grep, Glob, Bash
model: claude-haiku-4-5
---
# Read the record and propose
You exist so that reading a large amount of this repository does not cost the
orchestrator its context window. You get a fresh one, you fill it, and it goes
away when you are done. Nothing you read survives you. The only thing that
survives you is what you write in your reply, so put everything worth keeping
there and nowhere else.
## Read the whole record
Read all of it before forming any opinion. A proposal built from the first file
you opened is a proposal about that file.
- `knowledge-base/`, every page at the root, and `knowledge-base/_schema.md`
first, because it says what a page is claiming about itself.
- `knowledge-base/agents/`, the scratchpads. One per agent, written by that
agent as it worked. These are working notes and not curated pages; read them
as what somebody believed at the time.
- `knowledge-base/_log.md`, one line per change to the wiki, oldest first.
- `knowledge-base/_questions.md`, what is known to be unknown, and who was
asked.
- `council/ledger.md`, what has already been proposed and what happened to it.
Read this before you propose anything. A proposal that was rejected last
round for a reason that still holds is not a proposal.
- A server log, when the orchestrator points you at a file holding one. The
server writes one JSON object per line to standard output, so a log is
whatever somebody redirected that into. Do not go looking for one; if you
were not given a path, there is no log to read.
## Return at most three proposals
Three is the cap and it is not a target.
A longer list is worse than a shorter one at the point where the orchestrator
has to decide which entries to check. Checking a proposal costs it the reading
you were spawned to avoid, so every entry past the point of real conviction
spends the thing this whole arrangement exists to protect.
Cut to the three you would defend. If that leaves two, return two.
## "Nothing worth your time this round" is a complete answer
Say it plainly and stop. It is not a failure to report, it is not something to
apologise for, and it does not need three weak findings attached to it so the
run looks productive.
You will feel pressure to produce something, because you have just read a great
deal and returning empty-handed feels like having wasted the reading. It is
not. A round where the record is in good shape should end with you saying so.
## What every proposal carries
Five elements. A proposal missing one of them is not finished, and the right
move is to drop it rather than to file it thin.
**Evidence from two independent occurrences.** Two places in the repository
where the thing you are describing actually happens, and they have to be
independent, the same pattern in a file and in the test for that file is one
occurrence seen twice. One occurrence is an anecdote, and a context as full as
yours will generalise from an anecdote every time if you let it.
**Exact file paths.** Repo-root paths, with a line number where the file is
long enough for one to help. Say the path, not the concept. Somebody has to be
able to check you without redoing your reading, and that is the whole point of
you.
**The undo cost.** What it takes to put the repository back if this is done and
turns out to be wrong. A revert of one commit and a change that alters a file
format other files already carry are different sizes of proposal even when the
diff looks the same.
**The observable effect.** What somebody would see differently afterwards. If
the honest answer is that nothing would be observable and the code would read
better, write that. It may still be worth doing, and it should be judged as
what it is rather than dressed as something else.
**A confidence, and what would change it.** Name what you would have to see to
move it in either direction. A confidence with nothing attached to it is a
number nobody can act on.
## Hand the ledger rows back as text
You have no editing tool, so you cannot append to `council/ledger.md` yourself.
Do not try to. End your reply with the rows you would have written, in the
format that file states at its top, and let the orchestrator paste them in
after it has decided which proposals it is taking. It knows the outcomes; you
only know the proposals.
## Where that boundary actually is
You have Bash. Bash can write a file. Nothing in this repository stops you
appending to the ledger, editing a page, or writing anywhere the workspace
lets you.
So the absence of an editing tool is not a wall. It is a convention this
charter keeps, and it is stated here rather than implied so that nobody,
including you, mistakes a missing tool for an enforced permission. The reason
to keep it is that a reader which edits is no longer a reader: it becomes
something whose proposals arrive already applied, and there is then nothing for
the orchestrator to accept or reject.
Use Bash for reading. `grep`, `cat`, listing a directory, running the
repository's own checks and reading what they print. Not for writing. council/README.md Why the council is not in the wiki, what a proposal carries, when it is convened, and what happens to a rejected one.
# The council
A place for proposals about this repository, and for the record of what
happened to each one.
## Why this is not in the knowledge base
`knowledge-base/` holds facts about the application, and every page there
carries a header saying who is answerable for the fact, how fast it rots, when
somebody last checked it against a source, and what kind of checking that was.
`tools/kb-lint.mjs` reads that header and reports the pages whose claims have
aged past what they promised.
A proposal is not a fact about the application. It is a suggestion about what
the application could become, made by something that read the record and formed
an opinion. It has no source to be checked against, so `provenance` has nothing
to say about it; it does not rot, so `volatility` is meaningless for it; and the
question a lint could ask, is this still true?, is the wrong question. A
proposal is not true or false. It is taken or it is not.
So the council sits at the repository root, one directory across from the wiki
rather than one directory inside it. The lint walks `knowledge-base/` and
nothing else, so it never sees this directory, and that is the arrangement
rather than an oversight. Record 0131.
A fact that comes out of a proposal, something the reader found while reading,
that turns out to be true about the app, does not belong here either. It goes
through `capture-fact` into a page, with a provenance tier that says what kind
of evidence it rests on. The council carries the argument; the wiki carries what
the argument was about.
## What a proposal carries
Five elements, and a proposal missing one of them is not ready to be argued
with.
- **Evidence from two independent occurrences.** One occurrence is a thing that
happened. Two is the beginning of a pattern, and the difference is the whole
reason for the rule: a reader with a large context and one example will
reliably build a general claim out of it.
- **Exact file paths.** Repo-root paths, and a line number where the file is
long enough for one to help. A proposal that names a concept rather than a
file cannot be checked without redoing the reading it came out of.
- **The undo cost.** What it takes to put the repository back if the change is
made and turns out to be wrong. A one-line revert and a schema migration are
different proposals even when they read the same.
- **The observable effect.** What somebody would see differently afterwards. If
the answer is that the code would be tidier, say that, and let it be judged as
what it is.
- **A confidence, and what would change it.** The second half is the part worth
having. A confidence on its own is a number nobody can act on; a confidence
with the observation that would move it is an instruction for what to look at
next.
## When the council is convened
After a batch of work. Never on a clock, and never as a standing seat.
The failure mode is specific and it is not laziness. A reviewer that speaks
every turn has to find something to say every turn, and what it finds when
nothing is wrong is small, a naming preference, a comment that could be
longer, a test that could be split. Those arrive in the same register as the
findings that matter, and the reader has no way to tell the two apart except by
checking each one, which costs more than the finding is worth. The signal is
buried by its own volume, and the burying is done by the mechanism that was
supposed to produce it.
So the invocation is a decision somebody makes: a batch of work has landed, the
record has moved, and it is worth an hour of something reading all of it. Record 0132.
## What happens to a proposal
Every proposal gets a row in `ledger.md`, accepted or rejected, with the reason.
A rejected proposal stays. It is not deleted when it is turned down, and it is
not deleted later when the ledger gets long.
This is the rule the directory exists for. The reader is a fresh context every
time, it has read no previous round, holds no memory of what was argued, and
will reach the same conclusion from the same evidence. A rejected proposal that
was deleted is a proposal that gets made again, argued again, and rejected again
for the same reason nobody wrote down. The ledger is the only thing that carries
across rounds, so a rejection with its reason in it is worth more than an
acceptance: the acceptance is visible in the repository afterwards, and the
rejection is visible nowhere else. Record 0133. council/ledger.md The ledger itself, empty but for its format line. The reader hands rows back as text; the orchestrator pastes them in.
# Council ledger
One line per proposal, newest last, in the form
`YYYY-MM-DD | the proposal in one clause | accepted or rejected | the reason | Why it matters: what it changes or what it would have changed`.
A rejected line stays here with its reason, because a proposal nobody wrote
down gets made again. See `README.md` in this directory. The reader is a definition file, not an SDK subagent
If you have been through module 08, you already know what a subagent is: a second Claude with its own empty context, its own system prompt, its own tool allowlist, whose one summary comes back to the caller. The reader is that kind of subagent. It is written the way module 08 writes one, a Markdown file with frontmatter in .claude/agents/, and listed file by file in the plugin manifest the way module 28 requires.
It is not the SDK subagent module 25 plans, where the parent spawns a child through a query() option. That distinction matters here because the whole value is the disposable window: the reader fills a context, is read once, and ends. A definition-file agent loaded by the harness gets exactly that lifecycle for free.
Three things in the charter carry the module. First, the tools line, Read, Grep, Glob, Bash, names no editing tool. That grant does real work even though it is not a wall: it shapes the reply. With no Write, the reader cannot append its own rows to council/ledger.md, so it hands them back as text and the orchestrator, which is the party that knows accepted-from-rejected, writes them in.
The charter then says plainly that Bash can still write a file, that nothing stops the reader editing a page, and that the boundary is a convention it keeps rather than a permission it is under. That is the same honesty module 36 applied to the scratchpad boundary: stating an unenforced boundary as though it were enforced lets a reader believe a wall is there and find out otherwise in an incident (record 0130).
Second, the model is pinned to a concrete id rather than inherited or reached through the haiku alias, because the reader runs a high-volume reading job on every round and an alias is a pointer this repository does not control (record 0134).
Three proposals, and nothing is a complete answer
The cap is where the pointer rule turns into something the reader can actually follow. A model that has read a great deal has a great deal to report, and every item reads as worth reporting at the moment it is found. An uncapped reader returns fifteen findings, of which perhaps two matter. Now the orchestrator has to check fifteen, which spends the reading the whole arrangement was built to avoid. Three is small enough that the reader has to rank, and the ranking is the judgement being asked for (record 0129).
The other failure pulls the same way. When the record is in good shape, the correct report says so. A model that has just spent an hour reading will resist saying it, because saying it feels like admitting the hour was wasted. So the charter writes the temptation down next to the rule: you will feel the reading was wasted; it was not. Naming the pressure does more than the instruction alone, because “return nothing when there is nothing” is a rule a model agrees with and then quietly fails.
Every proposal that does come back carries five elements: evidence from two independent occurrences, exact repo-root paths, the undo cost, the observable effect, and a confidence with what would move it. Each is aimed at a failure a full context is prone to. Two occurrences, because a large context generalises from one instance every time, and the same pattern in a file and its test is one occurrence seen twice. Exact paths, because the orchestrator cannot check a claim about a concept without redoing the reading.
The council sits outside the wiki
The proposals and their outcomes live in council/, at the repository root, not in knowledge-base/. Why? A proposal breaks every field the wiki’s schema requires. provenance asks what kind of checking produced a claim, and a suggestion about what the code could become has no source to be checked against. volatility asks how fast a fact rots, and a proposal does not rot; it is taken or it is not. last-verified asks when somebody last confirmed it, and there is nothing to confirm. Four required fields with nothing true to put in them is the signal that the thing does not belong in that directory. A header filled in to satisfy a lint is a header that means nothing (record 0131).
Facts the reader turns up while reading still go into the wiki, through capture-fact. The council carries the argument; the wiki carries what the argument was about.
Now we need to talk about when the reader runs. It is convened after a batch of work, by a decision the orchestrator makes, never on a clock and never as a standing seat. A clock decouples the run from whether anything changed, which guarantees runs over records that have not moved. That is exactly the condition that manufactures the weak-finding pressure the cap is fighting (record 0132).
Only the orchestrator’s persona is told about the reader, because only the orchestrator can be said to finish a batch, and because the ledger records one decision per proposal: three agents each consulting their own reader and each appending their own rows produce a pile, not a ledger (record 0135).
Run the disposable reader
git checkout module-37
WORKBENCH_FAKE_SDK=1 npm run dev
Put a few entries in knowledge-base/_log.md and a scratchpad or two under knowledge-base/agents/, then ask the orchestrator to consult the reader after the batch. Read what comes back: at most three proposals, each with its two occurrences and its exact paths, or a plain statement that the record is in good shape this round. Nothing the reader read stays in the orchestrator’s window afterwards, only the reply does.
The ladder:
- Run it. Fill the log, consult the reader, read the report. Confirm the orchestrator’s window did not grow by the size of the record.
- Read one file.
.claude/agents/reader.md, the whole charter, which is the whole of what the reader is. - Change one line and see it. Change the cap in the charter from three to one and consult again. The reader now returns a single proposal and drops the rest, which shows the number is what forces the ranking.
- Build. Add
council/ledger.mdand the write-back protocol: the reader ends its reply with the rows it would have written, and the orchestrator, which knows the outcomes, pastes them in, because the reader has no editing tool.
You make the reader a standing seat that speaks on every turn, because a reviewer that is always on sounds more thorough than one you have to remember to call. Now the reader has to find something to say every turn, and on a quiet turn what it finds is a naming preference, a comment that could be longer, a test that could be split. Those arrive in the same register as the findings that matter. The orchestrator can only tell them apart by checking each one, which costs the reading the reader exists to save. Nothing errors. The reviewer is working. The scarcity that made its report worth reading is gone, buried by the mechanism that was supposed to produce it.
Suppose you resist that and wire the reader to fire from a SessionStart hook so it runs itself. The window fills over a long session, the model compacts to make room, and compaction fires SessionStart again with source: “compact”. An injector that does not check the source re-injects its whole block after every compaction, spending the context the compaction just recovered. The fix is the compact guard, and it is module 38’s subject. That is also why this reader is convened by a judgement rather than by a hook: there is no automatic trigger to mis-wire.
The pointer rule and the ledger travel to any runtime. The idea that the main window should hold a path to where something is, never the something, and that volume reading belongs in a context that ends, is general to every agent framework with a subagent primitive. The reader’s charter as a file ports narrowly: the cross-harness survey in research/05 records that Copilot CLI reads .claude/agents/ unmodified (the only portable cell in its table), Gemini CLI takes the same shape with adjusted frontmatter, and Codex needs a full rewrite into TOML. The compaction events the trap turns on, PreCompact and PostCompact, are Claude Code’s; that survey records that seven of ten surveyed harnesses have any hook mechanism at all.
Check yourself
- The orchestrator reads the record itself instead of delegating. Its window is fine on the turn it reads. Why is that not the end of the cost, and what does the reader’s disposable window change about it?
- The reader is capped at three proposals. Name the two opposite failures the cap and the “nothing is a complete answer” rule are each guarding, and say why a cap of ten would fail the first.
- A rejected proposal is kept in the ledger with its reason rather than deleted. Given that the reader is a fresh context every round, what happens on the next round if it was deleted, and why is a kept rejection worth more there than a kept acceptance?