DSH ↔ agentmemory session-memory bridge: mirrors session lifecycle into a local agentmemory daemon (REST), exposes memory_recall / memory_remember tools, and injects a per-session memory context window via agent/pre-step.
Install
# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)
dsh plugin --profile web add github:Yiipu/dsh-agentmemory
Any plugin you install runs third-party code with your own permissions — it can read your files, use your credentials, and reach the network, and tool approvals don’t sandbox it. GitHub-sourced plugins also run build scripts at install time. Only install sources you trust, and pin a commit (github:owner/repo#sha).
README
dsh-agentmemory — a DSH (DeepSeek Harness) Cordis plugin that bridges a session's activity into agentmemory, a local, self-hosted memory daemon.
English | 中文
The plugin subscribes to the DSH Session lifecycle events, mirrors session activity into the agentmemory daemon (REST, default http://localhost:3111), and exposes memory_recall / memory_remember model tools. It additionally injects a memory window into model requests once per session through the agent/pre-step waterfall.
All configuration comes from the plugin's row in cordis.yml — editing the config field of that row (or the file that carries it) is how you change configuration. There is no browser UI and no persisted config file.
agentmemory is a hard dependency. On load, the plugin checks
<baseUrl>/agentmemory/livezbefore registering any capability (tools, listeners). If the daemon is unreachable or does not reportstatus: ok, the plugin fails to load loudly — it does not silently degrade.
Capability overview
| Capability | Implementation |
|---|---|
| Session lifecycle → agentmemory | session/created → session/start; session/event → observe (buffered); session/flush → flush to disk; session/disposed → session/end |
| Model tools (read) | memory_recall → POST /agentmemory/search (auto-locates sessionId/project from the calling session) |
| Model tools (write) | memory_remember → POST /agentmemory/remember (decisions, preferences, architecture facts, and similar) |
| Memory injection | Sourced user/message via the agent/pre-step waterfall: ① project recall (form: 'recall') injects the project-level /context cross-session window once per session; ② semantic recall (form: 'semantic', optional) uses each user message to recall relevant memory titles via /smart-search; a pre-compaction re-inject keeps the project window when history compresses. The front end renders each injection as an independent "context injection" block (ContextMessageNode) |
| Configuration source | Read-only config from the cordis.yml row; no UI, no file persistence |
Install (static composition)
Mount the row from cordis-row.example.yml into a host composition cordis.yml (or into a per-session agent preset's composition under ${DSH_HOME:-$HOME/.dsh}/.agent-presets/<id>/). First install the package from GitHub into a profile:
dsh plugin --profile web add github:Yiipu/dsh-agentmemory
(A local dev checkout also works via dsh plugin --profile web add /path/to/checkout.)
Then add the row (@ begins a YAML reserved scalar, so the package name must be quoted):
- insert:
- id: agentmemory-bridge
name: "dsh-agentmemory"
config:
baseUrl: http://localhost:3111
enabled: true
Validate: node scripts/boot-check.mjs (7 checks, needs the daemon on :3111); node test/smoke.mjs (end-to-end, needs the daemon on :3111).
Requires Node >= 20 and a
shellcapability seam (the standard bash/pwsh executors).
Hard dependency: the daemon must be reachable at load
agentmemory is a hard dependency. apply() probes <baseUrl>/agentmemory/livez with one curl before registering any capability:
- reachable and
status: ok→ loading continues; - unreachable / not ok → plugin load fails (loudly), no silent degradation;
- no
shellseam → load fails.
This is a load-time hard gate. If the daemon drops during a run the plugin is tolerant: every call is logged and contained, and it never vetoes a session lifecycle event or a model step.
Configuration keys
baseUrl, secret (below), enabled (bridge master switch), enableTools, enableSessionStartEnd, curlTimeoutMs, observeBatchLimit, maxContentChars, maxArgsChars,
injectContext (project recall switch, default true), injectContextMaxChars (default 6000), injectContextOnCompaction (pre-compaction re-inject, default true),
injectSemantic (semantic recall switch, default false), injectSemanticMaxResults (default 8), injectSemanticMaxChars (default 3000).
Keys omitted from a row are filled with defaults by Cordis according to the plugin's Config schema — do not hand-write a merge. Cordis validates natively: an invalid value (such as curlTimeoutMs: -5) makes the plugin fail to load with a clear error. The full numeric bounds (min/max) are declared on each key in index.js.
secret: plaintext or an environment-variable reference
secret accepts two forms; whether you write plaintext is your choice:
| Form | Behavior |
|---|---|
secret: "xxx" |
Plaintext, used verbatim as the Bearer token |
secret: '${AGENTMEMORY_SECRET}' |
Reads the env var; if unset → load fails (loudly) |
secret: '${AGENTMEMORY_SECRET:default}' |
Reads the env var; if unset → uses default |
secret: '${AGENTMEMORY_SECRET:?goes nowhere}' |
Reads the env var; if unset → fails with the message goes nowhere |
Environment variables are resolved through the shell seam at the start of apply() (the sandbox has no direct env access).
Memory injection (read side → model + "context injection" front-end block)
The bridge uses DSH's native agent/pre-step injection channel (the same pattern as dsh-time-context) and appends sourced user/message rows to the tail of the incoming message batch. The two injection routes each have a distinct job and both deduplicate at the event level — they do not re-inject on every tool step.
Route 1 — Project recall (form: 'recall', on by default)
Injects once per session the agentmemory /context project-level cross-session window ("what this project has done before", excluding the current session). session/created → /session/start caches context from the response; the first step in agent/pre-step that has a cached window appends it once (st.injectedContext dedup); every user/message still refreshes the cache asynchronously.
Route 2 — Semantic recall (form: 'semantic', off by default)
Each user/message runs a /smart-search (BM25 + vector + graph) on the raw text and assembles the recalled memory titles into one injected message (st.semanticSeq dedups per message). Precise recall (/search) is still left to the agent's explicit memory_recall tool.
Pre-compaction re-inject (injectContextOnCompaction, on by default)
compaction/start triggers a /context refresh and the next agent/pre-step re-injects the latest project window so project background survives history compression.
Session lifecycle → agentmemory mapping
The bridge maps every DSH event to an agentmemory standard hookType so the daemon's compression pipeline reads real content (custom hookTypes collapse to {timestamp, hookType}, degrading summaries):
| DSH event | hookType | standard data fields |
dedup discriminator |
|---|---|---|---|
user/message |
prompt_submit |
prompt=content |
tool_input=content (identical prompts merge naturally) |
assistant/message |
post_tool_use |
tool_name='assistant_message', tool_output=content |
tool_input='#'+seq (unique; content not in Input) |
tool/call |
dsh_tool_call |
tool_name='dsh_call' |
tool_input='call#'+callId (not merged with the result) |
tool/result (ok) |
post_tool_use |
tool_name/tool_input/tool_output (callMeta fallback) |
tool_input=args |
tool/result (err) |
post_tool_failure |
same + error |
tool_input=args |
turn/end |
dsh_turn_end |
— | tool_input='turn#'+seq (unique) |
Dedup-safe design: agentmemory's mem::observe drops duplicates by sha256(sessionId, tool_name||hookType, tool_input[0..500]) with a 5-minute TTL; a hit discards the observation. The bridge uses a per-session monotonic seq and natural content/callId as the tool_input discriminator, so multiple rows of the same kind all persist while identical prompts / identical (tool, args) results still merge naturally.
Model tools
| Tool | Arguments | Description |
|---|---|---|
memory_recall |
query (required), limit?, sessionId?, project? |
Auto-locates sessionId/project from the calling session; recalls across sessions |
memory_remember |
content (required), type?, concepts?, ttlDays? |
Curated, durable memory; type ∈ pattern/preference/architecture/bug/workflow/fact |
Tools are defined with defineTool and registered through ctx.tools.register, and daemon transport runs over the host shell seam (inject: ['tools', 'shell']); both are cleaned up automatically with the plugin Fiber lifecycle. On failure, execute returns {ok: false, error} rather than throwing.
Model Experience
Common requests
What the model sees
The bridge does not alter accepted input. Its observable model input is additive after the incoming batch: one injected sourced user/message per projective event, whose text is the project /context window (route 1), the semantic recall titles (route 2), or a pre-compaction re-inject of the project window (source.kind === 'plugin', plugin === 'agentmemory', form 'recall' / 'semantic'). Tool calls memory_recall and memory_remember are registered only when enableTools is true.
Token effect
Conditional. Injection contributes extra input tokens only at the dedup boundaries — once per session for project recall, once per user message for semantic recall when enabled, and once per compaction — not per tool step. Capped by injectContextMaxChars / injectSemanticMaxChars. Tool schemas add a small fixed token cost while enableTools is true. Tool responses are returned to the model's context window normally.
KV Cache effect
Append-only, prefix-stable. Injected messages are appended at the tail of the step's message batch, so the prior message prefix is preserved and reusable; re-injection changes the suffix. The dedup conditions (injectedContext, injectedSemanticKey, compactionInject) prevent repeated appends of the same block at the same boundary, so a stable prefix is not invalidated by the bridge's own activity across steps.
Transport and failure semantics
- The sandbox has no
fetch/require/timers; outbound traffic goes through theshellcapability seam, one curl per call with the JSON body on stdin (--data-binary @-). - Never veto a lifecycle event: listeners are fully try/catch guarded.
- Infrastructure failures re-queue and retry at the next checkpoint; payload-level failures are logged and dropped.
- Flush race: if a flush is in flight when new events arrive, the
dirtyflag makes the in-flight loop re-send them, so nothing is lost.
Verification
cd plugins/agentmemory
node scripts/boot-check.mjs # module + Config schema + daemon livez + peer deps (7 checks)
node test/smoke.mjs # end-to-end (needs the daemon on :3111)
The smoke test covers: Config schema validation (including rejecting invalid values), the apply liveness hard gate (including loud failure on an unreachable daemon), defineTool tool registration, the three secret env-reference forms, observation persistence and session state, and the agent/pre-step injection shape with watermark pass-through.
Local development: resolving peer dependencies
The plugin imports @deepseek-ai/schemastery and @deepseek-ai/dsh-tools (peer deps, resolved by the harness at runtime). To run boot-check / smoke with plain Node from this repo without installing the whole harness, link the built-in packages in:
mkdir -p node_modules
ln -s <harness>/node_modules/@deepseek-ai node_modules/@deepseek-ai
(<harness> is the global dsh install directory; when installed via pnpm in a profile these resolve automatically and this step is unnecessary.)
Files
| File | Purpose |
|---|---|
index.js |
Single static entry: Config schema, inject, apply (lifecycle + tools + injection + livez hard gate) |
index.d.ts |
Config type and the plugin's exported type surface |
cordis-row.example.yml |
Static composition row example (name uses a resolvable package name) |
scripts/boot-check.mjs |
Boot/CI readiness check (module + schema + daemon + peer deps, 7 checks) |
test/smoke.mjs |
End-to-end smoke test |
package.json |
Publishable structure (dsh-agentmemory, with peer deps) |
Known Limitations and Deferred Work
- Memory is shared across sessions by default (no
agentIdis passed); to isolate, add anagentIdtoobserve/remember calls. - Project resolution order:
AGENTMEMORY_PROJECT_NAMEenv var → git toplevel basename → cwd basename. - The bridge does not modify any
@deepseek-aipackage and does not touch the shipped preset install directory.
Links
More in this category
vectorize-io/hindsight#coding-agents★ 19981
Hindsight, agent memory that learns: long-term project memory with auto recall and retain, knowledge pages, deep reflection, and per-repo memory banks.
omdsh-dev/dsh-mnemon★ 27
Cross-agent, local-first persistent memory plugin for DeepSeek Harness (DSH), powered by Mnemon. It shares long-term memory across Mnemon-enabled agents and adds runtime memory, searchable project documents, semantic recall, knowledge graph, and a Sidebar UI.
LoserFox/distill★ 19
Automatic conversation distillation: background subagent reflection + skill create/update.
Tyan66666/billion-context-dsh★ 13
Model-driven context compression (Active Context Pruning) for DeepSeek Harness: the model decides when and what to compress.
modusensus/dsh-mneme#dsh-mneme★ 12
Cross-session memory for DSH: SQLite + human-editable Markdown mirror, autoDream consolidation, six memory tools, and fully-offline semantic search (local embeddings, reranking, clustering).
Aik358/dsh-auto-memory★ 11
Auto-memory for DSH: three-layer memory (user / project notes / daily logs) with automatic injection, per-turn auto-consolidation, AI greetings, smart search, a calendar view and a settings page, plus inheritance of other AI tools' memories.