What it does
deja-vu indexes the session transcripts coding agents have already written to your disk, and serves them back as one memory layer any of those agents can query. The README’s framing is that “every memory tool starts empty and records forward. deja starts full”: there is no capture step, because the store it reads is the JSONL, JSON, Markdown and SQLite files Claude Code, Codex, Cursor, opencode, Zed and the rest leave behind — including everything written before you installed anything. The repository is the Go project behind the deja binary; the DSH plugin is one adapter in the same tree, published separately to npm as dsh-deja.
Read “no LLM, no embeddings” carefully. It does not mean deja understands your history by some non-linguistic trick. It means retrieval is a lexical keyword search over a locally built inverted index — the same family of technique as grep, with better ranking. Nothing calls a model at query time, which is what makes sub-millisecond lookups possible, and also what caps match quality.
How retrieval actually works
internal/sources parses each agent’s store into sessions and messages from a table of twenty-five sources: Claude Code JSONL under ~/.claude/projects, Codex rollout JSONL under ~/.codex, Cursor and opencode SQLite stores, Gemini CLI, Qwen Code, Kimi Code, Zed, OpenClaw, DeepSeek Harness, Hermes and the rest. After redaction, records go to records.bin with token postings in buckets/*.bin.
A query is tokenised, posting lists are intersected, candidates are grouped back into sessions and scored with BM25 (k1=1.2, b=0.75), user-message terms weighted ×1.3 and the score decayed by 1/(1+age_days). Bounded tie-breakers sit on top: a session that reached a conclusion is lifted, one that reports it reverted an approach and settled nothing else is damped, a frequently recalled session is lifted on a log2 curve capped at +50%, and recency is a hint rather than a filter.
Beyond speech the index keeps a tool-output role (what tools printed), files (paths a turn touched), command (an allowlist of build, test and VCS commands), edit (the exact bytes an edit replaced) and summary (a harness’s compaction digest). Those are searchable with --role but withheld from ordinary results, on the stated reasoning that “a path that happens to contain the words of a question is not an answer to it.”
Multi-word queries are AND, quoted phrases require contiguous text, and a query with no exact match falls back to word forms and close spellings. The default path has no paraphrase handling. The README says natural-language questions “fall back to a relevance tier” rather than being understood. Rephrased queries only hit if you run the optional deja embed step against a local Ollama/LM Studio endpoint, which writes a .vectors.bin sidecar and reranks up to 64 candidates — the only embedding path, off by default, and the only one that can put session text in front of a third party if you point it at a remote URL. Hence deja’s tool descriptions tell the model to “match on the most specific token available”: exact strings, identifiers and flags are what this design is good at.
What DSH actually gets
dsh-deja is not a shim. Three pieces make DSH a first-class citizen:
internal/sources/deepseek.gois a DSH session parser. It walks$DSH_HOME/sessions/<workspace-slug>/session-<uuid>/, acceptssession.jsonlandsession.v3.jsonlwith or without the.zstdsuffix, and maps DSH events — session metadata and titles,user/message, streamingassistant/chunkand packedtext-chunks, the completeassistant/message,tool/result,step/end— into the internal model. It filters plugin-spliced inbox content out of user messages by requiringsource.kind === "user", drops reasoning blocks in favour of spoken text, and handles the packedtext-chunksrows a reader knowing onlyassistant/chunkwould lose. The file says it was “verified against dsh 0.1.1-rc.2 driven by a local model.”cmd/deja/install_deepseek.gowrites a marked block into$DSH_HOME/cordis.patch.yml, registers an MCP server row, and generates$DSH_HOME/plugins/deja/command.jsfor/dejaandauto.jsfor automatic recall. DSH is a named install target (deepseek,deepseek-auto), sodeja install --autowires it.- The npm plugin registers six model-facing tools —
deja_recall,deja_session,deja_blame,deja_fix,deja_how,deja_remember— plus/deja. It injectstools,commandsandsystemPrompt, and registers recall throughctx.systemPrompt.contextat order 110 (project digest, once per session) and 120 (per-prompt recall). Its README says it avoidsagent/pre-stepbecause that seam “loads fine, completes the turn, and never reaches the model.”
Installing both halves is safe by construction: every registration is wrapped in guarded(), and contributions() checks $DSH_HOME/plugins/deja/ so what the installer already wrote is never registered twice.
Install, and what happens on disk
dsh plugin --profile web add dsh-deja
The plugin shells out to a deja binary rather than vendoring it, resolving $DEJA_BIN, then deja on PATH, then the copy npm brought along — @vshulcz/deja-vu, a dependency at ^0.20.1, which carries prebuilt binaries as six optionalDependencies (darwin/linux/windows × amd64/arm64, ~13–14 MB unpacked each).
No package in that chain declares a postinstall or any other lifecycle script. The wrapper’s bin/deja is a Node shim that resolves the platform package and spawns the real binary, printing deja: missing optional binary package … if that package is absent. Prebuilt binaries arrive as ordinary npm payloads, so nothing is downloaded and executed at install time. The release route differs: install.sh fetches the archive plus checksums.txt from GitHub Releases and verifies SHA-256 before installing, failing on a mismatch — that script does execute at install time.
Requirements are Node >=18 and the zstd CLI: DSH writes its session log as a chain of zstd frames by default, and deja is a zero-dependency Go binary, so it pipes the log through zstd -d -c -q. internal/sources/skip.go returns zstd CLI not found for a zstd-framed DSH store when the tool is missing, and those sessions are then unreadable. Plain session.jsonl stores need no external tool.
The index lives at ~/.cache/deja/index.db (DEJA_INDEX_DIR relocates it) as records.bin, buckets/*.bin, manifest.gob and sessions.gob. On the author’s store — 2,419 sessions, 179k messages, 1.9 GB of transcripts — the README measures a 200 MB index, about 10% of the corpus. To wipe it: deja uninstall --all, then rm -rf ~/.cache/deja.
Version numbers you will see
They do not move together: the npm plugin dsh-deja is at 0.20.8, the bundled binary package @vshulcz/deja-vu at 0.20.1, and the newest GitHub release at v0.20.1. So dsh plugin --profile web add dsh-deja installs a 0.20.8 Node plugin driving a 0.20.1 Go binary; the plugin’s version tells you nothing about the binary’s. Neither the repository’s root manifest nor the extensions/dsh manifest carries dsh.compatibility; the published 0.20.8 manifest does, and that is what this listing records.
Privacy
Indexing and search are local, and docs/SECURITY-MODEL.md states there is no background network traffic. Network use is confined to commands you run: deja update (GitHub releases, plus a SHA-256 check — the document notes the updater does not verify the published cosign signature over checksums.txt, so it “catches a corrupt download but not a tampered release”); deja sync ssh <host> (system ssh/scp to a host you name); deja doctor (one HTTPS GET to the releases API; --offline skips it); and deja embed (your configured endpoint). The MCP server is JSON-RPC over stdio with no socket.
There is no phone-home telemetry, but there is a local usage log: <index-dir>.usage.jsonl beside the index, appended on local search, recall, context and hook use plus compaction-capture events. It “does not contain session text or queries” — kinds, times, hashed identifiers and counts — rotates at 1 MiB and keeps 14 days. No source describes an opt-out.
Redaction runs before every write path: AWS keys, api_key=/token=/password= assignments, quoted secrets, bearer tokens and JWTs, PEM blocks, known provider prefixes, scheme://user:pass@host URLs, credentials passed on a command line, and high-entropy values in assignment positions. Values become [redacted:<kind>] and surrounding text stays searchable. The document is candid that “pattern matching is not secret detection”: an unknown token shape, an unlabelled credential, or a secret split across lines can pass through. DEJA_NO_REDACT=1 disables the pass, which the document calls unsafe, and the index is not encrypted at rest. Recall that reaches a model is wrapped in <deja-recall> markers declaring the content untrusted historical data whose instructions must not be followed.
Known limits
- Default retrieval is lexical. Exact strings, identifiers and rare tokens work; paraphrases are the weak case, and the sanctioned fix is running an embedding model — the thing the headline claim says it does not need.
- The quality figures are the author’s own. The README’s headline numbers — 85.3% hit@1 on LongMemEval-S, 69.7% on LoCoMo — come with the harnesses that produced them:
scripts/longmemeval/main.goandscripts/locomo/main.goare in the repository and take the public datasets as a-dataargument, printing ahit@1/hit@5/hit@10/MRRtable. The datasets themselves are not shipped. The README adds: “Audit what ‘relevant’ means before trusting any figure, ours included.” - Compaction recovery is host-limited. Automatic pre-compaction capture reads Claude Code and Codex transcripts; DSH gets per-prompt recall and the session-start digest, not the packet path.
- Freshness claims are bounded, and promoted notes carry an
accepted/rejected/superseded/stalestate that travels with hits and is never deleted. - Sync is trust-based: deja “does not authenticate peers beyond the system SSH client’s checks and does not revoke data after a peer imports it.”
- The six tools need a peer that may not be there. The plugin imports
@deepseek-ai/dsh-toolsin atryblock and, if that fails, skips the tools entirely — the/dejacommand and automatic recall still work, but the model losesdeja_recalland the rest with no error surfaced. - Most user-facing docs are not in the tree — the three
docs/*.mdfiles are, the guide pages the README links to are only on GitHub Pages.
When to use it — and when not to
Use it if you have years of agent transcripts on one machine and keep re-solving problems across different agents. The defensible wins are what the design is built for: deja blame <path> to see which sessions shaped a file before you change it, deja how <tool> to recover the invocation that worked on this machine, deja fix <error> to find what followed the same error last time, and the tool-output/files/command/edit roles that compaction summaries throw away. deja restore <path>, handing back a span from the old_string an edit recorded, is genuinely novel for a transcript index.
Do not install it expecting semantic memory. If “memory for coding agents” means facts a model extracts and paraphrases, this is a different design, and the difference shows the first time you ask in your own words and get nothing back. Think twice too if one local index holding redacted spans of every agent’s history — command output and replaced file content included — is more than you want; that extra material is exactly what makes the point-of-action recall possible.
FAQ
Does it need an LLM or an embedding model?
No. Indexing, ranking and recall are a local inverted index plus BM25, and nothing calls a model at query time. The optional deja embed command adds a vector sidecar for rephrased queries using a local Ollama/LM Studio endpoint or an OpenAI-compatible URL you configure; without it, “lexical search and MCP recall continue unchanged.”
Do my session histories leave my machine?
Not through indexing, search, MCP, stats or hooks: the MCP server speaks JSON-RPC over stdio and opens no socket. The network is used only by commands you run — deja update, deja sync ssh, the version check in deja doctor, and deja embed against whatever endpoint you point at.
Which harness versions does the DSH plugin declare?
The published dsh-deja@0.20.8 manifest carries dsh.compatibility.dsh as 0.1.1-rc.2 with dshReleases: {"0.1.1-rc.2": "compatible"}, and that single release is what this listing records. No later release is declared.
Why does DSH need the zstd CLI when other harnesses do not?
DSH writes its session log as a chain of zstd frames, and deja is a zero-dependency Go binary that will not link a zstd library, so it pipes the file through zstd -d -c -q. internal/sources/skip.go returns zstd CLI not found for a zstd-framed DSH store when the tool is missing. Stores written as plain session.jsonl do not need it.
Where is the index, how big is it, and how do I remove it?
By default in ~/.cache/deja/index.db — records.bin, buckets/*.bin, manifest.gob, sessions.gob — with DEJA_INDEX_DIR to relocate it and a <index-dir>.usage.jsonl sidecar. The README measures 200 MB for a 1.9 GB corpus of 2,419 sessions. The documented removal is deja uninstall --all then rm -rf ~/.cache/deja; deja forget rebuilds the index without matching sessions and writes tombstones so a later re-index cannot restore them.
Is it safe to run alongside deja install --auto?
Yes, by construction. deja install --auto writes an MCP server row and command.js into $DSH_HOME/plugins/deja/, and the npm plugin contributes only what is missing, so tools and /deja are never registered twice. The guards exist because a duplicate registration makes the host throw prompt context deja:recall is already registered, which takes down the whole profile rather than one plugin.