coding-agents
vectorize-io/hindsightHindsight:会学习的 Agent 长期记忆系统,自动召回/保存、知识页、深度反思与按仓库隔离的记忆银行。
dsh 长期记忆:对话自动蒸馏为原子事实、场景摘要与用户画像,每步模型调用前自动注入相关记忆。开箱零配置——BM25 + 向量混合检索,可选本地离线嵌入,chat/work 双族隔离。
A layered distillation memory plugin for DeepSeek Harness: conversations are processed in the background through L0 capture → L1 atomic memories → L2 scene consolidation → L3 persona distillation, and relevant memories are automatically injected into context before every model step — neither the user nor the model needs to do anything.
Requires Node ≥ 22.16 and DeepSeek Harness 0.1.2-alpha.1 ~ 0.1.2-rc.1
(as of 0.10.0 support extends to 0.1.2-rc.1, which the npm "latest" tag
already points to; for older hosts see the
release history).
Two invocation styles — the npx prefix can replace dsh in
any command below:
# Option 1: run the official CLI directly via npx (no pre-installed dsh)
npx -y @deepseek-ai/dsh plugin --profile web add dsh-layered-memory
# Option 2: with the dsh CLI installed (upgrade: npm i -g @deepseek-ai/dsh,
# then restart; dsh is a pnpm forwarder — npm i -g pnpm first if missing)
dsh plugin --profile web add dsh-layered-memory
# Alternative sources: GitHub repo / local path (dev & debugging, link: points at the repo; pnpm run build + restart dsh to apply)
dsh plugin --profile web add https://github.com/JunNanLYS/dsh-layered-memory
dsh plugin --profile web add /path/to/dsh-layered-memory
If your current agent can run terminal commands, send it this message as-is:
Please install the dsh-layered-memory plugin for the web profile of DeepSeek Harness.
Run only the two commands below and do not modify any other profile:
dsh plugin --profile web add dsh-layered-memory
dsh --profile web --dump-config
Confirm that dsh-layered-memory appears in the output, then report the result to me.
Do not close or restart my running DSH yourself; after installation, remind me to manually restart the DSH Web Host.
The agent should report the installation result and explicitly tell you whether
dsh-layered-memory has appeared in the configuration.
This package declares a dsh.bundle composition layer (cordis.patch.yml); after
installation the plugin entry is mounted automatically — no need to hand-edit
$DSH_HOME/profiles/web/cordis.patch.yml. Then restart DeepSeek Harness and verify:
the appearance of conversations/ records/ scenes/ and memory.db under
~/.dsh/memory/ means the plugin applied successfully; the "Memory" page in settings (the Memory Workspace with five zones)
and the memory chip in the input bar (Memory · Auto) mean the client half is ready.
⚠️ Security note: installing a plugin = running third-party code with your privileges. This plugin reads session content, writes files in its data directory, and calls the LLM/embedding services you configured; if that concerns you, review the source first (
src/).
Uninstall: dsh plugin --profile web remove dsh-layered-memory + restart. Data
stays in ~/.dsh/memory/; delete the whole directory manually if you don't need it.
Beyond web, the same plugin installs into terminal-shaped hosts:
# dsh-tui (Claude Code style TUI front door — see github.com/ccch1mneyyy/dsh-TUI · site https://dshtui.com)
dsh plugin --profile dsh-tui add @deepseek-harness-tui/dsh-tui # first time: create profile + install the TUI host
dsh plugin --profile dsh-tui add dsh-layered-memory
dsh --profile dsh-tui
/memory <auto|chat|work|off> switches the current session's memory mode; with no
argument a selection panel pops up;记忆:智能-style mode readout sits right above the prompt;/settings section: master switch, capture, distill, recall, distill model routing and
input budget are editable in place (same store as the web settings panel; complex routing
chains still go through web / YAML);dsh --profile headless "one-shot task"): capture and distillation work as
usual with no UI elements. Web and terminal hosts share the same ~/.dsh/memory/ store.git clone https://github.com/JunNanLYS/dsh-layered-memory
cd dsh-layered-memory
pnpm install && pnpm run build
dsh plugin --profile web add . # link: install; after code changes, pnpm run build + restart dsh
pnpm run smoke # smoke test (rebuild first: see command below)
npx tsc src/smoke.ts --outDir dist-smoke --module nodenext --moduleResolution nodenext --target es2022 --strict --skipLibCheck --esModuleInterop
Dependencies are managed with pnpm (matching the official dsh install channel; the version is pinned via the packageManager field).
The plugin attaches to DSH-native event seams (session/event for capture,
agent/pre-step for injection) and reuses the host's ctx.llm for distillation. Recall
is presented as message-side injection: relevant memories enter the conversation as a
synthetic message placed right before the user's new message, rendered as a
"Context injection · memory" row in the chat flow (expand to see the hits) — so you
can see "memory at work" directly. Injected content is bounded by length and
time budgets — oversized lines are truncated (pointing the model at the memory tools for
the full text) and a timed-out recall silently skips that turn, never slowing the chat.
Per-session dedupe: a memory already injected in this session is not injected again
(the model's context already holds it — follow-up questions on the same topic save
tokens); the record resets when the context is compacted or cleared, so memories can
flow back in, and an updated memory (new id after a content change) is never held back
by the old suppression. Freshness weighting: recall ranking applies a soft weight
relevance × max(0.5, 0.5^(days since last update / 30)) — among candidates of similar
relevance the fresh one wins (slots rotate naturally), while a strongly relevant old
memory still recalls fine (the floor caps its loss at half a ranking score, so
long-lived facts never sink); tune via recall.decayHalfLifeDays, 0 disables. It
also registers three model-callable memory tools: memory_search /
conversation_search / memory_read_scene.
Cost dashboard: every distillation LLM call (extract / dedup / L2 / L3) writes its
token cost to a SQLite detail table keyed by provider/model (configurable retention,
default 365 days with rolling cleanup on write; accounting failures only log a warning
and never block distillation). Visualize it under Settings → Memory → Insights → the Cost sub-page:
per-model trend lines (day/week/month granularity + last-N-days window + L1/L2/L3 layer
filter), a layer × time-window table (calls / output & reasoning tokens / mean / median),
and per-model totals — distillation overhead at a glance. Input is counted in characters
(DSH streaming usage carries no input tokens); output and reasoning in tokens.
In action: the "Context injection · memory" row surfaces relevant memories first, and
the model then calls memory_read_scene directly to read scene blocks before answering
from memory:
In restricted sessions where only the code-execution entry point is available, the model
reaches the memory tools indirectly through run_code (nested as SUBTOOL calls in the
trajectory view):
The conversation side is a distributed memory surface — each kind of information lives in the native host seat designed for it; the plugin no longer owns a strip:
Memory · {Auto|Personal|Work} chip in official composer-chip grammar; the text is
the resolved truth — Memory · write-only (injection off), Memory · paused
(gray dot), Memory · degraded (amber dot); zh/en bilingual, following the host
language.Memory scope {value} ›
and Data flow {value} ›; data-flow options live in a hover-only secondary
panel (Follow global / Read & write / Write only / Paused) — no click-pinning,
with bridge hot-zones so slow mouse travel never breaks the hover chain; full
keyboard path (arrow-key roving + focus reveal).aria-valuetext.session-modes.json (stacked with the
global switches — global is the master gate); L2/L3 are fully family-isolated.N pending distill telemetry
segment appended to the official stats line.memory_search and the other read tools return a write-only notice). The override
persists per session; switching back to "Follow global" clears it to the recall
toggle in Automation. Ideal for debug/eval/sensitive sessions that should absorb
without interference. Orthogonal to Paused: paused is full stealth (capture off
too), while write-only keeps the "in" and gates the "out"./memory command, a status line and a /settings section (see Quick Start).
Settings → Memory is the Memory Workspace (five-zone task nav, sticky tabs + arrow-key roving):
Screenshots show what the plugin looks like — this section answers "what does enabling it actually buy you?" with measured numbers from an automated benchmark (bench/, one command to reproduce). Method: the same scenario bank with verbatim-identical inputs runs in Group A (memory on) with 3 merged repetitions and Group B (memory off) with 1 repetition (a memory-off long task burns multiples of the tokens per scenario — a cost guardrail); the dialog track now runs Group A only (memory-off probes in independent sessions cannot succeed, so the control carries no information — retired). Dialog-track environment: DeepSeek official deepseek-v4-flash, plugin 0.8.5 (judge same-source as tested; every answer archived for manual audit), Windows; taxonomy adapted from LongMemEval / LoCoMo / AMB, with the extended probe types and lifecycle track informed by MemoryAgentBench / GoodAI LTM / BEAM.
The dialog track below is the fresh 0.8.5 baseline (fixed plugin + corrected judging criteria); the workflow-track numbers remain the archived 0.8.3 run (the bank has since grown to 8 scenarios with a prospective-memory addition — re-run pending).
0.8.5 baseline (Group A data; the dialog-track B arm is retired — Group A only).
Dual-channel recall (Group A): passive injection hit rate 78.1% (the answer's key points appear in the recall injection, 281/360); most of the rest the model recovered by actively calling the memory tools — 106 questions with active queries, 75 rescued by tools. The end-to-end 95.2% is the composite of both channels plus model utilization. With the memory store accumulating across scenarios for the whole run, 295 probe injections carried other scenarios' memories (honestly counted) — yet accuracy actually rose from 92.8% (early, small store) to 97.7% (late, largest store), and offline flooding with 600 extra synthetic records moved retrieval recall@5 by only −2.8pp: interference resistance under a growing store, measured.
Layered weaknesses: offline retrieval metrics (recall@5, controlled replay) total 73.3%, with event ordering at 0% and scene recall at 50% — end-to-end still 93%+ thanks to model robustness over adjacent injected memories. Efficiency triangle (the cost of memory): injections add no latency (injected turns respond 210ms faster on average), recall text is ~10.3% of per-turn input, and the whole distillation pipeline costs ≈2727 input / 240 output tokens per captured message (1172 calls, 0 failures).
Probe-phase completion 85.5% vs 43.5% (+42pp): both groups have live context during teach/change phases — the probe phase (continuation task in a fresh session) is the pure memory window. Group A scored a perfect 12/12 on all three new probe archetypes (workflow knowledge update / twin-runbook disambiguation / style-convention continuity), consistent across all three reps; Group B scored 0/4 on style-convention probes (naming/structure/thousands-separator/footer conventions exist only in memory — they cannot be explored out of the sandbox), while on the workflow-update scenario it can reverse-engineer the procedure by reading the script (discrimination limited by sandbox affordances, honestly noted).
Long-task cost: Group B burns 6.8× Group A's input tokens per scenario (1.81M vs 266k) — without memory the agent advances by re-exploring, and under a high reasoning effort it even builds its own projects to probe what a one-line script convention would have done; output tokens 3× (46.2k vs 15.4k), steps +70%. This is memory's core value: what it saves is not task difficulty, but pointless round-trips and re-exploration.
node bench/harness/run.mjs --arm A --repeats 3 --provider deepseek-official --model deepseek-v4-flash # dialog track (Group A only)
node bench/harness/run.mjs --track workflow --arm AB --repeats 3 ... # workflow track (A/B arms in parallel)
node bench/harness/run.mjs --track lifecycle --arm A ... # lifecycle track (gating/off/rebuild/forget)
node bench/harness/report.mjs --latest [dialog|workflow] # aggregate report
node bench/harness/retrieval-metrics.mjs <runDir> --flood 200,600 # retrieval metrics + flooding curve
contains-all plus an LLM judge against key points (every answer and verdict is preserved in result.json for human audit); for stale-bearing probes (updates/update-chains/forget) an old value only fails when stated as the current answer, and abstention probes allow citing real adjacent facts while denying the asked point; workflow completion is verified programmatically from produced files and their contents (four check kinds: positive / forbidden-word / must-not-exist / exists);--no-panel to disable) — per-arm scenario/phase/message-level progress, heartbeat & activity freshness (distinguishes "stuck" from "process died"), and cumulative cost as it accrues;compare.mjs (environment header check including git SHA + Group-B control-drift warning + retrieval-metric comparison);Full reports and per-question data: bench/baseline/.
Override configs go into the profile's own cordis.patch.yml as a top-level bare
patch entry (direct id:, not wrapped in insert: — an insert with the same id as
the bundle layer appends and causes duplicate loader entry id startup failure):
- id: dsh-memory
name: dsh-layered-memory
config: # keys replace whole lines (no deep merge); write out all keys you want to keep
family: auto # default mode for new sessions: auto | chat | work
llm: # static distillation route (both fields set = deployment pin,
provider: '' # which outranks the settings-page route chain; when empty the route
model: '' # follows the route-chain primary row in the settings page or the default model)