1. Executive Summary
dsh-mneme is a cross-session memory plugin for DeepSeek Harness, the
agent harness this atlas reviews as DeepSeek Harness. MIT, version 0.8.1,
372 commits since 13 August 2026 from three principal authors, about
15,000 lines of JavaScript in dsh-mneme/src/ with a
compiled copy in lib/, and 72 test files holding about
1,000 cases.
It stores memories in SQLite, mirrors them to Markdown a person can edit, injects relevant ones into the system prompt, gives the model nine tools, and runs a background consolidation it calls autoDream. The part worth reading is how that consolidation is held to account:
- Every run leaves a receipt.
dream_runsstores the exact input snapshot and its hash, the model's raw decision list, the per-id outcome and a receipt. - Every mutation leaves one too.
receipt_chainrecords each committed merge, conflict or update with a content-addressed input digest, winner and loser, and the record count before and after, so replaying a decision must reproduce the same result — and the tests replay it. - Decisions are applied under a snapshot compare-and-set. A decision computed from state that changed since the snapshot is skipped and reported, not applied.
- Rule changes age old verdicts. A
policy_epochon runs and receipts turns earlier decisions into historical evidence when the adjudication rules change.
Beside that, human edits are privileged: a Markdown file changed by
hand flows back over the machine value, and the machine value is kept in
content_history as human_override.
The design's weakness is its defaults. Conflict freezing (park
contradictions for a person), scope labelling, strict scope, and trust
weighting by epistemic status are all implemented, tested, and set to
false in src/config.js. Out of the box,
consolidation adjudicates contradictions itself, every memory is visible
to every agent and workspace, and epistemic_status is inert
data. Even with strict scope on, only rows whose scope was explicitly
declared are walled; automatically labelled rows stay visible, by
design.
Three marks: audit_log, human_review,
negative_eval.
2. Mental Model
A memory is typed (preference, project,
decision, summary and others), carries an
importance of 1 to 5, and an epistemic_status:
observation, inferred or
subjective, inferred on save by regular expressions over
the text (src/store.js:269-306) unless declared. It can be
forgotten or archived by flag, or deleted.
It changes state in three ways:
- The model saves, updates, forgets, archives or deletes through tools.
- A person edits the Markdown mirror or the panel.
- autoDream reads a snapshot and asks a model for
decisions from a closed set —
keep,merge,archive,conflict,update,create,supersede,differentiate(src/dream/decisions.js:7) — then applies them.
Diagram source
%% caption: how consolidation changes memory under a receipt, and where a person can intervene
flowchart TB
TOOLS["model tools<br/>save, update, forget, archive, delete"] --> MEM[("memories")]
MD["Markdown mirror<br/>edited by a person"] -->|"edit-back wins,<br/>old value to content_history"| MEM
MEM -->|"schedule or idle"| SNAP["autoDream snapshot<br/>+ snapshot hash"]
SNAP --> LLM["model decisions:<br/>merge, supersede, conflict, update, archive"]
LLM --> CAS{"targets unchanged<br/>since snapshot?"}
CAS -->|"no"| SKIP["skipped, reported"]
CAS -->|"yes, conflict and freeze on"| PEND["conflict_pending"]
PEND -->|"panel: keep A, keep B, mark"| APPLY
CAS -->|"yes"| APPLY["apply in a transaction"]
APPLY --> MEM
APPLY --> RC[("receipt_chain<br/>digest, winner, loser, counts")]
SNAP --> DR[("dream_runs<br/>input, decisions, outcome, receipt")]What it does not have: a status that withholds a memory from use
because it is unverified. epistemic_status multiplies a
search score by 1.0, 0.85 or 0.7 (src/service.js:32,
:774) and ranks consolidation winners, only when
trustEpistemicWeighting is on, and nothing filters on it,
so trust_state is withheld. forgotten rows are
excluded from reads but nothing consults them on save, so
tombstone is withheld. Entity attributes carry
valid_from and valid_until, but every read
takes the current row (valid_until IS NULL,
src/store.js:1898, :1955), so
bitemporal is withheld.
3. Architecture
| Module | Role |
|---|---|
store.js (2,307 lines) |
SQLite schema, migrations, CRUD, audit and receipt tables, entities |
service.js (2,055) |
Search fusion, injection selection, mirror sync, conflict resolution, failure recording |
api.js (1,306), api-standalone.js |
Panel and standalone HTTP routes |
dream.js (1,205), dream/decisions.js,
dream/sleep.js, dream/clustering.js |
Consolidation, its decision application, and the idle sleep cycle |
tools.js |
Nine model tools |
inject.js |
System-prompt injection |
summarize.js |
Session summarization |
search/bm25.js, search/adaptive.js,
reranker.js, vector-index.js,
local-embedder.js |
Retrieval |
scope.js |
Write-side scope resolution from the session header |
mirror.js |
Markdown mirrors with a generation fence |
runtime/ |
Download, verification and loading of the local embedding runtime |
Deployment and ergonomics
- What has to run: DeepSeek Harness with the plugin
installed
(
dsh plugin --profile web add @modusensus/dsh-mneme). A standalone HTTP API exists for use outside the harness. - Offline: switch
embedProviderfrom itsopenaidefault tolocal; the plugin downloads and verifies a local embedding runtime againstruntime-manifest.json. - Hand-repairable: yes, deliberately — the Markdown mirror is the editing surface, with a digest check that tells a human edit from a stale machine render.
The screen of this checkout found no auto-running configuration, one
build-time execution point (a root package.json lifecycle
script), one unpinned surface, and three manifests changed inside the
seven-day cooldown. Nothing was installed or run.
4. Essential Implementation Paths
- Schema —
src/store.js:5-260:memories,dream_runs,recall_runs,recall_evals,failure_memories,receipt_chain,conflict_pending,scope_changes,llm_audit_logs,entities,entity_attrs,entity_relations,mirror_state. - Consolidation —
src/dream.js, applying decisions throughapplyDecisionsinsrc/dream/decisions.jswith the compare-and-set guard (:261,:313-318); run rows atstore.js:1321, per-record receipts at:1396. - Conflict freeze —
conflict_pendinginserts atstore.js:1732;service.resolveConflictPendingreuses the conflict branch ofapplyDecisions; routes atapi.js:1047-1100. - Human edit-back —
service.js:1340-1377: a mirror digest match means the file is an untouched machine render and is skipped; otherwise the edit is applied and the prior content archived ashuman_override. - Correction mining —
store.saveFailure, called atservice.js:1836and:1947, records what a memory was and what the user changed it to. - Scope —
scope.jsresolvesagent_scopefrom the session header's agent preset andworkspace_scopefrom the workspace registry orcwd; the visibility predicate applies in search, list, count, get and injection. - Defaults —
config.js:270conflictFreezeEnabled,:366trustEpistemicWeighting,:454scopeEnabled,:461strictScope, allfalse.
5. Memory Data Model
memories (store.js:5-22, widened by
migrations): id, type, title,
content, tags, importance,
forgotten, archived, source,
content_history (the newest twenty prior versions with
their source: auto_merge, human_override or
overwrite), embedding,
epistemic_status, last_accessed_at,
_full_content, timestamps, and later
quality_score and the scope columns
agent_scope, workspace_scope with a source of
explicit or auto.
Scope has two strengths. With
scopeEnabled, saves are labelled automatically from the
session, and search re-weights rows from another scope down while
keeping them visible. With strictScope as well, a row whose
scope was declared explicitly — by a scope argument to
memory_save or memory_update, or in the panel
— is invisible outside that scope, and an anonymous session sees none of
them; auto-labelled and legacy rows remain visible
(test/scope-strict.test.js:41-66). Every change to a row's
scope is recorded in scope_changes with the actor, tool or
panel. Because the hard wall covers only declared rows and the whole
feature is off by default, scope_enforced is withheld.
Provenance is a source string and the
receipts: a memory produced by a merge can be traced to its inputs
through receipt_chain.sources.
6. Retrieval Mechanics
Three recall paths are fused: a LIKE keyword scan, BM25
over a tokenizer that keeps identifiers whole and splits CJK runs into
bigrams (search/bm25.js), and vector search against a
query-adaptive threshold — 0.5 for entity prefixes and decisive heads,
0.7 for very short queries, 0.6 for long ones, 0.65 otherwise
(search/adaptive.js). A reranker orders the fused set.
Injection is semantic-first: the injector takes the latest user message from the session event log, recalls against it, and fills the prompt with summaries, preferences and matched items, falling back to rule-based selection when there is no query. Forgotten and archived rows never inject.
Every retrieval can be recorded in recall_runs with the
query, mode, top-k, threshold and exact candidates, and
recall_evals stores operator-labelled precision, recall and
MRR checks separately so tests do not inflate the production trail.
7. Write Mechanics
The model writes through memory_save and friends,
synchronously into SQLite; embedding is scheduled fire-and-forget, so a
memory is keyword-retrievable at once and vector-retrievable when its
embedding lands. A quality filter scores saves. Session summarization
compresses conversations into summary memories. Entity
extraction is optional and off by default.
Consolidation is the heavy pass and runs out of band: on its
schedule, and in a sleep mode triggered by idleness with a minimum gap
between runs, which tiers memories by heat — keeping frequently read
ones, compressing stale ones to summaries, archiving the oldest. Model
calls for consolidation and summarization are logged in
llm_audit_logs with tokens, cost and duration, purged after
retentionDays (default 90).
8. Agent Integration
Nine tools — memory_save, memory_search,
memory_list, memory_get,
memory_update, memory_delete,
memory_forget, memory_archive,
memory_runtime — plus automatic injection, slash commands,
and a web panel in the harness with a conflict queue, settings and
memory browsing. The model can declare a scope on save and update, which
is how a strict wall is created.
9. Reliability, Safety, and Trust
Automated change is accountable. Consolidation is where memory systems most often lose information silently, and here it cannot: the input, the decisions, the outcome and a per-record digest are written, replay is tested to be idempotent, a decision against changed state is skipped, a failed receipt write never breaks a run, and a summary failure still records the applied decisions.
Human authority is explicit. A person's Markdown edit wins and the machine value is kept; a person can resolve a frozen conflict through the same code path consolidation uses.
The safe settings are not the default. With the defaults, a contradiction is resolved by the model, scopes do not isolate, and epistemic status does not affect ranking. An operator has to know to turn each on.
Ordinary writes are not in the audit trail. A
model's memory_save, memory_update or
memory_delete outside consolidation leaves no event row;
the update does leave the prior content in the row's capped history, and
a delete leaves nothing.
10. Tests, Evals, and Benchmarks
72 test files and about 1,000 test( cases under
node:test, covering the store, consolidation decisions,
receipts and replay (audit.test.js,
receipt-chain.test.js, policy-epoch.test.js),
conflict freezing, the mirror's digest and generation fences, scope
storage, retrieval and strict visibility, search fusion, the reranker,
the local runtime's download and verification, and a stress test. None
was run for this report.
The negative case is scope-strict.test.js:68: five rows
share a needle, and under strict scope the explicitly foreign row is
asserted absent while the global, own, auto-foreign and legacy rows are
asserted present — a populated result with the exclusion and its
controls in one assertion. :96 asserts the same wall on
list and count totals.
benchmark.test.js runs a seeded retrieval benchmark and
asserts the fused configuration never trails the legacy one on Recall@5;
no benchmark result is committed, and no paper is cited.
11. For Your Own Build
Steal
- Give consolidation a receipt per run and per mutation. Snapshot hash, raw decisions, per-id outcome, content-addressed input digest, and counts before and after make an automated rewrite replayable and diffable.
- Apply model decisions under compare-and-set. A decision computed against a snapshot must not land on state that moved.
- Version the adjudication rules. A policy epoch turns old verdicts into history instead of silently re-interpreting them.
- Let the human-editable mirror win, with a digest to tell a person's edit from a stale render, and keep the value it replaced.
- Record scope changes with the actor.
Avoid
- Shipping protections off. Conflict freezing and scope isolation that must be discovered and enabled protect only the operators who already knew to worry.
- A strict wall with a soft door. Rows labelled automatically stay visible across scopes under strict mode, so the wall's coverage depends on whether the model remembered to declare a scope.
- Auditing only the background writer. The model's own writes are the other half of the mutation surface.
Fit
This suits DeepSeek Harness users who want memory that improves in
the background and can be inspected afterwards — a single developer or a
small team on one machine, comfortable reading configuration. For
several agents sharing one store, turn on scopeEnabled,
strictScope and conflictFreezeEnabled from the
start, and expect to declare scopes explicitly where isolation matters.
It is a harness plugin, not a library for another runtime.
12. Open Questions
- Why are conflict freezing and scope isolation off by default? Both carry tests and a UI.
- Would auto-labelled rows ever be promoted to the strict wall?
- Is
epistemic_statusinference by regular expression accurate enough to weight ranking? The regexes are Chinese and English keyword lists.
Appendix: File Index
dsh-mneme/src/store.js— schema, audit tables, entitiesdsh-mneme/src/service.js— search, injection, mirror edit-back, conflict resolutiondsh-mneme/src/dream.js,src/dream/decisions.js,src/dream/sleep.js,src/dream/clustering.jsdsh-mneme/src/api.js— conflict queue routesdsh-mneme/src/scope.js,src/config.js,src/tools.js,src/inject.jsdsh-mneme/src/search/bm25.js,src/search/adaptive.js,src/reranker.js,src/local-embedder.jsdsh-mneme/test/audit.test.js,receipt-chain.test.js,conflict-freeze.test.js,scope-strict.test.js,mirror-edit-digest.test.js
Searches behind the absence claims
rg -n "DELETE FROM (dream_runs|receipt_chain)|UPDATE (dream_runs|receipt_chain)" dsh-mneme/srcrg -n "epistemic_status" dsh-mneme/src— weights and priorities, no filterrg -n "valid_until IS NULL|valid_from <=" dsh-mneme/src
History
2026-09-15 — 00c67eaf…
— first reading, at a commit dated 15 September 2026. Screened before
opening: no auto-running configuration, one build-time execution point,
one unpinned surface, three manifests inside the seven-day cooldown.
Nothing was installed or run.