Module 37 · 45 min

Keep the Main Window Small

You can add a reader agent that reads the whole record, returns at most three evidenced proposals, and costs the orchestrator nothing in context.

Surface
.claude/agents/reader.md · council/
Workbench tag
module-37
Claude Code
v2.1.251 (bundled)
claude-agent-sdk
v0.3.251
Docs checked
2026-08-30

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 orchestrator's window holds paths. The reader's own window holds the record. One thin arrow comes back: a short report and the ledger rows. The council sits outside the wiki, so the lint never sees it.

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.

chat-workbench/
├─ .claude/
│ └─ agents/
└─ council/
.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.
The reader's charter in full, then the two council files, at tag module-37. reader.md is first because it is the module's subject.

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.

Build

The ladder:

  1. 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.
  2. Read one file. .claude/agents/reader.md, the whole charter, which is the whole of what the reader is.
  3. 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.
  4. Build. Add council/ledger.md and 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.
The mistake most people make first

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.

Does this travel?

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

  1. 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?
  2. 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.
  3. 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?