A Second Mode, Kept Behind on Purpose

, about 13 minutes

Our long-form writing system has a second output mode that we have left behind on purpose while the architecture settles. What a lagging mode bought us, what it costs, and the rule that stops anyone spending on it too early.

In March 2026 the writing system we build grew a second output mode. Alongside novel projects it could create report projects: same entities, same planning machinery, same extraction pipeline, different prose. Six months later report mode is still there and still incomplete. A report project cannot write a chapter at all; the call throws before a model is reached.

That is deliberate, and it is not abandonment. We added report mode early, and on purpose, to force the structure. Our own repository rules say that anything unused is removed entirely, and a mode selector with a single mode behind it is unused code; the second mode is what made the seam legitimate. Report mode is also meant to be a product. We set it aside because the architecture underneath both modes was still moving, and testing or maintaining a second mode against a moving architecture is money spent twice. The plan is to build novel mode, make it good, and then bring report mode up to parity or build it out further, which may well take more than is obvious from here.

The structural effect was the point, and it arrived. The second mode keeps mode selection in the prompt corpus a key prefix rather than a branch, and it is why 263 prompt sections sit in a shared tier instead of a novel one. What the lagging mode then showed us was where the seam actually held. Across the API a single production site branches on report mode. Where report mode never went, sixteen call sites quietly hardcoded the first one. And none of it would be affordable without a written rule that stops anyone from spending on report mode before its turn, plus a test we refused to write.


Why the second mode came early

An interface with one implementer is a familiar smell, and the usual remedy is to delete it. Our repository rules say so outright: if something is unused, remove it entirely. A mode selector with only novel behind it would have been unused code, and under that rule it would have gone, taking the seam with it. We wanted the seam, so we gave it a second implementer.

A second implementer also answers a question that reading cannot. A seam is real when something different can run behind it, and reading the code will not tell you, because the code was written by someone holding one case in their head. The prompt that says chapter where it means unit of content. The enum whose members are all narrative. The context object that carries an arc because arcs were the only structure anyone had needed. None of these announce themselves. You find them by putting a second thing behind the seam and watching what fails to compile, what reads wrong, what has to be renamed.

A second mode does that job even while it lags, provided nobody deletes it to make the lag go away.

A mode is a key prefix

Prompts are assembled from sets, ordered lists of named text sections held as embedded JSON, and sections, which are plain text files. A set key is {mode}.{operation}; a section key is {mode}/{name} or shared/{name}. The caller passes the project’s mode and the operation, and the resolver does an exact dictionary lookup. On a miss it throws and lists the keys it does have. There is no fallback to novel and none to shared.

The corpus, counted from the repository on 2026-09-13 at prompt seed version 92:

Tier Sets Section files
novel 72 179
report 30 14
shared 13 263
direct 9 14

Report mode owns fourteen text files. Its thirty sets make 129 references to sections, and 113 of them point into shared/. Novel’s seventy-two sets make 292 references, 219 of them shared. Both modes are mostly built out of the same text.

That ratio is what the lagging mode has bought so far. Each shared section is text somebody had to decide was not novel-specific, and the second mode forced the decision, because otherwise it would have needed its own copy. Without report mode nothing pushes anyone to make that call, and the natural resting place for all 263 files is the novel tier. They would sit there unexamined until the day report mode’s turn came, and that day would begin with the discovery that the output-format instructions, the JSON contracts, the language rider and the deduplication rules had all grown narrative assumptions.

What separates the tiers is narrower than you would guess. Report has its own role text for structure creation, for continuing to the next section, for arc updates, for the entity-modify family. Those role files mostly perform a vocabulary substitution on a shared domain: “Chapters” are report sections, “Parts” are section groups, and arcs become argument threads whose phases still run Setup, Rising, Midpoint, Falling, Resolution. Read the two sets of role files side by side and you have the best evidence the second mode has produced. Parts, chapters, arcs, definitions and timeline items are not novel vocabulary wearing general names. They carry a second kind of document, and we know that because a second kind of document runs on them.

The seam held only where report mode ran

This is the finding we would not have reached by reasoning about the design.

Report mode goes through the prompt funnel, and the funnel is genuinely mode-parametric: one key, one lookup, no branching. In the entire API a single production site branches on ProjectMode.Report, and all it does is build a section key from the mode name and read it.

The extraction and planning services are another story. Sixteen assembler call sites there pass the literal ProjectMode.Novel instead of the project’s own mode: prose reference extraction, timeline discovery, arc phase tracking, beat membership, the completion re-judges, the planning runners. Each one hardcodes the mode it was tuned against. A report project’s extraction therefore runs novel prompts on report text, by construction.

