DeepSeek Harness
- ID:
deepseek - Store:
${DSH_HOME:-~/.dsh}/sessions/<workspace-slug>/session-<uuid>/session.jsonl.zstd— one append-only log per session - Read override:
DEJA_DEEPSEEK_ROOT(sessions root),DSH_HOME(the harness's own home, also honored) - Format: JSONL, written as consecutive zstd frames by default; raw lines
are a configuration and both are read
- Prerequisite: the
zstdCLI, for the same reason Zed needs it. Without
it the sessions are found and none of them can be read, and deja index says
so rather than reporting an empty store.
The first line is the session header — `{"type":"session","id":"session-<uuid>",
"createdAt":<ms>,"cwd":"…"} — and the project comes from that cwd`. Every
line after it is one event: {type, seq, time, data}.
These event types carry the conversation:
user/messagewithdata.source.kind == "user"is what a person typed. The
same type also carries what plugins splice into the turn — the sandbox policy
snapshot, the skill catalogue — under other source kinds, and those are the
harness describing itself rather than history worth recalling.
assistant/messageis the agent's turn, complete:data.message.contentis a
block array of text and reasoning. Only the text is recalled; reasoning is
the model thinking out loud rather than what it told the person.
assistant/chunkwithchunk.type == "text-delta"is the same answer as it
streamed. It is read only as a fallback, for a run interrupted before the
complete message landed — otherwise the answer would land twice.
text-chunksis a packed row: a run of three or more consecutive deltas
stored as one line with the pieces in data.texts. A reader that knows only
assistant/chunk keeps the stray deltas and loses every long answer, which is
exactly the interrupted case the fallback exists for.
tool/resultis tool output, whose content nests atool-resultblock around
the text; it is kept under the tool-output role so search can tell it from
speech.
session/title gives the session its name; when the model never answered, the
harness falls back to the first prompt.
- MCP:
deja install deepseekwrites$DSH_HOME/cordis.patch.yml, the
home-level patch layer every profile composes over its own. An MCP server here
is a plugin row (@deepseek-ai/dsh-mcp-client) inside an insert: list — a
bare row is rejected with entry "mcp-deja" not found, because a patch entry
addresses a row that already exists. After it, dsh lists mcp__deja__recall,
recall_context, remember, blame, fix and how itself.
- Skill: the shared
~/.agents/skills/deja-history/SKILL.md. dsh splices a
skill catalogue into the turn and reads that directory, so it needs no file of
its own — checked by asking a running dsh to list its skills.
- Command:
deja install deepseekalso writes a plugin at
$DSH_HOME/plugins/deja/command.js and names it from the same layer, because
dsh registers slash commands in code (ctx.commands.register) rather than
from a directory of markdown. A profile row may name an absolute path, which
is how deja ships one without publishing a package. Three details fail the
whole profile load rather than skipping the plugin, and each was found by
running it: the dependency is declared as apply.inject, the field is
handler and not handle, and a row naming a file that is not there yet
takes the profile down — so the plugin is written before the layer and the
layer is rewritten before the plugin is removed. The command plane itself is
a UI service, so /deja lives in the web profile; a headless run has no
command adapter, and what a headless boot proves is that the plugin registers
cleanly.
- Auto-recall:
deja install deepseek-autowrites a second plugin at
$DSH_HOME/plugins/deja/auto.js. It listens on agent/pre-step, the event
that carries the messages entering the step the agent is about to take, and
splices the recall block in front of the last user message. The handler is
middleware, so it calls next() first and returns that decision with the
longer message list — returning a bare {messages} ends the turn with
Cannot read properties of undefined (reading 'kind'). The plain
deja install deepseek target keeps the MCP server and /deja but writes no
such plugin, and removes it along with its row when someone drops back to it.
Verified against a local model with no tools in play: dsh answered a question
about a pool size that only the injected block carried.
- Resume: none. The launcher's examples mention a tui profile taking
--resume <session>, but this release ships no bundle for one — the two apps
are headless, which takes a task and exits, and web, whose flags are all
about the server. Reopening a conversation is something the web sidebar does,
so there is nothing for deja to print.
- Handoff: paste.
Format verified by installing dsh 0.1.1-rc.2, pointing it at a local model over
an OpenAI-compatible route, and reading what it wrote across sessions that
answered, called a tool, were interrupted mid-answer, and failed before
answering (@deepseek-ai/dsh-session-persistence-jsonl).
Last verified: 2026-08-21
deja reads this format and nineteen others, and turns what it finds into memory your agents can search. See the harness matrix for what is wired where, or install it and search your own history.