DeepSeek Harness
- ID:
deepseek - Store:
${DSH_HOME:-~/.dsh}/sessions/<workspace-slug>/session-<uuid>/session.v4.jsonl.zstd, one append-only log per session. The number is the format generation: earlier dsh builds wrotesession.jsonl.zstdandsession.v3.jsonl.zstd, and when dsh opens an older session it writes the whole history into a new-generation file and leaves the old one beside it, unchanged. deja reads the newest generation in each session directory and accepts anysession.vN.jsonl, raw or zstd-framed, so the next bump is read rather than skipped - 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. From v4 the message is the result itself: role: "tool", the text
blocks as its content and isError on the message, which is where a refused
edit is read from.
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 one tool, deja,
called with a mode of recall, context, blame, fix, how,
orient or remember.
- 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. Recall arrives through
ctx.systemPrompt.context, evaluated on every assembly. The obvious
alternative — a middleware on agent/pre-step that splices a message into
the step — loads, completes the turn and never reaches the model: a later
listener rebuilds its answer from the payload and the added message is
dropped with nothing reported. 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.
The same plugin listens on tools/post-execute, which runs on every tool
result: after a read, edit, write or str_replace_editor call it adds
deja hook-tool's line about the file, and after a bash or pwsh whose result ends in [exit code: N] or [killed by signal: X]
(the last line only, as dsh's own parser reads it) it adds
deja hook-tool-after's earlier fix for that error, both as
additionalContexts, which dsh hands the model on the next step. The marker
is dropped before the lookup, as the index drops it. Measured on dsh
0.1.1-rc.2: a bash that failed with an error two earlier sessions had fixed
was followed, in the next request, by a <deja-recall> message naming the
command that fixed it. tools/execute, the seam before the call, has no
channel to the model (#4293).
The workspace deja is asked about is the session's, read from the session
header (agent.session.header.cwd). One dsh web process serves sessions
from every workspace and never changes directory, so process.cwd() is only
where dsh was launched and is kept as the fallback. The plugin directory also
holds a package.json declaring "type": "module": Node takes a file's
module type from the nearest package.json above it, and a home directory
whose own package.json declares CommonJS otherwise makes dsh refuse both
plugins with "Failed to load the ES module".
- 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). The v4 generation
was checked against the source of @deepseek-ai/dsh-base 0.1.7-rc.2 (dsh
2.0.15): dsh-session-format names generation N session.vN.jsonl, and
dsh-session-format-v3-to-v4 changes the tool-result shape and namespaces
plugin sources and block types, leaving the event envelope as it was.
Last verified: 2026-10-02
deja reads this format and thirty-seven 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.
Found this useful? Star deja-vu on GitHub.