DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

BPTumbleweed /

BPTumbleweed/dsh-agent-memory

Verified

DSH 长期记忆插件 + 配套 CLI:实时采集对话证据、只读数据浏览、会话内工作状态面板。零运行时依赖、能力探测与熔断,面向跨版本升级设计;不接管偏好注入(留给官方 agent-instructions)。

★ 0 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: main@bb6ef379

dsh-agent-memory

A long-term memory plugin for DeepSeek Harness (DSH): real-time capture of conversation evidence, a read-only data browser, and a status panel inside the Web UI.

Built to survive DSH upgrades: zero runtime dependencies, every capability feature-detected, every callback wrapped — and it deliberately stays out of the prompt-injection path.

✅ Real-time capture Hooks session/event and appends human user messages to evidence/live-messages.jsonl
✅ Status panel /dsh-agent-memory/panel, surfaced as a 记忆 tab inside the conversation view
✅ Read-only data browser 10 whitelisted endpoints under /dsh-agent-memory/data/<kind>
✅ Self-check & circuit breaker Capabilities are probed at load; a failing one trips after N errors without affecting DSH
❌ No prompt injection Injection stays with the official @deepseek-ai/dsh-agent-instructions + $DSH_HOME/AGENTS.md. This plugin never touches agent/pre-step / agent.inbox — internal APIs and the most upgrade-fragile layer

Install

# from a local checkout
dsh plugin --profile web add link:/path/to/dsh-agent-memory

# from GitHub
dsh plugin --profile web add github:<owner>/dsh-agent-memory

# restart the instance the profile serves
systemctl restart dsh-web-<your-instance>

Verify what actually got composed — run it as the DSH user with DSH_HOME set, because running it as root reads a different profile:

sudo -u <dsh-user> env DSH_HOME=/var/lib/dsh dsh --profile web --dump-config \
  | grep -A8 dsh-agent-memory

Uninstall / roll back:

dsh plugin --profile web remove dsh-agent-memory

Configuration

Set in the entry's config (see cordis.patch.yml), or override from your own profile patch. The shipped defaults are machine-independent: the store lands under $DSH_HOME.

key default meaning
storeRoot $DSH_HOME/agent-memory memory store root
dshHome $DSH_HOME (or ~/.dsh) used to locate AGENTS.md
routePrefix /dsh-agent-memory prefix for the panel and data routes
breakerThreshold 5 consecutive failures before a capability trips
allowUnfencedPanel false serve the panel when the browser-trust fence is unavailable (fail-closed by default)

Memory store layout

The plugin only reads this layout; the companion CLI writes it:

