- name
- write-edition
- description
- Execute the edition from sift output. Launch reporters, review articles, compile, validate, Mara audit, publish. Mechanical when sift is right.
- version
- 2.7
- updated
- 2026-07-11T00:00:00.000Z
- tags
- ["media","active"]
- effort
- high
- disable-model-invocation
- true
- argument-hint
- [cycle-number]
- related_skills
- ["sift","post-publish","style-pass","capability-review","skill-check"]
- sources
- ["docs/EDITION_PIPELINE.md","docs/media/story_evaluation.md","docs/media/brief_template.md","docs/media/EDITION_FORMAT_TEMPLATE.txt"]
# /write-edition — Edition Execution
## Usage
`/write-edition [cycle-number]`
## The Principle
Sift did the editorial work — stories picked, reporters assigned, citizens verified, briefs on disk. This skill executes. Launch reporters, review what they write, compile the edition, validate, get Mara's approval, publish.
If sift is right, this is mechanical.
## Prerequisites
Verify these exist before starting:
- `output/dispatch_c{XX}.json` — from `/sift` — the mechanical launch spec. Per-article entries carry `{slot, section, briefFile, reporter, desk, headline, outputPath, voiceDirective, spine, threeLayerKeys, initsTouched}`; plus `letters[]`, `quickTakes[]` (QT entries may have `reporter: null`), and header fields (`cycle, edition, generatedAt, slateLockedBy, ...`). This file is the source of truth for Step 1 — launch from it directly.
- `output/reporters/{reporter}/c{XX}_{SLOT}_brief.md` — per-slot angle briefs from `/sift` (path is in each dispatch entry's `briefFile`; QT briefs live at `output/quick-takes/c{XX}_{SLOT}_brief.md`). **Naming is per-slot, not per-reporter** (G-W50 — the old `c{XX}_brief.md` per-reporter form was skill drift; a reporter with two slots has two briefs).
- `output/production_log_c{XX}.md` — from `/sift` (story picks, assignments, citizen table). S225 pipeline.23 consolidated edition-flow to this canonical S195 path; pipeline.32 (S248) folded the civic side in too — civic decisions now live in the `## /city-hall` section of this same unified log, not a separate `production_log_city_hall` file.
If `dispatch_c{XX}.json` is missing, `/sift` didn't complete its slate lock. Don't proceed.
## Step 0: Bootstrap production log if missing (G-W47)
If `output/production_log_c{XX}.md` is absent but `dispatch_c{XX}.json` is present, bootstrap the log from the dispatch header rather than halting — `/sift` produces the dispatch spec even on runs where the consolidated log wasn't written (pipeline.32 unified-path convention). Seed it with cycle/edition/generatedAt + the slate (one line per `articles[]`/`letters[]`/`quickTakes[]` entry: `slot | section | reporter | headline`), then continue. If BOTH are missing, `/sift` didn't run — stop.
## Step 0.5: Mags' page recall (read-only EIC conditioning) — pipe.40 T5
Before launching reporters, recall what Mags has been developing on her citizen page (POP-00005) — the inner life she brings to compiling this edition:
```bash
node scripts/magsPageRecall.js --cycle={XX} --context="<dispatch slate summary: the headlines/sections from dispatch_c{XX}.json>"
```
- **Read-only — NO `--mark`** (that's /sift's job; write-edition never rotates staleness and never appends to her page). Output is a memory-fenced block; it conditions editorial judgment during compile, never quoted as fact, never enters an article as canon.
- Fail-open: empty/failure → empty output, exit 0, continue. A missing block never blocks the edition.
## Step 0.7: Citizen quote-supply pre-pass (pipeline.43)
Every citizen quote in the edition should be the citizen actually speaking — one cheap `citizenVoice.js` call carrying their dials, bonds, tensions, and page memory — not a desk model inventing their voice. Runs in the main session (desk agents have no Bash), after the dispatch read, before any Step 1 launch.
1. **Build the batch file.** For each `dispatch.articles[]` entry, read its `briefFile` and collect the verified citizens attached to the story (POP ids from the brief's citizen section). One batch entry per citizen in `output/voices/voices_batch_c{XX}.json`:
`{"pop": "POP-NNNNN", "ask": "The Tribune is writing about <spine / headline angle>. What's your honest take, as someone living it? <citizen's context line from the brief>", "record": true}`
Ask wording is a starting value — tune against live quote quality (plan §Open questions).
2. **Letters ride the same pass (pipeline.43 Task 4).** For each `dispatch.letters[]` entry whose writer is a ledger citizen (has a POP id), add a batch entry whose ask is the letter seed: `"Write to the Tribune about <topic> — what would you actually say?"` Letters tolerate longer output — pass `--max-tokens` accordingly. Generic invented writers (S227 letters-texture exception) get no batch entry.
3. **Run the batch:** `node scripts/citizenVoice.js --batch=output/voices/voices_batch_c{XX}.json > output/voices/voices_c{XX}.json`. One output entry per citizen: `{pop, name, quote, disp, recorded, fallback}`. A `fallback` non-null entry (no DialState / call failure) means no supplied line for that citizen — explicit marker, never a silent gap. `--record` writes the *speaking* to the citizen's page (`daypart='PRESS'`) plus one gated `Reflection_Intake` row (`applied='no'`) at speak time; dials still apply only at the cycle drain. A fetched quote cut in editing still happened — page/intake record the speaking, publication records the printing.
4. **Log cost to the production log:** call count + token totals from stderr (expect ~10–25 DeepSeek calls per edition — pennies).
5. **Availability gate:** if `citizenVoice.js` doesn't support `--batch`/`--record` yet (engine-sheet pipeline.43 T1–2 pending), log `voice packet unavailable` in the production log and skip this step — desks fall back to the S227 quote rules for all citizens.
## Rules
- **Brief-led mode is canonical (S215, closes G-W4).** When a desk agent is launched by `/write-edition`, the agent's job is: read brief + IDENTITY.md, write. The desk SKILL.md boot sequences (LENS + RULES + workspace + voice files) are heavier than the brief-led model needs. The agents have been trained to short-circuit boot under brief-led invocation; this skill's rule overrides desk SKILL.md when there's conflict.
- **Reporters read their brief + IDENTITY.md. Nothing else.** No world summary, no city-hall log, no sheet queries.
- **No calendar dates.** Cycles, not months, not years (except past-event canon citations like "2032 MVP" or "2041 roster"). Day-of-week names (Monday/Tuesday/Wednesday) ARE allowed as scene texture (S227, closes G-W36) — the simulation has a 7-day week even if cycles span multiple weeks, and day-of-week reads as the kind of texture S208 anti-cookie-cutter discipline wants. "Tuesday morning light through stained glass" works; "October 17th" does not.
- **No engine language.** No "cycle weight," "civic load," "sentiment score," "domain count." Citizens don't know these terms. Weather is "cool evening, northwest breeze" not "Weather: 67F from engine."
- **Story-driven layout.** No fixed sections to fill. No filler.
- **Every citizen name was verified in sift.** If a reporter introduces a new name not in the brief, flag it. **Letters texture exception (S227, closes G-W40):** letter writers may name generic neighbors (Mrs. Chen, the guy who runs the bodega) and small new local businesses without prior sift verification — letters are voice-from-the-stoop and strict pre-clearance kills the texture. BUT any new business named in a letter must be promoted to BIZ-NEW in NAMES INDEX / BUSINESSES NAMED for ingest, and any new citizen named with enough specificity (full name + role + neighborhood) lands in NAMES INDEX em-dash form for POP-pending promotion.
- **Reporter-range quote invention (S227, closes G-W37) — DEMOTED to fallback by pipeline.43.** Supplied voice-packet lines (Step 0.7) are the primary quote source; in-scene invention now applies ONLY to (a) citizens with a `fallback` marker in the voice packet, (b) scene extras never attached to the story, or (c) cycles where the packet is unavailable (Step 0.7 availability gate). Within that scope the S227 craft rule is unchanged: when a beat reporter is "at the scene" of a covered moment, inventing direct attributed quotes from citizens present is within craft; a citizen referenced but not in-scene gets paraphrase only — no fabricated direct quotes. Capability + Mara graders should distinguish in-scene quote invention (allowed craft, fallback-scope only) from off-scene attribution (fabrication) and from paraphrase-then-attribute of a supplied line (also fabrication).
- **Output path (S215, closes G-W5).** Per-reporter path is canonical: `output/reporters/{reporter}/articles/c{XX}_{slug}.md`. Disregard any desk SKILL.md instruction to write to `output/desk-output/{desk}_c{XX}.md` — that's the legacy desk-packet path, retained only as historical reference. Single source: per-reporter, matches sift output structure.
- **Memory Fence (Phase 40.6 Layer 2).** The brief file handed to each reporter carries recalled canon excerpts from sift. Those excerpts must be wrapped via `require('/root/GodWorld/lib/memoryFence').wrap(text, 'bay-tribune')` before being embedded in the brief, so the reporter model receives them tagged as data, not instructions. Full convention: [[SUPERMEMORY]] §Memory Fence.
- **Context Scan (Phase 40.6 Layer 4).** Before Step 1 launches any reporter agent, scan the brief file with `require('/root/GodWorld/lib/contextScan').scanFile(briefPath)`. If `r.safe === false`, abort the launch and surface `r.matches` to Mags. Blocks are logged to `output/injection_blocks.log`. Never run a reporter against a flagged brief.
## Step 1: Launch Reporter Agents
Read the production log for assignments. Launch each reporter with direct editorial direction.
**Prompt structure per reporter — fully mechanical from the dispatch entry (G-W51, G-W52).** Every field below is a literal `dispatch.articles[i]` value. **Pass the brief PATH, never paste brief content** — the agent reads its own brief; pasting it doubles tokens and invites the agent to treat recalled-canon excerpts as instructions (defeats the Memory Fence).
```
You are {reporter}. Brief-led mode.
ARTICLE — {headline}
Read your brief at {briefFile}. Read your IDENTITY.md.
Editorial direction: {voiceDirective}
Use ONLY citizens named in the brief. Write to {outputPath}.
QUOTE DISCIPLINE: citizens listed under SUPPLIED CITIZEN LINES below speak in their own words — quote those lines verbatim, or trim from the front/back; never paraphrase-then-attribute, never rewrite wording inside a supplied line. For any citizen WITHOUT a supplied line, attribute a direct quote ONLY when the verbatim line is supplied in your brief (or the packet it cites). If the brief says "VERIFY before quoting," that is a HARD STOP — no quote unless the exact filed statement is in hand; otherwise paraphrase the action. A synthesized attributed quote is the highest-severity fabrication class.
SUPPLIED CITIZEN LINES (their own words, fetched from their real state):
{POP-NNNNN | Name | "quote"} ← one row per non-fallback voice-packet entry for THIS article's citizens; omit the block entirely if the packet is unavailable or all entries are fallbacks
Do NOT read other files — write.
```
**Voice-packet injection (pipeline.43).** The SUPPLIED CITIZEN LINES rows come from `output/voices/voices_c{XX}.json` (Step 0.7) — filter to the citizens attached to this article's brief, skip `fallback` entries. Fallback-marked citizens are covered by the in-scene invention rules (§Rules), same as before the packet existed.
Launch via the `{desk}` agent (the dispatch entry names it). `{reporter}` + `{briefFile}` + `{outputPath}` come straight off the entry; no lookups.
**Why the QUOTE DISCIPLINE line is in the launch prompt, not desk RULES (RB-1, C99 G-W2).** Under brief-led mode the agent reads only its brief + IDENTITY.md — desk RULES.md is trimmed out (see §Rules) — so a quote gate placed in RULES would never fire. The launch prompt is the one channel every reporter receives, so the universal gate lives here. C99: Jordan Velez ran a synthesized quote *"These should have been on the record two cycles ago"* attributed to Keisha Ramos despite the B1 brief's explicit *"VERIFY before quoting"* gate; the actual filed line was *"The process is clean — and now it's documented."* Step 2 Pass 2 (below) is the editor-side backstop.
**Unnamed-reporter QT sub-template (G-W49).** Quick-Take entries in `dispatch.quickTakes[]` may carry `reporter: null` (no beat reporter assigned — the QT is a compile-time short piece). Do NOT invent a reporter name. Launch the `{desk}` agent with:
```
You are the {desk} desk. Brief-led mode.
ARTICLE — {headline} (Quick Take, short)
Read the brief at {briefFile}. Write a tight short piece to {outputPath}.
Byline: "By Bay Tribune {Section}" (no individual reporter). Do NOT read other files — write.
```
If a QT has no `briefFile` or source material, it is a compile-time drop — see Step 3 QUICK TAKES handling, don't launch a placeholder.
**The E90 lesson:** Agents told what to write produce articles. Agents told to figure it out spend all their tokens reading files and produce nothing.
**Launch order (S215, closes G-W1 — rule, not named list):**
1. **Sports first** — assigned sports reporters (typically Anthony / Hal / P Slayer) launch first; sports articles are the lowest editorial-judgment work and stabilize fastest.
2. **Civic / business / culture in parallel** — assigned reporters across these beats launch concurrently. No serial ordering required.
3. **Conditional beats** — accountability (Jax) + health (Mezran) launch when assigned by sift.
4. **Letters LAST** — letters react to the edition's topics, so the slate needs to know what shipped. **Supplied-writer rule (pipeline.43 Task 4):** a letter whose writer has a non-fallback voice-packet entry (Step 0.7 step 2) uses that voiced text as the letter's *substance* — the letters desk formats (salutation, trim, house style) but may not author substance for supplied writers. Include the writer's voiced text in the letters-desk launch prompt as a SUPPLIED CITIZEN LINES row. Generic invented writers (S227 letters-texture exception) are unaffected.
5. **EIC-written sections — no agent launch (S227, closes G-W34).** EDITOR'S DESK + any QUICK TAKE that survived Step 3 routing are written by Mags at Step 3 compile time, not launched as desk agents. The slate may carry them as proposals; treat them as Mags's compile-time work, not Task-tool launches. Future-cold-read of this skill should not try to launch a "mags-corliss" desk agent — there isn't one.
Don't hard-code reporter names in this skill — sift's assignment table is the source of truth. Reporters not assigned this cycle don't launch.
**Reporter→desk-agent routing (S215, closes G-W2 + G-W3 + G-W9).** Reporter briefs come from `/sift` as per-reporter files, but the Task-tool agent catalog only exposes **desk agents** (civic-desk, sports-desk, culture-desk, business-desk, letters-desk, freelance-firebrand, podcast-desk, chicago-desk) — not individual reporters. The routing table that maps reporter → desk agent lives at `.claude/agents/REPORTER_DESK_INDEX.md` (single source of truth).
Sift may also write `output/dispatch_c{XX}.json` with the mechanical mapping `{reporter, story, desk_agent, brief_path, output_path, voice_directive}` per launch — when present, read it and launch from it directly (skips the index lookup). When absent, fall back to `REPORTER_DESK_INDEX.md`.
**Beat-axis routing** — some reporters cover multiple beats and need beat-conditional routing:
- Jax Caldera: nightlife/culture → `culture-desk`; accountability → `freelance-firebrand`
- (Add others as they emerge.)
Beat is editor-judgment per launch unless the dispatch.json names the agent. When in doubt: brief topic + reporter IDENTITY.md decide.
**Firebrand boot trim (S215, closes G-W11).** Freelance-firebrand IDENTITY/RULES/SKILL chain is heavier than the brief-led model needs. Under brief-led invocation, the agent self-trims to "read brief + IDENTITY, write." Same trim recommendation as G-W4 — desk and conditional agents alike.
**Output:** Each reporter writes to the `outputPath` named in its dispatch entry (`output/reporters/{reporter}/articles/c{XX}_{SLOT}.md`; QTs to `output/quick-takes/c{XX}_{SLOT}.md`).
**Mandatory dispatch-result table (G-W61).** After all launches complete, write one row per dispatch entry (`articles[]` + `letters[]` + `quickTakes[]` — every slot, no exceptions) to the production log:
```
| Slot | Desk Agent | Dispatch ID | Output Path | File Exists | Words | Status |
|------|-----------|-------------|-------------|-------------|-------|--------|
```
`File Exists` is checked at completion time (`outputPath` present on disk, non-empty). A slot that dispatched but produced no file is **DROPPED** — record it with the reason (e.g., `agent returned empty`, `session-limit kill` — the S231 G-S2 signature: `<total_tokens>0</total_tokens>` + ~500ms duration + session-limit string). Never let a slot vanish silently: C95 lost three culture pieces this way. Step 3 compile reconciles against this table — a slot with `File Exists = no` must appear in compile's dropped-slot report, not just disappear.
## Step 2: Two-Pass Review (G-W63)
**Pass 1 — completeness (mechanical).** Before reading for quality, reconcile coverage against the dispatch spec. Walk every `dispatch.articles[]` + `dispatch.letters[]` + `dispatch.quickTakes[]` slot and the Step 1 dispatch-result table; confirm each slot either (a) produced a file that will be placed in a section, or (b) is an explicit, reason-logged drop. A slot that is neither is a silent loss — recover the file or re-launch it before compiling. No slot leaves Step 2 unaccounted for. This pass is what makes the G-W61 culture-drop class structurally visible instead of discovered at print.
**Pass 2 — quality + framing.** Mags reads every surviving article. Not a scan — a read. Check:
- Did the agent follow the angle brief?
- Are citizen names correct? (verify any you're unsure of via MCP)
- Does the voice match the reporter?
- Any fabricated facts, stats, game results?
- **Any direct attributed quote not traceable to the voice packet or brief? (RB-1, G-W2 + pipeline.43)** Check every attributed quote against `output/voices/voices_c{XX}.json` FIRST (verbatim or front/back-trim match for packet-supplied citizens — a paraphrase attributed as their words is fabrication even if the meaning holds), the brief's supplied statement second. If the brief said "VERIFY before quoting" and the reporter quoted anyway, cut or paraphrase. In-scene reporter-range quote invention (§Rules, fallback-scope only) is allowed craft; off-scene synthesized attribution to a named civic figure is not.
- Any calendar dates that should be cycle references?
- Any engine language?
- Any names not in the brief? (hallucination flag)
**Two-pass canon verification order (S215, closes G-W13).** When verifying any cycle-current stat or fact (player batting line, council member name, initiative dollar figure), read **`output/world_summary_c{XX}.md` FIRST** — cycle-current ground truth lives there. Only if the world summary is silent on the fact should you grep `.claude/agents/civic-office-*/` files (career-canon roster lines) or `docs/media/2041_athletics_roster.md` (player career baseline). C93 lesson (G-W13): Aitken's `.243 / 3 HR / 11 RBI` line was flagged as fabricated because the verification order pulled career-baseline first; the world_summary carried the cycle-current line exactly. Cycle-stats live in world_summary; career-stats live in roster docs; both true at different time horizons.
Flag problems. Fix what's fixable. Cut what's broken. Better to have 8 clean articles than 13 with canon violations.
**Update production log** with editorial review (articles passed, cut, fixes applied).
## Step 3: Compile
The edition is story-driven, not section-driven.
1. Take all articles that passed review
2. Order by what's most worth reading — front page was picked in sift, but may change if an article exceeded expectations
3. Label each with its section tag from sift assignments
4. If a section has no story this cycle, it doesn't appear
Ver no GitHub