Nobody decided that. The seam held where it was leaned on and drifted where it was not, which is a fact about seams rather than about this codebase. A second mode holds structure open along the path it travels and nowhere else. A mode that is created but never extracted from will not hold the extraction layer open, however carefully that layer was designed.

We are not converting the sixteen sites now, while the extraction layer is still changing under novel mode. They are listed in a dated report, the change is mechanical, and it belongs to the work that brings report mode up, when there is a settled layer to convert.

The rule that keeps the lag cheap

Leaving a mode behind is cheap. Repairing it piecemeal is not, and the danger is not that somebody refuses to work on report mode. It is that everybody helpfully does, one small fix at a time, against an architecture that will move again next week.

So the instruction file every contributor and every coding agent reads, CLAUDE.md at the repository root, carries a section titled Report Mode Is Deliberately Behind, and it is blunt. Report-mode gaps are not to be reported as issues, defects or findings: not added to the issue registry, not written up in review documents, not raised in conversation as problems. No test may enforce parity between the modes. And report mode is not to be fixed speculatively, because adding a missing twin “while we’re here” “spends effort on a surface nobody is validating and creates content that will be redone in the parity pass.”

That last clause is the economic core of the rule. The parity pass is coming, and anything built for report mode before it is built against an architecture the pass will have to re-check anyway. The rule does not say report mode does not matter. It says the money spent on it now is spent twice.

Two more clauses do the rest of the work. The first is that cheap symmetry is fine and chasing symmetry is not. Text that is naturally mode-agnostic goes in the shared tier rather than the novel one, and that is not parity work, just putting the text where it belongs. The line falls at manufacturing a report artefact to match a novel one, which is what lets the shared tier keep growing without anyone having to defend each addition.

The second is that a novel-mode defect is still a defect when it also affects report mode. Without that clause the rule becomes a shield, and any bug in shared code can be reclassified as report neglect and dropped. The test is whether novel mode is broken, and that question has an answer every time.

The test we did not write

The obvious test writes itself. Walk the set catalogue and, for every novel.* key, assert that a report.* twin exists. It is a few lines long, and it is a trap.

Parity is the plan, so the test can look like an early statement of intent. It is not. It turns a question of timing into a build failure, and it fires while somebody is doing unrelated work. The next engineer to add a novel operation gets a red build and two choices: write a report twin they do not want and cannot validate, or edit the test. Most write the twin, because editing a test until it passes feels like cheating. The parity pass then happens anyway, one unvalidated twin at a time, against an architecture that has not settled, which is the exact spending the rule forbids.

The refusal sits where it will be read, in the doc comment on the test class that does hold the corpus together:

Deliberately NOT tested here: novel↔report parity. Report mode is knowingly behind while novel mode is the one being developed and verified (CLAUDE.md), so a test that failed whenever a new novel.* set had no report.* twin would be enforcing a decision nobody has made.

What is tested instead is per-set and blind to mode. Every set must resolve every section it names, so a set referencing text nobody ships fails the build instead of waiting for a live run to walk that path. Every set must carry the language contract unless it sits on an exemption list that stores a written reason per entry; there are twelve entries, and each is a verdict, a tool choice or one of the two language detectors. And a mode with no chapter-write set must throw, which is asserted directly against report:

var assemble = () => BuildRealAssembler().Assemble(
    ProjectMode.Report, "chapter-write", new PromptConditionContext { Language = "en" });

assemble.Should().Throw<InvalidOperationException>()
    .WithMessage("*report.chapter-write*");

Each of these asserts something about one set in isolation, and none of them says anything about how many sets a mode ought to have. The missing half of report mode is not a test failure; for now it is asserted behaviour.

Tests encode decisions, and a test that fails when someone adds a thing is asserting that things must come in pairs, starting now. Before writing one, ask who decided that, and when.

What it costs

Report mode is behind, and parts of it do not work.

A report project cannot write a chapter. There is no report.chapter-write set and there are no report/chapter-write.* sections, so the call fails before any model is reached. We had even documented the failure point wrongly: our plan said the write would throw at the resolver, listing the set keys, and the landing review found that the prompt builder reads the mode-keyed lead-in section first, so the exception comes from the section store and lists the section catalogue instead. The test above calls the assembler directly and never walks that path. Both are fail-fast and nothing was broken, but the document had described a path no test exercised.

