Specifications v0.1 draft Sheet 05 of 18
backlog-workspace@0.1
A backlog that has outgrown one file — a directory holding exactly one
[email protected] spine, the detail notes its tasks earned, an archive/ for
what closed, and sub-backlogs a task delegates to. The unit is a directory, and
[email protected] does not change.
- Status
- normative · draft, will change
- Refines
- [email protected] · §13, multi-file
- Artifacts
- schema.json
fixtures/ - Owns codes
- MDTO500–MDTO599 9 allocated
§ 01
The directory, and the file
Every other sheet on this site opens with one file. This one opens with a
directory, because that is the unit — and then with the one file inside it
that anything declares, to make the point the whole spec rests on: the spine is an ordinary
[email protected] page, and a tool that has never heard of workspaces still reads it
whole.
delegation/
INDEX.md spine
archive/
2026.md rollup page
station-picker.md archived note
dst-timings.csv artifact
harbours/
INDEX.md sub-backlog spine · delegated from INDEX.md:26
offline-cache.md detail note
pricing/
INDEX.md sub-backlog spine · delegated from nothing
voice/
INDEX.md sub-backlog spine · delegated from INDEX.md:17
lanes.md detail note
The shape above is read off the fixture’s own files; the label beside each one is
read out of that fixture’s normative .expected.json. Neither is typed
into the page, and the build fails if the two disagree — a file nothing classifies,
or a member that is not there. Everything is decided by position. A file belongs to
the nearest enclosing directory that has a spine, which is why lanes.md is
voice/’s note and offline-cache.md is the root’s,
and why archive/ holds notes rather than a backlog.
[email protected] file
static render
--- markdownto: [email protected] description: What Tidepool is doing next, and what it is waiting on. agentsfs_role: backlog title: Tidepool --- # Tidepool > A task earns a note of its own when it wants more than one line, and a workstream earns a > directory of its own when its decomposition would swamp this page. Nothing in a directory > is ready work until a task here points at it. ## Now - [/] Cache the tide tables so the app works offline → [[offline-cache]] ^offline-cache - [/] Voice v3 lanes → [[voice/INDEX]] ^voice-v3 ## Next - [ ] Decide the pricing page copy → [[INDEX]] ^pricing-copy - [ ] Home-screen widget showing the next high tide — blocked by [[#^offline-cache]] ^widget ## Done - [x] Rebuild the harbour dataset → [[harbours/INDEX]] ^harbours
Tidepool
4 openof 5 tasks across 3 bands · 1 blockedread-only
Markersopenin progressdonedropped (cancelled)
Now
2 in progressCache the tide tables so the app works offline → offline-cache
Voice v3 lanes → voice/INDEX
Next
2 openDecide the pricing page copy → INDEX
Home-screen widget showing the next high tide
05Done
closed1 done
Rebuild the harbour dataset → harbours/INDEX
The render on the right is mdto render on that spine and nothing else: no
note is opened, no sub-backlog is walked, no progress chip is computed. That is the layer
drawn honestly — [email protected] is what draws a spine, and everything the
workspace adds is a relation between files that a single-file renderer has no
business inventing. Note what the spine still is: five ordinary tasks, one blocker, one
arrow that is punctuation rather than syntax.
There is no markdownto: [email protected], and there should not be. A
spine that declared it would stop being readable by every tool that implements
[email protected] — every verb, every renderer, every fixture — and
nothing would be gained, because the fact a workspace adds is not a property of any one
file. Containment is the declaration: a directory with exactly one file
declaring backlog@ is a workspace, and that file is the spine.
It is the reason adoption is the creation of a directory rather than a migration of every
file. agentsFS names its spine INDEX.md; a .backlog.md beside
two notes is the same workspace. The envelope, never the filename, is authoritative
— and now the directory, never a filename, says which files belong
together.
§ 02
Grammar
Everything here is a rule about which files are in the directory and what
relations hold between them. Nothing here is a rule about the inside of a line: a spine
is parsed by [email protected] exactly as it would be alone on disk, and this layer
only ever refines values that parse already produced. Distilled for this page; the
normative text is specs/backlog-workspace/SPEC.md, and it is the one that
governs.
Membership: the nearest enclosing spine
One rule classifies a whole tree, and it is the only membership rule the spec has: every file belongs to the nearest enclosing directory that has a spine.
- spine
- The one file directly in the directory that declares
backlog@, any minor. The priority authority. None isMDTO500; two isMDTO501, because which page ranks the work is exactly the question the unit exists to answer. - detail note
- Any other
.mdthe workspace owns, at any depth. An ordinary document, usually with nothing but adescription:— earned, never default: a task gets a file the moment it wants more than its line. - artifact
- A member that is not
.md: a screenshot, a trace, a CSV of timings. Members so that “which task owns this file” has an answer, and never opened. - sub-backlog
- A subdirectory that has a spine of its own. It owns its own members, so the parent stops at its boundary. Recursion is the same rule applied again.
The workspace parses spines and nothing else. A detail note’s body is never scanned — not for tasks, not for identifiers, not for fields. Every fact this spec derives about a note comes from its path: where it sits, and what links to it. Two consequences are worth stating because they are what keep the layer cheap: resolving a workspace reads exactly the spines, and no note can ever contradict the spine about its own state.
The one reserved path: archive/
archive/ directly inside a workspace is reserved, and it is where growth goes
to go cold. A good backlog is also the history of what got done and when; the spine cannot
hold that and stay dense.
archive/<name>.md- An archived note — the detail note of a task that closed, moved here
whole. Exempt from
MDTO513: having no spine line pointing at it is what being archived means. archive/<year>.md- A rollup page: one line per closed task, appended in date order, sharded by
year so no single file grows without bound. An ordinary document — its
- [x]lines look like tasks and are parsed as none. A rollup is a record, not a backlog. - a spine in here
MDTO502.archive/is never scanned for spines, so a whole sub-backlog filed into it would have its tasks invisible forever — which is worse than either reading them or refusing them.closed: 2026-08-09- What the
archiveverb stamps in an archived note’s frontmatter. A fact-date and a courtesy to the reader; 0.1 never reads it back, and its absence is not a diagnostic. The path is what makes a note archived.
Containment and archive/ are the only two places Markdown To has made a path
semantic, and both are stated in the grammar rather than assumed by a tool. Containment is
unavoidable: a directory model whose membership was declared in file contents
would need a manifest, and a manifest is the generated preamble the governing principle
rejects.
archive/ was avoidable and was chosen anyway. Marking closed work in
frontmatter instead means the gardener’s sweep rewrites every note it touches, and a
reader cannot see what is cold by looking. One reserved name, in one position, is
a smaller tax than either.
Refinement one — cross-file blockers
[email protected] classifies every [[Name#^slug]] blocker as
external and never diagnoses it, because a single-file parse has no right to open
another file. A workspace has been handed the other files, so it refines — and refines
only.
- satisfied
- The target is a member spine, the slug is in it, and that task is
[x]or[-]. Keying on terminal rather than on done is backlog’s rule, unchanged, one file further out. - pending
- The target exists and is not terminal.
- unresolved
- The target spine is a member but has no such slug:
MDTO510, and the task stays blocked. Recovery always errs toward not offering work an author said was waiting. - external
- The name is not a member of this tree, so it stays exactly what
[email protected]made it.[[handover#^dataset]]is still external, still undiagnosed. Do not “fix” it by deleting it.
This is a strict addition. A [email protected]-only tool reading the same
spine sees precisely what it saw before, and the two disagree only in the safe direction: a
blocker the workspace calls satisfied is one the single-file tool still calls
external, and therefore still blocking. And MDTO510 is a warning where
the same mistake inside one file is the error MDTO411 — both files
conform on their own, and a file’s conformance must not depend on its neighbours, or
moving a file would change whether another file validates.
Refinement two — delegation
A sub-backlog’s work is invisible until the root points at it. A spine task delegates when its text contains a wikilink resolving to that sub-backlog’s spine — and the root spine stays the sole priority authority, which is what keeps a tree from needing a ranking function nobody wrote.
- [/] Voice v3 lanes → [[voice/INDEX]] ^voice-v3 - [ ] Decide the pricing page copy → [[INDEX]] ^pricing-copy - [x] Rebuild the harbour dataset → [[harbours/INDEX]] ^harbours
- ready, extended
- A task in a sub-backlog is ready when it is ready by backlog’s own rule within its own spine and every delegating ancestor is non-terminal and unblocked. Nesting-as-decomposition, across a file boundary.
- ordering
- The delegating line’s position ranks the whole subtree; inside it, the sub-spine’s own bands and document order apply. No band is merged across files and no order is invented between two subtrees that no single line ranks.
- line 2 —
[[INDEX]] MDTO515. A tree of agentsFS spines has anINDEX.mdat every level, so this reference is ambiguous by construction and resolves to none of them. The repair is a path suffix. It is also whypricing/then reportsMDTO511— one mistake, two findings, and the second explains the first.- line 3 — a done delegation
MDTO512: the task is terminal while the sub-backlog it delegates to still has open work. The cross-fileMDTO420, and a warning for the same reason — the file says what it says, and validation is advice.
A sub-backlog nothing delegates to is MDTO511, info rather than warning:
parked workstreams are legitimate, and the tasks in one are parsed, listed on request, and
never offered as ready. The → is agentsFS’s punctuation, not grammar. What
the workspace reads is the wikilink.
Refinement three — ticket links, and what stays file-scoped
A wikilink on a spine task that names a member is a ticket link: this task’s accumulated state lives in that file. It resolves by filename without extension against every member of the tree, disambiguated by path suffix, exactly as the shared conventions already prescribe for a wikilink.
- a note nothing links
MDTO513, info — unless it is insidearchive/. Either it was left behind when its line was archived, or it is a note nobody can reach.- an open task linking into
archive/ MDTO514, warning. The work is open and its note is filed closed: move the note back, or close the task.- a link naming no member
- Not a finding of any kind. Wikilinks are inert in the core, and a spine may reasonably link a file that is not in the workspace.
- identifiers
- Stay file-scoped. Two sibling notes may both own
^lanes; a cross-file reference always carries the filename, which is what disambiguates it. There is no workspace-wide identifier namespace and no diagnostic for a slug reused in two files.
Read [email protected] in full — the normative text Shared conventions, including §13
§ 03
Verb reference
[email protected] owns three verbs — graduate,
archive, sweep — and they are exactly the edits that move
work between files. Every single-file verb stays
[email protected]’s and is unchanged: add,
start, finish, drop, block,
unblock, promote, demote and reorder
edit one spine, and this namespace neither wraps nor replaces them.
Every verb here writes more than one file, so every verb here is a multi-file operation: a manifest of every file it will touch declared up front, every hash checked before a single byte is written, writes applied in declared order through the same temp-plus-rename a single-file write uses, and — because POSIX has no multi-file rename — rollback attempted and partiality reported, per file, rather than atomicity claimed.
That is why graduate was specified in [email protected] and
deliberately not shipped there: the edit was never in doubt, the transaction was.
It keeps its name and ships from the namespace whose unit can hold the
transaction — mdto backlog graduate stays “not
implemented in this build”, because a [email protected] file alone does not
know what directory it is in.
graduate
mdto backlog-workspace graduate <ref> --to <path>
[--sub <path>]Creates the note at <path>, then rewrites
the task’s text so it links it — marker, fields, blocker clause and identifier
preserved exactly. The children stay on the spine as well, because moving them is a second
decision an author may not want. Manifest: note (create) → spine (write), content
before the reference that points at it, so an interrupted run leaves an orphan rather than
a dangling link.
archive
mdto backlog-workspace archive <ref>…
[--year <YYYY>] [--sub <path>]Moves the task’s note into archive/, stamps
closed:, appends one line to the year’s rollup page, and deletes the
task’s lines from the spine. Refuses on a non-terminal task — archiving
is filing what closed, and there is no --force for that. A task with no note
contributes only the rollup line.
sweep
mdto backlog-workspace sweep [--band <name>]
[--sub <path>] [--dry-run]archive over every terminal task on the spine, in
document order, as one transaction: one manifest, every hash checked before the
first write, a mid-sequence failure rolled back and reported per file. The gardener’s
verb. A loop over archive is precisely what §13 exists to prevent
— a sequence that can stop in the middle with no report.
Which spine, and the common flags
--dir <path>- Names the workspace. Without it, the workspace is the nearest ancestor of the working
directory — the working directory itself first — that contains exactly one
file declaring
backlog@.MDTO500andMDTO501are refusals here, not findings: a verb that cannot say which page is the authority does not write. <ref>,--sub- The ref is backlog’s: a pinned identifier with or without the caret, otherwise a
case-insensitive substring of the task’s text. It is matched against the root
spine only unless
--subnames a sub-backlog, so a slug that exists in two spines is never ambiguous by accident. --expect <path>=<hash>- Repeatable: the expected source hash of one manifest file. Any mismatch aborts the
whole operation before any write and names the file whose hash moved — exit 23,
nothing written.
--forceoverrides the validation refusal, never this check. --dry-run- Prints the manifest and every file’s diff and writes nothing — and still performs the hash check, so a dry run of an operation that would refuse says so rather than describing an edit that could not be applied.
--json- The
mdtoJson: 1payload plus §13’sfilesarray, in manifest order, with each file’s action, whether it changed, its hash, and — on a failure — whether it was restored. The singularfile,hashandeditskeep describing the spine, so a reader that knows only the single-file shape still reads something true.
What no verb here ever does
Writes a byte before every manifest hash has been
checked·claims atomicity, or reports success after a
partial write·touches a member the manifest did not
declare·reformats a file it opened only to
hash·parses a detail
note·archives a non-terminal
task·derives a task’s state from the sub-backlog it
delegates to·resolves an ambiguous ref by picking the
first match·rewrites the markdownto version of
any member·writes into a workspace with an error —
and never against a moved hash, whatever --force says.
§ 04
Diagnostics carry the repair
Nine codes, and six of them are warnings and info rather than errors — which is the shape of a layer that sits over files that are each already conforming. A stale reference between two good files is a finding about the pair; only a directory that cannot say which page is the authority makes the workspace itself non-conforming.
Every other sheet on this site prints diagnostics that parse() produced a
moment before the page was built. These are read from the fixtures’
normative .expected.json files instead, because resolving a workspace
is not something parse() does: this spec ships its normative layer —
document, schema, fixtures, codes — ahead of the engine that will satisfy it. The
line of source under each finding is still read from the real fixture file, so a wrong
line number would show up here as the wrong line of Markdown.
-
MDTO500 error no-spine/ · line 1
no-spine/No spine: no file directly in this directory declares
markdownto: backlog@…, so it is not a workspace. -
MDTO501 error two-spines/roadmap.backlog.md · line 2
markdownto: [email protected]Two spines in one workspace:
INDEX.mdalready declares[email protected]. Move one into a subdirectory of its own, or merge them. -
MDTO502 error spine-in-archive/archive/voice.md · line 2
markdownto: [email protected]archive/voice.mddeclares[email protected], butarchive/is never scanned for spines, so its tasks would never be read. Move it out of the archive, or fold its lines into the year's rollup page. -
MDTO510 warning blockers/INDEX.md · line 18
- [ ] Write the migration note — blocked by [[voice/INDEX#^silence-trim]]Blocker
[[voice/INDEX#^silence-trim]]names a member spine but no^silence-trimin it, so it can never lift. Fix the reference. -
MDTO511 info delegation/pricing/INDEX.md · line 2
markdownto: [email protected]Sub-backlog
pricing/is parsed but never ready: no task on a member spine delegates to it. -
MDTO512 warning delegation/INDEX.md · line 26
- [x] Rebuild the harbour dataset → [[harbours/INDEX]] ^harboursTask is done but the sub-backlog it delegates to,
harbours/, still has 1 non-terminal task. -
MDTO513 info gardening/dst-bug.md · line 1
---Detail note
dst-bug.mdis linked from no task on any member spine. Archive it with the task it belonged to, or link it. -
MDTO514 warning gardening/INDEX.md · line 15
- [/] Cache the tide tables so the app works offline → [[offline-cache]] ^offline-cacheTask is in progress but the note it links,
archive/offline-cache.md, is filed closed. Move the note back beside the spine, or close the task. -
MDTO515 warning delegation/INDEX.md · line 21
- [ ] Decide the pricing page copy → [[INDEX]] ^pricing-copyMember reference
[[INDEX]]matches 4 members of this workspace, so it resolves to none of them. Add enough path to name one:[[pricing/INDEX]].
A member brings its own findings with it
A workspace is only as conforming as its members, and the layer adds nothing at all to a
member’s own diagnostics except the one thing a tree-wide report needs: which file
this is about. Below is a real sub-backlog spine and the real
mdto validate output for it — the same two findings the workspace reports,
with voice/INDEX.md attached.
--- markdownto: [email protected] description: The v3 voice work — one lane per speaker, so a lane can be re-rendered alone. --- ## Now - [/] Split the lanes so a lane can be re-rendered alone ^lanes - [ ] Loudness-match the three lanes — blocked by [[#^normalizer]] ^loudness ## Icebox - [ ] Trim the silence at the head of each lane
-
MDTO411 error line 9
- [ ] Loudness-match the three lanes — blocked by [[#^normalizer]] ^loudnessBlocker
[[#^normalizer]]names no task in this file;#^targets are resolved within the document. -
MDTO401 error line 11
## IceboxUnknown band
Icebox: [email protected] reserves the band namesNow,Next,Later,Someday,Done. Declarebands:in frontmatter to use your own.
The codes this spec owns
MDTO500–MDTO599 belongs to
[email protected], in two families: the unit in
500–509, relations in 510–519.
520–529 is held for the findings a transacting verb will
need. Codes are never reused, renumbered or repurposed — and every code below is
exercised by a directory fixture, which is the only reason this page can print them at all.
backlog@, so it is not a workspace.
archive/ declares
backlog@; the archive is never scanned for spines, so its tasks would never
be read.
^slug in it, so it can never lift.
MDTO420.
archive/ that no task on any
member spine links.
archive/:
the work is open and its note is filed closed.
MDTO500 and MDTO501 are the envelope errors of this layer. With
no spine, or no way to choose one, nothing else can be said — so nothing else is: no
member is classified and no relation is refined. Everything else recovers locally, exactly
as the shared conventions require, and a broken relation always leaves its task
blocked rather than ready.
§ 05
For agents
This is the spec an agent works from: pull the top ready item, leave the note, close the line, and let the gardener sweep. What it buys over freehand editing is that the two moves which touch several files at once — graduating a task into a note, filing a closed one — happen under a contract instead of a hope.
$ mdto spec backlog-workspace [email protected] — agent-facing specification 1. Purpose what the unit is: a directory, not a file 2. Grammar spine · notes · archive/ · sub-backlogs · three refinements 3. Rationale why [email protected] does not change 4. Examples directory fixtures + their expectations 5. Verbs graduate · archive · sweep $ mdto backlog-workspace graduate voice-v3 --to voice-lanes.md --dry-run manifest voice-lanes.md create INDEX.md write a1b3f0… --- /dev/null +++ b/voice-lanes.md --- a/INDEX.md +++ b/INDEX.md -- [/] Voice v3 lanes ^voice-v3 +- [/] Voice v3 lanes → [[voice-lanes]] ^voice-v3 note --dry-run: nothing written $ mdto backlog-workspace sweep --dry-run 3 terminal tasks · 5 files in the manifest · 1 rollup page appended
-
The spine stays the file you already know
An agent that has learned
[email protected]has learned every file in a workspace: same bands, same four markers, same blocker clause, same lazy identifiers. What is left to read is which directory owns what, and the three relations that cross a file boundary. Adoption is creating a directory, not migrating a format. -
Fixtures are the contract, and here they are directories
A case is a real directory tree with a sibling
<case>.expected.json— a member file would be part of the workspace it describes. It carries the resolution as well as the diagnostics, because a workspace’s normative facts are not only its findings: which file is the spine, what each member is, and how every cross-file blocker refined.universal
validaterenderspec -
Fetchable, not scrapeable
The normative document lives at a stable URL beside an
llms.txt, versioned with the spec, andmdto spec backlog-workspaceprints the same bytes locally.