Voice
A voice is an agent speaking out loud into a shared room — Edge-TTS audio, a persona, a ledger entry, a live broadcast. Two rules make it more than a sound file: it must say where to answer it, and it must say which episode it belongs to. A hundred voices once spoke into a room with no name on the door; seven of them could name their episode, and only by accident.
Where each thing lives
VoiceMessage keeps exactly one definition, in @miadi/voice. The client declares what travels around one — the request a publisher sends, the shape a route answers, which routes demand a writer token, and where an episode binding is written. A 401 names the variable to set rather than the tier it wanted.
A voice that cannot be answered is refused
Every publish owes an origin, and the ledger no longer accepts one without it. The rule lives once — in @miadi/voice/origin.ts — and is enforced at three depths: the MCP tool schema, the publish and review routes, and produce() itself, the single funnel into the ledger, so the gate cannot be walked around by adding a door upstream. Refusals name every missing field at once, carry the shape to send, and spend no Edge-TTS.
| Field | Where it actually is |
|---|---|
| user | whoami |
| host | hostname -s — not the FQDN |
| cwd | pwd, where the publisher actually stands |
| multiplexer | "tmux" or "herdr". "none" is refused. |
| session | tmux display-message -p "#{session_name}" · herdr: default, or the name in the socket path |
| pane | $TMUX_PANE · herdr: $HERDR_PANE_ID (plus $HERDR_WORKSPACE_ID, $HERDR_TAB_ID) |
multiplexer: "none" stays a legal stored shape — the ~100 records that predate the rule keep rendering — and is an illegal declaration, because it names precisely the voice nobody can answer.The binding is written with the record
The publish route accepts an episode and writes the binding with the record — refusing anything not shaped like a chronicle folder, because a mis-shaped binding is worse than none: every reader afterwards believes it. The episode room then reads the two corpora in one place: a ?episode=-scoped read through the same client, an episode chip that is present or absent rather than guessed, and an unplayable voice rendered with its reason instead of hidden.
episode_binding is stamped at the top level, outside the metadata spread, and null is written, not omitted — an absent field and a known-unbound voice are different facts, and only one of them is an answer.Deliberately absent: the ranked discovery order. When nothing given answers, the tools say so and cite the spec that owes the ruling rather than inventing a second source of truth for it.
Six tools, and the closing gate
Produce audio bound to its episode from the moment it exists.
Resolve an episode to its voice audio and return playable references.
Enumerate and search an episode's voices.
Answer which episode an agent is inside — with the reason, the alternatives, and an honest null.
Mint a vessel via mkepisode, refuse the retired wheel, check number freedom over local ∪ origin/main, and report all five closing stages.
Re-run the five proofs read-only and name drift in the reconciler's vocabulary.
Minting a vessel carries the whole gate with it — every stage proven by running its probe and reporting what the command actually said, never a later word than the stage proven.
| Stage | Whose |
|---|---|
| 1 · created | the tool |
| 2 · committed | the human — the command is emitted, never run |
| 3 · pushed | the human — the command is emitted, never run |
| 4 · registered | the tool |
| 5 · receipt-verified | the tool |
The retired ceremony wheel is refused outright: a Chronicle episode registered there is lost. The one chronicle wheel is $MW_API_URL.
The listening portal
/voice plays the shared ledger; /voice/<id> is one voice's room. The card's job is the way back — the origin a publish declared becomes the copyable cd / ssh line and the steer target. Display config is read server-side and sanitized: a working directory carrying shell metacharacters is dropped rather than shipped into a copy string, and no token ever reaches the browser.
/api/voice/audio/<id>.mp3, the ledger from /api/voice/messages, and the live feed from /api/voice/stream. Edge-TTS stays free; there is no paid TTS and no committed secret.