Back to home

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

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

TermDescription
BoardA persistent collection of items, located in a data directory, with a name and an id prefix
ItemThe smallest unit of work on a board (task/issue/todo), numbered like S-0001
JournalThe append-only event log (events.jsonl), the single source of truth for board state
SnapshotA periodic cache of materialized state (snapshot.json), speeding up startup; deletable and rebuilt at any time
SettleAn item reaches a terminal state: fulfilled (done) or withdrawn (cancelled)
UndoAppends 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/ledger replay 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

  1. dsh plugin add installs the bundle into the profile's dependencies and dsh.profile.bundles list.
  2. At startup, cordis.patch.yml is applied in layer order: it inserts a plugin line with id: slate-board, name: dsh-slate plus the default config.
  3. The loader resolves the dsh-slate package, waits for the tools and systemPrompt services, then calls apply(ctx, config).
  4. Tools register into ctx.tools via defineTool; schemas automatically enter the tool manifest of the system prompt.
  5. The slate-digest section registered via ctx.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).

ToolDescriptionKey parameters
slate_registerRegister a new itemtitle (required), description, priority, dueDate, tags
slate_updatePartially update fields (only actual changes are recorded)id, title/description/priority/dueDate/tags
slate_shiftAdvance state (free transitions between non-terminal states)id, to (recorded/active/parked), note
slate_settleSettle into a terminal stateid, kind (fulfilled/withdrawn), note
slate_reopenReopen a settled itemid, note
slate_commentAppend a timestamped commentid, text
slate_linkLink git commit hashes (append-only)id, commits
slate_undoUndo the most recent operation (compensation event persisted)id, note
slate_queryConditional query (see Query language)where, sort, limit
slate_showItem details + full history (audit)id
slate_ledgerRecent event loglimit, itemId
slate_digestBoard digest (counts/overdue/in-progress/due-soon)
slate_kanbanKanban 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.

KeyValuesSemantics
statusrecorded / active / parked / settled / open / overdueopen = unsettled; overdue = unsettled and due before today
priorityurgent / high / normal / lowexact match
tagany stringcontains the tag (repeatable)
creatoroperator idthe by of registration
dueYYYY-MM-DD (real date)due date ≤ that day (combine with status:overdue)
has-commitlinked 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:

EventPayload essentials
registeredtitle, description, priority, due date, tags
field_changedchange: { field, from, to } (reversible snapshot)
state_changedfrom / to / optional note
settledkind (fulfilled/withdrawn), fromState (pre-settle state) / optional note
reopenedoriginal kind (for chained undo)
comment_addedcomment text
action_revertedreverted 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

KeyDefaultDescription
dataDir.slateBoard data directory; relative paths resolve against the dsh process cwd
boardNameworkspaceBoard name (2–40 chars)
idPrefixSItem id prefix (1–4 uppercase letters)
snapshotEvery200Write a snapshot every N events (≥5)
writerNameagentOperator id used when this process writes events
digestEnabledtrueWhether to register the session digest section
digestOrder160Digest section ordering (tool description band 100–199)
digestMaxActive6Max 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

SymptomFix
Plugin doesn't load; logs mention dsh-slate needs ... @deepseek-ai/dsh-toolsHost 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 fileDelete 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.