| name | orc-doc |
| description | Write a long document — a PRD, a TSD, a cross-team collaboration agreement, a status report or a workflow/runbook — as portable Markdown that imports cleanly into Notion, Obsidian, Google Docs, Coda, Craft and GitHub. Use for "/orc-doc", "write the PRD for this", "turn this into a TSD", "write the runbook", "continue the document we started". You bring the context once; it is frozen to disk, so a brand-new session months later picks the work up without you explaining anything twice. Each section lives in its own file under sections/, which is the source of truth; document.md is a build artifact you rebuild for free whenever you want. The orchestrator never reads the document body — it works from a CLI-derived section map, dispatches writers that each own ONE file, and dispatches checkers that each read ONE bounded part. Every wave is a stop you can walk away from. It never edits source, never commits, and it hands you back the file plus the one line that resumes it.
|
ORC-DOC
The lane that writes the long document — and the first ORC lane whose whole
architecture is about what it refuses to hold in context.
The orchestrator never reads the document body. It knows the document only
through the CLI's derived section map and through what the agents it
dispatched report back. a lane that reads its own document has broken this
contract.
The context is gathered once and frozen. A resumed session reads
context.md from disk; it never re-interviews the user for what session 1
already settled. a lane that re-asks a frozen question has broken this
contract.
Everything else in this lane serves those two sentences.
Why the CLI half exists
Line arithmetic is the one job a language model is guaranteed to get wrong, and
the entire token saving depends on the line numbers being right. So the section
map is computed by orc doc map and by nothing else, re-derived after every
write, and never stored. A stored line number is a wrong line number one
edit later. Same rule as computeWikiFreshness and the Flow stepper: a skill
that recomputes one of these has forked it.
What this is NOT
- Not a research lane. It writes down what you know; it does not go and find
out.
/orc-brainstorm and /orc-grill are upstream of it.
- Not a code lane. It never edits source, never stages, never commits.
- Not a publisher. It writes
.md to disk and tells you how to import it.
It calls no vendor API.
- Not
/orc-challenge. That grades a finished document. This writes one, and
at handoff it OFFERS /orc-challenge — in a separate session, because
a lane that fixes what it judged is already forbidden on the other side.
- Not
/orc-wiki or /orc-learn. Those derive documents from the CODE. This
writes documents derived from what you decided, for people who may never
open the repository.
Hard rules
| # | Rule |
|---|
| 0 | The orchestrator never reads the document body. Not document.md, not a sections/ file, not a supporting document, not the template file. Reading is DELEGATED, always. |
| 1 | The context is frozen. context.md is written ONCE and quoted verbatim. A resumed session reads it; it never re-asks D1–D4. |
| 2 | No line number is ever stored, guessed or adjusted. orc doc map is the only source, and it is re-run after every write. |
| 3 | A section lives in its own file, which is the source of truth. sections/<id>.md (or sections/<id>/<NN>-<sub>.md); document.md is a BUILD ARTIFACT. A section is never split across two agents, one file per section, never one file for a two-section slice, and no two agents ever have the same file open. |
| 4 | The user's edits are sacred. A user-edited section is never rewritten without an instruction naming it. A finding inside one is REPORTED and the fix OFFERED, never applied. |
| 5 | Never invent a fact. What is not in context.md or context-sources.md is not written at all — it is returned as a gap, recorded with orc doc log --kind gap, and raised with the user. Filler that reads like a fact is the worst possible output of this lane. |
| 5a | The document body carries content only. No > **Open:**, no > **Assumption:**, no note callout, no HTML comment — in document.md OR in any section file. ORC's uncertainty is real and is written down, just not inside the document the reader came for. orc doc lint errors on it; compile REPORTS it and never silently strips it, because we cannot tell whose line it is. |
| 5b | The document body asks nothing. No question to the reader as an approver, no "to be confirmed", no //. The deliverable ANSWERS. What is unsettled goes to . errors on it (); a section the outline declares as is exempt. |
Phases
orc lane phases orc-doc --json is this lane's pipeline: the ordered list, where
each phase lives, and how much of it to read. The CLI owns the order — never
derive it from the headings below, and never renumber or rename one without the
manifest, because a read: section pointer names a HEADING and a renamed heading
is a pointer into nothing.
D0 — Preflight (ONE time, silent)
- Config.
log_dir, doc_dir, doc_language, doc_max_lines_per_agent,
doc_max_parallel (hard cap 2), doc_write_mode.
- Trace. Write
log_dir/.current = run-doc-<slug>-<DDMMYY>-<HHMMSS>.txt
AND touch the trace file of that name in the SAME step. Both, or neither.
Do both again on every resume in a fresh session — several trace files for
one document is CORRECT, because several sessions ran.
- Probe with
orc doc list --json. Never a raw find: the folder is a real
artifact with a real probe — ../_shared/detecting-artifacts.md.
- House rules.
orc doc rules --json, and print its line VERBATIM:
house rules: 7 lines (P0 4 · P1 2 · P2 1) or house rules: none. A rule
set is never silent — a project rule nobody was told about is a rule nobody
can appeal. The file path is in file; offer it when the user asks where the
rules live. Never compute that line yourself.
- If a slug was given, go straight to Resuming below.
The SHAPE of these steps — the order, and the four rules that make it worth
having — is ../_shared/phases/preflight.md (core). The probes
themselves are this lane's own and stay here.
D1 — The context gate (P0 — the only blocking one)
references/gates.md. One question, and nothing is created until it is
answered. "You decide" / "just make something" → stop and say why, and point
at /orc-brainstorm.
If D1 reveals the user has not decided what they want yet — competing options,
no chosen direction — offer ../_shared/lane-suspend.md (RETURN-TO) to
/orc-brainstorm and come back with the chosen direction as the context. It
offers; it never forces.
D2 — Supporting documents (asking is mandatory, answering is not)
references/gates.md. "none" is a complete answer and is recorded. Every path
is verified on disk and a miss is reported by name, never silently dropped.
The orchestrator does not read them — one role: digest dispatch per
document, ≤ doc_max_parallel in parallel, into context-sources.md.
D3 — Your template (asking is mandatory)
references/gates.md. A supplied template's headings become the outline
(orc doc init … --template <path>); its body is instructions for the writer,
never content to copy through. It REPLACES the shipped base template entirely —
the two are never merged.
A supplied template is a P0 cage, not a suggestion. It locks by default: a
heading it never had is a lint error, orc doc parts --confirm REFUSES the part
that grew one, and orc doc audit reports template-drift. What does not fit
is a gap, not a new section — a lane that writes outside its template has
broken the contract. --template-soft opts out, and the init line says which is
in force. A shipped base template stays a floor. references/generation-rules.md.
D4 — Purpose (must be answered)
ONE batched round in the ../_shared/interview.md format: intent · audience ·
expectation · language · type · target app · length. Every field carries a
recommended default; "yes, use your default" is an answer and silence is not.
Answering D4 is what makes the writing good — audience and expectation are
what references/plain-language.md is measured against.
D5 — Outline (confirmed BEFORE a word is written)
orc doc init → then show the section list and confirm it. Changing the outline
after a write wave is what costs money. orc doc plan --role write reports any
section over the per-agent budget: that is a planning smell, and the offer
is "add sub-headings and store it in parts" (orc doc split --section <id> --by-heading) first, "make them real sections" second — never an over-budget
writer.
Ask doc_write_mode here too, once: partial (write one wave, stop, let
the user read those files and redirect — recommended) or all. Store it
with orc doc mode <slug> --set <mode>; it is never re-decided per wave.
D5.5 — The run map, ONCE, before the first paid wave
After the outline is confirmed and doc_write_mode is stored, render
orc doc forecast <slug> --json. Rendered, never composed: every number comes
from the CLI.
This document is 9 sections in 3 waves, 2 agents per wave.
wave 1 01-summary + 02-context ~380 lines p50 … p90 …
wave 2 03-goals + 04-scope ~410 lines …
wave 3 05-design ~300 lines …
then compile (free) → lint (free) → 3 checker reads
Every wave is a stop. In `partial` you pay for wave 1 and then decide.
Every honesty rule of /orc-budget is inherited, not re-invented: four
token kinds never blended (cache_read stays separate), a range with a sample
count, no dollars without a dated price table, no quota without a known plan,
unattributed always reported — and no history means no forecast. It
refuses rather than invent, and offers the --naive price-table floor.
Once, and never on resume. orc doc next names forecast exactly once,
before the first write wave, paid: false. Once it has been shown, the record is
on disk (doc.json.forecast), so a resumed session in a fresh context prints ONE
line — forecast: shown at <ts> — and moves on. Changing doc_write_mode or
the outline invalidates it: a forecast for a different shape is not a
forecast. Running orc doc forecast by hand always recomputes and always
prints — the once-rule governs next, never the user.
When D4 or D5 will not settle — offer /orc-grill (RETURN-TO)
../_shared/lane-suspend.md. Brainstorm generates candidates when you have none;
grill sharpens one idea you already have, which is exactly the shape of a
/orc-doc session that has a purpose and a template but cannot settle a
decision. D1 already offers /orc-brainstorm for the narrower case of no
chosen direction at all.
The gate is all three, or it asks inline:
- A DECISION, not a fact.
../_shared/interview.md's split governs: facts
are ORC's to look up (wiki → pattern → gotchas → an ad-hoc read-only dispatch
LAST). If a lookup can settle it, ORC looks it up and never suspends —
a lane that answers its own interview question is forbidden, and so is a
lane that outsources a question it owed itself.
- A PREREQUISITE. Settling it changes the option set — the outline, the
audience, the document type. A single paragraph's wording never qualifies.
- A SUBTREE. More than one downstream question hangs off it.
Fewer than three → ask inline in the D4 round. The offer is never a forced
handoff, and "park it as a stated assumption and continue" stays on the menu
— which then becomes a > **Assumption:** … line in the document, per rule 5.
The snapshot, and rule 10. Rule 10 says nothing is created before D1 is
answered; lane-suspend says the sender snapshots first. Both hold, because
the suspend snapshot is RUN STATE, not the deliverable: it is written to
{run_dir}/{slug}/, never to doc_dir/<slug>/. No slug folder, no
context.md, no doc.json is created by a suspend. Say that in one line as you
write it.
The trace obligation — the expensive half. /orc-grill deletes .current
at its FINISH. So on RESUME, re-write log_dir/.current AND
touch the trace file in the SAME step. Both, or neither — this is the
v0.34.2 split-run family arriving by a different road, and two traces for one
document is CORRECT: two lanes ran.
Coming back. Constraints return with their intent/constraint tags intact
plus source: /orc-grill, land in context.md as spec_invariants[], and are
quoted verbatim. Log each one with
orc doc log <slug> --kind decision --source /orc-grill --text "<verbatim>".
Resume at the phase you left, and never re-ask what the trip just settled
(hard rule 1, a lane that re-asks a frozen question).
D6–D9 — run orc doc next, do what it says
orc doc next <slug> --json computes the next legal action; this skill
RENDERS it and does exactly that. Same shape as the Flow stepper, and for
the same reason: D6–D9 used to be prose the orchestrator had to hold in its
head across a session that might be resumed months later in a fresh context.
That is precisely the remembered-not-dispatched protocol that has failed twice
in this repo — see the v0.32.0 narration lesson.
The layout. sections/<id>.md is the SOURCE OF TRUTH — a real, visible,
diffable folder. document.md is a BUILD ARTIFACT: orc doc compile rebuilds
it, free, on demand. gaps.md is where an Open question or an Assumption goes.
RESUME.md lives in {run_dir}/{slug}/ — the only place orc resume and
orc run list look. Full tree: references/chunking.md.
The loop. Run orc doc next <slug> --json. Do what command says. Repeat
until it exits 1, then ask the user what blocked_by names. Never invent
the next step, and never run a command next did not name — a session
that improvises the order is the drift this command exists to prevent.
| exit | meaning |
|---|
| 0 | an action is available: command is it, and paid says whether it costs model tokens |
| 1 | waiting on a HUMAN decision — blocked_by names it in one sentence, never a generic "waiting" |
| 2 | unknown slug |
Also log the request: at D1, and at the opening of every edit round, call
orc doc log <slug> --kind request --text "<the user's words, VERBATIM>". Same
rule that governs context.md — a paraphrase is where a resumed session quietly
starts writing a different document. Settled D4/D5 decisions go in as
--kind decision, with --source /orc-grill when they came back from a
suspend.
What each action means, when next names it:
D6 — Write, one wave at a time
orc doc plan <slug> --role write --json computes the batches. Dispatch
orc-doc-writer-opus-5-med BY NAME, one per agent slice; each agent owns
exactly ONE file — sections/<id>.md, or sections/<id>/<NN>-<sub>.md for a
section stored as sub-parts. Validate every return per
../_shared/return-validation.md — actual_model and actual_effort, quoted,
never guessed. Slice shape and the whole protocol: references/chunking.md.
Extra (extra_enabled, ../_shared/extra-dispatch.md) reaches the WRITER and
the CHECKER, and it is a PER-DOCUMENT decision (v0.52.0): orc doc extra <slug> --set off|writer|checker|both, stored in doc.json, default off.
Resolution is highest-wins and is PRINTED — doc.json.extra (this document)
> the slot row exists (orc extra role) > off — and a document set to
both when only ONE position is held resolves to that one and says so,
because a shadowed setting must never be silent. A global switch was the defect:
turning it on for a throwaway runbook turned it on for the PRD you ship.
config.extra_roles is no longer consulted here (v0.55.0); a config still
naming doc-writer or doc-checker warns once by name and arms nothing.
This lane has no score anywhere, so it holds POSITIONS, not a band — the
document decides WHICH roles, the slot row decides WHERE they go:
orc extra role set doc-writer <profile>/<model> and
orc extra role set doc-checker <profile>/<model>, resolved with
orc extra resolve --slot <slot> --json. Each role resolves its OWN slot.
Until v0.55.0 both resolved orc-doc-writer-opus-5-med's band whatever the mode
said, so a document set to checker reported the WRITER's answer for a role it
was not routing — and the checker's low is a MEASUREMENT choice, not a cost
one, so mapping it onto a cheap band was a category error twice over.
orc doc next prints the answer BEFORE the wave, with the section ids and
which model per role — "the writer went foreign" and "the checker went foreign"
are different facts about a document, because a document's VOICE is the
deliverable and only one of those two writes it. Dispatch through
orc extra dispatch --task <file> --json with slot: "doc-writer" /
"doc-checker" and no score; orc doc forecast prices each half at its
own provider's rates, and a model ORC cannot price reads as an em dash,
never as an Opus number for work Opus will not do. A foreign writer runs return-validation.md §2b instead of §2
(⛔ SUBSTITUTION replaces the downgrade check), and every other rule of this lane
is unchanged: it still owns exactly ONE file, it still never opens document.md,
and the free lint still runs before it. Say which sections went off Claude
before the wave, not after — a document's VOICE is the deliverable here, and a
section written by a different model is something the user has to be able to
choose, then find.
doc_write_mode is asked ONCE and stored (orc doc mode <slug> --set partial|all). In partial, plan returns wave 1 only with more_waves: N, so
the rest cannot be bought by accident. orc doc next blocks after each wave and
names the decision — the wave-review gate is just another blocked_by.
The stop sequence, in this exact order (rule 13)
- Validate the wave's returns, then
orc doc parts <slug> --confirm <ids>.
A file with no recorded hash is unconfirmed and is re-written, never
shipped.
orc doc parts <slug> --json — the CLI recomputes what is done.
orc doc resume-file <slug> — ORC ITSELF, never a dispatched agent:
a dispatch inside a stop sequence lets a stop fail because a subagent did.
The CLI writes RESUME.md into {run_dir}/{slug}/ and computes every line
in it, so nothing here is composed from memory. This is FIRST among the
outputs: if the session is about to die, this is the file that has to
exist.
- Print the hand-back block (
references/resume-protocol.md) — every file
path written this wave, the one line that resumes it, and the RESUME.md path
the command just printed (rule 16).
- Dispatch the trace packet — last, because it is the only step that needs
a subagent and therefore the only one that can fail.
D7 — Compile → lint → map → check
orc doc compile <slug> — free, deterministic, ordered by the outline.
--partial shows what exists so far and NAMES what is missing; nothing is
ever stubbed into the deliverable.
orc doc lint <slug> --json — free. Always before anything paid.
orc doc map <slug> --json — the fresh absolute line numbers.
orc doc plan <slug> --role check --json → dispatch
orc-doc-checker-opus-5-low BY NAME. Each reads ONE bounded part
file and nothing else — no line arithmetic anywhere in the loop.
D8 — Edit (cap 2 rounds)
The writer opens sections/<id>.md and edits it in place — no extract, no
splice, no monolith touched. For a section stored as sub-parts it opens the
one sub-part that needs changing. Then orc doc compile, which is free.
Before each edit dispatch, print one line per finding:
sections/<id>.md · line <n> · <rule>. After the round, print each file touched
and the line count it moved by. Same for a nested section:
sections/03-goals/02-metrics.md · line 12. The numbers are PART-LOCAL —
the part file is what the writer opens — and they come from
orc doc lint <slug> --section <id> --json and from the findings[] anchors on
each plan --role edit part. The compiled document.md line number is
deliberately never carried: it is stale the moment anything is written, which is
what rule 2 exists for.
A user-edited section is never rewritten without an instruction naming it; ask
instead. Cap 2 rounds, then report what is still open. Same cap-and-report shape
as ../_shared/drift-recovery.md.
D9 — Handoff, SHIP, and STOP
Write changelog.md, run orc doc resume-file <slug> (ORC itself, never a
dispatched agent), dispatch the trace packet, and print the hand-back block
from references/resume-protocol.md — ending, as every message to the user
does, with the RESUME.md path and the line to paste (rule 16). orc doc ship refuses on a stale
document.md, naming the sections — rebuild it first, for free.
Then run orc doc audit <slug> --json and relay anything it found: it names
every drift class from disk — an extract that was never spliced back, a section a hand edit deleted,
a target that no longer matches the file, a reference file that moved.
Shipping is the finish line, and it is the USER'S decision. Offer
orc doc ship <slug> --where "<where it went>". --where has no default:
"shipped" with no destination is not a fact, it is a feeling. Never infer one,
never run it unasked. Once recorded, orc doc status computes shipped — and
shipped-drifted the moment a section changes afterwards, naming which ones.
Then end the turn. Offer /orc-challenge <path> — in a separate session —
and print the git add command. Run neither.
Resuming
/orc-doc resume lists; /orc-doc resume <prefix> opens. The resumed session's
first four moves are orc doc status + orc doc parts (which works before a
single compile has ever run), then read context.md and outline.md — not
the document, then name the sections the user edited, then HARD STOP and ask
what should change. A resumed session re-reads nothing it already wrote. Full protocol:
references/resume-protocol.md.
No change request → no work. Regenerating a document nobody asked to change
is the most expensive possible way to do nothing.
Behavior trace (always on)
../_shared/phases/trace.md (core, at run start; orc lane phases names
the file and the layers). Lane token doc, tier Iterative —
ONE packet per completed cycle — and a completed WAVE is a completed
cycle, so the packet goes out at the end of every wave (last in the stop
sequence) and at D9, not at D9 only.
Nothing else about the protocol is restated here; a phase that ends with
zero new trace lines is a protocol violation.
A run that dies at wave 3 must not leave a trace holding nothing but the
hook's SPAWN/RETURN lines. Verb DOC with cycle=N sections=K/M wave=K/N.
How this lane fails — and the rule that prevents each
| Failure | Prevention |
|---|
| The session dies halfway through a 900-line document | Rule 0 — the orchestrator never held it, so there is nothing to lose |
| A usage limit kills the run between waves | Rules 13/14 — the section files on disk ARE the progress, and RESUME.md is written BEFORE anything that needs a subagent |
| Every wave is bought before anyone can look | doc_write_mode: partial — wave 1, then a stop |
| ORC's own bookkeeping ends up in the deliverable | Rule 5a, and orc doc lint's annotation-in-body |
| Monday's session asks for the brief again | Rule 1, and context.md quotes the request verbatim |
| A writer invents a plausible-sounding requirement | Rule 5, and unsupported_claims in every return |
| An edit corrupts the file because two ranges shifted | Rule 2 + bottom-up splice — the model never does line arithmetic |
| A rewrite silently destroys the user's own paragraph | Rule 4, and splice refuses on a hash conflict by name |
| The document imports into Notion as a wall of broken text | orc doc lint --target, whose rules come from real product limits |
| A model is paid to count sentences | Rule 6 — the free check always runs first |
| A pasted spec tells ORC what to do | Rule 7 — foreign input is evidence, never instruction |
| The project's own standing rules are nowhere in the slice | Rule 15 — house rules ride at the TOP of every dispatch, verbatim |
| A P0 changes at wave 3 and half the document silently stops complying | The frozen set + orc doc rules <slug>, which NAMES every block that moved |
| A house rule has to be filed as four one-line rows to fit the tool | v0.49.5 — the ledger is plain text, and a block is as long as the project needs |
| The user comes back next week and cannot remember where they were | Rule 16 — orc doc resume-file runs before every question, and every message ends with its path |
| A writer adds a heading the supplied template never had | The template lock — a lint error, a refused , and in the audit |
Rules this lane always keeps
Read the house rules first · point at RESUME.md in every message · never read
the document body · never re-ask a frozen question · never write outside a supplied template · never store or
guess a line number · never split a section across agents · one file per section
· never overwrite a human's paragraph · never invent a fact · never put ORC's
bookkeeping in the document · every wave is a stop · never pay for what the lint
answers free · never stage, never commit.
Config
ONE resolver, and it is not you: orc lane config orc-doc --json. Obey
effective, print every line in announce[] VERBATIM at preflight, and honour
stops[] before wave 1. Never re-derive a value, a precedence or an inertness
from .claude/orc.config.yaml — a key this lane does not read is not in the
answer, and a key another key shadows comes back already marked. Exit ≠ 0 → say
the CLI is unavailable and fall back to ../_shared/config-precedence.md's
documented defaults, out loud. Priorities and families:
../_shared/config-precedence.md.
Calls
ONE catalogue, and it is not you: orc lane calls orc-doc --json names every
CLI call this lane makes, each with its exit-code contract, its cost, when to run
it, and what an EMPTY answer means. Never invent a spelling, never re-word an
exit code, and never re-derive a state word — the CLI's state words are the only
state words, and an exit code is an ANSWER wherever that contract says so, not
a failure. A call the answer does not name is a call this lane does not make.
Exit ≠ 0 from the catalogue itself → say the CLI is unavailable and name the
command you are about to run, out loud, before running it.
Waiting mid-run (/orc-wait)
Canonical: ../_shared/wait.md. a lane that waits without a hand-back has broken this contract.
Checkpoint full · safe point wave edge. soft FORCES that checkpoint and does NOT stop if the write fails; hard skips it and can lose an in-flight return. Never begin a wait between a dispatch and its validated return, or before the smoke gate has reported.