dsh-tasks-manager
DeepSeek Harness plugin: a project task board kept with the project as a queryable SQLite file, with a kanban/list view in the Web Client, model-facing task tools, and cards you can dispatch to the agent as background jobs.
- Stars
- 0
- Language
- TypeScript
- Created
- Aug 26, 2026
- Updated
- Aug 26, 2026
Introduction
@achasoft/dsh-tasks-manager
Task management for the DeepSeek Harness: a project task board kept with the project itself, a kanban and list view inside the Web Client, and board cards you can hand to the agent to work in the background.
The board is a SQLite database at .dsh/tasks.db in your project — so it survives across sessions, moves with the repository, and you can query it directly:
sqlite3 .dsh/tasks.db "select * from board"
What it adds
A Tasks view beside Chat and Trajectory, taking the whole centre column. Five columns (backlog, todo, in progress, blocked, done) with drag-and-drop between and within them, a dense sortable list view, filters over status, priority, labels, assignee and full-text search, and a card detail with an editable Markdown description, comments, and a complete history of every change.
Tools the model can reach, so you can just say what you want:
"add a task to rotate the staging API keys, urgent, label it security"
task_add, task_list, task_update, task_comment, and (off by default) task_delete. Cards are addressed the way a person quotes them — #12, 12, or the full id. Every model write is attributed to agent in the history, so you can always see who changed what.
Archive and reopen rather than delete: archiving hides a card from the board and keeps its comments and history intact. Deleting is a separate, confirmed action, and the model cannot do it unless you turn that on.
A Background panel listing every background task the session can see — shell commands, subagents, and cards dispatched from the board — with live status, on-demand output, and a stop control.
Dispatch a card to the agent. With a subagent provider configured, a card can be handed to the agent to work detached from your current turn: the card shows a live running state, the run appears in the Background panel and the harness's own job surfaces, and the outcome is recorded on the card.
Install
Add it to a dsh profile ($DSH_HOME/profiles/<name>/package.json):
{
"dsh": { "profile": { "bundles": ["@deepseek-ai/dsh-base", "@deepseek-ai/dsh-web-app", "@achasoft/dsh-tasks-manager"] } },
"dependencies": { "@achasoft/dsh-tasks-manager": "^0.1.0" }
}
then pnpm install in the profile directory and run dsh --profile <name>.
Everything is composed by default — unlike a provider-backed capability, nothing here needs a deployment-specific answer to work.
Configuration
Settings live in the Web Client under Settings → Plugins → Task management, and in your profile's cordis.patch.yml. A patch replaces a row's whole config, so restate every key the row needs.
| Key | Default | What it decides |
|---|---|---|
databasePath | .dsh/tasks.db | Relative resolves against each project's root, so every project gets its own board. Absolute pins every project to one shared board. |
projectRootMarkers | ['.git'] | Walked upwards from the session's directory; the first match is the project root. |
defaultStatus | backlog | Column a card lands in when its creator names none. |
newTaskPlacement | top | Whether a new card goes to the start or end of its column. |
journalMode | wal | SQLite journal pragma; change it only on a filesystem without WAL. |
busyTimeoutMs | 5000 | How long a write waits behind another writer. |
pollIntervalMs | 2000 | How stale the open board may be when a change came from elsewhere. Only a revision counter is polled. |
subagentProvider | '' | Which subagent provider a dispatched card runs on. Blank disables dispatch. |
dispatchStatus | in_progress | Column a card moves to when dispatched; none leaves it. |
dispatchCompletedStatus | none | Column a dispatched card moves to when its run completes. |
digestSize | 25 | How many tasks task_list returns when sorting by urgency. |
The tools row (tasks-tools) carries its own two:
| Key | Default | What it decides |
|---|---|---|
allowDelete | false | Whether the model may permanently delete a card. Archiving is recoverable; deleting is not. |
defaultListLimit | 50 | How many tasks task_list returns when the model names no limit. |
Disable the tasks-tools row alone to serve the board to people without giving the model write access to it.
The database
One table per concern, plus a board view for reading by hand:
tasks id, ref, title, body, status, priority, labels, assignee, rank,
archived, created_at, updated_at, completed_at, archived_at,
due_at, created_by, session_id, running_job_id, last_run
comments id, task_id, body, author, created_at, updated_at
activity seq, task_id, kind, actor, at, from_value, to_value, session_id
meta revision, next_ref
board a readable projection of `tasks` with local-time dates
Some things worth knowing about it:
refis never reused, including after a delete, so#12in a commit message keeps pointing at the same card forever.rankis a fractional index, not a position. Dropping a card between two others mints a key between theirs, so a drag writes exactly one row and two people dragging at once cannot scramble a column.activityis a real table, not a diff reconstructed from timestamps — it answers "when did this move, and who moved it".- Timestamps are epoch milliseconds; the
boardview converts them. - The file is created
0600. The layout version is stamped inPRAGMA user_version; a database written by an incompatible build is refused rather than migrated in place.
Editing the database with sqlite3 while the board is open is fine — the next poll picks it up.
How the two halves talk
The browser drives the board over ctx.connection.rpc, the harness's generic unary RPC channel, on /dsh-tasks. There is no Typert contract and no generated artifact, so this package builds and installs without a deepseek-harness checkout. It owns its own payload validation instead, in src/domain/validate.ts — the same code that validates the model's tool arguments, so the two callers cannot drift apart.
The board state is deliberately not in the session log. An out-of-tree plugin cannot append its own session event types: the persistence coordinator refuses to reload a log carrying a type absent from the harness's generated KNOWN_SESSION_EVENT_TYPES, and Session.append has no way to mark one ignorable. Freshness comes from polling one integer (board.revision) instead — which also covers what a session log never could: another session's writes, and your own sqlite3 edits.
Development
pnpm install
pnpm run typecheck
pnpm test
pnpm run build
pnpm run build runs tsc for declarations, then tsdown for the two artifacts the harness's client module system expects: a plain-ESM node half that keeps every @deepseek-ai/* specifier as an import (so it shares the running installation's service singletons), and a browser half wrapped in the loader's window.__ModuleLoader__.load handoff with its CSS Modules inlined.
The devDependencies link a sibling deepseek-harness checkout for types. To develop against a different location, adjust the link: paths in package.json.
Layout
src/domain/ the task vocabulary, fractional indexing, and validation — no node or browser imports
src/host/ the SQLite store, the RPC router, the settings section, and the job producer
src/tools/ the five model-facing tools
src/client/ the browser half: the Tasks view, the board components, and the settings card
Licence
MIT