Markdown To

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
Read agent skill · authoring and editorial guide

§ 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.

Fig. 1 The unit — a conformance fixture, file for file directory
specs/backlog-workspace/fixtures/valid/delegation/ 9 files, 4 workspaces
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.

Fig. 2 The spine — still exactly a [email protected] file static render
tidepool/INDEX.md
---
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
rendered · mdto render static · no script

Tidepool

4 openof 5 tasks across 3 bands · 1 blockedread-only

Markersopenin progressdonedropped (cancelled)

01

Now

2 in progress
  • Cache the tide tables so the app works offline → offline-cache

    in progress^offline-cache

  • Voice v3 lanes → voice/INDEX

    in progress^voice-v3

02

Next

2 open
  • Decide the pricing page copy → INDEX

    ^pricing-copy

  • Home-screen widget showing the next high tide

    blocked by ^offline-cache^widget

05

Done

closed1 done
  • Rebuild the harbour dataset → harbours/INDEX

    ^harbours

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.

No file declares this spec

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.

2.1

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 is MDTO500; two is MDTO501, because which page ranks the work is exactly the question the unit exists to answer.
detail note
Any other .md the workspace owns, at any depth. An ordinary document, usually with nothing but a description: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.

2.2

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 archive verb 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.
Two paths that mean something, in a project that has never had one

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.

2.3

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.

2.4

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.

delegation, and the two ways it goes wrong
- [/] 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 an INDEX.md at every level, so this reference is ambiguous by construction and resolves to none of them. The repair is a path suffix. It is also why pricing/ then reports MDTO511 — 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-file MDTO420, 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.

2.5

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 inside archive/. 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.

Three verbs, one contract: conventions §13

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 transactionmdto backlog graduate stays “not implemented in this build”, because a [email protected] file alone does not know what directory it is in.

verbsignaturesource edit

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.

3.1

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@. MDTO500 and MDTO501 are 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 --sub names 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. --force overrides 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: 1 payload plus §13’s files array, in manifest order, with each file’s action, whether it changed, its hash, and — on a failure — whether it was restored. The singular file, hash and edits keep 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.

Read this before the list

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.md already 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.md declares [email protected], but archive/ 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-trim in 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]] ^harbours

    Task 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.md is 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-cache

    Task 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-copy

    Member reference [[INDEX]] matches 4 members of this workspace, so it resolves to none of them. Add enough path to name one: [[pricing/INDEX]].

4.1

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.

tidepool/voice/INDEX.md 2 errors
---
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

mdto validate · verbatim 2 diagnostics

  • MDTO411 error line 9

    - [ ] Loudness-match the three lanes — blocked by [[#^normalizer]] ^loudness

    Blocker [[#^normalizer]] names no task in this file; #^ targets are resolved within the document.

  • MDTO401 error line 11

    ## Icebox

    Unknown band Icebox: [email protected] reserves the band names Now, Next, Later, Someday, Done. Declare bands: in frontmatter to use your own.

4.2

The codes this spec owns

MDTO500MDTO599 belongs to [email protected], in two families: the unit in 500509, relations in 510519. 520529 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.

MDTO500 error No spine: no file directly in this directory declares backlog@, so it is not a workspace.
MDTO501 error Two spines in one workspace directory. Reported on the second in sorted path order, naming the first.
MDTO502 error A file inside archive/ declares backlog@; the archive is never scanned for spines, so its tasks would never be read.
MDTO510 warning A cross-file blocker names a member spine but no such ^slug in it, so it can never lift.
MDTO511 info A sub-backlog no task delegates to: parsed, listed, never ready. Parked workstreams are legitimate.
MDTO512 warning A terminal task delegates to a sub-backlog that still has non-terminal tasks. The cross-file MDTO420.
MDTO513 info A detail note outside archive/ that no task on any member spine links.
MDTO514 warning A non-terminal task links a note inside archive/: the work is open and its note is filed closed.
MDTO515 warning A member reference matches more than one member, so it resolves to none of them. Add a path suffix.
A workspace that cannot resolve reports exactly one finding

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 — working name normative layer shipped · engine next
$ 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, and mdto spec backlog-workspace prints the same bytes locally.

    fetch markdownto.ai/specs/backlog-workspace.md