dsh-plugin-sodamem
Bi-temporal memory backed by a SodaMem daemon: recall is contributed to the prompt while each turn is assembled and every closed turn is ingested, with no tool call and no model call on the read path; each recalled fact cites the turn it came from and carries validity dates, so superseded facts stop
Install
dsh plugin --profile web add dsh-plugin-sodamem
Sandbox test results
| Stage | Result |
|---|---|
| 1 install | ✗ |
| 2 list | — |
| 3 mount | — |
| 4 boot | — |
dsh: initialized profile web at /home/node/.dsh/profiles/webFour stages run in an isolated sandbox: install (network on) → list → mount → boot (last three with no network).
README
dsh-plugin-sodamem
Long-term memory for DeepSeek Harness, backed by SodaMem.
- Recall — while a turn is being assembled, the plugin retrieves an evidence block for that turn's question and contributes it to the prompt. BM25 + vector + entity fusion, no model call.
- Retain — when the turn closes, its messages are ingested back into the store.
Neither is a tool call, so the model can't skip either one and neither costs tool-schema space.
SodaMem is a separate open-source memory engine that runs as a local daemon. This repo is only the dsh plugin; you need the daemon running for it to do anything.
What you get that a notes file doesn't
Every recalled fact names the turn it came from. This is one line of a real evidence block, verbatim:
support=I flew United to Boston last week. predicate=User flew United to Boston entities=airline=United|destination=Boston source=s4/s4_turn_0 ← the actual turn, not "some earlier chat" date=2023-06-10
FactEvent → SourceSpan → RawTurn is a foreign-key chain. When the model asserts something about the user, there is a row explaining why.
Facts expire. SodaMem keeps four time axes — when the event happened, when the fact was true, when it was said, when it was stored. "I moved to Chicago last year" and "I'm moving next year" are different rows, and a fact that stopped being true stops coming back. Corrections are ADD-only: a new version plus a SUPERSEDES edge, never an in-place rewrite.
The engine is benchmarked and the answers are published.
benchmark · score ·
LongMemEval-S · 92.8% (464/500) · 500 answers + 8,427 evidence rows, re-gradable with any judge
LoCoMo · 86.88% (1338/1540) · end-to-end QA, LLM-as-judge
Those are SodaMem's numbers — the engine this plugin talks to, not the plugin itself.
Why not the MCP bridge?
SodaMem already has an MCP integration for dsh, in the main SodaMem repo (integrations/deepseek-harness/). It exposes memory as tools, which means the model has to choose to call them — and on most turns it simply doesn't. The strongest thing SodaMem offers, the zero-LLM GET /v1/context evidence block, ends up left to the model's discretion.
MCP cannot fix that. A tool is pull-only, and nothing in the protocol lets a server contribute to the prompt or observe a turn closing. This plugin uses the harness's own seams instead — system-prompt/assemble for recall and agent/turn-stopping for retain — so both happen unconditionally.
· MCP bridge · This plugin
Recall · model calls a tool, if it decides to · every turn, automatically
Retain · model calls a tool, if it decides to · every closed turn, automatically
Model can skip it · yes · no
Costs tool-schema space · yes · no
Do not run both against the same store. They would recall the same facts twice and ingest every turn twice. Pick one.
Requirements
- Node >= 22 (the harness requires it; the plugin uses
AbortSignal.any) - A running SodaMem daemon — see below
Install
dsh plugin --profile tui add dsh-plugin-sodamem
That is all that is needed. The package ships a dsh.bundle manifest pointing
at [cordis.patch.yml](./cordis.patch.yml), so dsh adds the plugin to the
profile's bundle list and composes its row — with working defaults — into the
profile tree. Confirm it landed:
dsh --profile tui --dump-config | grep -A6 'id: sodamem'
Start the daemon first (once per machine):
sodamem daemon ensure # defaults to http://127.0.0.1:8000
Fact extraction needs LLM credentials on the daemon side. Put SODAMEM_LLM_PROVIDER / SODAMEM_LLM_API_KEY / SODAMEM_LLM_MODEL in the daemon's environment or .env. Without them recall still works, but every retain will be accepted and then fail during extraction.
Configure
The bundled cordis.patch.yml ships defaults that boot (apiUrl
http://127.0.0.1:8000, userId default). To change them, override the row
in your own profile cordis.patch.yml, which applies after every bundle
layer:
# $DSH_HOME/profiles/<profile>/cordis.patch.yml
- id: sodamem
config:
apiUrl: 'http://127.0.0.1:8000'
apiKey: 'dev'
userId: 'your-user-id'
tokenBudget: 1200
A patch replaces the targeted row's whole config rather than merging into
it, so restate every key you want to keep.
Without installing as a bundle, the same row can be inserted ad hoc:
npx @deepseek-ai/dsh web --patch ./sodamem-plugin.patch.yml
Config fields
There are four, and they are all connection or scope facts.
field · required · default · what it is
apiUrl · yes · — · Origin of the SodaMem daemon
apiKey · yes · — · Sent on every request. Any non-empty string works when the daemon runs with auth disabled — there is no magic fallback
userId · yes · — · The SodaMem user_id every read and write is scoped to
tokenBudget · no · 1200 · Token budget for the recalled evidence block
There is deliberately no switch that turns recall or retain on or off, and no strategy selector. Auto-injection is the entire point of the plugin; a knob to disable it would just be a slower way to use the MCP bridge.
session_id on retain is the agent's id (in dsh, an agent and its session share one identity). agent_id is deliberately not sent — it would be the session id, which would narrow retrieval and fragment recall across sessions.
Remote mode only
The plugin talks HTTP to a daemon. It has no data-root option and imports nothing that can open a store locally, and that is a deliberate constraint rather than an unfinished feature.
Two processes writing one `SOD
…(truncated)
Rendered from the README published on npm. Formatted only, not edited.
Versions
Published on npm::2,最后更新 2026-08-18