dsh-todo-ledger
Repository: https://github.com/haruri03/dsh-todo-ledger
A host-level DSH plugin that turns the todo ledger workflow into a resident prompt section,
a set of native tools, and context stored inside the repository's _todo/ directory.
It addresses the failure mode where rules live in a skill or AGENTS.md, the model forgets to
read them, and the task list has to be rewritten every turn:
| Before | This plugin |
|---|---|
Rules live in AGENTS.md / a skill and stop working when the model does not read them |
A host-level systemPrompt.section is present every step of every session, with no load step to forget |
The system todo_write list is overwritten each turn and progress is lost |
Authoritative state lives in the repository's _todo/ files and survives sessions and compaction |
| Context exists only in conversation history and disappears after compaction | todo_checkpoint / todo_note write conclusions and next steps into _todo/context/, and a snapshot is injected on every prompt assembly |
| The model has to assemble shell commands itself | 11 native tools with schema validation and readable errors |
The file format is fully compatible with an external todo CLI: the plugin and the original CLI can
both write the same repository and both can run doctor on it.
Install (manual)
Choose one:
Human: copy this directory to $HOME/.dsh/profiles/<profile>/node_modules/dsh-todo-ledger,
then append this row to the profile's cordis.patch.yml:
- insert:
- id: todo-ledger
name: './lib/index.js'
Agent: run plugin_manager install_bundle --target "<path-to-plugin>".
Install from Git
When the repository root is the package (it contains package.json and dsh.bundle), install it
directly from its Git URL:
plugin_manager install_bundle --target "git+https://github.com/haruri03/dsh-todo-ledger.git"
The same operation is available to humans through the Web plugin page ("Add plugin") or the CLI:
dsh plugin --profile <profile> add "git+https://github.com/haruri03/dsh-todo-ledger.git"
npm package names and tarball URLs are also accepted. To update, remove and reinstall, or wait for an upstream release.
After installation, every new session carries a [todo-ledger · cross-session tasks and context]
prompt section, and the tool list gains:
todo_list / todo_add / todo_show / todo_status / todo_complete / todo_archive / todo_note / todo_checkpoint / todo_snapshot / todo_doctor / todo_init
No restart is required.
Layout
Inside the repository root (the directory that contains _todo/; a repository that already keeps todo/ keeps working, the plugin accepts either name):
_todo/
├── todo.md queue: `- [status] slug`
├── tasks.md index: `- [status] **slug** — title → [details](tasks/slug.md)`
├── tasks/<slug>.md details: goal / subtasks / acceptance / progress / blockers / files
├── archived/<slug>.md completed + ## Delivery record (includes the sign-off quote)
├── AGENTS.md directory notes generated by todo_init
└── context/ added by this plugin
├── now.md current goal / next step / key files (overwritten by todo_checkpoint)
└── log.md append-only log (automatic tool trace + todo_note)
States are todo / doing / blocked / review; there is no done. review means work is
finished and waiting for the owner's sign-off. Closing a task requires both evidence and
signoff (the owner's own words); neither may be omitted. This is a hard rule of the todo ledger
workflow.
Tools
| Tool | Purpose |
|---|---|
todo_list |
List tasks and states (optionally filtered); call it before starting work |
todo_show |
Show one task's details |
todo_add |
Create a task (unique kebab-case slug) |
todo_status |
Change state and sync the task frontmatter, index, and queue |
todo_complete |
Close and archive a task; requires evidence + signoff |
todo_archive |
Abandon a task; additionally requires reason |
todo_note |
Append to context/log.md; with slug, insert at the end of that task's ## Progress section (the placeholder is removed on first write); with state, advance the state machine in the same call |
todo_checkpoint |
Overwrite context/now.md: current goal / next step / key files |
todo_snapshot |
Read-only: return the snapshot that would be injected into the prompt (check whether the ledger is stale) |
todo_doctor |
Two-way consistency check; returns errors and warnings |
todo_init |
Create the _todo/ skeleton (only when the user asks) |
Every tool accepts an optional root, so it can operate on repositories outside the workspace:
todo_doctor { root: "<repo-root>" }.
Sidebar panel

The plugin also serves a sidebar panel for the ledger of the current session working directory: browsing is read-only, and the panel's only action is accepting a review task. It is opt-in: nothing is injected into the layout until a user opens the panel from the right sidebar's guide capsule (named after the plugin); it then renders as a sidebar tab.
- Four sections —
doing,todo,review,blocked— with a count each, plus a per-state colour so the shape of the ledger reads at a glance. - Ordering is fixed:
doingfirst, thentodo,review,blocked; an empty section still shows. - The panel reads; it does not write task content. Polling runs only while the panel is visible (every 5 s), so a closed or backgrounded tab costs nothing.
- When a session has no ledger above its working directory, or the ledger files disagree, the panel
says so instead of guessing. Run
todo_doctorto repair the latter.
Each row under review carries an accept action. Accepting a review task from the panel archives
it through the same path as todo_complete, recording the panel click as both the evidence and the
sign-off (accepted from the ledger panel at <ISO timestamp>). Only review tasks can be accepted
this way; any other state is refused with an inline error rather than an alarm.
The panel talks to two host routes, both read-only in intent and dispatched as GET:
| Route | Purpose |
|---|---|
/todo-ledger/projection?sessionId=<id> |
Ledger projection for that session's working directory |
/todo-ledger/complete?slug=<slug>&sessionId=<id> |
Accept one review task; the only side-effecting call |
A host restart is required after installing or updating the host-side code; the panel itself is plain client JavaScript shipped by the package, so it needs no build step and appears after a page refresh.
Dependencies
Tasks can wait for other tasks. Pass depends_on (a list of slugs) to todo_add, and the task is
created blocked with blocker_cause: dependency until every predecessor is formally completed.
If the predecessors are already completed, the task is created as todo straight away.
graph LR
spec["Design the API contract<br/>todo"] --> impl["Implement the endpoint<br/>blocked"]
spec --> form["Wire the form to it<br/>blocked"]
impl --> notes["Draft the release notes<br/>blocked"]
form --> notes
legacy["Migrate the legacy config loader<br/>blocked, manual"]
Completing spec promotes impl and form to todo in the same call. notes stays blocked until
both of its predecessors are completed — one is not enough. legacy has no predecessor: its
blocker_cause: manual means nothing unblocks it automatically.
- Completion only. A predecessor in
reviewdoes not count; neither does an archived task that was dropped instead of completed. Onlytodo_complete— or the panel's accept — unblocks. - Validated at creation. Unknown, already-dropped, self-referential and circular predecessors are
rejected, as is a
dependencyblocker with no predecessor. - Repairable.
todo_doctorreports dependency fields that disagree with the archives.
How context survives
- On each system-prompt assembly the plugin finds the nearest
_todo/above the session working directory and injects:- Ledger last written: N ago (the newest mtime among
context/log.md,tasks.md, andnow.md); - Session staleness: how many tool calls happened since the last
todo_*write and how many of them were writes; 3 or more writes adds a ⚠ telling the agent to update the ledger first; - Task totals (four states), review items (with
(Nd)days waiting) and blocked slugs; - Progress of doing tasks: the last real line of the task's
## Progresssection (≤80 chars); the whole line is omitted when there is no progress; a state unchanged for 2+ days is markedstate Nd unchanged; template placeholders are filtered out; - The body of
context/now.mdand the last 6 log entries.
- Ledger last written: N ago (the newest mtime among
- At every turn end (
agent/turn-stopping) the plugin counts write-class operations per turn boundary. At 3 or more it appends a factual log entry (tool calls / writes / state changes) without inventing semantics. If the turn only wrote notes and never advanced a state, the entry says the state machine did not advance. It deliberately does not measure from the lasttodo_*write, because a finaltodo_notewould reset the counter and hide a turn that edited many files but left a single note. todo_checkpointrecords conclusions, not long excerpts;todo_noterecords small findings; writes append a trace automatically.- After compaction, a new session, or a DSH restart, the next prompt still carries the snapshot, so work can continue.
- Without a
_todo/directory the plugin only injects the rules and mentionstodo_init; it never creates the directory on its own.
The unsolved half: the semantic summary is still written by the model. The plugin turns "the ledger is stale" into a visible fact and a ⚠ instead of inventing conclusions for the model.
Configuration
This row in cordis.patch.yml can be adjusted:
- id: todo-ledger
name: './lib/index.js'
config:
root: "<repo-root>" # optional: pin the repository root
sectionOrder: 180 # prompt section order, default 180
maxSnapshotChars: 4000 # snapshot size limit, default 4000
Without root, the plugin searches upward from the session working directory for
_todo/tasks.md; if that fails it uses the TODO_ROOT environment variable.
Verification
node test/smoke.mjs
Coverage: prompt section registration and snapshot rendering, the schemas of all 11 tools, the full
init → add → status → note → checkpoint → doctor → complete/archive flow, and rejection of a close
without signoff. When TODO_CLI points at an external todo.js, the test also verifies
list/doctor interoperability.
Uninstall
plugin_manager remove_bundle --target dsh-todo-ledger
The plugin removes only its own registrations; the _todo/ directory (including context/) is left
untouched.
No comments yet. Be the first to write one.