JohnXu22786
task-board
Cross-session event-sourced work ledger for DeepSeek Harness: task tracking, audit history, kanban export, dsh bundle plugin.
- Stars
- 0
- Language
- TypeScript
- Created
- Aug 16, 2026
- Updated
- Aug 16, 2026
Introduction
dsh-slate · Work Board
A cross-session work ledger plugin: registration, tracking, state transitions and completion records for tasks/issues, all persisted by event sourcing — every operation is an append-only, immutable event, history is fully traceable, and any session that opens the same board picks up where the last one left off.
- Zero runtime dependencies (pure Node.js ESM, only TypeScript build output)
- Distributed as a DeepSeek Harness (dsh) bundle, also usable as a standalone CLI
- Board digest is automatically injected into the model context of every turn (systemPrompt section)
- Built-in priority, due dates, overdue detection, kanban export, git commit linking
Contents
- Installing in DSH
- Concepts
- Quick start (CLI)
- Integrating with dsh
- Tools at a glance
- Query language
- Data and storage format
- Configuration
- Skill file (optional)
- Full example
- Troubleshooting
- Development
- License
Installing in DSH
The plugin is published for DeepSeek Harness (dsh). Install it into a profile with:
dsh plugin --profile demo add github:JohnXu22786/task-board
Remove it with:
dsh plugin --profile demo remove dsh-slate
Concepts
| Term | Description |
|---|---|
| Board | A persistent collection of items, located in a data directory, with a name and an id prefix |
| Item | The smallest unit of work on a board (task/issue/todo), numbered like S-0001 |
| Journal | The append-only event log (events.jsonl), the single source of truth for board state |
| Snapshot | A periodic cache of materialized state (snapshot.json), speeding up startup; deletable and rebuilt at any time |
| Settle | An item reaches a terminal state: fulfilled (done) or withdrawn (cancelled) |
| Undo | Appends a compensating event that reverses an item's most recent operation (registration and undo themselves are excluded) |
State machine
recorded (todo) ──► active (in progress) ──► parked (on hold)
▲ │ │
└────────────────────┴─────────────────┘ (free transitions between non-terminal states)
▲
└────────── reopen ◄── settled (terminal state)
Design highlights
- Append-only, never mutate: every write operation is a new event; old events never change.
- Crash-safe: each event is fsync'd after writing; a half-written line left by an interrupted crash is discarded with a warning on open.
- Auditable:
show/ledgerreplay the full operation sequence for any item or the whole board (who, when, what). - Multi-writer: the long-running process (dsh) holds no write lock; the CLI uses a write lock; every read operation syncs with disk first, and appends realign with the disk sequence numbers first.
Quick start (CLI)
Requires: Node.js ≥ 20.6.
# Run the repo build directly (or unpack after npm pack)
node dist/cli.js --help
# Or install as a global command
npm install -g .
slate --help
export SLATE_HOME=~/.slate/my-project # data directory (default <cwd>/.slate)
slate init --name demo # initialize a board
slate register --title "Fix login race" --priority urgent --due 2026-08-18 --tag bug
slate shift --id S-0001 --to active --note kickoff
slate link --id S-0001 --commits a1b2c3d4
slate settle --id S-0001 --kind fulfilled --note done
slate digest # current state overview
slate query --where "status:open" --sort due
slate kanban --file kanban.md # export kanban
slate undo --id S-0001 # undo the most recent operation
The full command table is in slate --help (including the common parameters --home/--by/--json/--help).
Integrating with dsh
Plugin shape
This package ships as a standard dsh bundle:
package.json # dsh.bundle manifest (patch entry) + peerDependencies
cordis.patch.yml # plugin line: id / package name / default config
dist/index.js # plugin entry: apply(ctx, config)
dist/cli.js # bundled CLI (bin: slate)
skills/slate.md # optional skill file (see below)
The entry module follows the dsh official docs:
export const name = 'dsh-slate';
export const inject = ['tools', 'systemPrompt']; // depends on the tools and system-prompt services
export function apply(ctx: Context, config?: unknown) { ... }
Inside apply(): host version guard → config validation → open the board (background async, non-blocking load) → register 13 slate_* tools → register the slate-digest prompt section. All registrations are cleaned up automatically on plugin unload (ctx.effect).
Installation
Requires a host dsh that provides @deepseek-ai/dsh-tools >= 0.1.0-rc.6 (@deepseek-ai/dsh 0.1.0-rc.6 or newer; the next release line is verified).
# Pack from the source directory
npm pack # produces dsh-slate-0.1.0.tgz
# Install into a profile (e.g. web)
dsh plugin --profile web add ./dsh-slate-0.1.0.tgz
# Verify the plugin line is active
dsh --profile web --dump-config | grep slate
# Start
dsh web
A startup log line [dsh-slate] Board ready: workspace @ ... means the load succeeded.
How loading works
dsh plugin addinstalls the bundle into the profile's dependencies anddsh.profile.bundleslist.- At startup,
cordis.patch.ymlis applied in layer order: it inserts a plugin line withid: slate-board,name: dsh-slateplus the defaultconfig. - The loader resolves the
dsh-slatepackage, waits for thetoolsandsystemPromptservices, then callsapply(ctx, config). - Tools register into
ctx.toolsviadefineTool; schemas automatically enter the tool manifest of the system prompt. - The
slate-digestsection registered viactx.systemPrompt.section()is assembled on every request — board state is automatically injected into session context (counts + overdue/in-progress/due-soon lists), so the model senses the current work without querying. The digest section renders synchronously: it syncs with disk immediately before every tool call, and the plugin also runs a background sync every 30 seconds, so external writers' changes lag by at most one cycle.
Overriding the default config
Users can override the line by id in their own profile cordis.patch.yml (dsh rule: a later layer with the same id replaces the whole line — all keys must be supplied):
- id: slate-board
name: dsh-slate
config:
dataDir: /absolute/path/to/board
boardName: my-project
idPrefix: W
snapshotEvery: 500
writerName: agent
digestEnabled: true
digestOrder: 160
digestMaxActive: 8
Note: boardName/idPrefix/snapshotEvery in plugin config are declarative overrides — only explicitly configured keys are written into the board metadata; unset keys fully respect the board's existing config.json (e.g. a board name and id prefix initialized via CLI), and are not reset to defaults on every startup.
Sharing a board with the CLI
The dsh process and the CLI can read and write the same data directory: in-process, every tool call syncs with disk first; the CLI syncs before every operation too. Keep the convention "only one side writes at a time" — the write lock only prevents mutual exclusion between CLIs; if the long-running process and a CLI write concurrently, event sequence numbers may collide (surfacing as log gap errors, never silent corruption).
Tools at a glance
All tools return a canonical JSON string (slate_kanban excepted — it returns the Markdown kanban text directly).
| Tool | Description | Key parameters |
|---|---|---|
slate_register | Register a new item | title (required), description, priority, dueDate, tags |
slate_update | Partially update fields (only actual changes are recorded) | id, title/description/priority/dueDate/tags |
slate_shift | Advance state (free transitions between non-terminal states) | id, to (recorded/active/parked), note |
slate_settle | Settle into a terminal state | id, kind (fulfilled/withdrawn), note |
slate_reopen | Reopen a settled item | id, note |
slate_comment | Append a timestamped comment | id, text |
slate_link | Link git commit hashes (append-only) | id, commits |
slate_undo | Undo the most recent operation (compensation event persisted) | id, note |
slate_query | Conditional query (see Query language) | where, sort, limit |
slate_show | Item details + full history (audit) | id |
slate_ledger | Recent event log | limit, itemId |
slate_digest | Board digest (counts/overdue/in-progress/due-soon) | — |
slate_kanban | Kanban Markdown export | — |
Parameters are first validated by the defineTool schema (types, required, enums), then domain rules validate semantics (state machine, date authenticity, hash format, etc.). Domain errors carry error codes, e.g. [ITEM_NOT_FOUND] Item not found: S-9999.
Query language
where is a comma-separated list of key:value conditions, AND-ed together; has-commit is a valueless condition.
| Key | Values | Semantics |
|---|---|---|
status | recorded / active / parked / settled / open / overdue | open = unsettled; overdue = unsettled and due before today |
priority | urgent / high / normal / low | exact match |
tag | any string | contains the tag (repeatable) |
creator | operator id | the by of registration |
due | YYYY-MM-DD (real date) | due date ≤ that day (combine with status:overdue) |
has-commit | — | linked to at least one commit |
Examples:
status:active,priority:high
status:overdue
priority:urgent,status:open,tag:auth
sort: default (unsettled first → priority → due date → registration order), priority, due, newest, oldest.
limit: integer from 1 to 500.
Data and storage format
<dataDir>/
config.json board metadata: { boardName, idPrefix, snapshotEvery }
events.jsonl append-only event log (single source of truth), one JSON event per line
snapshot.json materialized state snapshot (atomically written cache, deletable)
.lock CLI write lock (a stale lock older than 15 seconds is taken over)
Event types and payloads:
| Event | Payload essentials |
|---|---|
registered | title, description, priority, due date, tags |
field_changed | change: { field, from, to } (reversible snapshot) |
state_changed | from / to / optional note |
settled | kind (fulfilled/withdrawn), fromState (pre-settle state) / optional note |
reopened | original kind (for chained undo) |
comment_added | comment text |
action_reverted | reverted event sequence number, summary, compensation payload (persistent snapshot of the undo intent) |
Every event carries seq (globally sequential), itemId, at (ISO time), by (operator). Updating multiple fields produces multiple field_changed events (each field change is an independent audit record); undo reverses them one by one. Event format evolves with v0.x and is not guaranteed backward compatible; migrate from an export file when upgrading.
Recovery semantics: on open, the snapshot accelerates materialization, then events after the snapshot are replayed; a missing/corrupt snapshot, illegal item structure, or a snapshot lagging the log triggers a full replay automatically. A gap in log sequence numbers or a corrupt line mid-file (including lines that are "valid JSON but illegal structure") is treated as data corruption and rejected with a CORRUPT_LOG error. The export command produces a git-friendly full-state file:
slate export --file board.json # { items, events, ... }, committable and diffable
Configuration
| Key | Default | Description |
|---|---|---|
dataDir | .slate | Board data directory; relative paths resolve against the dsh process cwd |
boardName | workspace | Board name (2–40 chars) |
idPrefix | S | Item id prefix (1–4 uppercase letters) |
snapshotEvery | 200 | Write a snapshot every N events (≥5) |
writerName | agent | Operator id used when this process writes events |
digestEnabled | true | Whether to register the session digest section |
digestOrder | 160 | Digest section ordering (tool description band 100–199) |
digestMaxActive | 6 | Max items listed per class in the digest (1–20) |
On the CLI side, the data directory is set by the SLATE_HOME environment variable or the --home argument.
Skill file (optional)
skills/slate.md is a dsh skill description (tool usage and workflow suggestions). dsh's local skill provider scans user/project skill directories; copy the file over:
mkdir -p ~/.dsh/skills && cp skills/slate.md ~/.dsh/skills/slate.md
# or place it in the project: .dsh/skills/slate.md
Full example
The CLI's human-readable output is Chinese (labels like [高] map from priorities urgent|high|normal|low); the JSON output (--json) is language-neutral:
$ SLATE_HOME=~/boards/webapp slate init --name webapp
石板已就绪:/home/you/boards/webapp
$ slate register --title "Refactor auth module" --description "Split OAuth from local login" --priority high --due 2026-08-25 --tag auth
已登记 S-0001 [高] Refactor auth module (due 2026-08-25)
$ slate register --title "Add unit tests" --priority normal --tag test
已登记 S-0002 [普通] Add unit tests
$ slate shift --id S-0001 --to active --note kickoff
已推进 S-0001 [高] Refactor auth module (due 2026-08-25)
$ slate link --id S-0001 --commits 9f8e7d6c
已关联提交 S-0001 [高] Refactor auth module (due 2026-08-25 · 1 提交)
$ slate settle --id S-0002 --kind fulfilled --note 42 tests
已结算 S-0002 [普通] Add unit tests (结算:fulfilled)
$ slate query --where "status:open" --sort due
S-0001 [高] Refactor auth module (due 2026-08-25 · 1 提交)
$ slate show --id S-0001
S-0001 [高] Refactor auth module (due 2026-08-25 · 1 提交)
History:
#1 2026-08-16T09:00:01.000Z cli 登记「Refactor auth module」 · S-0001
#2 2026-08-16T10:12:00.000Z cli 推进 recorded → active · S-0001
#3 2026-08-16T11:03:22.000Z cli 修改 commits:[] → [9f8e7d6c] · S-0001
$ slate undo --id S-0001 --note wrong hash
已撤销 S-0001 [高] Refactor auth module (due 2026-08-25)
$ slate ledger --limit 4
#1 2026-08-16T09:00:01.000Z cli 登记「Refactor auth module」 · S-0001
#2 2026-08-16T10:12:00.000Z cli 推进 recorded → active · S-0001
#3 2026-08-16T11:03:22.000Z cli 修改 commits:[] → [9f8e7d6c] · S-0001
#4 2026-08-16T11:05:40.000Z cli 撤销 #3(修改 commits:[] → [9f8e7d6c]) · S-0001
In a dsh session, the same operations happen through the slate_register, slate_shift, slate_settle etc. tools; every turn's system prompt carries the current board digest automatically — no explicit query needed.
Troubleshooting
| Symptom | Fix |
|---|---|
Plugin doesn't load; logs mention dsh-slate needs ... @deepseek-ai/dsh-tools | Host dsh is too old. Upgrade: pnpm dlx @deepseek-ai/dsh@0.1.0-rc.6, then re-install with dsh plugin --profile web add |
[BOARD_LOCKED] | Another CLI process holds the write lock; once confirmed no other process is running, delete <dataDir>/.lock |
Log gap error 日志序号断裂 / [CORRUPT_LOG] | Sequence collision from concurrent writers or manual log edits; avoid writing to the same board from the long-running process and a CLI at the same time, or give them different data directories |
slate: [ITEM_NOT_FOUND] | Id doesn't exist; verify with slate query |
| Corrupt snapshot file | Delete snapshot.json; the log will rebuild it via full replay |
Development
npm install
npm run typecheck # tsc --noEmit
npm test # build + node --test (41 tests: domain rules/storage/queries/views/CLI/dsh adapter)
npm pack # produces the distributable bundle
Module structure:
src/
index.ts dsh entry: version guard, config, tool registration, digest section
guard.ts host dependency version validation
cli.ts standalone command line
engine/
types.ts domain vocabulary and types (events/items/state machine)
rules.ts pure rules: replay, input validation, command → event, undo compensation
journal.ts append-only log + snapshot + lock + external-writer sync
board.ts command facade (shared by CLI and adapter layer)
queries.ts query language parsing and execution
views.ts digest/kanban/single-line rendering
test/ node:test cases (run against the build output)
The engine is pure Node with no framework dependencies and can be reused directly in other plugins/harnesses: Board, Journal and all types are exported from dist/index.js.
License
MIT — see LICENSE.