Realises the reframe in agent-plan-authoring
(prompts/exemplars over tool straitjackets) with two concrete backbones the draft lacked: a
versioned plan schema as the generate↔serve contract, and a collapse of the
19 MCP plan tools to ~4. History: reckon-mcp-plan
(the original surface), reckon-mcp-gaps (power-user adds).
Authored 2026-05-29 — and authored as a reckon HTML plan, because writing a markdown design doc for an
HTML planning system was the very inconsistency this plan exists to end.
§1 — Problem
Two coupled problems, both surfaced by using the system heavily this week:
No formal schema = no contract. What a valid plan "is" lives in three places that
can drift: PLAN-FORMAT.md (prose), _plan_html.py (parse/render code), and the
skills (examples). Agents generate HTML; the server serves/parses it; nothing
machine-checkable guarantees the two agree. Malformed data-reckon state can be written and
only discovered when the SPA fails to render it. And the head meta already differs between projects
(reckon plans carry plan-title/plan-status; imas-ambix plans don't) — drift in
the wild, today.
Tool sprawl + gaps. 19 MCP tools (reckon/mcp.py), all one shape
(read dict → mutate one path → version-checked write) — thin presets over 5 store
primitives, yet with real gaps (no create_sprint until this plan; no create_plan;
no reindex). Both too many and too limited.
§2 — The schema IS the contract (the backbone)
A single, versioned, machine-readable plan schema becomes the contract between
agent-generated HTML and reckon-served content. This is the architectural keystone — it is what makes
"let agents write HTML, keep tools thin" safe: the schema (not a thicket of per-field tools) is
what enforces validity.
One source of truth. A Pydantic model PlanState (reckon already depends
on pydantic) defining every field: head meta (slug, type, status, roi, effort, tier, summary, milestone,
sprint, depends_on, informs, version, modified, impl) and body sections (decisions[], followups[],
questions[], research[], comments{}, plus the index/sprint envelope). A JSON Schema is derived
from it and published (served at /_shared/plan.schema.json) — the artifact agents and the
server both reference.
Validated on BOTH sides.Generate side: the write boundary
(edit_plan, §3) parses the candidate HTML → PlanState and rejects on
validation failure with field-level errors, so malformed state never lands. Serve side: the
parser/renderer (_plan_html) becomes schema-typed (parse → model → render), and
doctor validates every served plan against the schema to catch drift.
Versioned + maintained. The schema carries a schema_version; plans
declare the version they target. Format changes are made in ONE place (the model); parser, renderer,
skills, validator, and the published JSON Schema all regenerate/reference from it; doctor
migrates or flags older plans. This is the explicit answer to "creation AND maintenance of a schema":
the maintenance contract is "change the model → everything else follows; never hand-edit the three
drifting copies again."
This directly serves your framing: the schema is our contract between generated and served
content. With it, agents can author HTML freely (their strength) and the system still guarantees what
the server receives.
§3 — Tool-surface collapse (19 → ~4), built on the schema
The 19 tools are presets over 5 store primitives; the store already supports create
(write_plan at version 0; patch_plan on the index slug). So this is a
surface change, not a store rewrite. Target surface:
Tool
Role
Replaces
read_plan
Context injector — returns parsed state +
version + the schema/dos-don'ts inline; no slug → project discovery
The one safe write — small op vocabulary
(set / append / resolve / lock), slug="index" covers sprints+inventory,
create via version 0; schema-validates the result, version-checks,
renders schema-correct HTML, stamps
Maintenance — validate every plan against the
schema; reindex index.json rollups; report/migrate nonconformant plans
(new)
(discovery)
folded into read_plan (project mode)
—
Why any tools at all? Only for what direct HTML editing can't safely give in a
multi-agent (fleet) world: version-checked atomic writes (concurrency), schema-validated structured-state
HTML, auto-stamping, and reindex. Everything else — all prose, structure, new sections — is skill-driven
direct HTML authoring. The read tool is a prompt injector; the write tool is a thin, real safety
boundary.
§4 — Skills own authoring
The reckon-* skills become the primary surface for making plans: they carry the
schema, worked exemplars, dos/don'ts, and the lifecycle workflow, and they author/edit prose directly in
HTML. agent-plan-authoring already drafts this philosophy; this plan makes it real by giving
it the schema (what to conform to) and edit_plan (the safe boundary). Net division: skills
= how to author; schema = what's valid; edit_plan = how to write it safely.
§5 — Cross-project consistency (design once, for all served projects)
The schema must cover what is actually in use, not just one project's habits. Four projects are
served (mounts.json): imas-ambix (32 plans), imas-efit (9), reckon (its own), vv. The
schema-design research reads plans across all four to catalogue the real variety of meta fields and
data-reckon sections/attributes (the plan-title/plan-status
divergence is one known example), then defines the unifying schema as their normalisation. One contract,
all projects — so a plan authored in any repo renders consistently and validates against the same schema.
§6 — Already shipped (this session)
create_sprint MCP tool — fills the gap that blocked opening a sprint (both existing
sprint tools require the sprint to pre-exist). +3 tests; full suite 33 passed; committed to reckon main.
This analysis, grounded in reading mcp.py + _store.py + _plan_html.py.
Author the schema (§2) + publish the JSON Schema. Add edit_plan + create_plan
+ doctor + schema-injecting read_plan — additive, non-breaking.
Wire schema validation into edit_plan and the parser/renderer; tests.
Rewrite the reckon-* skills to schema + exemplars + dos/don'ts, using only
read_plan + edit_plan.
Deprecate the 15 granular mutators as thin aliases → edit_plan; run doctor
across all four projects; migrate nonconformant plans; then remove the aliases. MCP changes take effect
on server restart, so the 3-repo cutover is coordinated, not hot.
§ Decisions
What is the authoritative form of the plan schema?
Pydantic PlanState is the single source of truth; JSON Schema derived from it and published at /_shared/plan.schema.json. reckon already depends on pydantic>=2; one model types the parse→model→render round-trip AND the edit_plan write-boundary validation, ending the three-way drift between PLAN-FORMAT.md, _plan_html.py, and the skills. Locked up-front by the orchestrator because it gates F2's design (the schema must exist before the tools and skills can be built on it).
How strict is schema validation at the write boundary?
edit_plan REJECTS nonconformant writes (malformed state never lands), while read_state() stays LENIENT and never raises and doctor WARNS/migrates existing plans. This is the keystone constraint for F2: PlanState wraps read_state/write_state (from_html/to_html); validation is invoked only at the edit_plan/doctor boundary, so the running server + the 54 tests + every existing plan across 4 repos keep parsing. 'required' (docs-project/slug/title/status) means required-on-write, with read defaults (status→draft, title→<title>→slug). Locked up-front; gates F2/F3.
Target tool surface?
Target surface ~4: read_plan (context injector + discovery), edit_plan (the one safe write: op-vocab set/append/resolve/lock + create, slug=index covers sprints/inventory, schema-validated), doctor (validate + reindex), discovery folded into read_plan. doctor is worth exposing as an MCP tool because agents reindex/validate after bulk edits — distinct from the existing CLI `reckon doctor` (an infra-checker), so name/scope the MCP plan-validator to avoid collision. The 15 granular mutators become thin aliases→edit_plan in F5, then removed. Locked up-front.
§ Followups
F1 — Cross-project schema research (read ALL served projects)
Catalogue the real plan structure across imas-ambix, imas-efit, reckon, vv + the current implicit schema (PLAN-FORMAT.md, _plan_html.py), so the schema is their normalisation, not one project's habit.
Project: reckon
Plan: reckon-schema-and-tooling (§2, §5)
Tier: sonnet
Read the CURRENT implicit schema and ALL served projects, produce a variety report that the schema must cover:
- reckon/PLAN-FORMAT.md + reckon/reckon/_plan_html.py (parse/render — the de-facto schema)
- reckon/docs/agent-plan-authoring.html §3 (the machine-readable-contract reasoning)
- Every *.html plan across the 4 mounts (~/docs-server/mounts.json): imas-ambix (32), imas-efit (9),
reckon, vv. Enumerate: head meta fields used (note divergences e.g. plan-title/plan-status present in
reckon+efit but not imas-ambix), and every data-reckon section + its element attributes
(decisions/followups/questions/research/comments + index sprints/milestones/inventory).
Output: a normalised field inventory (required vs optional vs project-specific), the divergences to
reconcile, and a proposed PlanState field list. DO NOT change any plan. Report only.
DONE (F1 workflow weun9ks12, 6 agents). Catalogued the current implicit schema + all 4 served projects: imas-ambix 27 plans, imas-efit 502 (!), reckon 5, vv 4 (1 marked). Normalised field inventory + divergences delivered. KEY FINDINGS for F2: (1) plan-title/plan-status are universal in efit/reckon/vv but 81% in ambix — the gap is the LENIENT-READ path (read_state never raises; status→draft, title→<title>→slug), NOT a write downgrade → mark required-on-write. (2) docs-project is required identity but read_state() DROPS it — PlanState must add a `project` field and read_state must capture it. (3) Canonical shapes: decisions = dict[str,Decision] keyed by data-key (write contract); comments = dict[str,list] keyed by section anchor (default _top); parse_plan()'s decision LIST is a read-only SPA projection (.as_list()), not a second store — `chosen` there is redundant with `choice`. (4) Normalisations: roi med→mid; reckon-type doc→research; status enum must be the UNION draft|pending|active|in-progress|blocked|shipped|done|superseded|abandoned|archived|historical|reference or reject-on-write rejects valid plans. (5) Drop stray plan-project (2 efit root plans; not in _SCALARS). (6) IndexState: envelope {updated,project,doc,data{_version,active_sprint_id,sprints[],milestones[],timeline[],blockers[],projects?}}; inventory[] is SYNTHESISED live by discover_plans (never persisted); sprint items heterogeneous (bare-string|object → coerce). (7) Undocumented-in-prose but live in code: plan-archived/plan-read flags, the index envelope, timeline[]/blockers[]. Full proposed PlanState/IndexState field lists in the workflow output; fed verbatim into F2.
F2 — Author the versioned PlanState schema (the contract)
From F1: a Pydantic PlanState model (source of truth) + derived JSON Schema served at /_shared/plan.schema.json, versioned (schema_version). Type the _plan_html parse→model→render round-trip from it.
Project: reckon
Plan: reckon-schema-and-tooling (§2)
Tier: opus
Depends on F1. Author reckon/reckon/_schema.py: a Pydantic PlanState (+ IndexState) model covering the
normalised inventory, with schema_version. Derive + write a JSON Schema to docs/_shared/plan.schema.json
(served). Re-type _plan_html parse→PlanState and render PlanState→HTML so the round-trip is schema-checked.
Tests: every existing plan across the 4 projects parses into PlanState (xfail+catalogue any that don't —
those are F5 migrations). Keep optimistic-concurrency + atomic .tmp write unchanged.
SHIPPED (commit cf5fb2b). reckon/_schema.py: PlanState (source of truth) + Decision/Followup/Question/ResearchItem/Comment + IndexState/IndexData envelope; SCHEMA_VERSION=1.0; derived JSON Schema at docs/_shared/plan.schema.json (gen_json_schema/write_json_schema). _plan_html.py: ADDED from_html/to_html wrappers + additive docs-project capture; read_state/write_state behaviour byte-identical (existing callers untouched). Lenient read (never raises: med→mid, doc→research, status union via json_schema_extra not Literal, derived statuses, extra=ignore); strict validate_for_write() is the reject boundary. canonical_dump()=model_dump(exclude_unset=True) reproduces read_state's sparse-top/dense-nested shape; IndexData._version alias + by_alias dump override avoids the index-counter-zeroing trap. Verified: model_dump-shape==read_state dict, byte-identity to_html∘from_html==write_state∘read_state, state-level round-trip. Tests: 702 passed (54 pre-existing green); cross-project conformance 600/600 plans parse — ZERO F5 migrations needed. NEXT: f-tool-collapse (F3 edit_plan+doctor+read-injector, opus) + f-skills-rewrite (F4 skills, sonnet) dispatch in parallel on this schema.
F3 — edit_plan + doctor + read-injector (additive, non-breaking)
The ~4-tool surface, built on the schema. Add alongside the 19; don't remove yet.
Project: reckon
Plan: reckon-schema-and-tooling (§3)
Tier: opus
Depends on F2. In reckon/mcp.py + _store.py: add edit_plan(project, slug, ops[], expected_version, create=False)
— op vocab set/append/resolve/lock; slug="index" covers sprints+inventory; create via version 0; validates the
resulting PlanState against the schema (reject with field errors); version-check; render schema-correct HTML.
Add doctor(project) — validate every plan vs schema + reindex index.json rollups. Enrich read_plan to inject
schema/dos-don'ts + a discovery mode. Keep all 19 existing tools working. Tests via `uv run --with pytest
pytest tests/ -q` (NOT global pytest — it runs a python without bs4). 33 existing must stay green.
SHIPPED (commit 622ead9), additive + non-breaking. reckon/_store.py: pure op-engine apply_ops + per-verb helpers (_apply_set/append/resolve/lock/move), OpError, id/ts helpers, new_plan_html template. reckon/mcp.py: edit_plan(project, slug, ops, expected_version, create=False) — dict-in/dict-out: ops mutate a deepcopied working dict, validated via PlanState.validate_for_write (plans) / IndexData (index), rejected with field errors on failure, then persisted via the existing version-checked atomic write_plan (no model_dump round-trip → sidesteps the exclude_unset/fields_set trap). doctor(project) audits every plan (warn half; never mutates) + recomputes rollups. read_plan gains discovery mode (slug omitted → inventory + followups/questions/sprints facets) + with_schema=True context injection (schema + dos/don'ts + op_vocab). All 19 prior tools stay registered + green. Verbs set/append/resolve/lock/move map all 14 mutators; create via version-0 template (failed create unlinks the stub — no orphan); set impl clamps; set inventory.* is a durable no-op (folds update_inventory_item). +44 tests; 746 passed. Live integration check: F4's create/lock+append/ship/index/move exemplars all execute end-to-end; all 3 reject paths fire. SHARP EDGE for F5: edit_plan re-validates the WHOLE plan, so a plan with a pre-existing violation is un-editable via edit_plan until fixed — safe at cutover only because F1 found 600/600 conform; F5 MUST run doctor pre-cutover before removing the granular escape hatch.
F4 — Rewrite reckon-* skills around schema + edit_plan
Skills own prose authoring; reference the schema + exemplars + dos/don'ts; use only read_plan + edit_plan.
Project: reckon
Plan: reckon-schema-and-tooling (§4)
Tier: sonnet
Depends on F2/F3. Rewrite skills/reckon-create, reckon-edit, reckon-ship, reckon-status to: lead with the
canonical authoring prompt + copy-paste exemplars + dos/don'ts (fold in agent-plan-authoring §3-§5), point at
docs/_shared/plan.schema.json as the contract, and use ONLY read_plan (context) + edit_plan (write). Remove
references to the 15 granular tools.
SHIPPED (commit 9c17c35). reckon-create/edit/ship/status SKILL.md rewritten to lead with the authoring contract (agent-plan-authoring §3 tag-rationale + §4 exemplars + §5 dos/don'ts), point at docs/_shared/plan.schema.json as the published derived contract, and use ONLY read_plan (context/discovery) + edit_plan (the one safe write). All 15 granular-tool references removed (grep: 0 matches across the 4 skills + PLAN-FORMAT.md). reckon-status stays pure-read (read_plan discovery; edit_plan named as banned). PLAN-FORMAT.md aligned: names the schema as authoritative, adds the previously-undocumented plan-archived/plan-read flags + the index.json envelope ({updated,project,doc,data{_version,sprints,milestones,timeline,blockers}}) + synthesised-inventory + lenient-read/strict-write rules. Each skill carries a concrete edit_plan exemplar; all reconciled against F3's shipped signature via the live integration check. NOTE carried to F5: AGENTS.md still enumerates the 15 old tools — update it to the ~4-tool surface as part of the F5 cutover.
F5 — Migrate all projects to schema; deprecate→remove granular tools
doctor across all 4 projects; migrate nonconformant plans (incl. the plan-title/plan-status divergence); alias the 15 mutators → edit_plan; validate a real edit session; remove aliases.
Project: reckon
Plan: reckon-schema-and-tooling (§5, §7)
Tier: opus
Depends on F2/F3/F4. Run doctor on imas-ambix, imas-efit, reckon, vv; migrate nonconformant plans to the
schema (reconcile divergent meta). Make the 15 granular mutators thin aliases → edit_plan (back-compat).
Validate a real multi-plan edit session across 2 repos. Then remove the aliases + bump. Coordinate the
server restart (MCP changes are not hot). Lock the three decisions in this plan with rationale.
CODE-COMPLETE + committed; operational cutover handed to F7 (f-cutover). DONE: doctor swept all 4 projects (imas-ambix 29/29, reckon 5/5, imas-efit 554/562, vv 1/4 → 11 metadata-less violations catalogued — all slug/project-empty on README/RCA/raw-report files; NOT a cutover gate because serve.py's POST path stays unvalidated so they remain fixable). Granular tools DIRECT-REMOVED (advisor: skip the alias window — skills already on edit_plan + single restart; commit c03edfd unregisters 19, keeps read_plan/edit_plan/doctor + private funcs). 3 decisions locked up-front. Config-home resolution shipped non-breaking (75c5b06). PENDING in F7 (user-gated): the coordinated reckon serve restart + MCP reconnect (flips to ~/.config/reckon + the 3-tool surface), post-restart cross-repo edit_plan validation, the 11-file migration backlog (vv reports → reckon-type=research; don't fabricate), and dropping the ~/docs-server fallback once soaked. NOTE: imas-codex needs NO edit — it consumes only port 8765 (unchanged), not the config path.
F6 — Infra consolidation: rename config home ~/docs-server → ~/.config/reckon (XDG); coordinate with F5 cutover
Raised by the user mid-fleet on 2026-05-29: ensure reckon is served consistently and remove legacy 'docs-server'-era infra. ALREADY DONE this session (committed): removed dead dotfiles symlinks (~/docs-server/{serve.py,ui.jsx,README.md}); removed stale ~/docs-server/state/imas-ambix.old.1779364556; terminated 6 abandoned `reckon mcp` stdio servers (multi-day-old sessions); corrected AGENTS.md server-ops (the live server is `reckon serve` in a zellij session named 'reckon', NOT tmux 'docs-server'). DECIDED by the user: rename the config home from ~/docs-server/ to ~/.config/reckon/ (XDG). This is a cross-cutting, restart-coordinated change → fold into F5 (server restart is not hot). Still pending: ~/docs-server/home.html symlink removal (kept until /_projects/ route dependence is confirmed; reckon ships its own docs/home.html so the fallback is unused).
Project: reckon
Plan: reckon-schema-and-tooling (§7, with F5)
Section: infra consolidation
Tier: opus
Context
User asked (2026-05-29) for consistent reckon serving + removal of legacy 'docs-server' infra. Symlink/backup/process/AGENTS.md cleanups already landed. This followup is the cross-cutting CONFIG-HOME RENAME, coordinated with F5's server restart (MCP/serve code changes are not hot).
State to read
GET /plan/reckon/reckon-schema-and-tooling (decisions, followups, version)
reckon/reckon/_store.py (_state_root, _mounts_path), reckon/reckon/serve.py (_resolve_paths, /_projects/ route), reckon/reckon/cli.py (sync/serve/doctor defaults)
~/docs-server/ (current config home: mounts.json + state/ symlinks + home.html)
Locked decisions to honour
config-home target → ~/.config/reckon/ (XDG) [user, 2026-05-29]
validation-strictness → reject-write-warn-doctor
tool-count-target → four
schema-source-of-truth → pydantic-derives-jsonschema
Open decisions to surface (do not resolve)
whether to keep a back-compat fallback to ~/docs-server/ for a deprecation window vs hard cutover
Constraints
- Add a resolution order in _store.py/serve.py/cli.py: RECKON_* env override → ~/.config/reckon/ → (optional, deprecation-window) ~/docs-server/ fallback. Keep RECKON_MOUNTS_PATH / RECKON_STATE_ROOT working (tests depend on them).
- Physically migrate: mkdir -p ~/.config/reckon; move mounts.json; recreate the per-project state symlinks; remove the dead ~/docs-server/home.html symlink (confirm /_projects/ first).
- Update reckon-sync skill + AGENTS.md (this repo) + the imas-codex tunnel config ([tool.imas-codex.docs-server] in ~/Code/imas-codex/pyproject.toml) to the new path.
- This is NOT hot: restart `reckon serve` (in the zellij 'reckon' session) and reconnect MCP as part of the F5 cutover, after F2/F3 land.
Done-when
1. reckon resolves config home at ~/.config/reckon/ (env override still wins); mounts.json + state symlinks live there; server + MCP restarted and serving all 4 projects (GET /<project>/ → 200).
2. No remaining references to ~/docs-server/ as the canonical path in reckon code, skills, AGENTS.md, or imas-codex tunnel config (a documented deprecation fallback is acceptable if that open decision lands that way).
3. tests green via `uv run --with pytest pytest tests/ -q`.
4. followup written into plan + this followup marked resolved.
CODE-COMPLETE + committed; live cutover handed to F7 (f-cutover). DONE this session: removed dead dotfiles symlinks (~/docs-server/{serve.py,ui.jsx,README.md}); removed stale imas-ambix.old backup; terminated 6 abandoned reckon mcp servers; corrected AGENTS.md server-ops (zellij not tmux); shipped the XDG config-home resolution code (_config_home: RECKON_HOME → ~/.config/reckon → ~/docs-server fallback; 75c5b06) non-breaking; COPIED (not moved) ~/docs-server → ~/.config/reckon (mounts.json + 4 state symlinks, dead home.html omitted) and verified the new home serves all 4 projects; updated reckon-sync skill to the new path. CORRECTION to the original F6 plan: imas-codex tunnel config needs NO edit — it consumes only port 8765 (unchanged), not the ~/docs-server path (its references are stale comments only). PENDING in F7 (user-gated): the reckon serve restart + MCP reconnect that flips to ~/.config/reckon, then dropping the ~/docs-server fallback after a soak, and removing the now-stale ~/docs-server/home.html symlink.
All reckon-repo code is shipped + committed and NON-BREAKING (schema cf5fb2b, edit_plan+doctor 622ead9, skills 9c17c35, surface-collapse c03edfd, config-home 75c5b06). ~/.config/reckon is copied + verified (serves all 4 projects); ~/docs-server retained as fallback. The running `reckon serve` + every `reckon mcp` stdio server still run OLD code (19 tools, ~/docs-server) until restarted. This is the single USER-GATED cutover (it bounces the live SPA + resets MCP) plus post-restart validation + deferred cleanups.
Project: reckon
Plan: reckon-schema-and-tooling (cutover; follows F5/F6)
Section: §7 cutover
Tier: opus
Context
All reckon-repo code for schema + tool-collapse + config-rename is shipped, committed, non-breaking.
~/.config/reckon is copied + verified (serves all 4). The running reckon serve + reckon mcp servers
still run OLD code until restarted. This card is the single coordinated cutover. USER-GATED (bounces the
live SPA at :8765 + resets MCP connections), so confirm timing before executing.
State to read
GET /plan/reckon/reckon-schema-and-tooling
reckon/_store.py:_config_home ; reckon/mcp.py registration block ; docs/_shared/plan.schema.json
Locked decisions to honour
schema-source-of-truth → pydantic-derives-jsonschema
validation-strictness → reject-write-warn-doctor
tool-count-target → four
config-home → ~/.config/reckon (XDG), ~/docs-server fallback retained for now
Open decisions to surface (do not resolve)
when to DROP the ~/docs-server fallback (after a soak) ; whether to fully DELETE the unregistered private granular _funcs + prune test_mcp_tools.py
Constraints
- Restart is not hot; MCP needs a fresh stdio spawn (Claude Code reconnect).
- Do NOT move ~/docs-server; it stays as fallback until explicitly dropped.
Done-when
1. reckon serve restarted in the zellij `reckon` session (ss -ltnp | grep :8765 shows the new pid); GET /<project>/ → 200 for all 4 + GET /_shared/plan.schema.json → 200.
2. MCP reconnected — agent-facing surface is exactly read_plan + edit_plan + doctor.
3. doctor run via MCP across all 4; the 11 metadata-less violations reviewed (imas-efit ×8, vv ×3) — migrate (judgement: vv's 3 bare reports → reckon-type=research/exempt; do NOT fabricate plan metadata) or record as intentionally-exempt.
4. A real cross-repo edit_plan session validated (a lock + followup on a plan in 2 repos via MCP).
5. Optional cleanups once soaked: drop the ~/docs-server fallback; delete the dead private granular _funcs + redundant test_mcp_tools.py coverage (keep the edit_plan equivalents).
6. F5 + F6 confirmed resolved; this followup resolved; plan-status → shipped when the cutover lands.
CUTOVER LANDED + VALIDATED (this resolution written via the live edit_plan tool — dogfood). Server restarted (pid 1150279) on new code reading ~/.config/reckon; all 4 projects → 200 and /_shared/plan.schema.json → 200. MCP reconnected → live agent surface is read_plan + edit_plan + audit (doctor renamed → audit per user; commits e479e22 + f2b8bfd). Validated via the LIVE `audit` MCP tool across all four: reckon 5/5 and imas-ambix 28/28 conformant (0 violations); imas-efit 555/561 + vv 1/4 = the documented, intentional content backlog (handed off, not a gate — serve.py POST stays unvalidated so all remain fixable). Legacy generator infra (plan.html/README.html) now excluded from discovery. Remaining work is OPTIONAL/soak → f-soak-cleanup.
The schema-as-contract + read_plan/edit_plan/audit surface + XDG config home are live and validated. These are the non-urgent post-soak cleanups; the system is fully functional without them.
Project: reckon
Plan: reckon-schema-and-tooling (post-cutover soak + optional cleanups)
Tier: sonnet
Context
The schema + 3-tool surface (read_plan/edit_plan/audit) + XDG config home shipped, are live, and were
validated via the audit tool. None of the below is urgent.
State to read
reckon audit <project> for all 4 ; reckon/mcp.py registration block (dead private _funcs) ;
reckon/_store.py _config_home ; ~/.config/reckon vs the ~/docs-server fallback
Done-when
1. After a soak on the XDG home: decide whether to DROP the ~/docs-server fallback from _config_home()
(or keep as permanent insurance) and remove the stale ~/docs-server/home.html symlink.
2. Delete the ~24 unregistered private granular _funcs in mcp.py + prune test_mcp_tools.py (the
edit_plan-equivalent coverage already lives in test_edit_plan.py). Low-risk cleanup.
3. Cross-repo content backlog cleared: imas-efit via its docs-html-migration-cleanup plan (committed
a1adb415); vv's 3 bare analyses (vv-lateral-displacement-analysis, vv-previous-analysis-critique,
vvgs-pendulum-mechanism) → reckon-type=research + docs-project/slug/title/status=reference (do NOT
fabricate plan metadata). reckon audit <proj> → 0 violations.
4. This followup resolved or closed ("done — no followup").
Reject an append whose id already exists in the target collection
The append op accepts a duplicate id, so a collection can hold two items that address the same way. Observed live today: two orchestrator sessions working the same repository each appended a followup to uniform-worker-dispatch and both chose f-uwd-003, because both read the plan, saw 002 as the highest, and incremented. The plan now carries two distinct followups under one id - one on the single-goal check over-matching conjunctions, one on the subjective-term check firing inside machine-readable.
The consequence is that resolve by id is ambiguous by construction. Whichever the implementation picks, the other stays open forever or is closed with an outcome describing work that was never done, and no version check catches it: both writes were correctly version-paired and neither conflicted, because appending is additive and the collision is in content rather than in sequence.
This is not an exotic race. Sequential ids invite it whenever two sessions hold the same plan, and concurrent orchestrators across worktrees are now the intended operating mode rather than an edge case - the same day this was observed, two sessions were dispatching against overlapping plans by design.
Reject an append whose id already exists, naming the collision, so the writer retries with a distinct one. Consider having the op mint the id when none is supplied, since a caller-chosen sequential id is the trap itself. A test appending the same id twice, and one appending concurrently from two readers of the same version, would both fail today.