Specifications v0.1 draft Sheet 03 of 03
audio@0.1
A narration manuscript: a piece of writing meant to be heard, where
headings are chapters, prose is the script, and the small amount of direction a narrator
needs lives in frontmatter and two sparse inline fields. It is the first spec with a
produce adapter — and the file stays a document afterwards.
- Status
- normative · draft, will change
- Inherits
- shared conventions envelope, fields, MDTO001–099
- Artifacts
- schema.json
fixtures/ - Owns codes
- MDTO300–MDTO399 13 allocated
§ 01
The file, and the manuscript
On the left, a conformance fixture —
specs/audio/fixtures/valid/full-direction.md, byte for byte. On the right,
the manuscript summary: the direction the file declares, its chapters, and how much of
each one is actually narrated. It is deliberately not a player, a waveform or a
transcript — this spec describes what would be said; saying it belongs to a
synthesis adapter.
audio@0.1
static render
--- markdownto: audio@0.1 title: How the naming argument ended voice: warm-narrator pace: relaxed pronunciations: agentsFS: agents F S mdto: em dee tee oh Kaufmann: KOWF-mun --- # How the naming argument ended We spent four days on a name. Here is the short version, and then the version I am less proud of. ## The short version agentsFS was never meant to be said out loud. It was a directory name, and directory names are allowed to be ugly, because the only thing that ever reads them is a shell. Then we put it on a slide. [pause:: 2s] Kaufmann pointed out that we had all been saying it differently for a month, in meetings, to each other, without anyone stopping to check. That was the end of the argument, and it was also the end of the slide. ## The version I am less proud of > [narration:: skip] Reminder: clear the Kaufmann anecdote with him before this goes out. The command is mdto, which nobody can say either, and which I defended for two entire days on the grounds that it was shorter. [pause:: 1.5s] Names are not decisions. They are the residue of decisions, and you can read the whole argument back out of them later, which is the part that should have worried me. ## Postscript [narration:: skip] Kaufmann has since changed his mind twice, both times by email, both times at approximately eleven at night.
How the naming argument ended
11 narratedof 13 blocks across 4 chaptersread-only
How the naming argument ended
We spent four days on a name. Here is the short version, and then the version I am less proud of.
The short version
5/5agentsFS was never meant to be said out loud. It was a directory name, and directory names are allowed to be ugly, beca…
Then we put it on a slide.
Kaufmann pointed out that we had all been saying it differently for a month, in meetings, to each other, without anyone…
The version I am less proud of
3/4Reminder: clear the Kaufmann anecdote with him before this goes out.
The command is mdto, which nobody can say either, and which I defended for two entire days on the grounds that it was s…
Names are not decisions. They are the residue of decisions, and you can read the whole argument back out of them later,…
Postscript
1/2Kaufmann has since changed his mind twice, both times by email, both times at approximately eleven at night.
The struck-through rows are the blocks that will not be spoken. A skipped block is
marked, never hidden — it is still visible, still diffable, still part
of the document; it simply is not read aloud. The markup was produced by
@markdownto/web at build time; see
site/tools/build-specs.mjs.
Every other route to narrated audio starts by converting a document into a production format — a JSON script, an SSML tree, a timeline — after which there are two artifacts and the readable one stops being true. Here there is one file. It reads as a document because it is a document, and the direction it carries is the direction a human narrator would need written in the margin, and no more.
If a construct would only ever be read by the TTS pipeline and never by a person holding the page, it does not belong in this spec.
§ 02
Grammar
Distilled for this page. audio@0.1 shares the envelope, inline-field and
preservation rules with every other spec, and nothing else — it has no items, so
the shared item vocabulary (priority, due, owner)
is not recognized here. Those keys are unknown fields in a manuscript: preserved, never
narrated, never an error.
Direction lives in the envelope
--- markdownto: audio@0.1 title: Field notes, week 32 # optional, reserved — never narrated voice: warm-narrator # optional, spec-defined pace: relaxed # optional, spec-defined pronunciations: # optional, spec-defined agentsFS: agents F S mdto: em dee tee oh Kaufmann: KOWF-mun ---
voice- A single token matching
[A-Za-z][A-Za-z0-9._-]*(MDTO300), resolved against the provider’s catalogue at production time, not at validation time. paceslow|relaxed|natural|brisk|fast. Case-insensitive; defaultnatural(MDTO301).pronunciations- Written form to spoken form. Matching is case-insensitive on whole-word boundaries; multi-word keys are allowed; when several keys could match at one position, the longest wins and ties break by source order. That is the whole of the determinism rule, and it is why the IR carries them as an ordered array rather than a map.
The catalogue lives with the provider and changes without the file changing. A
validator that reached the network would break the offline guarantee, and would make
the same file valid on Tuesday and invalid on Wednesday. So the file is checked for
shape, and the name is resolved where the failure is immediate, obvious and
free. MDTO306 exists so an implementation that does hold a
catalogue can say something useful without making it fatal.
The same rule draws the line for everything else: frontmatter carries direction
that belongs to the text; flags carry facts about a particular run. So there
is no format:, no model:, no output: and no
provider: — those are produce flags, recorded in the
build receipt where a metered service can be audited against them.
The chapter level is discovered, not fixed
A chapter is opened by a heading at the document’s chapter level. The
chapter level is ## if the document contains at least one ##
heading, and # otherwise. The level is decided once, for the whole
document, by a single pass over its headings. It never varies within a file.
# Three letters to a younger engineer
# On being slow
# On being wrong
no `##` anywhere, so `#` is the chapter level
# Field notes, week 32
## The idea
## What I built
the file contains `##`, so `##` is the chapter
level and that `#` is the leading title heading
- below the level
###under##chapters is ordinary narrated content. It is read where it stands and opens nothing — which is what makes a long chapter with sections in it a legal manuscript rather than a structural error.- above the level
- Permitted only as the first block after the frontmatter, where it is the
document’s title heading. Anywhere else it is
MDTO310: it is narrated in place and opens nothing, so the prose beneath it cannot silently migrate. - the implicit chapter
- Narratable content before the first chapter heading forms a leading chapter titled
null— a preface, or a whole document with no headings at all. It exists only when there is such content. - numbering
- 1..N in document order, counting the implicit chapter. These are the numbers
preview --chapter Naddresses and the numbers that prefix output filenames. Titles need not be unique; chapters are addressed by number, never by name.
A file whose chapters are # and which wants a separate
# title heading — there, the title becomes chapter 1. Fixing the
level at ## instead would have made the letters-in-one-file shape invalid
and, worse, would have silently reinterpreted a #-chaptered file as one
chapter with a very long title: the failure mode where the tool is confidently wrong
and says nothing. If you want a title heading, use ## for
chapters — that is the repair, and it is why MDTO310 says
so out loud.
What is narrated
Within a narrated block the rule is what a person would read aloud off the page:
link text but not the URL, a wikilink’s name but not its brackets, a tag’s
word but not its #, the emphasized words but not the markers. An inline code
span is read as its literal characters; only block code is excluded. An image
contributes nothing, because alt text describes a picture and turning it into speech
would be the spec guessing.
- narrated
- Paragraphs · headings at any level, including the one that opens the chapter · lists of every kind, each item its own utterance · blockquotes.
- never narrated
- Fenced and indented code · tables · HTML blocks · thematic breaks, which insert nothing, not even silence. Excluded by kind, permanently: speech is linear and these are not, and there is nowhere on a fence line to write a direction field, so a default that could be overridden would be one nobody could reach.
- never narrated, ever
- Inline fields, in any Markdown To spec. That is exactly what makes them usable as
direction here. List markers, ordinal numbers and task checkboxes are not read either
—
- [x] Water within reachis read as “Water within reach”. - nothing at all
- A document with no narratable content is
MDTO330: it names itself a manuscript and givesproducenothing to say.
Two fields of direction, and no more
A direction field applies to the top-level block that contains it, wherever in that block it is written — position is never meaningful, here or in any other spec. The one finer grain is the list item, because a list is a sequence of separate utterances rather than one run of prose.
Then we put it on a slide. [pause:: 2s] Kaufmann pointed out that we had all been saying it differently for a month. > [narration:: skip] Clear this anecdote with him first.
[narration:: skip]- The block it appears in is not narrated.
skipis the only value in 0.1 (MDTO320). On a chapter heading it silences the title only — the chapter still exists, is still numbered, and its blocks are still narrated. [pause:: …]- Holds silence after the block. A number and a unit,
msors; greater than 0 and at most 60s. The unit is required — a bare2isMDTO321, because seconds and milliseconds are three orders of magnitude apart and guessing is not worth being wrong once. - at most one of each
- Per block. A repeat is the shared
MDTO023: two values with no defined precedence is ambiguity.
[narration:: skip] and not a comment
An HTML comment would hide the text from every viewer, which is precisely what the governing principle rejects. A skipped block is still visible, still diffable, still part of the document; the renderer marks it so a reader can see the difference. A skipped block is not deleted content and must never be treated as deletable.
Read audio@0.1 in full — the normative text Shared conventions
§ 03
Verb reference
audio@0.1 owns estimate, produce,
preview, voices. It shares nothing with todo or
kanban, which is the clearest demonstration that verb vocabularies are
spec-owned: those specs mutate a list, this one spends money on a manuscript.
Every verb here is read-only with respect to the file.
estimate
mdto audio estimate [--file f]
[--voice v] [--pace p] [--json]Validates, builds the narration plan, and reports —
per chapter and in total — narratable characters, estimated duration and
estimated cost. It must not call the provider and must be deterministic. Cache
awareness is the point: it reports both the full cost and the
incremental cost of running produce right now against the current
cache. If no price basis is known it says the cost is unknown; it never invents a
number.
producemetered
mdto audio produce [--out DIR]
[--chapters 2-5 | --chapter N]
[--voice v] [--pace p] [--model m]
[--dry-run] [--yes]Chaptered generation, cached and resumable. It prints the
estimate and requires confirmation before any provider call, and it refuses on any
error-severity diagnostic with no override — todo and kanban offer
--force because the worst case is a bad edit to a text file; here the
worst case is a bill. Writes NN-<slug>.mp3 per chapter, a joined
file, and a receipt whose cost figures are measured, never estimated.
preview
mdto audio preview --chapter N
[--voice v] [--pace p] [--play]Generates exactly one chapter. --chapter is
required: preview exists to answer “does this voice work for this passage”
without buying the book. Because the cache is keyed on content and settings and not on
which verb asked, a preview is a cache entry — a later
produce with matching settings reuses it and does not pay twice. That
safety is structural, not a check someone has to remember to write.
voices
mdto audio voices [--provider p] [--json]The catalogue: name, language, a short description, and
whether it is usable. The only audio verb that needs no file, and the only one that may
reach the network. It is also what MDTO306 is checked against, and the
answer to “what do I put in voice:”.
The cache key is the chapter’s content, and nothing else
A SHA-256 over the spec identifier, the effective voice, pace, provider and model; then, for each narrated unit of that chapter in order, its speech text, and after any unit carrying a pause, that pause in milliseconds. Excluded deliberately: the chapter’s index, its title slug, the output path, the document title, the rest of the frontmatter, source line numbers, and every other chapter. The consequences are the contract.
- edit chapter four
- Regenerates chapter four. Nothing else is re-bought, ever.
- insert a new chapter two
- Renumbers the output files and regenerates none of the old ones.
- reflow a paragraph
- Changes no key, so nothing regenerates.
- change
voiceorpace - Changes every key, so everything regenerates — which is correct, and
estimatewill have said so first.
Chapters are the unit of narration, of output, of caching, of preview and of resumption, and that is not five coincidences. Generation is the expensive step, so the boundary the author already drew for the reader’s benefit is reused as the boundary that keeps a typo in chapter four from re-buying chapters one through three. A tool that regenerates a whole book because one paragraph changed is not a slow implementation of this spec; it is a non-conforming one.
What no verb ever does
Modifies the manuscript·pins or normalizes an identifier·rewrites a field or a frontmatter value·inserts silence the file did not ask for·narrates a code block or a table·calls a provider before the estimate has been shown and acknowledged·regenerates a chapter whose key is unchanged·produces audio from a file with an error ·invents a cost.
§ 04
Diagnostics carry the repair
Direction is the part of a manuscript a reader cannot proof-read, so its diagnostics work hardest. Each one names the offending value, says why the spec refuses it, and — where the mistake has a common cause — says what the author probably meant.
--- markdownto: audio@0.1 title: Rehearsal notes --- The room went quiet. [pause:: 2] Then the door opened. [pause:: two seconds] She still did not look up. [pause:: 0s] [pause:: 90s] [narration:: whisper] Say this next part gently. [narration:: skip] Cut this line for now. [pause:: 3s] [narration:: skip] And that is the whole scene, which takes four minutes and feels like ten.
-
MDTO321 error line 6
The room went quiet. [pause:: 2]Malformed
pauseduration2: a duration is a number followed bymsors, for example2sor750ms. The unit is required. -
MDTO321 error line 8
Then the door opened. [pause:: two seconds]Malformed
pausedurationtwo seconds: a duration is a number followed bymsors, for example2sor750ms. The unit is required. -
MDTO322 error line 10
She still did not look up. [pause:: 0s]pauseduration0sis out of range: a pause must be greater than 0 and at most 60s. -
MDTO322 error line 12
[pause:: 90s]pauseduration90sis out of range: a pause must be greater than 0 and at most 60s. -
MDTO320 error line 14
[narration:: whisper] Say this next part gently.Unknown value for
narration: audio@0.1 defines onlyskip, foundwhisper. Per-block voice direction is deliberately not in 0.1. -
MDTO323 warning line 16
[narration:: skip] Cut this line for now. [pause:: 3s]pauseon a block marked[narration:: skip]has no effect: a skipped block contributes neither speech nor silence. Move the pause to a neighbouring block. -
MDTO324 warning line 18
[narration:: skip]Block consists only of direction fields and produces neither speech nor silence.
[narration:: skip]applies to the block it is written in, not to the block after it.
An invalid narration value recovers to narrated: the spec will
not stop reading somebody’s text because of a typo. An invalid or out-of-range
pause produces no silence and the block is otherwise unaffected — and it is not
additionally reported as having no effect, because the diagnostic it already
has is the one that matters. Note that [pause:: 90s] above reports only
MDTO322 for exactly that reason.
MDTO320 is the sharpest example of a diagnostic pointing the way:
[narration:: whisper] is not an unrecognized value, it is a
named extension point that 0.1 chose not to take. Per-block voice
direction is deferred explicitly. Do not start honouring values
MDTO320 rejects.
The codes this spec owns
voice is not a well-formed voice name: a single
token, no spaces.
pace is not one of the five defined values.
pronunciations key is not a string. This bites
more often than it looks: YAML reads 2026: as an integer.
pronunciations value is not a non-empty
string. To leave a word alone, delete the entry.
pronunciations keys differ only in case;
matching is case-insensitive, so neither could win.
pronunciations key never occurs in any
narrated text. Harmless, and usually a typo.
voice is well-formed but not in this
implementation’s catalogue. Deliberately unfixtured — what it reports
depends on a local registry, not on the file.
chapter-NN.
MDTO330 fires.
narration; 0.1 defines only
skip.
pause duration.
pause duration is out of range: greater than 0
and at most 60s.
pause on a skipped block has no effect; skip
wins.
§ 05
For agents
This is the one spec where a wrong guess costs real money, so its agent-facing document spends most of its length on why. An agent that understands the cache contract will not re-buy a book; one that does not, will.
$ mdto spec audio audio@0.1 — agent-facing specification 1. Purpose what the format is for 2. Grammar heading = chapter, prose = script 3. Rationale why it is shaped that way 4. Examples valid + invalid + diagnostics 5. Verbs estimate · produce · preview · voices $ mdto audio estimate week-32.audio.md 6 chapters · 18,412 narratable characters est. 14m 20s at 14 chars/sec, pace relaxed full 6 chapters to generate incremental 1 chapter — 5 served from cache note produce is metered — hosted tier coming $ mdto audio produce --chapter 4 --dry-run 04-what-i-got-wrong.mp3 2,904 chars est. 3m 27s cache miss — chapter text changed note --dry-run: no provider call made
-
Read-only, structurally
No audio verb edits the
.mdfile, pins an identifier, normalizes a field or stamps a duration. Artifacts are written beside the file, never into it — which is why this spec needs no--pin, no patch engine and no conflict protocol on write. The only thing that can change the file is the author. -
Nothing is derived in the IR
No character counts, no durations, no cache keys, no costs. The IR says what the file says; a receipt says what one run did. Keeping them apart means a figure can never be stale relative to the document it describes, and the metered tier’s numbers come from one auditable place.
-
Fetchable, not scrapeable
The agent-facing document lives at a stable URL beside an
llms.txtso an agent never has to scrape a human documentation site to author a conforming manuscript.fetch
markdownto.ai/specs/audio.mduniversal
validaterenderspec