Draft — design proposal. Thesis: agents don't need a tool straitjacket; they write HTML well. What they need is a great prompt, templates, exemplars, clear dos & don'ts, and the reasoning behind the machine-readable contract — plus a shared understanding of the lifecycle and agile models reckon supports. The MCP tools stay, but as an optional version-safe writer, not the only door.

§1 · Problem — tool-mediation friction

The reckon MCP exposes ~19 tools (read_plan, patch_plan, lock_decision, resolve_followup, add_sprint_item, …). They're well-built, but treating them as the mandatory interface has costs:

Reframe: the HTML is the plan and the store. Lead with the document and the prompt; offer the tools as a convenience for the one thing they're genuinely better at — version-safe concurrent writes.

§2 · The model — the plan HTML is the document AND the store

One docs/<slug>.html file is the entire plan. Its state lives as ordinary HTML in that same file — there is no sidecar. Two regions matter:

An agent edits the file directly. The server parses these regions on GET /plan/<project>/<slug> so the dashboard, status, and fleet-prompt generator can read state; it rewrites them on POST (or via MCP). Editing the plan = editing the HTML. That's the whole mental model.

§3 · The machine-readable contract — every tag, and WHY

Only a small, fixed set of things must be machine-readable, and only because a human-or-machine reader downstream needs them. If a field isn't consumed by a view, it doesn't need a tag — write prose.

Head scalars (<meta name="plan-*">)

TagWhy it's machine-readable (who consumes it)Owner
plan-slug / docs-projectServer keys the plan + project; required for discovery & cross-plan links.author
plan-title / plan-summaryDashboard cards, search, the fleet-prompt header.author
plan-statusLifecycle filter (active/blocked/shipped…); kanban columns; "what's open" queries.author / set on transition
plan-roi · plan-effortSprint ordering & capacity planning (ROI×effort).author
plan-milestone · plan-sprintMilestone rollup & sprint membership.author / sprint tool
plan-tierModel-tier hint for dispatch (haiku/sonnet/opus).author
plan-depends-on · plan-blocksThe dependency DAG → critical-path & the generate-prompt route.author
plan-impl · plan-versionServer-owned. impl is computed; version is the optimistic-concurrency counter. Never author these.server
plan-modifiedStaleness detection; server-stamped on write.server

Body sections (data-reckon)

Rule of thumb: tag it only if a view reads it; otherwise prose. Keep the machine-readable surface minimal and derivable.

§4 · Templates (copy-paste exemplars)

A new plan (minimal, valid, parseable)

<!doctype html><html lang="en"><head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="docs-project" content="PROJECT">
<meta name="plan-slug" content="SLUG">
<title>TITLE | PROJECT</title>
<link rel="stylesheet" href="/_shared/foundation.css">
<link rel="stylesheet" href="/_shared/dashboard.css">
<meta name="reckon-type" content="plan">
<meta name="plan-title" content="TITLE">
<meta name="plan-summary" content="ONE-LINE SYNOPSIS">
<meta name="plan-status" content="draft">
<meta name="plan-roi" content="high"><meta name="plan-effort" content="M">
<meta name="plan-milestone" content="M1"><meta name="plan-owner" content="NAME">
<meta name="plan-modified" content="YYYY-MM-DD">
<!-- do NOT author plan-impl / plan-version: server-owned -->
</head><body><main class="plan-doc">
  <h2 id="s1">§1 · …</h2><p>prose…</p>
  <section data-reckon="decisions" id="decisions"></section>
  <section data-reckon="followups" id="followups"></section>
</main></body></html>

A non-plan doc (RCA, ticket, explainer)

Same skeleton, but <meta name="reckon-type" content="doc"> and no lifecycle fields. Never author these as markdown.

A decision (locked)

<div class="r-dec" data-key="resize-backend" data-choice="torch"
     data-by="Simon McIntosh" data-when="2026-05-27T09:30:00Z">
  <p class="r-dec-q">Resize backend — cv2 or torch?</p>
  <p class="r-dec-opts">
    <button class="r-opt chosen" data-value="torch">torch F.interpolate</button>
    <button class="r-opt" data-value="cv2">cv2.resize</button></p>
  <p class="r-dec-rat">cv2 absent from venv; torch matches the daemon byte-for-byte.</p>
</div>

Open decision: same markup with empty data-choice and no rationale.

A followup (with the §05 dispatch prompt)

<article class="r-fu" data-id="f-2026-05-27a" data-status="open"
         data-tier="sonnet" data-written-by="NAME" data-written-at="YYYY-MM-DD"
         data-recommends-skill="/reckon-ship SLUG --section 3">
  <h4 class="r-fu-title">Imperative one-liner</h4>
  <div class="r-fu-body">2–3 sentences: why queued now, what landed before.</div>
  <pre class="r-fu-prompt">Project: …
Plan: SLUG (§N)
Tier: sonnet
Context … / State to read … / Locked decisions to honour … /
Open decisions to surface (do not resolve) … / Constraints … /
Done-when: 1) artefact 2) tests green 3) followup written + this one resolved</pre>
</article>

Resolve by adding data-resolved-at/data-resolved-by and an <p class="r-fu-outcome">; status is derived from resolved_at.

Collapse-on-landing (closure)