<storeRoot>/
├── bin/                         companion CLI (symlink or copy the repo's bin/ here)
├── secrets.files                optional: extra files to harvest secret values from
├── preferences/merlin.md        the preference set (this is what gets injected)
├── preferences/evidence.md      per-item provenance (not injected)
├── skills/index.md, *.md        reusable playbooks
├── evidence/user-messages.jsonl rolling working set of human messages
├── evidence/live-messages.jsonl written by this plugin, merged by the CLI
├── evidence/signals.jsonl       candidate preferences awaiting distillation
├── evidence/archive.jsonl       ledger of compacted raw messages
├── digest.md                    session-start brief
└── bin/memory-scan.py, memory-note.py

Raw messages are a working set, not an archive: the authoritative copy is DSH's own session log, so older records are compacted away and can be rebuilt from $DSH_HOME/sessions.

Companion CLI

The panel shows real data only if something fills the store. That is what bin/ does:

# point it at your DSH home and memory root, then do a first full backfill
python3 bin/memory-scan.py --full --root ~/agent-memory --dsh-home ~/.dsh

# record one durable preference right now (also refreshes digest.md)
python3 bin/memory-note.py "prefers terse, evidence-backed answers" --section "沟通"

# after distilling candidates, clear the pending counter
python3 bin/memory-scan.py --mark-distilled

Path resolution — first match wins:

value order
store root --root → $AGENT_MEMORY_ROOT → the store the script itself sits in (<store>/bin/…) → $DSH_HOME/agent-memory
DSH home --dsh-home → $DSH_HOME → ~/.dsh
session logs --sessions → $DSH_HOME/sessions/<derived from cwd>

The easiest wiring is to symlink or copy bin/ into your store (<store>/bin/): the scripts then auto-detect the store, and the panel's copy-ready commands work as-is.

Run it on a timer (5 minutes is plenty — an idle incremental scan costs ~0.2 s):

# /etc/systemd/system/agent-memory.service
[Service]
Type=oneshot
User=<your-user>
Environment=DSH_HOME=/var/lib/dsh
WorkingDirectory=/path/to/your/project   # used to pick the right session directory
ExecStart=/usr/bin/python3 /path/to/agent-memory/bin/memory-scan.py --quiet

What the CLI does with secrets

Everything written into the store passes through bin/redact.py first: known secret values harvested from configured files, plus structural patterns (bearer/basic headers, token=, cookie records and cookie headers, JWTs, sk-…, ghp_…, bcrypt hashes, private keys, literal password=… assignments). List extra sources in <store>/secrets.files (one path per line); each is auto-detected as a Netscape cookie jar, a KEY=VALUE file, or a single-token file. $DSH_HOME/.credentials.yaml is read when present. Run python3 bin/redact.py for its self-test.

Storage model

Raw messages are a working set: after each run the oldest records beyond EVIDENCE_KEEP (120) are compacted away and a line is appended to evidence/archive.jsonl. Nothing is lost — the authoritative copy is DSH's own session log, and --rebuild regenerates the store from it.

Two layers of memory

global per-conversation
file preferences/global.md sessions/<session-id>.md
injected by the official agent-instructions via $DSH_HOME/AGENTS.md this plugin, through agent/pre-step
scope every conversation only that conversation
budget 6 KB warning (AGENTS_WARN_BYTES) 2 KB, truncated beyond (sessionInjectMax)
# universal habits → every conversation
python3 bin/memory-note.py "always back up before changing config" --section "干活"

# only this conversation (session id defaults to $DSH_SESSION_ID)
python3 bin/memory-note.py "this task touches CSS only" --scope session --section "约定"

The plugin registers a per-agent agent/pre-step handler, calls next() first so other plugins keep working, then inserts the session memory before the last user message — so the user's current instruction is still the last thing said. The injected message carries source: { kind: "plugin", plugin: "dsh-agent-memory", form: "session-memory" }.

That hook is an internal API, so this layer is treated as the fragile one: it is feature-detected, wrapped in the circuit breaker, and bounded by a byte budget. If it ever stops working, the panel shows sessionMemory: unavailable/tripped and global injection plus the store are unaffected.

Session files that have not been updated for 90 days are moved to sessions/archive/ (never deleted), and sessions/index.json tracks size/entries/over-budget for every conversation.

Endpoints

route purpose
/dsh-agent-memory/panel self-contained HTML panel, auto-refreshing
/dsh-agent-memory/status.json machine-readable status
/dsh-agent-memory/data/<kind> human live signals preferences digest agents skills journal archive prefEvidence

Every route passes DSH's browser-trust fence (connection.requestRejection); when the fence is unavailable the panel is fail-closed. The data endpoints are a fixed whitelist, so there is no arbitrary-path entry point.

Compatibility rules

  1. Zero runtime dependencies — only node:fs, node:path, node:os, node:module.
  2. No hard dependencies — inject: []; webServer/connection are awaited optionally through ctx.inject([...], cb) and degrade to unavailable.
  3. All callbacks wrapped — nothing propagates into DSH.
  4. Circuit breaker — a failing capability disables only itself, and the reason shows up in the panel.
  5. Writes only under storeRoot — never edits DSH config or other plugins.
  6. Degradation chain — plugin down ⇒ the CLI timer still collects; panel down ⇒ journal/digest still work; plugin gone ⇒ injection and the store are unaffected.

package.json carries dsh.compatibility.dshReleases, and the panel displays the detected DSH version, so a version mismatch after an upgrade is visible at a glance.

Development

node test/selftest.mjs    # 8 checks (plugin)
python3 bin/redact.py     # 8 checks (redaction engine)

Plugin checks: message filtering/dedupe, the panel, the trust fence (fail-closed), the circuit breaker, loading with no services present, a guard asserting every value path used by the panel script exists in the payload, and the data-endpoint whitelist.

License

MIT

—/ 5

No ratings yet

Verified DSH bundle

Commit bb6ef379be47

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout