ADR-140: Standards-doc voting format (warp#1392 Phase 0)
Status: Accepted Date: 2026-05-06 Related: ADR-115 (principle-aware PR reviewers — first read-side consumer), ADR-117 (module guidelines tier — the artefact this format extends), ADR-139 (folder + component layout standards — sibling structural canon, peer pattern), warp#1392 (parent epic — persona-as-employee voting layer), warp#1395 (ON-PERSONAS canonical paper), warp#1474 (Phase 0 ticket — this PR)
Context
The fleet's standards docs are static. ADR-117 declared the module-guidelines tier (dark-factory/guidelines/<module-type>@v<n>.md) as the canonical home for per-module-type rules; ADR-139 extended the same tier to cover folder and component layout. Both are operator-authored, frozen at submission, and revised only through fresh ADRs or version bumps that originate from operator intuition. Agents read them, apply them, and accumulate hundreds of PR-shaped data points about which rules pinch — none of which surfaces back to rule-revision.
Three closed-loop gaps motivate this work, drawn from the operator's 2026-05-05 architecture conversation captured in warp#1392's body:
- No update-from-experience pathway. When a guideline rule contradicts the repo state it governs (e.g. the global CLAUDE.md
e2e/<feature>.spec.tsstamp vs ai-frontend's actualtests/e2e/), the contradiction sits visible to every agent until an operator-authored PR arrives. The agent that noticed it has no structural channel to push back. - No persona track record. The 4 reviewer personas (Priya/Marcus/Lin/Dana, soon Sasha/Quinn) review work but their judgment is one-shot per PR — there is no accumulating record of which persona's argument-shape produced clean merges in which scope. Vote weight is ungrounded.
- No shared-across-fleet structural argument. When agent A in repo X disagrees with a rule, agent B in repo Y has no visibility into the disagreement until the rule changes (or doesn't). The disagreement is lost; the rule outlives its evidence base.
The fleet has already moved in this direction on adjacent axes: warp#1154 mandated concrete file/path references in ticket bodies (anti-bluffing); ADR-115 gave persona reviewers a principle-quoting discipline. This ADR closes the third side of the loop: rules themselves carry structured arguments with bound examples and append-only vote ledgers, so the read-side (Phase 1, brief generator) and write-side (Phase 2, post-merge vote) consume a fixed shape.
The framing per feedback_guidance_vs_structural.md: agents don't remember docs or accumulate reputation; systems do. This ADR moves doc-revision authority from operator-only to substrate-mediated, with structural gates around what counts as a valid argument.
Decision
Standards docs (today at dark-factory/guidelines/<doc>.md@v<n>; target state at gyrum-knowledge-base/standards/<doc>.md per ADR-116 once that substrate ships) carry sections with structured arguments. Each section declares one active_argument (the highest-weighted at read time) and an array of arguments. Each argument carries a fixed schema, a bound example, and an append-only vote_ledger.
The decision lands in eight phases under parent epic warp#1392. Phase 0 (this ADR + the seed smoke-spec-path doc) ships the format. Phases 1-7 (read-side, write-side, retirement, power scoring, knowledge-bank promotion, recusal, /agents page) consume it. Phase 8 (archive) preserves retired arguments. This ADR scopes Phase 0; Phase 1+ stay open under warp#1392.
Argument schema
Every argument MUST declare these fields. Submission without any of them is gate-rejected by the validator (the seed validator stub ships in this PR; full validator hardens in Phase 0b under warp#1392):
| Field | Shape | Notes |
|---|---|---|
id |
A/B/C/... |
Unique within the section. Lexicographic order = submission order. |
proposer |
<persona>/<model_pin> |
E.g. lin/opus-4.7, jon-laptop/operator-seed. The validator checks the slash-shape; the power layer (Phase 4) reads this to apply weights. |
proposed_at |
ISO date YYYY-MM-DD |
Submission date. Decay (Phase 4) reads this. |
proposition |
One sentence, declarative | The rule the argument advances. Present tense. |
rationale |
Markdown paragraph(s) | The why. Cites evidence (PR refs, ADR refs, repo state). |
evidence_pr_refs |
List of <owner>/<repo>#<N> strings |
At least zero entries (for original arguments seeding from operator authority). Phase 1+ arguments cite real PR evidence. |
example |
Object with artefact + diff_snapshot_ref + explanation (all required) |
Hard schema gate. No example, no submission. Bind the rule to a concrete artefact at submission time so reviewers and voters can verify the argument lands somewhere real. |
modification_kind |
One of four declared values (below) | Shapes vote routing and what the argument modifies. |
target |
Sibling argument id (e.g. A) |
Required when modification_kind != original. Names the argument being modified. |
vote_ledger |
Array of vote entries (may be empty) | Append-only. Each entry: {voter, vote: +1/-1, weight, ts, rationale}. Phase 2 writes here. |
The four modification kinds
| Kind | Targets | Shapes |
|---|---|---|
original |
none | The seed argument — the rule as first stated. One per section at minimum; usually operator-authored at v1. |
example-patch |
one original |
The rule is correct; the bound example is wrong or stale. Voting decides whether to swap the example. Rule unchanged. |
counter-example |
one original |
The rule is correct; this example demonstrates a different way to apply it. Both examples may coexist after vote. |
counter-argument |
one original |
The rule itself is contested. Voting decides which proposition wins; the loser's argument retires (Phase 3). |
The kind shapes vote routing because it shapes what the vote means. A +1 on an example-patch says "yes, swap the example"; a +1 on a counter-argument says "yes, the rule is wrong". Phase 2's gyrum-vote-on-docs wrapper presents the right prompt per kind.
Vote ledger entry shape
- voter: <persona>/<model_pin>
vote: +1 # or -1
weight: 1.4 # snapshot of voter's power for this section's scope at vote time
ts: 2026-05-06T09:42:11Z
rationale: "<≥50 chars; mandatory; why this vote>"
scope_match: 0.9 # optional; how well the voter's primary scope matches this section
The ledger is append-only. Phase 2 writes; Phase 3 reads (recomputes weighted totals using current power scores at curator-run time, not stored weights — decay is read-time per warp#1392's Phase 4 spec). A vote entered against an argument that later retires (Phase 3) survives in the archive (Phase 8) but no longer affects live tallies.
Retirement margin and quorum
Phase 3's gyrum-doc-curator retires losing arguments when both conditions hold for a section:
- Quorum: ≥10 weighted-vote total across all arguments in the section. Below this, no retirement fires regardless of margin.
- Margin: ≥+5 between the top-1 and top-2 arguments by weighted total.
The +5 margin is a structural number, not a tunable: small enough to fire on real consensus, large enough that a 3-vote drift does not retire a contested rule. Sections with only one argument that hits ≥10 weighted votes ≥+5 enter "consensus-locked" state and Phase 5 (knowledge-bank promotion) fires for the bound example.
Hybrid bootstrap
Phase 4's power-scoring storage seeds with operator-declared rows (e.g. lin/opus-4.7 / go-handler / 20). Retirement does NOT fire from day zero — it unlocks once ≥10 (persona, model) tuples have ≥3 in-scope pr_merged_clean events each. This prevents Phase 3 from acting on bootstrap-only weights; the power layer must observe real work before it gets to retire rules.
Until the unlock condition is met, the vote ledger accumulates but no retirement fires. Phase 1's brief generator (and Phase 2's vote write-side) work from day zero — only the retirement loop is gated on bootstrap maturity.
Promotion to knowledge-bank
When a section reaches consensus-locked (one argument, ≥+5, ≥10 votes), Phase 5 promotes the bound example:
- Source: the argument's
example.artefactpath - Target:
gyrum-knowledge-base/examples/<rule-id>/<example-N>.md(per ADR-116's indexing contract; symlink/redirect from current dark-factory path until ADR-116 substrate is live) - Auto-refresh: re-run example against current codebase, regenerate diff snapshot, update line numbers
- Indexing: the brief generator (Phase 1) attaches the closest-matching example to dispatched-agent context when scope tags match
The promotion is structural — Phase 5 fires automatically, not by operator decision. The +5 retirement margin == +5 promotion margin symmetry is intentional: the same evidence that retires losers crowns winners.
Reconciliation policy
When stamped guidance (CLAUDE.md prose, agent-brief templates) disagrees with a published voting-format doc, the doc wins. Same shape as ADR-139's reconciliation policy. The stamped prose is updated in a follow-up PR to either (a) cross-reference the doc, or (b) be removed. Stamped prose is never the source of truth once a voting-format doc exists for the rule.
Validator stub
scripts/validate-voting-doc.sh ships in this PR as a minimal validator: checks each argument has the required fields, validates modification_kind ∈ {original, example-patch, counter-example, counter-argument}, validates proposer matches the <persona>/<model_pin> shape, and validates target is present when modification_kind != original. Full validator (schema-shape gates on example.* fields, vote-ledger entry shape, append-only enforcement) is Phase 0b under warp#1392.
The validator's exit contract matches validate-projects.sh: exit 0 + summary on stdout when valid; exit 1 + per-error list on stderr when invalid; exit 2 on setup failure.
Consequences
What becomes easier.
- Standards docs gain a fixed wire-shape that Phases 1/2/3/5 of warp#1392 can consume without per-doc parser variance. Brief generators read
active_argument; vote wrappers append tovote_ledger; curator computes weighted totals overarguments[].vote_ledger[]. The format IS the protocol. - Disagreement becomes structural. An agent that finds a rule pinches files a
counter-argumentwith a bound example; the disagreement accumulates votes; if the evidence is real, the rule retires. The path from "this rule is wrong" to "the rule changed" is a wire format, not an operator interrupt. - Bound examples crowd out folklore. Every argument cites a concrete artefact; reviewers and voters verify against the artefact, not the prose. The
e2e/vstests/e2e/contradiction (the seed doc's demonstrator) loses its ability to recur silently — the active argument names the path AND the canonical example, both checkable. - Persona competence per scope becomes measurable. Phase 4's
agent_powerreadsproposerandvoterfields;vote_aligned_with_outcomeevents (Phase 2) accumulate per(persona, model_pin, scope)tuple. Routing decisions (Phase 1's brief generator) read these scores. Hard work routes to highest-score(persona, opus); observer work routes to highest-score(persona, haiku).
What becomes harder.
- Argument submission carries a non-trivial cost: rationale ≥50 chars, bound example with
artefact+diff_snapshot_ref+explanation,evidence_pr_refsfor non-originalkinds. This is intentional — bluffed arguments are the failure mode. Bluff cost == real-argument cost is the wrong shape; the validator gates the structure and Phase 2's prompt enforces the rationale length. - Two surfaces declare the canonical state during Phase 0-1 transition: the voting-format doc (canon) and the still-stamped CLAUDE.md prose (legacy). The reconciliation policy above is the rule, but the transition window allows both surfaces to exist; reviewers must resolve in favour of the doc.
- Schema migration discipline.
@v1→@v2of a voting-format doc is a new immutable file; the previous version stays so in-flight cites and in-flight argument submissions resolve to the schema they were written against. Fleet-wide migration of in-flight votes is a Phase 0b concern (separate ticket if needed). Hand-editing@v1is not a path. - The validator stub is intentionally minimal in this PR. Phase 0b hardens it (full schema-shape gates on
example.*fields, vote-ledger entry shape, append-only enforcement). Until Phase 0b lands, malformed submissions can pass the stub — Phase 1/2 consumers must defensive-parse, not assume validity.
What we sign up to maintain.
- The
dark-factory/guidelines/directory grows with voting-format docs. Each new doc is a new file at<topic>.md@v<n>; bumps mint immutable new files. The convention matches ADR-117 and ADR-139. - The validator stub at
scripts/validate-voting-doc.sh. Phase 0b owners will harden it; until then, the stub catches the most common shape failures and is wired into PR review per ADR-115's principle-quoting flow. - The format-version cite discipline in PR bodies (
Voting-format: v1). Phase 1's brief generator and Phase 2's vote wrapper anchor on the cite; missing cite is a reviewer-visible finding (same shape asGuideline: <module-type>@v<n>per ADR-117). - Reconciliation backlog: stamped CLAUDE.md prose duplicating a voting-format doc's rule is migrated to a cross-reference under warp#1388's feedback loop. Each new voting-format doc triggers a small reconciliation ticket.
Alternatives considered
- Keep operator-authored revisions only; no voting layer. Rejected: the failure mode this ADR is reacting to. Operator-only revision means hundreds of PR-shaped data points about rule-pinch never surface; rules outlive their evidence. The cost of the voting substrate is bounded (one schema, one validator, one wrapper per phase); the cost of folklore-bound rules is unbounded as the fleet grows.
- Free-text comments instead of structured arguments. Rejected: free-text is what stamped CLAUDE.md prose already is. Voting on prose is what the operator already does (ad-hoc, in-session). The structural value of this format is the schema; without the schema, Phases 1/2/3 cannot consume a fixed shape and the brief generator is back to grep-the-prose.
- Three modification kinds (skip
counter-example). Rejected:counter-exampleis the load-bearing kind for "the rule is right but applies differently in repo X" — a common shape (warp#1385's per-repo-overrides discussion). Without it, repo X's evidence collapses into eitherexample-patch(which says the original is wrong) orcounter-argument(which says the rule is wrong); both misframe the disagreement. - Five modification kinds (add
clarificationfor prose-only edits). Rejected by Occam's razor: prose-only edits are rule-version bumps under ADR-117's existing bumping policy, not vote-shaped. Adding a kind doubles the prompt cost in Phase 2 for a case that already has a structural home. - One example per section, not one per argument. Rejected: the
example-patchandcounter-examplekinds rest on per-argument examples. A section-level example collapses the kinds into one ("the example is wrong") and loses the rule-vs-application distinction that motivates the four-kind taxonomy. - Quorum threshold of 5 (instead of 10). Rejected: the operator's 2026-05-05 sketch named ≥10 explicitly. A 5-threshold lets a single 4-vote cluster drive retirement; the +5 margin alone is not enough when the absolute count is tiny. The 10-threshold is the structural floor that makes the margin mean what it says.
- Retirement margin of +3 (instead of +5). Rejected: the operator's sketch named +5. A +3 margin retires arguments on noise; +5 requires real consensus. The symmetry with the +5 promotion margin (Phase 5) is intentional — the same evidence retires losers and crowns winners.
- Store snapshot weights in the vote ledger; do not recompute. Rejected: the operator's spec named recompute-always. Snapshot weights age as power scores drift; a stale snapshot from 90 days ago shouldn't drive today's retirement. Recompute-with-decay-at-read-time is the structural shape; the curator is the consumer.
- Auto-fire the brief-generator and vote-wrapper from Phase 0. Rejected: scope. Phase 0 ships the format and one seed doc; consumers come in Phase 1/2. Coupling them collapses the phasing and ships an untested 3-phase substrate at once. Single-PR-per-phase is the warp#1418 pattern proven yesterday.
Migration to gyrum-knowledge-base
Per ADR-116, the gyrum-knowledge-base RAG service is proposed — not yet shipped. ADR-117 and ADR-139 already specify guidelines live at dark-factory/guidelines/ today. When the gyrum-knowledge-base service ships and ADR-116 transitions to accepted, the canonical voting-format-doc path migrates to gyrum-knowledge-base/standards/<doc>.md per ADR-116's indexing contract, and the dark-factory copies become symlinks or redirects. The migration is tracked separately under ADR-116; this ADR follows ADR-117/ADR-139's current path until the gyrum-knowledge-base substrate is live.
References
- ADR-115 — principle-aware PR reviewers (first read-side consumer of the version-cite flow)
- ADR-116 — gyrum-knowledge-base RAG service (proposed; future canonical home)
- ADR-117 — module guidelines tier (the artefact this format extends)
- ADR-139 — folder + component layout standards (sibling structural canon, peer pattern)
- warp#974 — first-use smoke gate (the rule whose contradiction motivated the seed doc)
- warp#1385 — EPIC: folder + component layout standards (sibling epic)
- warp#1388 — living-standards feedback loop (Phase 1+ dispatch surface)
- warp#1392 — EPIC: persona-as-employee voting layer (parent of this ADR)
- warp#1395 — ON-PERSONAS canonical paper (architectural framing)
- warp#1418 — Phase 0 of warp#1385 (the layout-standards Phase 0; pattern this PR mirrors)
- warp#1474 — Phase 0 ticket (this PR)
Supersedes: none Superseded by: none yet