<section id="s12-5" class="section-landed">
  <header><span class="badge badge-shipped">✓ landed 2026-05-27</span>
    <h2>§12.5 — Bulk-encode rbb</h2></header>
  <p class="landed-summary">Encoded 9,527 shots on 4×H200 in 2h.
    Full record: <a href="archive/SLUG-12-5-landed.html">…landed</a> (abc1234).</p>
</section>

§5 · Dos & don'ts

DoDon't
Edit the plan HTML directly; treat it as the document it is.Author markdown for anything with a table/timeline/status (use reckon-type=doc).
Keep plan-status/roi/effort/sprint accurate as reality changes.Author plan-impl or plan-version — they're server-owned.
Reuse the exact class/attr names from §4.Invent new tag names or restructure data-reckon sections.
Write a §05 prompt on every followup; resolve the driving one.Leave a resolved followup without resolved_at/outcome.
Collapse a shipped section to a 2–4 line summary + archive link.Let the evergreen grow into an unreadable transcript.
Use MCP/reckon-edit when a human might be editing concurrently (version safety).Blind-overwrite without reading current version first.
When editing by hand instead of a tool, announce "bypassing /reckon-edit because …".Silently bypass — that hides drift.

§6 · Lifecycle tracking

Phases: draft → active → in-progress → blocked → shipped → archived, carried in plan-status + the status badge. Transitions:

§7 · Agile models reckon supports

§8 · The MCP's right role (reframed)

Not a straitjacket — a version-safe writer + a parser/reader for tooling. The agent contract becomes:

  1. Read current state (read_plan or GET /plan) to learn the version and what's there.
  2. Write either via MCP (when a human/another agent might be editing the same plan — optimistic concurrency protects you) or by editing the HTML directly and announcing the bypass (fine for a lone writer).
  3. Treat version/impl/modified as server-owned.

The tools should feel like git: there when you want safety, never the only way to change a file. Their real value is (a) parsing state for views, and (b) refusing a write whose expected_version is stale.

§9 · Best-practice recommendations (mine)

§ Decisions

Are MCP tools the mandatory interface, or optional?

Agents write HTML fluently; mandating 19 tools adds friction and couples authoring to server availability (it caused a markdown slip during an outage). Keep tools for concurrent-write safety + parsing; lead with prompt/templates/exemplars.

How do we keep render/parse in sync?

Three render/parse symmetry bugs shipped on 2026-05-27 (chosen/choice, followup status, generate-prompt gate). A round-trip test is the cheapest durable guard.

Duplicate state flags, or derive from one source?

Duplicated flags are where drift bugs live. One source of truth per field, derived everywhere else.

§ Followups

Write the canonical authoring prompt + in-repo exemplar plan

Distil §3–§5 into a single agent-facing authoring prompt and a fully-annotated docs/_exemplar-plan.html that agents copy. Fold the prompt into reckon-create/reckon-edit SKILL.md so it's the first thing an agent reads.
Project: reckon
Plan:    agent-plan-authoring (§4–§5)
Tier:    sonnet
Context  This design plan landed; now make it operational.
Done-when
  1. docs/_exemplar-plan.html — annotated, copy-paste-ready, passes the parser.
  2. A "Authoring prompt" block added to reckon-create + reckon-edit SKILL.md
     (templates + dos/don'ts + the reasoning table from §3).
  3. This followup resolved; followup written for the CI round-trip test.

Add the render/parse round-trip parity test to reckon CI

Guard against the symmetry bugs: assert render(parse(html))==html and parse(render(state))==state across decisions, followups (incl. resolved), questions, meta scalars. Seed it with the three 2026-05-27 regressions.
Project: reckon
Plan:    agent-plan-authoring (§9)
Tier:    sonnet
Done-when
  1. tests/test_roundtrip.py — render∘parse idempotent on a fixture plan.
  2. Regression cases: chosen/choice, followup status←resolved_at, gen-prompt gate.
  3. Wired into the reckon test run; this followup resolved.

DONE via the schema work (commit cf5fb2b) — the canonical round-trip parity test landed in tests/test_schema.py (test_state_round_trip + the model_dump-shape == read_state and to_html∘from_html == write_state∘read_state byte-identity assertions). Deliberate improvement on the original done-when: the byte-equality render(parse(html))==html invariant is fragile (write_state normalises whitespace/attr-order, so it flags every hand-authored plan); replaced by the robust STATE-LEVEL invariant parse(render(state))==state PLUS byte-identity of the regenerated reckon-owned sections. Covers the 2026-05-27 symmetry regressions: choice-vs-chosen (chosen is now derived, never stored) and followup/question status←resolved_at (derived in both parser and model). Wired into the standard reckon run (uv run --with pytest pytest tests/ -q); 702 passed.

Close the milestone-tooling gap

Sprints have add/move/update tools; milestones have none and the data was lost in a migration. Add add_milestone/update_milestone (or document index.json milestone editing) so milestone rollups aren't read-only-and-empty.
Project: reckon
Plan:    agent-plan-authoring (§7)
Tier:    sonnet
Done-when
  1. Milestone create/update path (MCP tool or documented index.json edit).
  2. imas-ambix M0–M3/PS milestones repopulated as a test case.
  3. This followup resolved.