Dialogue · Design notes · Architecture
Archived — Superseded — it still calls the per-room cast “planned” and the panel turn “not streamed”, and it diagrams the frozen AI↔AI (exp-010) pipeline. The live architecture is the function map. Kept for provenance; no longer maintained. See the function map for current docs.

Topology — how the browser and the local backend fit together

A local Python server (lib/run_room.py — FastAPI on localhost; it can't live on Cloudflare Pages, it's a stateful process holding live conversations). A RoomManager holds many independent rooms; each is one running megaprompt context with its own memory, persisted to rooms/<id>/ and resumed on restart. Per turn it assembles the prompt, makes one non-streaming API call, parses the output into the confer + the replies, and returns JSON — the browser animates the egg-hatch client-side (it is not streamed). Freezing a room writes the same artefacts the batch schemes emit, so viewer, index, and audit are untouched.

BROWSER · lib/room-ui.html cream / coral · light · dark · e-ink multi-chat list · message stream · @-mention chips confer drawer · egg-hatch (client-side) POST …/{id}/say { text } JSON reply ↑ { confer · replies · cost } one round-trip per turn — not streamed PYTHON SERVER · lib/run_room.py localhost · FastAPI · RoomManager — many independent rooms, each resumable RoomManager many rooms prompt assembler SP + profiles (cached) response parser confer ↔ replies logger + persist resume on restart one context per room — no routing between minds ENGINE · megaprompt — one model voices the whole panel system = SP + host profiles (cached prefix) · messages = the running transcript 李 Li Ka-shing Д Vladislav Doronin fixed 2-host cast today — per-room cast is the next step @name → answers as that one persona, skipping the confer <confer> + <speak who> kept verbatim Anthropic API one non-streaming call · sonnet 4.6 · cached prefix every turn (not just on close) rooms/<id>/ · state.json · event-log.json · transcript.md live, resumable, gitignored — the hosts remember each chat across restarts schema: scheme · charA/charB · session · events[] (verbatim prompts) freeze → experiments/exp-010__…/ (committed) EXISTING PIPELINE — unchanged viewer* · index · post-run audit * a live room freezes into a normal replayable experiment — the N-way viewer update (human + N hosts) comes later deferred: the python (isolated) engine + self-election bids — still the batch research thesis, not used by the live panel

One human turn, step by step

Two visible layers — there's no private <think>; the one model stages everything. Confer (the panel's internal back-and-forth → side drawer) → respond (one message per host → main chat). The whole turn comes back as one JSON reply; the browser reveals it with the egg-hatch.

The human types (optionally @-addressing a host). The server appends their message verbatim to that room's running transcript — no wrapper, the dumb-pipe rule — and logs a guest speak event. @addressed → the model answers as that one host, confer skipped. Otherwise:

megaprompt · confer, then respond

  1. The single model, holding the whole cast, writes the internal deliberation — the hosts reacting to each other, a short back-and-forth (at most 4 turns per host, no fixed rounds).
  2. It settles a relevance-led order in that confer (the host the turn most implicates leads), then writes one spoken reply per host — lead in full, a distinct point added, a bare concur where someone would only agree.
  3. The response parser splits the raw output: <speak> inside <confer> → the side drawer; <speak> after it → the main chat.
  4. The server returns the whole turn as one JSON reply; the browser reveals it with the egg-hatch (a confer capsule + one bubble per host). Not streamed.
  5. Floor returns to the human. A host can hold — say nothing this turn — so it never forces a flat chorus.

Why megaprompt for this: one model staging the confer + responses makes it trivial to change the format, add or drop a character, or dial the depth of the deliberation — all prompt edits, no routing or per-context plumbing.

Adding / removing a voice — the per-room-cast port

A chat starts from a picker (POST /api/rooms { cast }); build_system_blocks(cast) builds that room's cached system prefix. The user can add or retire hosts live — changes land at the next turn boundary, never mid-volley, and are shaped to respect the cache.

cast changes, cache-aware

  1. Add → the joiner is omniscient (the one model already holds the thread); it greets in one line, caught up. Open decision: append the new profile to the tail so only the new tail is a cache write (cheap join), vs. rebuild the whole materials block (simpler, but a full cache write). Today's single cache breakpoint forces the rebuild — cheap joins need per-profile breakpoints.
  2. Retire → keep ≥1. Open decision: leave their materials dormant + append "stop voicing X" (preserves the prefix, cheap reads), vs. rebuild the smaller prefix (a cache write next turn). The current mock shows the rebuild path.
  3. Logged as join / leave events; the log generalizes charA/charBparticipants[].
confer / deliberation — the back-and-forth among the hosts, logged as first-class confer events: how each turn's responses were formed. Shown in the slide-out drawer, never in the main chat.
relevance-led order — the host the turn most implicates leads; the others react to the delivered line (not the backstage draft), so going first actually matters.
@-address — the override: skips the confer and answers as the one named host.
per-turn cost — every turn records its tokens + API price as a cost on the coordinator event (the dear cache-write on turn 1, cheap reads after); shown on the turn's boundary in the chat, totalled per chat.
keep-confer — the model's raw output (incl. <confer>) is stored verbatim in the room's history, so the hosts remember how they reasoned across turns.
local-only backend — the server is a researcher tool on localhost; only a frozen session is committed and published to the CF-Pages viewer.