From 4bc473743330ac9b060732f859f129db3ce6dec1 Mon Sep 17 00:00:00 2001 From: Chris Guidry Date: Mon, 3 Aug 2026 17:53:15 -0400 Subject: [PATCH] Plan the Archivist and the aside, and catch plan 0011 up to the code Three actors now share the world's files: the DM writes the record, the Archivist curates the notes, and the Weaver will only read. Plan 0012 designs the Archivist: entity notes with no required sections, addresses that resolve forever through aliases, a cursor that trails the record, wikilink backfill behind a render-equivalence guard, and a session brief the next session starts from. Plan 0013 gives the DM a private channel: the aside tool commits secret canon to the transcript, because a secret that lives only in thinking tokens drifts, dies at session end, and cannot be proven fair. Plan 0011 now matches the shipped code: the wikilink examples show their syntax again, rendering is checked off, the establish tool is gone, and stubs are seeded notes instead of empty sections. The roadmap orders the new phases: behind the screen, then the Archivist, then the Weaver. Co-Authored-By: Claude Fable 5 --- plans/0000-roadmap.md | 36 +++- plans/0011-session-state.md | 102 ++++++----- plans/0012-the-archivist.md | 337 ++++++++++++++++++++++++++++++++++++ plans/0013-the-aside.md | 126 ++++++++++++++ 4 files changed, 551 insertions(+), 50 deletions(-) create mode 100644 plans/0012-the-archivist.md create mode 100644 plans/0013-the-aside.md diff --git a/plans/0000-roadmap.md b/plans/0000-roadmap.md index 7ad6c91..e18288f 100644 --- a/plans/0000-roadmap.md +++ b/plans/0000-roadmap.md @@ -117,10 +117,30 @@ the real design happens. The blurbs here stay deliberately loose. - [ ] **Phase 10: Persistence.** Character sheet, world entities, and worlds that live somewhere real on disk. This phase decides the world root, the one-line decision three plans have stepped around. A fresh - session's first prompt carries a recap built from the campaign log, so - the DM comes back remembering the story and not just the clock. *You + session's first prompt carries the session brief and the replayed tail + of the transcript, so the DM comes back remembering the story and not + just the clock. *You can now: your character persists across sessions.* -- [ ] **Phase 11: The DM's storytelling voice.** Instructions that teach +- [ ] **Phase 11: Behind the screen.** The `aside` tool: the DM commits + secret canon to the transcript in a fenced block the player never + sees. Secrets must be written, because a secret that lives only in + the model's thinking drifts, dies at session end, and cannot be + proven fair. Replay routes asides back to the DM alone, and the + scanner reads them, so secret entities become stubs too. *You can + now: lose to a mystery whose answer was on disk the whole time.* + ([0013](0013-the-aside.md)) +- [ ] **Phase 12: The Archivist.** A background agent that trails the + transcript and tends the world's files. The record (transcript and + campaign log) stays append-only and belongs to the session; the notes + (entity files) are the Archivist's to write, rewrite, and cross-link. + It turns stubs into briefings, backfills wikilinks so late-named + entities get their history retroactively, discovers aliases, and + merges duplicates, and ends each pass by rewriting `session.md`, the + brief the next session starts from. The only thing it may do to the + record is point at the notes. *You can now: come back tomorrow and + find the innkeeper's page written up and a brief for where you left + off.* ([0012](0012-the-archivist.md)) +- [ ] **Phase 13: The DM's storytelling voice.** Instructions that teach the DM craft, not just rules, drawn from real storytelling theory: cadence and rhythm (short beats when danger is close, longer lines when the world is calm), scene shape (arrive late, leave early, open @@ -131,7 +151,15 @@ the real design happens. The blurbs here stay deliberately loose. the base game sets the craft and a world or module tunes the voice. *You can now: feel the difference between a bar fight and a quiet morning.* -- [ ] **Phase 12 and beyond: ideas that have to earn it.** Character +- [ ] **Phase 14: The Weaver.** A background agent ahead of the story + where the Archivist is behind it. It reads the record and the notes, + follows the threads the table left hanging, and drafts future + possibilities: hooks, encounters, consequences already on their way. + It writes only to its own prep area, never to the notes, so what is + merely intended never reads as canon. Deliberately loose; its real + design starts after the Archivist has run for a while. *You can now: + walk into a plot that was waiting for you.* +- [ ] **Phase 15 and beyond: ideas that have to earn it.** Character creation, combat and initiative, background world motion, advancement, name generation, colors and theming (the styles all live in `markdown::style` waiting for it). Each one starts as a conversation, diff --git a/plans/0011-session-state.md b/plans/0011-session-state.md index 32c5d83..fbb153c 100644 --- a/plans/0011-session-state.md +++ b/plans/0011-session-state.md @@ -33,17 +33,17 @@ different channel. When the DM narrates, it uses wikilinks to name entities: ``` -Vera Blackwater +[[Vera Blackwater|the old assassin]] ``` -The DM writes `Vera Blackwater` in the narration, the engine -sees `Vera Blackwater` as the canonical name, resolves it to the entity -file, and injects that entity's `## Is` section (description, knows, -wants, will) into the next turn's prompt. The alias direction matters: -the part before the pipe is always the canonical address, so there is no -ambiguity about which entity to pull. +The DM writes `[[Vera Blackwater|the old assassin]]` in the narration, +the engine sees `Vera Blackwater` as the canonical name, resolves it to +the entity file, and injects that entity's note into the next turn's +prompt (the note format is [0012](0012-the-archivist.md)'s). The alias +direction matters: the part before the pipe is always the canonical +address, so there is no ambiguity about which entity to pull. -If the DM writes `Vera` without a pipe, the engine tries `Vera` as a +If the DM writes `[[Vera]]` without a pipe, the engine tries `Vera` as a canonical name. For now a reference resolves only when it exactly matches a known canonical name; a best-match that tolerates abbreviated names, and the ambiguity it must guard against, is deferred until stubs are @@ -55,38 +55,44 @@ When the DM uses a wikilink that does not resolve to any existing entity, the engine creates a stub: ``` -worlds/{world}/entities/{canonical-name}.md +{world-root}/entities/{canonical-name}.md ``` -The stub contains only a name and two empty sections: - -```markdown -# Vera Blackwater - -## Is - +(Where the world root lives on disk is phase 10's decision.) +The stub is born as a small note: frontmatter for the machines, and the +narration that summoned it as its first prose. The note format is +decided in [0012](0012-the-archivist.md). -## Was - +```markdown +--- +kind: unknown +aliases: [] +stub: true +--- + +First seen #d1-1830: "behind the bar, Vera Blackwater watches the +door without appearing to." ``` -The stub goes into the current turn's context (it adds only the name, -which is not much) and the entity is registered so future wikilinks -resolve to it. The DM can later call `establish` to fill in description, -knows, wants, will, which also moves the entity from the flat -`entities/` namespace into the typed directory (`npcs/`, `locations/`, -`items/`, etc.). +The shipped scanner predates this format: stubs on disk today still +carry `## Is` / `## Was` sections and a `## Mentions` stamp. They move +to this shape with 0012's first build step. + +The stub joins the turn's referenced set, so step 3's injection will +carry it into the next prompt, and the entity is registered so future +wikilinks resolve to it. There is no `establish` tool. A scoped wikilink like +`[[people/Joseph Black]]` declares a kind inline, and everything else +about fleshing a stub out is the Archivist's job +([0012](0012-the-archivist.md)). -`Campaign` discards its world root when it opens, exposing only its -`campaign-log/` and `transcript/` subdirectories, so the `Dm` cannot -reach the `entities/` namespace through it. Step 2 must thread the world -root into the `Dm` — either store the path beside the campaign or give -`Campaign` a `world()` accessor — so establishing a stub knows where to -write. +`Campaign` keeps its world root and exposes it through a `world()` +accessor. The `Dm` reads it when it builds the scanner, so establishing +a stub knows where to write. -A stub that is never established costs nothing. A future planner agent -can sweep stubs between sessions and establish the ones that matter. +A stub that stays thin costs nothing. The Archivist sweeps thin stubs +after a session and writes up the ones that matter +([0012](0012-the-archivist.md)). ## The bus is session-scoped @@ -129,7 +135,7 @@ never leave last turn's references inside this turn's reading. The scanner is a listener on the session-scoped bus. Every delta of the DM's narration passes through it. It extracts wikilinks, resolves them, and accumulates the set of entities referenced this turn. Before the -next turn, those entities' `## Is` sections are pushed into the prompt. +next turn, those entities' notes are pushed into the prompt. Entities referenced this turn stay in context for the next turn. An entity that falls out of the narration falls out of the prompt. The DM's @@ -144,17 +150,20 @@ needed — the conversation is the record. On restart, the engine replays the last N turns of the transcript into the conversation history. The wikilinks in the DM's narrated lines tell -the scanner which entities to load. The restart prompt carries the same -entities the DM was talking about last session, plus a campaign log -summary for the broader timeline. +the scanner which entities to load. For the broader arc, the engine +also scans the session brief, `session.md`, through the same path when +one exists: wikilinked prose whose links resolve to current notes. The +Archivist will write the brief each pass +([0012](0012-the-archivist.md)); until it exists, a hand-written one +works. ## Rendered wikilinks In the terminal, a wikilink renders as bold text using the display form (the part after the pipe, or the canonical name if there is no pipe). -`Vera Blackwater` shows as **the old assassin**. The raw -wikilink syntax is preserved in the transcript file on disk, so the -engine can re-scan it on restart. +`[[Vera Blackwater|the old assassin]]` shows as **the old assassin**. +The raw wikilink syntax is preserved in the transcript file on disk, so +the engine can re-scan it on restart. ## What this does not cover @@ -165,9 +174,9 @@ engine can re-scan it on restart. it is not urgent while entities are small stubs. It will be a natural follow-on once character sheets and richer entities push the prompt toward a limit. -- **The planner agent.** A background agent that sweeps stubs and - establishes the important ones. Its design starts after this phase - ships, when we have real stubs to look at. +- **The Archivist.** The background agent that writes the entity notes + and backfills wikilinks. Its design is + [0012](0012-the-archivist.md). ## Implementation order @@ -178,12 +187,13 @@ engine can re-scan it on restart. names - [x] 2. Auto-establishment — create stub entity files when a wikilink does not resolve -- [ ] 3. Context injection — push referenced entities' `## Is` sections into - the next turn's prompt -- [ ] 4. Wikilink rendering — format as bold in the terminal, preserve raw +- [ ] 3. Context injection — push referenced entities' notes into the + next turn's prompt +- [x] 4. Wikilink rendering — format as bold in the terminal, preserve raw syntax in the transcript - [ ] 5. Restart mechanism — replay last N turns of the transcript, scan - wikilinks, inject context + wikilinks and `session.md` when one exists, inject context, and + route asides to the DM alone ([0013](0013-the-aside.md)) ## Relationship to the Python prototype diff --git a/plans/0012-the-archivist.md b/plans/0012-the-archivist.md new file mode 100644 index 0000000..9f14a63 --- /dev/null +++ b/plans/0012-the-archivist.md @@ -0,0 +1,337 @@ +# 0012: The Archivist + +## What we're building + +Storied grows from one actor into three. The DM runs the game at the +table. The Archivist tends the world's files after the fact. The Weaver, +someday, works ahead of the story. This plan names the three, draws the +boundary between them, and designs the Archivist. + +The Archivist is a background agent. It trails the transcript, writes +and rewrites the entity notes, and backfills wikilinks so the record +stays navigable. It never authors history. + +## Two kinds of files + +Everything the game persists is one of two things, and the two have +opposite write rules. + +**The record** is the transcript and the campaign log. It is +append-only, and only the session writes it: the player's words, the +DM's narration, and the DM's secret asides +([0013](0013-the-aside.md)) land in the transcript, and the DM's +`mark` calls land in the log. The record is what happened. Nothing +rewrites it. + +**The notes** are the entity files and the session brief, +`session.md`. They are mutable and curated, like the pages of a DM's +prep notebook. A note gets rewritten freely as the +story moves. The notes are what we currently believe about the world, +built from the record plus whatever Chris edits in by hand. + +This split answers a question phase 9 kept circling: where does an +entity's history live in its file? It doesn't. An entity's history is a +query against the record. Search the transcript and log for the entity's +wikilinks, and every scene it ever appeared in comes back, anchored in +game time. There is no per-entity timeline to keep in sync with the real +one. + +## The three actors + +Each actor has a one-line contract with the record. + +- **The DM** writes the record. Its narration and its `mark` calls are + the game's history. +- **The Archivist** may only point at the record. It writes the notes + freely, and the only change it may make to the record is adding + wikilinks. +- **The Weaver** may only read. It will write to a prep area of its own, + never to the notes, so speculation never sits next to canon. Its + design is a later plan. + +## What an entity note looks like + +The `## Is` / `## Was` sections came from the Python prototype's theory +of what a character is made of. That theory forces every entity through +a character-shaped mold, and a tavern does not want things. We're +dropping it. + +A note is frontmatter plus prose. The frontmatter carries the two fields +the machines need: + +```markdown +--- +kind: person +aliases: [Vera, the innkeeper, the old assassin] +--- + +Vera Blackwater keeps the Rusty Anchor and a throwing knife under +its bar. She retired from killing for the Crows eight years ago, +and the debt she left behind did not retire with her. + +## Regulars she watches + +- [[people/Joseph Black]], who owes the wrong people money +``` + +The body is whatever prose serves this entity. A tavern's note grows a +list of regulars; an assassin's note grows what she wants; a cursed +dagger's note grows the rumor of where it has been. There are no +required sections. The shape comes from the entity, not from a template. + +A stub is born already useful. Instead of empty scaffolding, it holds +the narration that summoned it: + +```markdown +--- +kind: unknown +aliases: [] +stub: true +--- + +First seen #d1-1830: "behind the bar, Vera Blackwater watches the +door without appearing to." +``` + +That seed is injectable on the next turn, readable by a human, and raw +material for the Archivist to rewrite into a real briefing. The +`## Mentions` section goes away; the seed line carries the same anchor. + +`stub: true` is what the Archivist's sweep filters on. `kind: unknown` +cannot mark stub-ness alone, because a scoped wikilink births a stub +whose kind is already declared. The scanner sets the flag at birth, and +whoever turns the seed into a real briefing, the Archivist or Chris, +deletes it. A curated note has no `stub` field at all. + +## Addresses last forever + +The transcript is full of wikilinks, and restart replays it. Renaming an +entity's file would break every old link, and we do not rewrite the +record. So the rule is: an address, once written in the record, resolves +forever. + +The mechanism is redirects. `aliases` holds every name that reaches this +entity, including retired addresses. When the Archivist renames or moves +a note, the old address goes into `aliases`. When it discovers that two +notes describe the same being, the merge is the same trick: pick a +primary, turn the other into an alias, combine the prose, and backfill +the links. Resolution, which today matches exact addresses and bare +names, grows to match aliases too. + +This rule also takes the weight off the directory layout. A kind can +show up in the path (`people/Joseph Black`) or in frontmatter, and we +can reorganize later, because resolution follows names and aliases, not +paths alone. + +## What the Archivist does + +Five jobs, all downstream of the record. + +1. **Write the notes.** Read the record since its cursor, then rewrite + the notes for the entities that were part of it. Turn seed stubs into + briefings. Keep a note current when the story contradicts it: when + the tavern burns down, the note stops calling it cozy. +2. **Backfill wikilinks.** Entities get established late, constantly. + Session 1's record says "the innkeeper"; session 3 reveals she is + Vera Blackwater. The Archivist sweeps back and links the old plain + text mentions, so a late entity gets its whole history + retroactively. The link graph gets denser as hindsight improves. +3. **Keep the name book.** When narration calls one entity by a new + name, record the alias. Every alias it records makes the scanner's + bare-name resolution better on the next turn. +4. **Classify and merge.** Give a `kind: unknown` stub its kind, and + fold duplicate notes together through the redirect rule above. +5. **Prep the session brief.** End a pass by rewriting `session.md`, + the "previously on" the next session starts from. Its own section + below. + +There is no `establish` tool for the DM, in this phase or any other. +The DM already establishes things the only way it should: by narrating +them. A scoped wikilink like `[[people/Joseph Black]]` declares a kind +inline when the DM knows it. Everything past that is the Archivist's +job, because a whole-session view can reconcile, dedupe, and classify in +ways a mid-scene tool call cannot. + +## How it runs + +The Archivist trails the record with a persisted cursor: "consolidated +through here." A finished turn is the event that wakes it, and it +decides whether enough has accumulated to be worth a pass. The cursor +makes it restartable and idempotent, and it makes cadence a knob rather +than an architecture decision: the same agent can run live during a +session or catch up between sessions, because either way it just +advances the cursor. + +Backfill stays cheap the same way. It is triggered by a new entity or a +new alias, and it visits the places a search for those names turns up. +It is not a standing re-read of all history. + +## The guard on the record + +Backfilling means the Archivist touches the record, and that needs a +hard boundary: **the words are immutable; the markup may gain links.** +`[[Vera Blackwater|the innkeeper]]` renders as exactly "the innkeeper", +so a link-only edit leaves the DM's words byte-identical once wikilink +syntax is stripped. That property is mechanically checkable, so the +guard is code, not a prompt instruction. The Archivist proposes +annotations, a span and an address, through a narrow engine path, and +the engine applies only the ones that pass the check. + +`Transcript::append` promises today that it never rewrites bytes already +on disk. Annotation does not loosen that promise; it gets its own write +primitive beside `append`, with the render-equivalence check built in. + +## How the DM and the Archivist interact + +They never speak. They share files and one rule. + +The rule: **mid-session, the conversation beats the notes.** The +Archivist is always a little behind the table, and that is fine, because +everything past its cursor is already in the DM's own conversation +history. The DM's prompt says the notes may trail the conversation, and +the conversation wins. + +The loop closes between sessions. The Archivist catches up, the notes +get richer, and the next session's scanner injects those richer notes +back into the DM's prompt. Backfilled links pay off at restart too: +replaying the transcript re-scans its wikilinks, so an annotated +transcript reloads more of the world than it saved. + +One consequence of the write rules: if the campaign log turns out too +thin to answer history queries well, the fix is on the DM's side, better +prompting toward richer `mark` calls. The Archivist never fills a gap in +history, because a consolidator inventing log entries after the fact is +fabricated canon with a timestamp. + +## The session brief + +The Archivist ends a pass by rewriting `session.md`: a short +"previously on" for the next session. It is a note like any other, +prose dense with wikilinks, the same dialect the DM narrates in: + +```markdown +The party is mid-ambush on the coast road. [[Vera Blackwater|Vera]] +is bleeding, [[people/Joseph Black|Joseph]] has the ledger, and the +[[The Crows|Crows]] have not answered for the burned warehouse. +``` + +At session start the engine feeds the brief through the same scanner +path as narration: its wikilinks resolve, the referenced notes inject, +and the session's entity registry is seeded before the first turn. The +links are pointers, not snapshots, so the brief always resolves to the +current version of a note, including one the Archivist rewrote in a +later pass or one Chris hand-edited over breakfast. The prose between +the links carries the one thing no entity note can hold: the +cross-entity scene state, who is where, what is mid-flight, what is +unresolved. + +Two rules keep it honest. The brief summarizes backward: everything in +it must be supported by the record, and what might happen next belongs +to the Weaver's prep area, never the brief. And it is one file, +rewritten every pass: the history it discards is exactly what the +record already keeps. + +A brief can carry a mystery's state fenced in `[!aside]` blocks +([0013](0013-the-aside.md)), so resuming a secret does not spoil the +one note a player might fairly want to read. + +The brief fans out. Ten links inject ten notes, so this is where +0011's context budget question starts to bite. The mitigation is +editorial: the brief links what matters for resuming, not everything +the campaign has met. Curation is the Archivist's job. + +Until the Archivist exists, a hand-written `session.md` works the same +way, which is how the startup mechanism can ship before the agent +does. Where the file lives is decided with the world root in phase 10. + +## The loop, drawn + +``` + you + │ what you do + ▼ + ┌────────┐ narration and `mark` calls ┌───────────────────────────┐ + │ the DM │ ────────────────────────────▶│ the record │ + └────────┘ │ transcript/ campaign-log/ │ + ▲ │ append-only; only the │ + │ │ session writes it │ + │ └───────────────────────────┘ + │ the scanner injects │ ▲ + │ notes for whatever │ trails it, │ adds wikilinks only; + │ the DM just named │ with a cursor │ the words never change + │ ▼ │ + │ ┌───────────────────────────┐ + │ │ the Archivist │ + │ └───────────────────────────┘ + │ │ + │ │ writes and rewrites freely + │ ▼ + │ ┌───────────────────────────┐ + └────────────────────────────────────│ the notes │ + next turn's prompt │ entity notes + session.md │ + │ frontmatter + prose │ + └───────────────────────────┘ + + (the Weaver, later: reads the record and the notes, writes only its + own prep area) +``` + +## What this changes in what's already built + +- The stub format. `stub_body` writes frontmatter and a seed line + instead of `## Is` / `## Was`, and the `## Mentions` stamp becomes + that seed line. +- Context injection (0011 step 3) injects the note body, not an `## Is` + section. +- Resolution grows alias matching alongside exact addresses and bare + names. + +## What this does not cover + +- **The Weaver.** It gets a roadmap entry and a one-line write contract + here, and nothing more until the Archivist has run for a while. +- **Which model plays the Archivist.** Per the ground rules, we pick at + the last minute. It is a separate agent loop against the same + OpenAI-compatible API, and it does not have to be the DM's model. +- **Cadence tuning.** Turn-end, session-end, or somewhere between: the + cursor makes this a knob, and we will set it by feel at the table. +- **The context budget.** Richer notes make injection bigger, which + makes the budget question from 0011 more real. Still deferred. + +## Milestones + +Ordered so something is playable after every step, the mechanism lands +before the agent that uses it, and the Archivist earns record access +last. + +1. **The note format.** Seeded stubs, frontmatter, alias-aware + resolution; the `## Mentions` stamp folds into the seed line. *You + can now: open a stub and read the scene that created it.* +2. **Injection.** 0011 step 3 on the new format: referenced notes + reach the next turn's prompt. *You can now: watch the DM keep an + entity's facts straight two turns after naming it.* +3. **Session start.** 0011 step 5: replay the last turns of the + transcript, and scan `session.md` through the same path when one + exists. The Archivist does not exist yet; a hand-written brief + works from day one. *You can now: write a brief yourself, restart, + and the DM picks up where you left off.* + +Phase 10, persistence, lands here: a real world root gives the notes +and the brief a durable home. + +4. **The first pass.** The Archivist as a between-sessions run: the + cursor, read the record since it, rewrite the touched notes, write + the brief. It does not touch the record at all yet, so it needs no + guard. *You can now: end a session, run the Archivist, and come + back to written-up notes and a fresh brief.* +5. **The guard, then backfill.** The annotation primitive with its + render-equivalence check, and only after it exists, link backfill + on new entities. *You can now: search an entity and find the scenes + that predate its name.* +6. **The name book.** Alias discovery, classification, and merge via + redirect. *You can now: call her Vera all session and every link + still points home.* + +Live trailing, waking on turn-end during a session, stays out of the +milestones. The cursor makes it a cadence knob to turn if +between-session passes ever feel too slow. diff --git a/plans/0013-the-aside.md b/plans/0013-the-aside.md new file mode 100644 index 0000000..443c05c --- /dev/null +++ b/plans/0013-the-aside.md @@ -0,0 +1,126 @@ +# 0013: The aside + +## What we're building + +The DM's only channel today is its public narration. Everything it +invents is spoken to the player, so the DM cannot know anything the +player does not. That flattens the game: no hidden motives, no +mysteries with committed answers, no trap the DM planned two rooms ago. + +The DM should get to hallucinate secret truth too. The founding rule +tells us where it has to live: hallucination becomes canon when it is +written down. So secrets get a written home, behind the screen. The +mechanism is one tool, `aside`, and one markdown form for secret +content that works across the whole file system. + +## Why thinking tokens are not enough + +The model's hidden reasoning is a tempting free channel, and it fails +four ways: + +1. **Secret drift.** If the killer's identity exists only in turn 12's + thinking, turn 40's model invents a new one. A mystery with no + committed answer wanders. +2. **Session death.** Thinking is never replayed. The DM comes back + tomorrow not knowing its own secrets. +3. **Provider fragility.** The DM's model is configuration on an + OpenAI-compatible API. Reasoning traces differ by provider, some + are inaccessible, and none replay. The written record is + model-agnostic. +4. **The Archivist is blind to it.** An unwritten secret cannot be + consolidated into the notes or carried into the session brief. + +Thinking stays what it is, a scratchpad. The prompt rule is: if it +must still be true tomorrow, write it behind the screen. + +## The `aside` tool + +The DM calls `aside` with a piece of secret canon. The engine appends +it to the transcript at the current game time, the same way `mark` +writes at the current clock. The model gets a terse receipt, because +the API requires a tool result. The player sees nothing: no line, no +icon, no pause. + +The secret is still provable after the fact. The transcript on disk +timestamps it, and file history shows the answer existed before the +player found it. A human DM cannot prove that. + +## The form on disk + +An aside is a callout blockquote: + +```markdown +> [!aside] +> [[Vera Blackwater|Vera]] is lying; the gold was never in the +> strongbox. +``` + +The rule: a blockquote whose first line is `[!aside]` is behind the +screen. One marker, greppable, and honest narration cannot produce it +by accident. In any markdown viewer it reads as a quote, visually +fenced from the scene around it. A renderer that ever shows the DM's +side of the table captions it "The DM notes:". + +## Behind the screen, everywhere + +The same block works in any note. The Archivist can fence +secret-derived content inside an entity's briefing, and the session +brief can hold the state of a mystery without spoiling it: +DM-facing injection keeps aside blocks, and any player-facing surface +skips them. That resolves a real tension with `session.md`, which is +the one note a player would legitimately want to read as a +"previously on." + +Reading the raw files past the fences is the same honor system phase 8 +established for `visibility: screened` lore. The screen line is +enforced where the game renders, not by hiding bytes from the person +who owns the disk. + +## What the engine does with an aside + +- **The scanner reads it.** Secret entities are entities: + `[[The Crows]]` in an aside establishes a stub like any narration + reference would. Asides are tool output, not narration deltas, so + the tool feeds the scanner's state itself rather than riding the + bus. +- **Replay routes it.** On restart, asides rejoin the DM's + conversation history and never reach a player-facing rendering. + This lands with 0011's step 5. +- **`recall` sees behind the screen, and says so.** Search returns + whole time-block entries, so the `> [!aside]` fence travels with any + hit that contains one, and the DM can tell a recalled secret from + recalled speech. The one place that needs real awareness is the + player-facing summary line: its match count must count only entries + with a public match, or the count alone leaks that a secret about + the keyword exists. +- **The Archivist consolidates it.** An aside is record like any + other line ([0012](0012-the-archivist.md)): source material for the + notes, subject to the same link-annotation guard, never rewritten. + +The write contracts do not move. The session still writes the record, +and an aside is the DM's output. The record simply carries a screened +stream it always had room for in the visibility vocabulary. + +## What this does not cover + +- **Visibility gradations.** An aside has one level: DM-only. If + screened-but-acknowledged content ever earns its way in, it is a new + conversation. +- **Secret `mark`s.** The campaign log stays all public, so recall + over the log stays trivial. A secret event can be an aside. +- **A visible receipt.** A table-feel line like "the DM jots something + behind the screen" is atmospheric, and it is cut for now. If we + miss it, it is a display toggle, not an architecture change. + +## Implementation order + +- [ ] 1. The form: recognize and append `[!aside]` blocks in the + transcript, at the current clock +- [ ] 2. The tool: `aside` in the DM's toolbox, terse receipt, no + player-facing output +- [ ] 3. The scanner path: aside text runs through wikilink resolution + and can establish stubs +- [ ] 4. Replay routing, with 0011 step 5: asides to the DM's history + alone +- [ ] 5. Aside-aware recall: the public match count excludes entries + that matched only inside an aside -- 2.51.2