Every packages/*/* README now carries a canonical '## Known Limitations and
Deferred Work' section: condensed, evidence-backed bullets for consumer-visible
gaps (unimplemented features, platform caveats, MVP cuts) and consciously
postponed work (TODO/FIXME/XXX markers, RFC deferrals still open). The ten
pre-existing ad-hoc variants ('What is NOT here (TODO)', 'Deferred',
'Limitations (MVP)', 'Known limitations (tracked TODOs)', ...) are normalized
into the canonical heading.
A new doc-sync gate, scripts/verify-readme-limitations.ts, enforces the shape:
exactly one limitations-like heading per package README, byte-equal to the
canonical h2, with at least one bullet; near-miss headings fail so variants
cannot creep back. Packages with genuinely nothing to declare (dsh-brand,
dsh-timeout, dsh-subagent-mock, dsh-app-boot) are whitelisted in the script and
must NOT carry the section; whitelist entries are validated against the scanned
package set so a rename fails loud.
Wired into the doc-sync chain (package.json) and the run-gates doc-sync leaf
set; the standing rule lands in packages/AGENTS.md and the adding-a-package
cookbook; decision record in
docs/rfc/implemented/process/2026-07-10-readme-known-limitations-gate.md
(RFC index regenerated).
Also fixes two stale '(deferred)' markers claiming dsh-compact-basic is
unimplemented (the dsh-compact seam README's package table and the seam's
module doc comment).
@deepseek-ai/dsh-tool-todo
The model-facing todo_write tool: the agent's whole task list, replaced wholesale on each call.
What it does
Registers one tool, todo_write(todos: [{ content, status }]), on ctx.tools. The model sends the ENTIRE list every call — there are no partial updates or per-item edits. Each call appends a todo/write event (the full list snapshot) to the calling agent's session log via agent.session.append('todo/write', { todos }); the current list is the most recent such event (last-write-wins on replay).
status is one of pending, in_progress, completed — exactly the ACP PlanEntryStatus triple.
Single owner
The list belongs to the ONE agent session that called the tool. There is no subagent/shared/swarm scope: a non-agent caller (no exec.agent) has nowhere to write the list and is rejected. This is a deliberate scope limit — see the RFC.
Validation
Beyond the schema's type/required/enum checks, execute rejects an empty or duplicate content and more than one in_progress task (a coherent plan has at most one task active). Ordering and the discipline of keeping the list current are left to the model via the tool description.
Rendering
The tool writes only the session event; it does not render. UIs subscribe to session/event and render the todo/write data themselves: the stdio app's readline UI prints a glyphed checklist, and the ACP bridge maps the list to a plan sessionUpdate (synthesizing the priority ACP requires).
Export shape
A function/namespace plugin: it exports name / inject / apply and NO default. A stray export default would collapse the module via the Loader's unwrapExports and drop inject (see docs/postmortem/0001).
Known Limitations and Deferred Work
- Single-owner scope only — the list belongs to the one calling agent session; subagent/shared/swarm scopes are a deliberate cut (see § Single owner), and a non-agent caller is rejected.
- The item shape is deliberately minimal —
contentplus three-statestatus; no id, priority, or active-form fields, and the ACP bridge synthesizes thepriorityACP requires. - Whole-list replacement is the only operation — no partial updates, no read-back tool; the model must resend the entire list each call.