中文版见 upstream-proposal.zh.md。Protocol spec: protocol-v1.md。
This document makes the case that the official DeepSeek Harness ctx.memory seam should adopt
dsh-memory-protocol/v1 — the protocol dsh-memento rehearses in the community — and lists
the concrete differences and migration path. It is written for harness maintainers.
1. The write gate is enforced where every write path meets — inside the service.
The official seam today has no memory service; memory plugins are expected to bring their own
stores, and a gate enforced in a tool layer is bypassable by late tool injection (the failure
mode Hermes documented in issue #48181).
The protocol pins the enforcement point: every write (add/replace/remove/consolidate/
seed) rides an approval transport inside the provider, writePolicy is configuration the
model can neither see nor change, and a session-level never stance pre-empts everything.
2. Model-visible ⟺ reconstructable, by construction.
Approval payloads carry the complete change (approve-what-you-see: full old text, full new text,
per-target excerpts for consolidation); every allowed write lands an audit row naming the real
decision source; every denied write lands a <action>-denied row before the error propagates.
Together with the harness's own approval/asked + approval/decided pair, any state change is
rebuildable from the session log — the same invariant the harness already applies to its own
model-visible surfaces.
3. Local-first, zero network, zero credentials.
The reference provider is one local SQLite file (0600, WAL) with a monotonic schema version
and loud failures (STORE_CORRUPT / STORE_UNSUPPORTED_VERSION). The official seam would
inherit "memory stays on the user's machine" as a first-class property rather than a per-plugin
hope.
4. Bounded and honest budgets.
Hard per-track/per-layer character budgets with structured BUDGET_EXCEEDED errors (usage +
limit + needed) give the model a deterministic consolidate-and-retry loop. No silent truncation,
no hidden auto-compaction — both of which are otherwise attractive and dangerous shortcuts.
5. One conformance suite for a whole ecosystem.
test/protocol-conformance/ is a distributable case set: any provider claiming compatibility
runs the same cases dsh-memento's own provider runs in CI. Adopting the protocol turns the
20+ memory plugins from "each its own warehouse" into "one protocol, many stores" — the
interoperability point competitors (Claude Code / Codex / OpenCode / Hermes) do not offer
across their closed memory forms.
| Aspect | Official seam today (rc.6) | dsh-memory-protocol v1 |
|---|---|---|
| Memory service | none (MCP "memory = external server" position; plugins bring their own) | typed ctx.memory + adapter registry ctx.memoryAdapters |
| Write gate | per-plugin, usually tool-layer | enforced inside provider write methods; policy is model-invisible config |
| Entry model | per-plugin | protocol v1: two tracks × two layers × per-agent key + tags + per-entry version |
| Budgets | per-plugin | hard per-track×scope character budgets with structured errors |
| Audit | per-plugin | approval pair + provider ledger + <action>-denied rows; reconstruction guaranteed |
| Interop | none | conformance suite + reference adapters (mem0, Hermes memory.md, CLAUDE.md) |
| Session events | — | `memory/added |
- Adopt the entry vocabulary (tracks/scopes/agentKey) — dsh-memento's
types.d.tsmerge declarations are a drop-in starting point; no behavior change for existing plugins. - Register
memory/*session event types inKNOWN_SESSION_EVENT_TYPES(or add anignorableappend surface). The reference implementation already gates emission on that set, so no data or audit gap appears before or after the change. - Land the provider service shape (
budgets/add/replace/remove/query/seed) as the official Service Definition; the protocol core (lib/protocol.mjs, zero DSH dependencies) is structured to be lifted into the harness as-is. - Adopt the conformance suite as the ecosystem gate:
dsh plugin verifycan run it against any installed memory plugin.
Backward compatibility: the protocol is a normalization and extension of dsh-memento's shipped
0.3.x seam — existing behavior is preserved; tags/version/adapters are additive. The
reference implementation stays installable as a community plugin regardless of whether or when
the harness adopts the protocol.
Status check 2026-09-09, read-only against
origin/master=5dda764e(0.1.5-alpha.1). The sections above were written when the official harness had no storage subsystem. That has changed, so this proposal is realigned: instead of a new top-levelctx.memoryseam, memory protocol v1 now mounts on the official storage hub -- its persistence ridesctx.storage.domain, and the json/sqlite dual backend already exists. The earlier sections stay as the protocol rationale; the rc.6-era "Differences from the current official seam" table stays as a historical snapshot, superseded by this section.
The packages/storage group (host-side only; no model-facing surface)
ships four packages:
storage(@deepseek-ai/dsh-storage) -- the hubctx.storage: a named backend table (BackendRegistry:register/get/names; errorsduplicate-backend/backend-not-found) plus a data-form mount table.StorageFormsis an empty declaration-merge interface;mount(form, facility)returns an unmount disposer (errorduplicate-mount),form(form)resolves a mounted form (errorform-not-mounted), and thedomaingetter is the typed accessor for the domain form.storageBackendServiceKey(name)yields the lifecycle-only keystorage.backend.<name>that domain providers inject so activation cannot race backend registration.storage-json(@deepseek-ai/dsh-storage-json) -- registers backendjson(one human-readable file per unit, or per-record documents).storage-sqlite(@deepseek-ai/dsh-storage-sqlite) -- registers backendsqlite(units as JSON documents in one database).storage-domain(@deepseek-ai/dsh-storage-domain) -- plugin namestorage-domain,inject: ['storage']; mountsDomainFacilityunderStorageForms['domain']and providesctx.storageDomain.Config={ backend: string, routes?: Record<string, string> }-- the default backend plus per-domain overrides.DomainFacility.open(spec)opens a declared domain over a routed backend (errors:already-open,backend-not-found,facet-unsupported,invalid-recordwith an optionalbackup-and-skippolicy, and backendversion-mismatch/malformed-medium);get(name)is the untyped lookup;closeAll()closes everything.defineDomain/domainTable/descriptorOfdeclare specs with zod record schemas; open domains emitdomain/changedevents.
The domain layer is deliberately generic: schema-validated, change-emitting
KV with no memory semantics (no budgets, no approval, no audit). The
workspace subsystem is its first consumer, and
session-projection-cache/src/spec.ts already declares its domain with
defineDomain -- the mounting pattern this proposal now follows.
From "new ctx.memory seam" to "memory protocol v1 on ctx.storage.domain".
- Entry model ->
DomainSpec. Declare the memory store once withdefineDomain: tracks become tables, entries become zod-validated records keyed by agent key + entry id,versioncarries the protocol schema version, andcompatibleVersionscarries the migration list. The domain layer's loudinvalid-recordfailure (plus the optionalbackup-and-skippolicy) matches the protocol's "fail loud, never silently truncate" stance. - Medium -> existing backends. No backend work: open the domain over
sqlite(point updates, WAL) orjson(portable, human-readable) through the domain plugin'sConfig.backend/Config.routes. The json/sqlite dual backend already exists upstream. - Service -> thin protocol layer over the domain handle. The official
seam contributes the storage spine; the protocol's semantics -- the
write gate enforced inside the provider, model-invisible
writePolicy, per-track/per-layer budgets, the audit ledger, the conformance suite -- stay in the memory layer (MemoryProtocolCore/MemoryService), which owns theDomainhandle returned byctx.storage.domain.open(spec)and keeps the approval transport on top of it. - Naming -> StorageForms / domain vocabulary. When a service-shaped
face is wanted, promote the memory facility to a mounted data form via
declaration merging (
interface StorageForms { memory: MemoryFacility }, thedomain: DomainFacilityprecedent), mount it inapplywithctx.storage.mount('memory', facility)(disposal unmounts), and reach it asctx.storage.memory-- thectx.storage.domaingetter is the precedent. InjectstorageBackendServiceKey('sqlite')so the form never activates before its backend (the storage-domain precedent). The earlierctx.memory/ctx.memoryAdaptersvocabulary is superseded byctx.storage.memoryplus the adapter registry inside the facility.
- Declare the memory
DomainSpecand publish it; existing plugins see no behavior change, and the conformance suite remains the acceptance gate. - Route the domain to
sqliteby default (orjsonfor portable stores) via the domain pluginConfig-- no backend code changes. - Land the memory data form (
StorageForms['memory']) as the official provider shape: the protocol core rides theDomainhandle, keeps the approval transport and audit ledger, and mounts/unmounts as an effect. - Register the
memory/*session event types when the session event vocabulary opens up (unchanged from above); conformance suite adoption unchanged.
Backward compatibility: the earlier "new ctx.memory seam" path is a
subset of this framing -- the protocol still ships and rehearses as a
community plugin; only the official surface it targets has changed.