Forty-two of novel’s operations have no report twin: beat-sheet writing and rewriting, beat membership, the arc work, chapter rewrite, chapter summary, the roleplay pair, the thirteen analysis sets, the entity-create batch family. Every one of report’s thirty operations exists in novel. That is the size of the parity pass before anything is built further.

The report text has drifted too. Its next-section role prompt asks the model for definition subtypes Source, Framework and Method. The enum the parser binds to has none of them; it has Character, Location, Item, Concept, Organization, Creature, Term, Relationship and Rule. The prompt was written against a vocabulary that never landed in the domain, and because nobody runs report projects, nothing has ever complained.

One report set also reaches across the tier boundary. report.beat-sheet-instruction reads novel/beat-sheet.role, the only cross-mode section reference in the corpus. One leak in 129 references is a good rate. It is still a leak, and we can see it only because the tiers are directories and the references are greppable.

None of that is the real cost, though. The real cost is what a stranger sees reading the repository cold: a half-finished feature with broken prompts in it, and every instinct they have trained telling them to finish it now or delete it. Not the thirty-six kilobytes of text, but the standing invitation to well-meaning repair at the wrong time. That is what the rule exists to refuse.

What makes the half-built mode safe at all is that a missing set fails loudly and at once. The design rule is written down and the resolver implements it: exact match on the key, an exception listing every available key on a miss, no fallback. The tempting alternative, falling back to the novel set when a mode has none, looks more robust and is far worse. A report project would then silently run narrative prose instructions over analytical content and produce output that is plausible, wrong and expensive to diagnose. Half a mode that throws is a known state. Half a mode that improvises is a bug nobody can trace six months later.

The rule has one soft spot we know about. Mode-keyed sections read straight from the store, outside a set, can be guarded with an existence check, so an absent section is skipped rather than raised. There is one such site, in cross-project chat, and both of its files exist. Watch the pattern rather than the instance: the strictness lives in the set path, and every read that bypasses the set path bypasses the strictness too.

When deferring the second mode is wrong

The preconditions are demanding, and each one is a way to get this wrong.

The lagging mode has to be cheap to keep alive. Ours is thirty set files and fourteen text files, about 440 lines of prompt text, riding on the same entities, the same execution machinery and the same storage. If keeping a second mode in the tree meant a second pipeline, a second UI or a second persistence path, carrying it behind would cost more than the structure it holds, and you would be maintaining two systems while pretending to defer one.

Nobody who will judge the product can be allowed to reach it. This is the hard boundary. The moment a lagging mode is selectable by someone who will form an opinion of the product from it, it stops being deferred work and becomes a surface, and every gap in it is a defect whatever the intent. Behind is a legitimate state for internal work and never for a delivered feature. If you cannot keep it out of reach, finish it or hide it.

The architecture has to actually be moving. Deferral is right while the ground under both modes is still shifting, because that is when parity work gets done twice. Once the first mode has settled, the same rule turns into an excuse, and the gaps listed above stop being deferred and start being neglect.

You need a written rule with an owner, and the willingness to enforce it. Without one, entropy runs in a single direction. Contributors are helpful, coding agents are extremely helpful, and a deferred mode gets built out by accretion, one reasonable-looking twin at a time, against an architecture that has not finished changing.

And the catch-up has to be budgeted as what it is. Each mode carries its own tuning cost: by the count in the report that raised this, the novel corpus took sixty-nine recorded tuning revisions to reach seed version 84. Text costs almost nothing to add and a great deal to calibrate. Bringing a lagging mode up is a programme, not a sweep, and the drift it has accumulated (the sixteen hardcoded sites, the subtypes that never landed) is part of the bill.

If those hold, a deferred second mode is one of the better deals in software: it keeps the abstractions honest at almost no cost while the first mode gets good. If they do not, you are either building a second product by accident or carrying a dead one.

What carries over

Find the assumed dimension first. Which axis does the code claim to be general over, and has anything different ever run along it? Look for the enum with one interesting member, and the parameter that is always passed the same literal.

If a second case exists and its time has not come, keep it in the tree and keep it thin. Write down that it is behind, and why, where it will be read before somebody repairs it.

And write the rule before the lag sets in: what is in scope, what cheap symmetry means, which defects still count, which test must not be written yet. The rule is the cheap part, and it decides whether the parity pass happens once, on a settled architecture, or a little at a time on a moving one.

The question underneath is not should this be configurable. It is has anything other than the default ever actually run, and if not, what makes anyone think it could.


We build and maintain software systems, and develop the Quillsmith applications. This paper describes production architecture rather than a reference design; the counts are from the shipping repository at prompt seed version 92, on 2026-09-13.

All papers