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:
Discoverability tax. An agent must load 19 schemas, learn each
field, and chain read→patch with version checks — for what is, underneath, "edit
this HTML element." Modern agents author HTML fluently; the indirection adds steps
without adding capability.
Availability coupling. When the MCP server is down (it bounced
several times on 2026-05-27), a tool-only model leaves agents stranded — and one
slipped to authoring markdown because the "HTML path" felt gated behind a
tool. The document is a file; it should always be editable.
Brittleness when the schema drifts. Tools encode field names;
when render/parse fall out of sync the tool lies (we hit three such bugs:
chosen vs choice, followup status not derived
from resolved_at, the generate-prompt gate). A prompt+exemplar model
makes the contract visible and reviewable in the artifact itself.
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:
Head scalars — <meta name="plan-*"> tags carry
the at-a-glance state (status, roi, effort, milestone, sprint, impl, …).
Body sections — <section data-reckon="…">
blocks carry the structured records (decisions, followups, questions, research,
comments). Everything outside them is free prose you write however you like.
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-*">)
Tag
Why it's machine-readable (who consumes it)
Owner
plan-slug / docs-project
Server keys the plan + project; required for discovery & cross-plan links.
The dependency DAG → critical-path & the generate-prompt route.
author
plan-impl · plan-version
Server-owned. impl is computed; version is the optimistic-concurrency counter. Never author these.
server
plan-modified
Staleness detection; server-stamped on write.
server
Body sections (data-reckon)
decisions — <div class="r-dec" data-key data-choice data-by data-when> with .r-dec-q (question), .r-dec-opts (option buttons), .r-dec-rat (rationale). Why: a decision is a contract — the aggregator and the fleet-prompt need the locked choice + rationale, and the audit trail needs who/when. Derive "locked" from a non-empty data-choice (do not add a separate flag).
followups — <article class="r-fu" data-id data-status data-resolved-at data-resolved-by> with title, body, a §05 <pre> prompt, and an .r-fu-outcome. Why: the followup chain IS the work queue; list_followups, "what's next", and fleet dispatch read it. Derive status=resolved from resolved_at (the 2026-05-27 bug was deriving it wrong).
questions — open design questions with options + resolution. Why: surfaced as blockers on the generate-prompt; resolved ones link to the deciding decision.
research / comments — references and section-anchored notes. Why: carried into the fleet prompt so a worker reads the same context.
Rule of thumb: tag it only if a view reads it; otherwise prose.
Keep the machine-readable surface minimal and derivable.
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
Do
Don'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:
draft→active when work starts; →blocked with a
blocker record + owner/next-step when stalled; →shipped when
impl reaches 1.0 and the driving followup resolves.
Per-stage archival. On a phase transition, write a NEW file
under docs/archive/<slug>-<phase>.html (e.g.
-shipped.html, -12-5-landed.html) — the frozen audit trail,
excluded from the live inventory. Don't overwrite the evergreen.
impl fraction = shipped-items / items; computed by the server —
reflect reality via section status, don't hand-set the meta.
Collapse-on-landing (§4 above) keeps the evergreen a
30-second-scan dashboard, with detail in the archive.
§7 · Agile models reckon supports
Sprints (S0,S1,… in index.json): a themed,
time-boxed set of plan items with status planned/active/done and an
active_sprint_id. Managed via add_sprint_item /
move_sprint_item / update_sprint (or edit index.json).
Closing an expired sprint and rolling unfinished items forward is normal hygiene.
Milestones (M0,M1,…): named targets with evidence
(commits) and dependencies; plans tag into one via plan-milestone.
Gap to fix (see followups): milestones have no write tool yet.
ROI × effort drive sprint ordering & capacity. Keep them
honest; they're the prioritisation signal.
The followup chain is the work queue. Each followup carries a
ready-to-paste §05 prompt, a tier, a blocked_by, and a
recommends_skill. A worker resolves its driving followup and writes the
next — the chain never silently ends (close it explicitly with
outcome: "done — no followup").
Fleet dispatch. For multi-item sections, the §05 prompt + the
dependency DAG (depends-on/blocks) let a coordinator fan out
non-overlapping workers and walk the critical path (the generate-prompt feature).
§8 · The MCP's right role (reframed)
Not a straitjacket — a version-safe writer + a parser/reader for
tooling. The agent contract becomes:
Read current state (read_plan or
GET /plan) to learn the version and what's there.
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).
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)
Prompt-first, tool-second. Ship a single canonical
authoring prompt (the §4 templates + §5 dos/don'ts + §3 reasoning) that an
agent reads once. The tools become an optional appendix, not the entry point.
One self-describing exemplar in-repo. Keep a fully-annotated
docs/_exemplar-plan.html agents copy from — examples beat schemas.
Derive, don't duplicate. Every piece of state should have ONE
source: status from resolved_at, "locked" from
data-choice, impl from section counts. Duplicated state is
where the render/parse bugs lived.
CI round-trip parity test. Add a test asserting
render(parse(html)) == html (idempotent) and
parse(render(state)) == state. This catches the symmetry bugs
(chosen/choice, status/resolved_at) before they ship — the single highest-leverage
robustness fix.
Minimal machine-readable surface. Fewer tags, all consumed by a
view. Everything else is prose. Less to learn, less to drift.
Bypass-with-announcement is first-class, not an exception. Make
hand-editing a documented, blessed path so agents don't reach for markdown when a
tool is unavailable.
Optimistic concurrency + an escape hatch. Keep version checks,
but offer a clearly-labelled force/merge for the lone-writer case so a transient
version mismatch never blocks progress.
Dogfood. Track reckon's own design as reckon plans (this page is
one). If the authoring experience hurts here, fix it here first.
§ 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.