🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

记忆引擎 skepsun/dsh-engram

DeepSeek Harnessv0.9.0
skepsun8 天前有提交已提供中文文档扫描:含敏感能力

为 DeepSeek Harness 设计的极简长期记忆插件,融合了

38.7/100
综合分
8
GitHub 星标
1,584
npm 周下载
—/5
用户评分

数据截至 2026/10/30 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add dsh-engram
npm 源 · dsh-engram 的发布者账号已校验与本仓库一致

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:待复验
是什么[目录]—
dsh 原生插件 · chat
暂无时间戳·判据 →
装得上吗[实装验证]—
曾在 dsh 0.2.1-rc.5 下真实安装成功,但当前 dsh 已更新至 0.2.2-rc.1,本站尚未在新版下复验
L4 · 真实安装等级说明 →
数据截至 2026/10/31·判据 →
安全吗[自动扫描]—
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
数据截至 2026/11/05·判据 →
还在维护吗[仓库]✓
活跃:最近一次提交在 8 天前
暂无时间戳·判据 →
图例:✓ 通过! 需注意— 未判定 / 无数据· 「未判定」= 本站尚无数据,不代表插件有问题
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →

可信度证据

安装兼容性检查✓ 自动检查通过
展开查看检查明细 ↓

以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。

✓npm 包dsh-engram @ 0.9.0
✓Node 引擎要求 >=22.19.0 · 基线 Node 22.19 满足
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明

验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/11/1 08:18:33

安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

可执行外部命令读写/删除本地文件写入剪贴板(不计风险)发起外部网络请求
查看扫描证据(共 92 处,展示前 65 条)
  • 发起网络请求
    lib/client.js:67
    return readJson(await fetch(`${API_PREFIX}/overview`));
  • 发起网络请求
    lib/client.js:71
    await fetch(`${API_PREFIX}/memories${query({ workspace: opts.workspace, q: opts.q, kind: opts.kind, status: opts.status, limit: opts.limit })}`)
  • 发起网络请求
    lib/client.js:75
    return readJson(await fetch(`${API_PREFIX}/tasks${query({ workspace, includeStable: includeStable ? "1" : void 0 })}`));
  • 发起网络请求
    lib/client.js:83
    return readJson(await fetch(`${API_PREFIX}/approvals${query({ workspace })}`));
  • 发起网络请求
    lib/client.js:87
    await fetch(`${API_PREFIX}/approvals`, {
  • 发起网络请求
    lib/client.js:96
    return readJson(await fetch(`${API_PREFIX}/toolstats?days=${days}`));
  • 写入剪贴板
    lib/client.js:1765
    void navigator.clipboard.writeText(text).then(done).catch(done);
  • 读取本地文件
    scripts/dsh-engram.mjs:51
    await access(path);
  • 执行外部命令固定命令
    scripts/dsh-engram.mjs:80
    const found = execFileSync("which", ["dsh"], { encoding: "utf8" }).trim();
  • 读取本地文件
    scripts/dsh-engram.mjs:130
    entries = await readdir(dir, { withFileTypes: true });
  • 读取本地文件
    scripts/dsh-engram.mjs:153
    entries = await readdir(root, { withFileTypes: true });
  • 读取本地文件
    scripts/dsh-engram.mjs:314
    text = await readFile(preset.path, "utf8");
  • 读取本地文件
    scripts/dsh-engram.mjs:422
    dirs = await readdir(profilesDir, { withFileTypes: true });
  • 读取本地文件
    scripts/dsh-engram.mjs:431
    text = await readFile(join(profilesDir, dir.name, "cordis.patch.yml"), "utf8");
  • 读取本地文件
    lib/store.js:790
    if (!existsSync(resolved)) artifactReason = `artifact not found on disk: ${resolved}`;
  • 读取本地文件
    lib/anchor.js:30
    return createHash("sha256").update(readFileSync(file)).digest("hex").slice(0, 32);
  • 读取本地文件
    lib/anchor.js:56
    const stat = statSync(abs);
  • 读取本地文件
    lib/anchor.js:79
    stat = statSync(anchor.path);
  • 写入本地文件
    lib/push-legs.js:62
    mkdirSync(dirname(path), { recursive: true });
  • 读取本地文件
    lib/push-legs.js:63
    if (existsSync(path) && statSync(path).size > PUSH_LEGS_MAX_BYTES) {
  • 读取本地文件
    lib/push-legs.js:63
    if (existsSync(path) && statSync(path).size > PUSH_LEGS_MAX_BYTES) {
  • 读取本地文件
    lib/push-legs.js:64
    const kept = readFileSync(path, "utf8").split("\n").filter((line) => line.length > 0).slice(-PUSH_LEGS_KEEP);
  • 写入本地文件
    lib/push-legs.js:65
    writeFileSync(`${path}.tmp`, `${kept.join("\n")}\n`, "utf8");
  • 写入本地文件
    lib/push-legs.js:66
    renameSync(`${path}.tmp`, path);
  • 写入本地文件
    lib/push-legs.js:68
    appendFileSync(path, row, "utf8");
  • 读取本地文件
    lib/push-legs.js:86
    raw = readFileSync(pushLegsPath(engramDir), "utf8");
  • 读取本地文件
    lib/web-provision.js:393
    if (existsSync(dir)) return dir;
  • 读取本地文件
    lib/web-provision.js:400
    if (existsSync(dir)) return dir;
  • 读取本地文件
    lib/web-provision.js:409
    return existsSync(file) ? file : undefined;
  • 读取本地文件
    lib/web-provision.js:416
    entries = await readdir(presetsDir, { withFileTypes: true });
  • 读取本地文件
    lib/web-provision.js:532
    const readText = opts.readText ?? ((preset) => readFile(preset.path, "utf8"));
  • 写入本地文件
    lib/web-provision.js:533
    const writeText = opts.writeText ?? ((preset, text) => writeFile(preset.path, text, "utf8"));
  • 写入本地文件
    lib/web-provision.js:535
    opts.writeBackup ?? ((path, text) => writeFile(path, text, { flag: "wx" }));
  • 读取本地文件
    lib/web-provision.js:649
    const readText = opts.readText ?? ((preset) => readFile(preset.path, "utf8"));
  • 写入本地文件
    lib/web-provision.js:650
    const writeText = opts.writeText ?? ((preset, text) => writeFile(preset.path, text, "utf8"));
  • 写入本地文件
    lib/status.js:74
    await mkdir(dirname(path), { recursive: true });
  • 写入本地文件
    lib/status.js:76
    await writeFile(
  • 写入本地文件
    lib/status.js:81
    await rename(tmp, path);
  • 读取本地文件
    lib/status.js:91
    return JSON.parse(await readFile(contextGcStatusPath(), "utf8"));
  • 写入本地文件
    lib/status.js:115
    await mkdir(dirname(file), { recursive: true });
  • 读取本地文件
    lib/status.js:134
    const text = await readFile(gcAttemptPath(), "utf8");
  • 写入本地文件
    lib/status.js:165
    await mkdir(dirname(file), { recursive: true });
  • 读取本地文件
    lib/status.js:183
    const text = await readFile(pruneAttemptPath(), "utf8");
  • 发起网络请求
    eval/gate-audit.mjs:507
    const response = await fetch(`${base}/overview?workspace=${encodeURIComponent(workspace)}`);
  • 发起网络请求
    eval/gate-audit.mjs:523
    const response = await fetch(url);
  • 读取本地文件
    scripts/gen-release-notes.mjs:20
    const pkg = JSON.parse(readFileSync(resolve(root, "package.json"), "utf8"));
  • 执行外部命令来自配置/变量
    scripts/prepublish-check.mjs:33
    execFileSync(cmd, args, { cwd, stdio: "inherit" });
  • 读取本地文件
    scripts/prepublish-check.mjs:38
    for (const entry of readdirSync(dir, { withFileTypes: true })) {
  • 读取本地文件
    scripts/prepublish-check.mjs:43
    acc = Math.max(acc, statSync(p).mtimeMs);
  • 读取本地文件
    scripts/prepublish-check.mjs:59
    if (!existsSync(p)) throw new Error(`${bundle} is missing — run \`npm run build:client\` before publishing`);
  • 读取本地文件
    scripts/prepublish-check.mjs:60
    if (statSync(p).mtimeMs < newestSrc) {
  • 读取本地文件
    scripts/prepublish-check.mjs:73
    const tarball = readdirSync(tmp).find((f) => f.endsWith(".tgz"));
  • 读取本地文件
    scripts/prepublish-check.mjs:83
    const name = JSON.parse(readFileSync(join(root, "package.json"), "utf8")).name;
  • 删除本地文件递归
    scripts/prepublish-check.mjs:108
    rmSync(tmp, { recursive: true, force: true });
  • 读取本地文件
    scripts/setup-links.mjs:38
    existsSync(join(dir, "vendor", "schemastery", "package.json")) &&
  • 读取本地文件
    scripts/setup-links.mjs:39
    existsSync(join(dir, "packages", "settings", "settings", "package.json"))
  • 读取本地文件
    scripts/setup-links.mjs:47
    if (!existsSync(p)) throw new Error(`DSH_HARNESS_DIR 不存在: ${p}`);
  • 读取本地文件
    scripts/setup-links.mjs:73
    if (!existsSync(store)) return null;
  • 读取本地文件
    scripts/setup-links.mjs:76
    for (const name of readdirSync(store)) {
  • 读取本地文件
    scripts/setup-links.mjs:81
    if (existsSync(join(pkgDir, "package.json")) && major > bestMajor) {
  • 执行外部命令来自配置/变量
    scripts/setup-links.mjs:169
    const r = spawnSync(npm, ["install", "--no-audit", "--no-fund"], {
  • 删除本地文件
    scripts/setup-links.mjs:197
    unlinkSync(mount);
  • 删除本地文件
    scripts/setup-links.mjs:206
    rmdirSync(mount);
  • 写入本地文件
    scripts/setup-links.mjs:222
    mkdirSync(dirname(mount), { recursive: true });
  • 执行外部命令来自配置/变量
    scripts/setup-links.mjs:236
    const check = spawnSync(
扫描于 2026/11/5(1 天前) · 范围 入口闭包 + 全包扫描 · 来源 npm 发布包

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

社区实装还没有人打卡 · 装过的第一个就是你
依赖的 DSH / Cordis 模块
@deepseek-ai/cordis@deepseek-ai/dsh-storage-domain@deepseek-ai/dsh-settings@deepseek-ai/schemastery@deepseek-ai/dsh-compaction@deepseek-ai/dsh-compaction-basic@deepseek-ai/dsh-llm
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

功能亮点

  • 自动捕获关键事件
  • 按需召回记忆
  • 节省上下文token

极简、省 token 的 DSH 长期记忆插件:零 LLM 自动捕获、符号索引渐进披露、ESR 证据闭环。

v0.9.02026-10-29
· dsh-engram v0.9.0 这一版几乎没改产品代码,改的是「我们凭什么说自己有用」——评测从「跑得动」变成了「作弊不了」。 从 0.8.8 到这里共 22 个 commit,lib/ 一行未动,唯一的用户可见改动是 CLI 的一处 preset 扫描优先级。剩下的全部落在 eval/、test/ 和 docs/:把上一版还只能"声称"的 东西变成能复现的读数。 · 修复 · web-compaction 的 preset 扫描不再被陈旧的 legacy 目录抢占:--presets-dir 现在优先于一切。此前一个更早的 checkout 留在机器上时,它会先认领 preset id, 声明式 patch 文件按 id 被去重掉,于是 status 报的是 legacy 布局、 而声明式那份被显示成 custom。三条 Linux CLI 测试抓到了这个组合 (scripts/dsh-engram.mjs)。 · 评测基础设施(本版真正的内容) · G6 有了网络围栏(--sandbox --net-fence --net-endpoint host:port): bwrap 搭挂载树 → setpriv 掉到专用 uid → 三条按 uid 生效的 iptables 规则, 默认 REJECT,只放行 loopback 和一个模型端点。按 uid 而不是按进程过滤, 所以开发会话、harness、Web GUI 的网络不受影响。 跑矩阵前有两个自证,不通过就不计分:出网探针(端点可达 / github 000 / DNS 死) 和写目录探针(臂必须能在自己的 workspace 里落盘)。 · 第九轮结果:9 任务 × 2 seed × 2 臂,p-a 18/18 vs p-b 0/18,Δ +100.0pp, 18 对配对,infraRows 0,contaminated 0(eval/g6/runs/r9-net/)。 18 份控制日志里有 4 份试图出网,全部拿到 exit 124 / "network unavailable"—— 这是第七轮「控制臂去 GitHub 查到答案」的反面。 · 语料第 4 条自证:工作区 stub 必须导出 prompt 点名的函数,参数个数必须与 prompt 一致 (oracle 可以少传、不能多传)。这条当场抓出三处不一致:retry 的 stub 签的是 shouldRetry(status, code, attempt)、ranking/dedupe 的 stub 导出名 prompt 从未提过。 一份靠"猜哪份文档算数"得来的通过不是证据。 · 污染判定改为三分类(classifyLeak → memory / artifact / network), 并提供 quality-ab.mjs classify 从既有日志重算:围栏下"试过出网"不再等于"拿到了"。 · G7 从「未测」变成实测:账本自身开销最坏操作 0.2956%(闸门 =0.1.2-alpha.2 =0.1.2-alpha.2 =0.1.2-alpha.2 =0.1.2-alpha.2 =0.1.2-alpha.2 =0.1.2-alpha.2 =0.1.2-alpha.2 · 兼容性:配置键与数据格式与 0.8.8 完全一致,无需迁移;无破坏性改动。 · 验证 npm test → 519/519 npm run typecheck → ok npm run eval → BENCH PASS npm run build:client → 无 diff(bundle 新鲜) npm pack → 459KB / 94 files
v0.8.82026-10-27
· dsh-engram v0.8.8 0.8.7 的推送腿遥测写错了一层目录——装上真机、重启、看一眼状态,5 分钟内就露馅了。 · 修复:push-legs.jsonl 写到了 engram/engram/ 0.8.7 引入的推送腿遥测,engramHome() 已经以 engram/ 结尾,写入端又拼了一次 engram,于是文件落在 ~/.dsh/engram/engram/push-legs.jsonl;而 dsh-engram status 读的是 ~/.dsh/engram/push-legs.jsonl。 结果是一个工作正常的遥测,在状态里显示成「还没记录」——正好是这一版想消灭的那种 「看起来像没发生」。单测没抓到,因为测试自己造目录、两边用的是同一个临时路径。 现在把契约写死在一处:pushLegsPath(engramDir) 的参数就是 engram 目录本身, 写入端传 engramHome()、读取端传 /engram,注释里写明为什么不能拼第二次。 新增一条回归测试直接断言 pushLegsPath(dir) === join(dir, "push-legs.jsonl")。 · 顺带得到的一个真数据点 修好路径、把线上那条记录归位之后,本机(dsh 0.2.1-rc.2 CLI + web)的答案是: 推送腿:事件日志读法 snapshotEvents · 最近 2026/10/27 15:44:35 · snapshotEvents×1 也就是说我们自己的宿主走的是 snapshotEvents——ctx.sessionQuery 在这套组合里 没挂上(宿主已把它标为 deprecated,但仍在)。这解释了为什么 0.8.6 补的 seq+eventAt 第四手值得留着:桌面运行时那类宿主一旦连 snapshotEvents 也去掉, eventAt 就是最后的退路。 · 证据 · npm test 489/489(+1 路径契约回归) · npm run typecheck 通过;prepublishOnly 真实 tarball + peers 后 host/client 双入口导入通过 · 真机验证:dsh-engram status 打印出上面那行读法;~/.dsh/engram/engram/ 这个错误 目录已清理,记录已归位到 ~/.dsh/engram/push-legs.jsonl · DSH 版本支持(必填) · 支持的 DSH 范围:>=0.1.2-alpha.2 =0.1.2-alpha.2 <0.3.0-0、@deepseek-ai/cordis@^4.0.2、@deepseek-ai/schemastery@^3.18.2
v0.8.72026-10-27
· dsh-engram v0.8.7 「特性睡着了」这件事,现在有离线证据可查;顺手拆掉一句永远不会兑现的状态承诺。 0.8.6 修了 issue #9 的能力问题(宿主不暴露批量读接口时退回 seq+eventAt)。 这一版补的是可观测问题:就算下一次再遇到同类宿主,也不该有人靠解 zstd 帧 才能证明「不是还没触发,是根本不会触发」。 · 新增:推送腿(push legs)有了落盘证据 [RECALL]、会话 episode、[RESUME] 简报、会话结束的 todo sink —— 这四件事共享 同一个前提:会话事件日志可读。它们一起醒,也一起睡。 以前这里只有一个 warn,打在一个没人留着的终端上。现在每个会话装配时,插件把 实际走了哪条读法追加进 $DSH_HOME/engram/push-legs.jsonl(有界:超 32 KB 只留尾 50 行): {"at":1793086321920,"pid":59392,"sessionId":"session-…","source":"sessionQuery"} source 就是 sessionEventsSource() 的裁决:legacy / sessionQuery / snapshotEvents / eventAt / none。 dsh-engram status 多一行: 推送腿:事件日志读法 sessionQuery · 最近 2026/10/27 15:32:01 · sessionQuery×3 · eventAt×1 推送腿:⚠ 事件日志不可读(none)—— [RECALL]/episode/resume/todo-sink 休眠中 · 2026/10/27 15:32:01 · none×1 第二条就是 issue #9 那台机器的样子——不用开日志、不用解帧,一眼能看出是休眠还是没触发。 · 修复:minimal 预设的状态行在撒谎 dsh-engram status 对未接管的预设一直打印「出厂布局,重启 dsh web 后自动接管」。 但 DSH 0.2.1-rc.2 的 minimal.patch.yml 里 compaction 组缺 tool-result-pruner 行, 换行不变量(要标准形状)不成立,swapCompactionRow 返回 null,boot 时记的是 skipped / layout-mismatch——那句承诺永远不会兑现,重启多少次都是 stock。 现在对 stock 再做一次换行探测,换不动就直说: minimal: stock — 出厂布局但与标准形状不同(行不全),自动接管不会发生;要 Context GC 请手工接线或改用 standard · 证据 · npm test 488/488(+5:push-legs 落盘、最新裁决、休眠措辞、有界重写、坏文件不抛) · npm run typecheck 通过;prepublishOnly 真实 tarball + peers 装上后 host/client 双入口导入通过 · 本机 0.8.6 实机取证:状态快照 pid 59392(15:31:47 写入)→ recall-injections.json 的 lastInjection 为 15:32:01,即重启后 14 秒推送腿真的动了 · 升级后请做的一件事(如果你在 issue #9 里报过障) npx dsh-engram status 把「推送腿」那一行贴回 issue。如果它写 none,说明桌面运行时的 Session 连 eventAt 都不给,那才需要走「直接读 session.v4.jsonl.zstd」这条重路子 (Node 22 无内置 zstd,要引依赖);如果写 eventAt 或 sessionQuery,说明已经修好。 · DSH 版本支持(必填) · 支持的 DSH 范围:>=0.1.2-alpha.2 =0.1.2-alpha.2 <0.3.0-0、@deepseek-ai/cordis@^4.0.2、@deepseek-ai/schemastery@^3.18.2
数据来自仓库的 GitHub Release,可能与插件实际版本不一致。

README

由 DeepSeek 最新模型翻译生成
dsh-engram · English · 已交付 UI 控件清单 为 DeepSeek Harness 设计的极简长期记忆插件,融合了 symbolic-index 与 pi-esr 的思想——目标只有一个:省 token。 - 零 LLM 摄入 — 纯模式匹配从工具结果自动捕获有意义的事件(带书面 -m 提交信息的 git 里程碑、关键文件编辑、反复出现的错误),另有显式 engram_store。热路径上没有任何模型调用,纯粹的操作——git push / git stash / 无提交信息的 commit——刻意从不记录(见下方「自动捕获策略」)。 每条写入先过一道确定性密钥脱敏器——API key、JWT、Bearer token、私钥、AWS/Stripe/Slack/GitHub token 与 key=value 形密钥一律替换为 <REDACTED:…> 标记,且在去重哈希 / 字符上限 / 落盘之前完成,敏感内容永远进不了磁盘。测试失败用纯模式识别(npm test / node --test / vitest/pytest/jest + 失败行)自动捕获为 tags:["error","test"]、信号上调。
展开完整文档(README 剩余 1 节)
dsh-engram

· English · 已交付 UI 控件清单

为 DeepSeek Harness 设计的极简长期记忆插件,融合了 symbolic-index 与 pi-esr 的思想——目标只有一个:省 token。

- 零 LLM 摄入 — 纯模式匹配从工具结果自动捕获有意义的事件(带书面 -m 提交信息的 git 里程碑、关键文件编辑、反复出现的错误),另有显式 engram_store。热路径上没有任何模型调用,纯粹的操作——git push / git stash / 无提交信息的 commit——刻意从不记录(见下方「自动捕获策略」)。
每条写入先过一道确定性密钥脱敏器——API key、JWT、Bearer token、私钥、AWS/Stripe/Slack/GitHub token 与 key=value 形密钥一律替换为 <REDACTED:…> 标记,且在去重哈希 / 字符上限 / 落盘之前完成,敏感内容永远进不了磁盘。测试失败用纯模式识别(npm test / node --test / vitest/pytest/jest
+ 失败行)自动捕获为 tags:["error","test"]、信号上调。DSH goal 域打通:completed/blocked 的 goal(goal/change 会话事件)自动沉淀为 handoff/error 记忆(tag:goal),目标结局不随会话消散(读取面见 GET /api/dsh-engram/goals)。
- 用户纠正/偏好学习(autoCapturePreferences,默认开,零 LLM) — 「AI 犯错被纠正后,教训要留存」(correx、PAHF、Mem0 偏好提取):一个 session/event 监听器盯 user/message,用确定性纠正/偏好句式把用户话语沉淀为独立的 preference 记忆 kind——中文(不要用X、改用X、以后统一用X…)与英文(don't use X、 switch to X、prefer X、never use X…)模式,三种方向(avoid / switch-to / prefer),并带疑问句 / 一次性操作指令 / 记忆工具元话语过滤器抑制误报。preference 记忆在 [ENGRAM] 块里压过其它所有 kind([R] 前缀),让用户的长期规则成为每会话模型看到的第一行;每会话上限 + 近期窗口去重与自动捕获同纪律。零 LLM、零向量、确定性纯函数——提取片段有界,每条写入依旧先过密钥脱敏器。
- 符号索引 + 渐进披露 — 一个紧凑的 [ENGRAM] 块(默认预算 700 字符 ≈ 175 token;每条记忆一行)在组装提示词时注入,并按会话冻结,让请求前缀字节稳定以复用 KV 缓存。模型需要细节时用 engram_recall / engram_detail 下钻,而不是把命中的原文灌进上下文。召回对内存池做 进程内 BM25 排序(TF·IDF + 标签/短语加权 + 时间衰减因子,确定性、零依赖);
命中实体锚定的记忆时附带实体邻域关系简表(复用 esr_link:node --rel--> node · conf%);
与历史失败高度同源的新错误会唤醒旧 error 记忆(刷新 recency + 命中,向 promoteHits 爬升直至重回索引,失败不重复堆积);本地零命中时自动兜底到 DSH 自带的跨会话全文索引 (ctx.sessionQuery,按 cwd 过滤)——不另建 SQLite,完全复用宿主。当 FTS 兜底也返回空、且查询含 CJK 中文(FTS5 中文分词整段只算一个 token)时,召回会对最近若干会话日志做有界的子串扫描(zstd 解压 + LRU 缓存,限文件数与字节),追加命中会话的确定性 # past sessions 行。
- 会话启动自动召回(autoRecallOnStart,默认开) — 纯拉取式设计有个被实证戳穿的盲区:召回工具只交模型自觉,而真实会话里模型几乎从不主动调 engram_recall(实测 19 个会话数千次工具调用只出现 ~5 次召回)。因此宿主在新会话首次组装时,用会话首条用户消息对工作区做一次确定性 BM25 召回,把命中前 ≤3 条直接注入 [RECALL] 块(默认 700 字符预算,"少而准", agentmemory 候选①)——相关记忆不依赖模型想起来就已在上下文里。纯规则、零 LLM、纯读不 bump 命中;superseded 过期真相不占注入槽(仍可 engram_detail 重取);与 [ENGRAM] 一起按会话冻结保持前缀稳定。
- 读路径激活(D) — 命中条目的单行预览被截断(>140 字符)时带 … 标记,且块尾引导行点名该具体 id:#<id> truncated — engram_detail <id> (full) · engram_recall <query>。
模型由此知道完整记录就在一次确定性 engram_detail 之后,而不是以为预览就是全文(实证盲区:66 个会话 0 次 engram_detail——下钻路径此前对模型不可见)。
- 记忆间语义(supersede/contradict) — engram_store 可选收 supersedes / contradicts 记忆 id(同工作区校验)。被 supersede 的「过期真相」在召回里降级到尾部、并从 [ENGRAM] 块中剔除(新陈述占行);被 contradicts 的记忆保留排序但标注 · contradicted by <id>。
旧行永不删除——可重取,只是排得诚实。可选 autoSupersede 配置(默认关)对"实体锚定 + 替换式更新(改用/不再/no longer/switched…)"自动打 supersedes;显式 supersedes 永远优先。
- 失败→解法闭环(零 LLM) — error 记忆带 cmd: 签名标签;同命令随后跑通时自动沉淀 procedure 记忆(fixed: <cmd> — N earlier failing runs now succeed),并把旧 error 行打上 resolved(不影响其排序),召回因此浮出解法而不是过期失败。
- ESR-lite 证据闭环 — esr_task / esr_close / esr_link 给任务一个 draft → active → stable 生命周期,其中 stable 必须要有真实证据(artifact / evaluation / memory_ref),把"缺什么" 摊在明面上,而不是让 agent 没有证据就宣布完成。可开 verifyArtifact(默认开):非 URL 的 artifact 按工作区(= 会话 cwd)解析并在磁盘上实存校验,路径不存在则任务保持 ACTIVE、给出原因; force:true(或关掉该开关)可跳过磁盘校验——三种证据门照样必填。工具与网页表单共享同一个证据门(store.evidenceGate),口径绝不会漂移。
任务不再是孤立节点:esr_task(entity=…) 一键挂点——自动建 ent_<slug> 节点并挂
task --relates_to--> entity 边(幂等),挂着领域锚点的任务立刻带关系;
esr_dep 双写——依赖边同时进 tasks[].deps(blocker 逻辑)与 links 表,图谱里每条任务依赖都是一等可见的边。
- ESR 触发机制(pi-esr 对齐,混合:静态协议 + 冻结快照 + 单调渐进 actionables + 拉取) —前缀缓存稳定是核心。模型「何时用 ESR」靠静态方法论(engram:esr-method,每轮逐字节相同); [ESR] 块 = 确定性冻结快照(会话开始定下、确定性排序、时间戳排除、明示 WILL NOT auto-refresh)+ 本会话单调渐进的 actionables:promote:/root-cause:/close:/stale:/escalate: 在它们成熟的那一刻被一次追加进块并冻结(永不回撤)——前缀只在「真正出现新决策点信息」时变化、绝不逐轮漂移,同时保留决策点提醒(漏斗最窄处 / 复发失败 / 收工 / stale / 平衡)。实时状态仍可 拉取:esr_status(全量视图 + since_revision 增量短响应 + 派生 actionables)。
recorder 由 tools/result 订阅喂入(actionables 的数据源);P4 转化度量照常(#suggest- 标记 + 10 分钟归因 + GET /api/dsh-engram/triggerstats)。纯规则、零 LLM。
- 会话结束 todo 自动沉淀(draft 兜底,autoSinkTodosOnEnd 默认开) — 会话内 todo 仍是轻量、随会话而逝的"工作记忆";但当会话在其拆解边界(session/disposed)关闭时仍挂着 pending todo,这些待办会自动转为该工作区的 ESR draft 任务(名称=todo 原文、描述「源自会话计划」、按已存在任务名去重、受 maxTasksPerWorkspace 上限约束),计划从此不静默蒸发。落到 draft 而非 active:不占 [ESR] 的 active 行、不产生证据义务,仍需刻意的 esr_claim / esr_task 推进才变 active——「进行中默认不写」的取舍不变,只把「收尾丢数据」的缺口补上。可关(profile patch 或设置卡)。
- 记忆 GC(pi-esr 约束) — 定时、机械、只归档的回收:TTL 过期记忆归档、超容量工作区淘汰低价值条目、 stable 任务超保留窗离开 [ESR] 表面、悬空链接边清理。工作集(active 任务引用 / 任务记忆 / 已入索引命中)永不触碰;不硬删任何东西——归档条目保留 id、始终可重取。
- Context GC(自动 GC = 替代 DSH 自动 compact,opt-in) — 不是记忆面板 GC:开启后接管 DSH 自带的上下文压缩(compaction 服务),把「有损 LLM 全量摘要」换成机械驱逐 + 重取指针——扫描被驱逐轮次里的 engram/ESR 锚(#记忆id、tsk_、ent_、file_path),只留一行「细节在 engram_detail / engram_recall / [ESR] 里」;只有无锚轮次才走 scoped LLM 叙事兜底(gcNarrative,默认开、可关成纯机械)。
六条 GC 约束(working-set-protected / pointer-salience / no-provenance-no-evict…)全部落地;任何错误回退默认压缩,永不破坏 compact。
- Web 查看器 — 一个带统计的记忆浏览器和配置卡片,完全构建在 DSH 原生设置槽位上(不碰任何第三方 UI 包)。

MIT · node >= 22.19 · host 半边 + 浏览器半边合在一包

为什么还要一个记忆插件?

对既有 DSH 插件生态的调研显示,记忆领域里"召回桥 / 审批门 / LLM 蒸馏 / 向量+图"这几个方向已经挤满了。
dsh-engram 补的是对 token 纪律真正重要的三个空白:

1. 写入路径没有模型 — 捕获是确定性的模式匹配。
2. 提示词里不灌全文 — 只注入有界的符号索引 + 会话启动时自动召回的最相关 ≤3 条(预算内),更深的检索按需进行("检索到≠灌全文")。
3. 诚实的任务闭环 — 没有证据就不能宣布 STABLE。

DSH 已经提供跨会话 FTS(ctx.sessionQuery)、存储(ctx.storageDomain)、提示词注入钩子和设置槽位; dsh-engram 只是这些能力之上的一层薄组合层,而非重新实现。

安全模型

DSH 插件生态还很年轻、缺乏背书:没有官方目录、没有签名校验,且 harness 的读写权限三档约束不了插件自身代码。因此记忆插件面对的信任何题比工具更高——它趴在你的提示词前缀上,还替你在磁盘上写东西。dsh-engram 的模型,直说如下:

- 永远不联网。 本插件没有出口 socket、没有遥测、没有更新探针。任何东西都不可能把会话带出本机。
- 无 shell、除一个存储文件外不碰文件系统。 不执行命令、不碰任意路径。全部数据只落在一个 storage-domain 单元 ~/.dsh/storages/dsh_engram.json(外加经 DSH 自身 ctx.sessionQuery 做的会话日志读取)。
- 密钥在落盘之前就被脱敏。 每条写入路径——engram_store、自动捕获,以及本节的导入/恢复——都先把文本过一遍确定性的 redactText 规则引擎(API Key、JWT、Bearer token、私钥、key=value 密钥形态),之后才做去重哈希、字符上限与落盘。敏感文本永不落盘,召回时自然也不会有。
- 要做什么坏事,必须借你 harness 自己的权限。 插件只通过你已信任的工具同款的 seam 向宿主"请求"做事;不扩沙箱策略、不拉高到 danger-full-access——你保持 profile 默认,engram 就只继承那些边界。
- Web API 只读为主、loopback 栅栏。 /api/dsh-engram 下的 GUI 路由拒绝任何非回环调用方,除非你用 trustedHosts 显式放行某个主机名;即便放行,同源栅栏依然生效。
- 可核验。 npm run dsh-engram -- doctor(scripts/dsh-engram.mjs doctor)报告插件被配置去碰什么;整个 store 可检视、不透明的东西为零。如果对这一段有怀疑,要查的代码就是 lib/redact.js、lib/store.js 和本插件的 cordis.patch.yml。

这不是说它是个气闸:如果你的 profile 给了 agent danger-full-access 或 shell,agent 可以用——engram 是记忆层而不是沙箱。上面这句话的意思是:dsh-engram 自己不新增任何攻击面。

数据契约:导出 / 导入

记忆不该当人质。四张表(memories / tasks / links / entities)可以用一个稳定、带版本号的机器可读格式整体倒出再恢复——重装、换机,或者(写一层薄适配器之后)搬到别的 harness / MCP 端点 / Claude Code skill,语料都跟得走。契约是带自描述头的纯 JSON:

GET /api/dsh-engram/export?workspace=/path/to/project # 单个工作区 GET /api/dsh-engram/export # 全部工作区

{
"meta": { "format": "dsh-engram/export", "version": 1, "exportedAt": 1724…, "workspace": "/path/to/project" },
"memories": [ / 全部行,含归档——备份不丢溯源 / ],
"tasks": [ / draft/active/stable + 归档 / ],
"links": [ / 类型化边 / ],
"entities": [ / 图节点 / ]
}

恢复默认幂等、破坏性操作有门禁:

POST /api/dsh-engram/import # body: { payload, mode, dryRun?, workspace?, confirm? }

- mode: "merge"(默认)— 只写 id 尚不存在的行,备份可以随便重复套用而不会重复;每行保留自己的 workspace。
- mode: "replace" — 先把目标 workspace 的四张表清空,再只恢复属于它的行。破坏性操作,因此 body 里还要求 confirm: "restore"。
- dryRun: true — 只算出完全相同的执行计划(会写什么 / 跳过什么),不写任何一行。
- 导入遵守与 storeMemory 相同的不可变约定:记忆文本落盘前先过密钥脱敏;会破坏契约的行(缺 id/workspace、空文本、超 maxMemoryChars、merge 时 id 已存在、超工作区记忆上限)会被跳过并写进响应报告,而不是中止整批。

单工作区的恢复往返:

curl -s "http://127.0.0.1:3080/api/dsh-engram/export?workspace=$PWD" -o engram-backup.json
curl -s -X POST http://127.0.0.1:3080/api/dsh-engram/import \
-H "content-type: application/json" \
-d ""

兼容性

- DSH 版本:>=0.1.2-alpha.2 <0.3.0-0(本版 0.7.2;已发布的 0.3.7、0.4.0 仍声明旧的 0.1.x 范围 >=0.1.2-alpha.2 <0.2.0-0 —— 见下文矩阵)
- 最后验证日期:2026-10-27 —— DSH 0.2.1-rc.2(dsh --version)+ web profile 实机组合;npm test 483/483 通过
- 安装方式已验证:dsh plugin --profile web add dsh-engram

一句话结论:所有已真正发布的版本(0.2.0 … 0.3.6)只支持旧版 DSH(0.1.0-rc.7 / 0.1.1 API 图,peers ^0.1.0-rc.7)。DSH 0.1.2-alpha.2 及更新版本与它们不兼容——settings/client API 族在该版本断裂。新版 DSH 支持自 0.3.7 起提供——它是新 API 代际的第一个发布(见下文)。

| dsh-engram 发布版本 | 状态 | 支持的 DSH | 声明方式 |
| 0.2.0 … 0.3.6(npm 全量 + GitHub v0.3.3–v0.3.6) | 已发布 | 仅旧版 —— 0.1.0-rc.7 / 0.1.0-rc.8 / 0.1.1-rc.1 / 0.1.1-rc.2 | peers @deepseek-ai/dsh-@^0.1.0-rc.7 |
| 0.3.7 / 0.4.0(npm) | 已发布 | 新版 DSH,settings installSection —— >=0.1.2-alpha.2 <0.2.0-0 | engines.dsh + peers 同范围 |
| 0.5.0(npm) | 已发布 | DSH 0.1.2-alpha.2 … <0.3.0-0 —— 设置卡迁移到 entry-id volatile Config(0.1.7+/0.2.x),≤0.1.5 自动回退 installSection | engines.dsh + peers >=0.1.2-alpha.2 <0.3.0-0 |
| 0.5.1 | 已发布 | 同 0.5.0 —— 会话扫描修复(session.v<N>.jsonl.zstd,issue #8)、数据导出/导入 GUI、替换/冲突可视化(issue #3) | 同 0.5.0 |
| 0.6.0 | 已发布 | 同 0.5.0 —— Context Ledger、BoundaryPrune/ResultPack 默认开 + 账本否决、JIT 推送、Context GC 改 opt-in | 同 0.5.0 |
| 0.7.0 | 已发布 | 同 0.5.0 —— 会话 episode、恢复简报、实体页/实体卡、可重验证锚点 + 分层 TTL | 同 0.5.0 |
| 0.7.1(npm) | 已发布 | 同 0.5.0 —— web 平面 Context GC 适配 DSH 0.2.0 声明式预设(14 空格缩进)、bundle patch 重新启用 host 平面 tool-result-pruner、压缩节省接入 Context Ledger | 同 0.5.0 |
| 0.7.2 | 已发布 | 同 0.5.0 —— L1 边界剪枝改经 ctx.inject 武装,修掉「pruner 行比插件晚激活一帧就永久休眠」的启动时序竞态 | 同 0.5.0 |
| 0.7.3–0.7.4(npm latest) | 已发布 | 同 0.5.0 —— 设置卡迁到「Engram 记忆 → 设置」tab(DSH 0.2.1 删了旧 section);修 DSH 0.2.x 上「推送腿」整条静默失效([RECALL] 启动召回、JIT 推送) | 同 0.5.0 |
| 0.7.5–0.7.13 | 本地 | 同 0.5.0 —— 账本按会话落行 + gate-audit;L1 从「自己剪」转成「记账 + 归因」;压缩入账读真实事件形状;会话计数重启不清零;CLI 尊重 DSH_HOME | 同 0.5.0 |
| 0.8.0–0.8.5 | 本地 | 同 0.5.0 —— 证据化 procedure、导出 DSH Skill、subagent 交接胶囊、审批门 + GUI、记忆行文件信号(⚠file-changed/⚠file-missing)、G6 门改单边 + 隐藏规格语料(Δ=+58.3pp) | 同 0.5.0 |
| 0.8.6(npm) | 已发布 | 同 0.5.0 —— 修 issue #9:宿主不暴露 sessionQuery/snapshotEvents 时退回 seq+eventAt 逐条读,四项注入特性不再静默休眠;warn 报出宿主 surface;G6 第五轮 Δ=+90.0pp | 同 0.5.0 |
| 0.8.7(npm) | 已发布 | 同 0.5.0 —— 推送腿落盘($DSH_HOME/engram/push-legs.jsonl)+ status 打印事件日志读法;minimal 预设不再承诺永远不会发生的自动接管 | 同 0.5.0 |
| 0.8.8 | 本地 | 同 0.5.0 —— 修推送腿遥测写错目录(engram/engram/)导致状态显示「还没记录」;pushLegsPath 契约写死在一处 | 同 0.5.0 |
| 0.9.0(本期 / this release) | 本地(tag,未发 npm) | 同 0.5.0 —— 产品代码只有一条 CLI 修复(--presets-dir 优先);主体是评测:G6 网络围栏(per-uid 出网白名单 + 两条前置探针)、密钥语料第 4 条自证、污染三分类与重算、G7 实测 0.2956%。不是 1.0:宿主仍 rc、G1 未测、减法债未还 | 同 0.5.0 |

API 族断裂点:0.1.2-alpha.2

0.1.2-alpha.2 是新 API 族的第一个版本,也是第一个让已发布插件失效的版本:

- @deepseek-ai/dsh-settings 移除了 settingsNamespace() 与
installSettingsSection()(≤ 0.3.6 的每个发布都直接 import 这两个符号),
改为 settings.installSection——旧宿主端在 0.1.2-alpha.2+ 上直接加载失败。
- Web 客户端注入集整体更换:@deepseek-ai/dsh-client-runtime 在新代际已不存在 (被 dsh-api-remotes + dsh-client-locale / -conversation / -renderer / -ui-settings-plugins 取代),设置卡此前改走 ctx.remote.settings。
- 同一边界的伴随升级:@deepseek-ai/cordis ^4.0.1 → ^4.0.2、 @deepseek-ai/schemastery ^3.18.1 → ^3.18.2、@deepseek-ai/dsh 不再随包发布 config/agent-presets(改为 dsh.configTrees 机制)。

web 设置卡已不依赖上面任何一项:自 0.3.7 起,配置卡通过插件自有 HTTP 路由(/api/dsh-engram/settings,与记忆 viewer 同样的同源 fetch)读写 dsh-engram settings namespace,因此在 0.1.2-alpha.2 分界线两侧都是代际无关的。

0.1.7 / 0.2.0 还有一次 settings 断裂——0.4.0 在最新 DSH 上启动被拒的真正原因:@deepseek-ai/dsh-settings 在 0.1.7-alpha.2 再次移除了 settings.installSection(0.2.x 一路保留)。新设置模型是条目式的:可编辑字段是插件自身 Config 的 .volatile() 字段(由 loader 校验并热提交),设置表单以 profile 条目 id(engram)为命名空间,不再需要任何“挂载”调用。0.5.0 在 installEngramSettings 里对两代际做特性检测:

- DSH ≥0.1.7 / 0.2.x —— 34 个 GUI 旋钮即 Config 的 volatile 字段(lib/settings.js makeEngineConfigSchema,由 lib/index.js 导出为 Config);卡片仍走同一 /api/dsh-engram/settings HTTP 路由读写条目 engram, service.settings 接缝在 loader/volatile-update 事件上把 resolved 重新同步,卡片编辑对新会话即时生效。
- DSH ≤0.1.5 —— 照旧注册旧 settings.installSection 命名空间(dsh-engram)。

声明范围 >=0.1.2-alpha.2 <0.3.0-0 同时覆盖两代际;HTTP wire 命名空间在两代际都保持 dsh-engram(由接缝的活动命名空间驱动写入)。

所以:跑 ≤ 0.3.6 的已发布版本时请把 DSH 保持在 0.1.1-rc.2 或更旧;升级 DSH 到 0.1.2-alpha.2 之前,先升级插件到 >=0.3.7。

当前发布版本 —— 0.7.2

修复 L1 边界剪枝的启动时序竞态(真机定位:组合里多挂两个 bundle 就复现)。

BoundaryPrune 在插件安装时用严格 ctx.get("toolResultPruner") 取宿主剪枝器;而 0.2.0 的 web profile 里,剪枝器的 fiber 比插件晚激活约 100 ms(实测:dsh-context + dshmarket + dsh-engram 组合下 t0 strict=undefined / loose=object,100 ms 后 strict 才拿到服务)。严格查找此时为空 → installBoundaryPrune 放弃 → L1 整个进程生命周期都不武装,账本唯一的「剪枝节省」入账腿静默断掉;设备快照还会把这种"还没到"报成"行不存在",把人引向错误的排查方向。

- 改经 ctx.inject(["toolResultPruner"], …) 武装 — cordis 在服务真正激活那一刻回调(重载后也会重新武装),不再赌安装顺序;武装成功后立刻重写状态快照。
- 区分「缺失」与「晚到」 — boundaryPruneAvailability 增加非严格查找:行被禁用/没装报 no toolResultPruner service … (the bundle patch must re-enable …),只是 fiber 未激活报 registered but its fiber is not active yet。两种原因以前在快照里长得一模一样。
- 验证 — 用真实 profile 的 bundle 组合(含 dsh-context)在隔离 DSH_HOME 复现 armed:false,修复后同一组合 armed:true · threshold 8192;npm test 406/406。

上一发布版本 —— 0.7.1

修复 DSH 0.2.0 声明式预设下的 web 平面 Context GC 接管:旧版自动装配以 agent.cordis.yml 的 4 空格布局为锚,而 0.2.0 把预设换成 dsh-web-app/presets/<id>.patch.yml 里的 @deepseek-ai/dsh-agent-preset 声明(行在 14/16/18 空格缩进),导致 autoWebCompaction 静默跳过所有预设。

- 结构化定位 compaction 行:找 group: true 的 compaction 组,组内按 - id: + name: 双重校验定位 stock/engram 行,替换块继承该行自己的缩进;旧版与声明式布局都 swap → restore 逐字节还原。顺带修掉旧 stock 替换串不含行尾换行、可能半改写文件的隐患。
- 声明式预设发现:roster 不再带 path 时把 id 映射到出厂 <presetsDir>/<id>.patch.yml (DSH_WEB_PRESETS_DIR 可覆盖);无出厂文件的用户自定义声明式预设跳过并打日志。
- status/doctor 适配:自动发现两种布局并显示布局类型;doctor 新增 autoWebCompaction=false、node_modules 预设(npm update dsh 会恢复出厂布局)、 profile patch preset- 覆写(优先于 shipped 文件)三类提示。
- 迁移:打开 autoWebCompaction 后重启一次 dsh web,下一次启动即幂等接管所有 stock 声明式预设;npm test 396/396。

上一发布版本 —— 0.7.0

把「记忆」从事件记录推进到可维护的当前真相:会话结束自动沉淀 episode、新会话给恢复简报、esr_node 升级为实体页、文件锚点让过期真相可被检测。

- 会话 episode:session/disposed 零 LLM 生成紧凑纪要(文件/命令/失败→修复/ 决策/未闭环),不进索引,每 workspace 保留 20 条。
- 恢复简报:新会话注入最近 7 天最新 episode([RESUME] ≤240 字符 + 下钻指针)。
- 实体页 + 实体卡:truth / timeline / open loops / conflicts / relations;索引前置最多 2 张实体卡,engram_detail(entity=…) 下钻。
- 可重验证锚点:带 filePath 的记忆记录 mtime/size/sha256;会话启动有界重验证,改动/缺失 → stale(不进索引/truth,仍可 detail 重取)。
- 分层 TTL:preference/procedure 永久、episode 14 天、error 60 天、其余 180 天。
- 迁移:只有新写入会落锚(不回溯历史);升级后重启 dsh web;episode 要等一个会话结束,resume brief 在下一个新会话生效。0.6.0 的账本/默认值/JIT 保持不变。

已发布的 0.4.0 声明 >=0.1.2-alpha.2 <0.2.0-0——这个范围被 semver 门槛在 所有 DSH 0.2.0-0 预发布上拒绝(<0.2.0-0 排除 0.2.0-rc.x),且其宿主设置端仍在调用 dsh-settings 0.1.7+ 已移除的 installSection API。0.5.0 两处都修复:范围放宽到 <0.3.0-0(用 DSH 自身的 includePrerelease 检查验证过, 0.2.0-rc.2 及整个 0.2.x 线都放行);设置卡迁移到上面描述的 entry-id volatile Config 模型(并带特性检测的旧代际回退)。

| 组件 | 适配版本 |
| DSH runtime / host / client 包族 | >=0.1.2-alpha.2 <0.3.0-0 |
| @deepseek-ai/cordis | ^4.0.2 |
| @deepseek-ai/schemastery | ^3.18.2 |
| Node.js | >=22.19.0 |

0.1.2-alpha.2 是这组 API 的最低版本:宿主设置注册按代际特性检测——DSH ≤0.1.5 走 settings.installSection 注册 dsh-engram 命名空间;≥0.1.7 / 0.2.x 走插件 Config(34 个 GUI 旋钮标记为 .volatile(),条目 id engram)。浏览器设置卡在两代际都经插件自有 HTTP 路由(/api/dsh-engram/settings,见上文 Route B)读写。完整回归(npm test 396/396,含新增的 volatile-Config 模型 settings-v2 覆盖)在 DSH 0.2.0-rc.2 依赖集(cordis 4.0.4 + schemastery 3.18.4 + cosmokit,与全局 DSH CLI 同版本)
上通过;.volatile()/loader 热提交路径与 describe 侧投影已针对真实 dsh-settings
0.2.0-rc.2 工具函数验证;并在隔离的 dsh 0.2.0-rc.2 profile 上完成了真机启动烟测并通过:插件启动无版本范围拒绝、设置卡表单以条目 engram 对外提供(全部 34 个 GUI 旋钮,含 trustedHosts)、/api/dsh-engram/settings 的 GET → PUT(mutate ops, revision 0→1)→ unset 往返落盘并读回。旧的 0.1.0-rc.7 与 0.1.1 API 图不在本发布版本的兼容范围内。

如需按验证版本安装 DSH:

npm install --global @deepseek-ai/dsh@0.2.0-rc.2
dsh --version

安装

从 GitHub(本仓库)
dsh plugin --profile web add github:skepsun/dsh-engram

发布到 npm 之后
dsh plugin --profile web add dsh-engram

本地开发(符号链接——改动立即生效)
dsh plugin --profile web add link:/path/to/dsh-engram

然后重启 dsh web。数据保存在 ~/.dsh/storages/dsh_engram.json。

npm 与 GitHub 两种装法都不需要手动补依赖:pnpm 会自动安装 zod,
并把可选的 @deepseek-ai/ peers 嵌套装进插件自身的 node_modules,CLI 也会自动把插件登记进 profile 的 dsh.profile.bundles。下面的 setup-links 只在 link: 开发工作流里需要——pnpm 故意不为符号链接目录安装依赖。

需要新建会话才能看到注入的 [ENGRAM]/[ESR] 块和全部工具——提示词与工具注册表都是按会话装配的。

link: 安装的依赖准备

符号链接安装的插件从自身 checkout 的 node_modules 解析 import,而这层依赖不被 git 跟踪,换机器(尤其 Windows)会报 ERR_MODULE_NOT_FOUND: Cannot find package 'zod'(接着是 @deepseek-ai/ peers)。
一条命令重建依赖层:

cd /path/to/dsh-engram
node scripts/setup-links.mjs # 把 @deepseek-ai 工作区包软链进 node_modules,
并安装 zod(优先复用 harness pnpm store,找不到则回退 npm install)

脚本会自动定位 harness:../deepseek-harness(仓库上一级平级),也支持「仓库父级平级」布局(如 E:\deepseek-harness 与 E:\kototoro_demo\dsh-engram)——都找不到再用 DSH_HARNESS_DIR 指定。
node scripts/setup-links.mjs --check 只打印状态不写入。

在 Web 端能得到什么

重启后,全部落在 DSH 原生设置界面里:

- 侧边栏「ESR 看板」入口 + 全屏看板 — 侧栏(New Session 下方)新增一行 ESR 看板 入口,右侧带实时活动任务数徽标(30s 轮询 /overview 汇总各工作区 active 任务)。点击在中间列打开全屏看板:草稿 / 进行中(证据缺口) / 就绪(证据齐) / 已闭环 四列 + 工作区筛选 + 搜索 + 内联新建表单 + 每张卡片的「补齐证据 → 关闭」表单(与 esr_close 同一证据门:artifact + evaluation + memory_refs)。头部带「看板 / 图谱」切换:图谱视图复用完整的关系图谱(esr_node/esr_link 力导向图,实体圆节点 + 任务勾选徽标,支持拖拽/缩放/点选查看关系明细),跟随工作区筛选,20s 轮询保持实时。入口与看板按 task-board 的 DOM 级挂载惯例自愈(MutationObserver 重插/重挂),并与 task-board / ssh 面板做跨面板互斥(打开本面板会关掉对方,点侧栏会话/项目行自动回到对话)。对话子树始终挂载在下方、由 html[data-dsh-engram-board-active] 控制显隐,切换零状态丢失。

- 输入框上方的「任务」统一条 — 接管 DSH 内建 todo 工具的自己同款 dock 槽位(同一个 conversation.input.dock 单元格 / id: todo、更低 priority,从而遮蔽内建 TodoPanel),把两套任务平面合并成一个现代化控件:会话当前计划(todo_write 的 todos 投影)+ 工作区持久 ESR 任务(证据缺口徽标 + 内联「补齐证据 → 关闭」表单)
+ 关系图(以 节点 → 关系 → 节点 芯片呈现,实体/任务名自动解析)。有内容才显示, 15s 轮询保持实时;若 loopback 围栏的 API 不可达,内建计划仍照常渲染(只是不显示 ESR 部分)。
条首有工作区切换 chip:默认跟随当前会话(标题注明),下拉可把 ESR 任务/关系的来源固定到任意工作区(打 ✓ 标记),再点 × 或「跟随会话」即恢复;切换即时重取,内置 todo 仍属本会话——纯 UI 焦点切换,不动模型会话上下文(注入块按会话冻结,前缀稳定)。

- 设置 → Engram 记忆 — 独立的一级设置页签(位于「插件」之后),不再是「插件」页里的子 tab;默认「全部工作区」视图完整展示所有工作区的记忆/任务/关系(按工作区分组,工作区下拉 + 上一/下一工作区翻页;记忆表格另行 10 条/页分页 + 跳页下拉,仅「类型 / 内容 / 操作」三列——正文列占满,时间、标签、signal/hits/TTL 等全部折叠进内容行内(meta 行 + 标签行),正文限高三行省略、行内「展开全文/收起」与 hover 均可看全文,归档/删除按钮竖向堆叠)。概览统计卡片(各工作区/类型的计数、自动捕获总量、各工作区 [ENGRAM] 索引 token 估算、GC 累计统计)、可搜索/可过滤的记忆表格(含归档与删除操作)、ESR 任务看板(「新建任务」表单 + 点击「填写证据关闭…」补 artifact/evaluation/memory_ref 转 STABLE)、节点与关系清单(节点 = 模型用 esr_node 登记的领域对象,如包/服务/仓库/概念;关系 = esr_link),以及一个独立的 关系图谱 页签:手写 SVG 力导向图(无第三方图库,保持 bundle 纯净),实体为圆形节点、任务为勾选徽标、关系按类型着色带方向箭头;支持拖拽节点/平移/滚轮缩放/重组,悬停高亮邻域、点选节点在悬浮面板查看其全部关系与关联对象,悬空链接(端点缺失)单独计数提示。
以及一个 注入预览 页签:用与 systemPrompt 完全相同的纯函数实时渲染模型每个会话看到的 [ENGRAM] 索引块(order 40)与 [ESR] 任务/闭环块(order 41),终端风双栏展示(行级着色:块头/任务行/drill 行/escalate 提醒高亮),附行数·字符·~tokens 成本与记忆/任务/关系/节点计数芯片,每 20s 自动刷新、可一键复制注入块原文(新增 GET /api/dsh-engram/preview?workspace=…)。
以及记忆 GC 面板(dry-run 开关 + 运行按钮 + 指针报告)。
任务卡片(看板与 ESR 页)都带 证据进度环:一个三弧 SVG 圆环对应 artifact · evaluation · memory_ref 三道闭环门——全绿=证据齐可闭环、琥珀=有缺口、灰=尚无证据;看板头部还有一个聚合环,显示全部进行中任务的证据完备度(%)与就绪数,一次看清整盘闭环进度。
纯 SVG 实现(无图表库,保持 bundle 纯净)。
以及一个 遥测仪表盘 页签:把 /stats 的真实调用累计(工作区 × 天滚动)画成纯 SVG 仪表盘——三枚大圆环直读 ESR 主动性(与 escalate 阈值 0.34 比对,偏低标橙并提示)、召回命中率、detail 转化,外加一块 Context Ledger 面板(净 token、节省/注入/重写罚金三卡、近 14 天净收益红绿柱),五张小指标卡(累计调用 / esr / 记忆 / 平均命中每查询 / 失败),近 14 天 mem-vs-esr 堆叠柱状图 + 工具调用 Top 8 横向条形图(mem 蓝 / esr 紫,与全文配色一致),20s 自动刷新,样本不足(<10 次)自动标注。
以及 详情侧栏(主从布局):任务 / 记忆 / 节点 / 关系行都可一键在右侧打开详情卡——任务含状态徽标、证据进度环、完整 id/时间线、缺口清单、可跳转的记忆引用与「补齐证据→闭环」表单(独立于行内表单,成功后联动刷新);记忆含全文、标签、signal/hits/TTL/来源会话元数据;节点含全部入射/出射关系(按类型着色 + 方向 + 置信度);点击任务里的记忆引用可直达该记忆详情,找不到时给提示。
POST /api/dsh-engram/tasks 与 POST /api/dsh-engram/tasks/close(与 esr_task / esr_close 同一证据门)。
模型侧的主动行为由 [ENGRAM]/[ESR] 注入块驱动:多步工作即时建任务、反复出现的领域对象即时登记节点、相关任务/节点即时互连。

真实行为观测(agent 遥测) — ESR 页顶部新增「agent 行为观测」面板:每次模型调用 engram_/esr_ 工具都实时累计到按(工作区 × 天)的 usage 滚动行(新增 usage 表 + GET /api/dsh-engram/stats),折算成指标: ESR 主动性 = esr 工具调用数 /(记忆 + esr 工具调用总数);召回命中率 = 有命中的 engram_recall 次数 / 总次数; 平均命中/查询;detail 转化 = 命中召回后很快跟一次 engram_detail 的比例(会话内 8 事件窗口);失败数按工具记。
面板同时列出各工具调用计数与最近 14 天逐日滚动。这些是真实会话的真实数字——想提升 ESR 主动性,就观察面板上 esr 占比并调整注入提示。
- 设置 → Engram 记忆 → 「设置」tab — 配置卡默认展开(DSH 0.2.1 去掉了 settings.plugin.item 槽位,旧的「插件 → 插件配置」位置不再渲染)。同一张卡、同一套传输:标题 + 一行说明 + chevron,展开后 ~12 个选项分四组——捕获与检索 / 索引 / 生命周期与 GC / 安全。改动对新会话生效(已冻结的块保持不变);Discard / Save,标题栏带「未保存」徽标。
卡片走插件自有 /api/dsh-engram/settings 路由(不经过 isLoopback 围栏),经授权隧道访问 GUI 时也能改。
- 设置 → 插件 → 插件配置 → dsh-engram(仅 DSH ≤ 0.2.0,旧 settings.plugin.item 座位)— 与内置「终端 / Agent 循环 / 网页搜索」同款的 默认折叠卡片:标题 + 一行描述 + 箭头,点击展开/收起;展开后常用 11 项 + 高级分组按「捕获与检索 / 索引 / 生命周期与 GC / 安全」四个分组展示。改动对新建会话即时生效(已冻结的块保持稳定);支持放弃修改 / 保存,有未保存改动时标题上出现「未保存」徽标。
卡片通过连接自身的 settings RPC 直连命名空间(不走 isLoopback 门控的 scope),因此即使 GUI 经运营商授权的隧道访问也保持可编辑。

浏览器半边由 DSH 的 client-module loader 直接从本包提供(dsh.client + exports["./client"],无需重建 web 应用);数据来自 loopback 围栏保护的 /api/dsh-engram/ 路由族。围栏默认关闭隧道访问;如需经授权的隧道域名访问记忆查看器,把域名加进插件的 trustedHosts 配置(如通过 registry 或 profile patch):

// patch/engram.json
{ "engram": { "trustedHosts": ["cream-club-fragrances-caught.trycloudflare.com"] } }

修改 client/src 后重建 bundle:

npm run build:client

测试与评测

npm test # 381 项单元测试(含 usage 滚动 / /stats 路由 / Context GC / ESR 触发 / BoundaryPrune / Context Ledger / JIT 推送)
npm run eval # 离线召回 + 结构基准(确定性语料,跑真实 store/recall 路径)

另有三个会话语料分析工具(零 LLM、确定性,读 ~/.dsh/sessions 真实日志):

node eval/oracle-analysis.mjs # 浪费源量化:结果重放暴露 / 融合候选 / 机制激活率
node eval/simulate-boundary-prune.mjs # L0 反事实仿真:边界剪枝的预期节省与偿还门扫掠
node eval/dryrun-boundary-prune.mjs # BoundaryPrune 全量干跑:真实激活面与节省

npm run eval 的检索部分参照 LongMemEval 的问答式评测:受控语料(ASCII + CJK、标签/实体/时间戳已知),对真实 domain.recall() 逐一校准 Precision@k / Recall@k / MRR / Hit@1(probe 覆盖 tag 精确、子串、多词、CJK、短语唯一、标签排序、负样本无回);结构部分借鉴 StructMemEval:精确去重率(同文本存 3 次折叠为 1)、实体锚定覆盖率、节点/链接卫生(无悬空链接)。数字诚实、非调优——任何人跑出来都一样,复现即所得。

两层「真实测试」的分工:npm run eval 回答「检索层本身有多好」(确定性、可复现); /api/dsh-engram/stats + 观测面板回答「真实会话里模型实际怎么用」(ESR 主动性、召回命中率、detail 转化),两者结合才能判断:召回层没问题但命中率低 = 模型没学会问;反之亦然。

工具

| 工具 | 用途 | 类型 |
| engram_store | 显式存入一条记忆(kind、tags、可选实体锚点、可选 supersedes/contradicts id,可选 source/conditions) | 写 |
| engram_recall | 工作区记忆的确定性关键词召回;可选 search_sessions 跨会话 FTS;可选 scope=global/recallScope 跨工作区召回(带 [W:<ws>] 来源标记) | 读 |
| engram_detail | 一条记忆 id 的完整记录(来源、标签、命中数) | 读 |
| esr_task | 创建任务实体(draft → active) | 写 |
| esr_close | 按证据协议关闭任务(artifact + evaluation + memory_ref) | 写 |
| esr_link | 在两个实体之间添加类型化关系(迷你图) | 写 |
| esr_dep | 在任务间添加依赖边(blocks / relates-to / parent-of) | 写 |
| esr_claim | 原子认领任务(assignee + claimedAt,draft → active) | 写 |
| esr_unclaim | 释放已认领任务的 assignee | 写 |
| esr_ready | 列出可认领任务(无 blocker、无人认领) | 读 |
| esr_status | 拉取实时 ESR 状态 + 派生提示(since_revision 增量短响应) | 读 |
| esr_node | 创建/更新实体节点(稳定符号) | 写 |
| esr_gc | 运行本工作区的记忆 GC(dry_run:true 预览不落库) | 写 |
| esr_model | 工作区预计算心智模型(brief/full,max_chars) | 读 |

[ESR] 块是每会话冻结快照,绝不中途刷新——要最新状态调 esr_status。

GC:两块——记忆面板回收 + Context GC(替代自动 compact)

记忆面板回收(存储维护)

定时回收(gcIntervalHours,默认 24h)+ esr_gc 手动触发 + GUI 按钮,按 pi-esr 方式把存储保持在有界内——机械、工作集保护、只归档:

- TTL 过期记忆归档(软删;id 保留,可通过 GUI 的 archived 筛选检索);
- 超容量工作区淘汰最低价值的非保护记忆;
- stable 任务超 gcStableRetentionDays 归档、离开 [ESR];
- 两端点都已消失的链接被清理(悬空边)。

GC 永不触碰工作集:active 任务 memory_refs 引用的记忆、task 类记忆、已入索引的命中(hits >= promoteHits)。用 esr_gc + dry_run: true 先预览。不硬删——报告的末尾为所有归档项附上重取指针,归档可恢复、不是丢失。

Context GC(替换 DSH 自动 compact,opt-in)

DSH 默认的上下文压缩是有损 LLM 全量摘要(compaction-basic):被驱逐的历史被压缩成散文,不可查询、摘要本身还烧 token。dsh-engram 的自动 GC 替代它:ContextGcEngine extends BasicCompactionEngine 只重写唯一的 summarize() 钩子,把摘要正文换成:

1. 扫描被驱逐消息的 provenance 锚——engram_store/engram_recall/engram_detail 回显的 #记忆id、esr_ 涉及的 tsk_/ent_、file_path 锚(esr_gc 等管理工具不算锚);
2. 指针摘要:每条被驱逐类别都带显式重取调用(engram_detail(id: "…") / engram_recall(query) / [ESR] 块 / esr_ready),active 工作集在摘要里复述不驱逐;
3. 兜底叙事:只有无锚轮次(纯对话/推理)才走 scoped LLM 摘要(gcNarrative,默认开;关掉后整条路径零 LLM,无锚轮次截断原文保留)。

触发时机完全跟随 DSH 现有 compact(step pressure / context-overflow / /compact);锁、回放校验、 tool-call/result 配对、token 定价全部复用基本引擎。任何错误 → 回退默认压缩,永不破坏 compact。
装配即注册 compaction 服务,卸载/reload engram 自动还回默认引擎。自 v0.6 起默认不接管:默认挂裸 BasicCompactionEngine(宿主 LLM 摘要),要换成指针摘要需显式打开 gcReplacesCompaction(web 预设还需 autoWebCompaction)。

收缩闸门(shrink gate):harness 拒绝任何不比被驱逐片段更小的 checkpoint(summary is not smaller than the shadowed content),否则压缩回滚、上下文永不缩小、最终触发模型侧溢出。Context GC 据此自预测闸门:用 host tokenMeter 复刻 harness 的 framing 估算(实测逐 token 一致),把指针摘要/叙事体按被驱逐 span 的 token 预算动态裁剪尾部(指针头与工作集保留,细节仍在会话日志可重取),保证 checkpoint 严格缩得更小、压缩真正提交——不出现"start 涨、summary 不涨"的假接管。
完整用法见 docs/CONTEXT-GC-GUIDE.zh.md(从 0 开始的使用教程)。

启用入口(这一节就是答案:该功能开在哪)

Context GC 的装配在两个平面,但两个开关都是 opt-in(默认不接管):

- host 平面(headless / TUI / base 型 profile)——开箱即用,无需任何配置:主插件 lib/index.js 在 ctx.effect 里直接
mountCompactionEngine(ctx, resolved, { readWorkspace }) → new Engine() 注册 compaction 服务;
配合插件 patch 禁用基座 compaction-basic 行,engram 即为该平面唯一 provider。
- gcReplacesCompaction: false(默认)→ 挂裸 BasicCompactionEngine(DSH 默认 LLM 摘要), compaction 服务永不缺席;
- gcReplacesCompaction: true → ContextGcEngine(机械驱逐 + 重取指针,opt-in);
- 开启:profile patch 里给 engram 行加 config: { gcReplacesCompaction: true },或在设置卡打开。
- preset 平面(web profile)——显式开启后覆盖全部 stock 预设:web 面把 compaction 放在 agent preset 的 isolate realm(shipped 预设自挂 compaction-basic),host 条目进不去、profile patch 也够不到预设文件(mountPreset 的 Include 不带 patches——已对源码核实)。所以插件在启动时(agentPresets 服务就绪后)自动改写每个仍处于出厂 stock 布局的预设——默认预设 + 整张 roster,两代预设布局都覆盖:旧版 <root>/<id>/agent.cordis.yml 组合文件(DSH ≤ 0.1.x,行在 4 空格)与声明式 @deepseek-ai/dsh-web-app/presets/<id>.patch.yml(DSH ≥ 0.2.0,行在 @deepseek-ai/dsh-agent-preset 声明里、14 空格缩进;没有出厂文件的用户自定义声明式预设不碰):把 compaction 组里的 compaction-basic 行换成 dsh-engram/compaction。行按 YAML 结构定位(带 group: true 的列表项)、按 - id: 与 name: 双重校验,与缩进列数无关。这一自动装配是:
- 默认关(opt-in)(autoWebCompaction: false;设置卡「记忆 GC」可开),开启后幂等——已替换则跳过; 关掉的含义:只是在之后的启动时不再自动接管(headless/TUI/base 的 host 平面不受影响)——它不会还原之前已接管的预设,要还原用 npm run web-compaction:revert;
- 逐预设判定,只碰 stock 布局——用户自定义过 compaction 组的预设,以及根本没有 compaction 组的预设(如 shipped minimal),一律不写、只记日志;
- 带备份 + 严格校验:每个文件写前在旁边留 .engram.bak(create-only,保留首次原件;旧版布局为 agent.cordis.yml.engram.bak,声明式布局为 <id>.patch.yml.engram.bak),写后重读校验,任何一步可疑即回滚该文件;改写失败只 warn,该会话仍用 DSH 默认摘要, 永不影响宿主。
- 配置真实传导:写进预设行的引擎配置来自设置卡/配置里的 gcReplacesCompaction 与 gcNarrative——改了这两个开关,下次启动会自动把已接管的行刷新成新配置(幂等重写),所以设置卡对 web 平面同样生效,不再硬编码 true/true。
- 手动的完全等价物(预览 / 审计 / 卸载前回归,随 npm 包发布的 CLI,自动发现两代预设布局——旧版根、出厂声明式预设与上次启动记录的路径;--presets-dir <dir> 覆盖声明式目录探测、 --file 指定单个):
npx dsh-engram status # stock → 未动;wired → 已接管;custom → 不碰
npx dsh-engram doctor # status + 按缺口排序的下一步建议
npx dsh-engram enable # 等价于启动时自动装配(通常 no-op)
npx dsh-engram revert # 还原全部预设的 stock 行(卸载 dsh-engram 前先跑这个)

(仓库内 npm run web-compaction: 是同一 CLI 的别名。)
替换后的 compaction 组形如:
- id: compaction
name: cordis:group
group: true
isolate:
compaction: true
toolResultPruner: true
config:
- id: engram-compaction # ← 替换原 compaction-basic name: dsh-engram/compaction config: gcReplacesCompaction: true # 来自设置;false=该 session 用默认 LLM 摘要 gcNarrative: true # 来自设置;false=纯机械无 LLM
- id: command-compact
name: '@deepseek-ai/dsh-command-compact'
- id: tool-result-pruner
name: '@deepseek-ai/dsh-compaction-tool-result-pruner'
原有配置保留

⚠️ 卸载前先 revert:预设引用 dsh-engram/compaction 后,卸载 dsh-engram 会让该行悬空、 web 会话挂 loading。所以卸载 engram 前先 npx dsh-engram revert(还原全部预设,或逐个恢复各自的 .engram.bak)。harness 大版本升级会覆盖 shipped 预设,悬空引用随之自愈。

从 npm 安装后的第一印象(可感知 / 易引导 / 易配置)

- 半自动(opt-in):默认保留 DSH 自己的摘要;在设置卡打开 gcReplacesCompaction(host 平面)与 autoWebCompaction(web 预设)后,启动一次即由 mountCompactionEngine 接管、并把全部 stock 预设改写成 dsh-engram/compaction。开启后无需再手动改文件。
- 可感知:插件启动时把权威状态写到 $DSH_HOME/engram/context-gc.status.json (host 平面模式 + web 平面每个预设的接管结果 + 生效配置)。随时 npx dsh-engram status 或 npx dsh-engram doctor 查看;web 记忆看板(ESR 任务看板)头部也有一枚状态徽标("Context GC·主机 ·N 预设",来自 overview API)。启动日志也有两行关键确认: engram context-gc: compaction = Context GC … 与 engram web-provision: wired Context GC into preset …。
- 易配置:一个设置卡(「记忆 GC」区)管全部:autoWebCompaction(web 自动接管,默认关)、 gcReplacesCompaction(用 Context GC 还是宿主摘要,默认关)、gcNarrative(叙事兜底开关)——改动后 dsh web 重启生效,web 预设行会被自动刷新成新配置。

验证是否生效:dsh web(或对应 profile)重启后——
- host 平面(headless/TUI/base):启动日志出现 engram context-gc: compaction = Context GC (mechanical eviction + re-fetch pointers);
- web 平面:日志出现 engram web-provision: wired Context GC into preset "standard" (…); restart dsh web so sessions pick it up (每个被接管的预设一行);或 npx dsh-engram status 显示 wired;任意预设的会话内 command-compact/压力触发即走机械驱逐 + 重取指针。
- (若见 … keeping the existing compaction service 说明同域已有 provider、未替换成功;web-provision … skipped (custom) 说明某预设是自定义/无 compaction 布局、未被触碰;dsh-compaction-basic unavailable 说明环境缺依赖,自动回退默认压缩。)

BoundaryPrune(子任务边界剪枝,默认开 + 账本否决)

Context GC 管「压缩的摘要内容」,BoundaryPrune 管「确定性剪枝的触发时机」——在 todo 完成推进 / goal 终态这些子任务边界,主动调用 DSH 自带的 toolResultPruner(确定性 surface-replace,零 LLM),把早已超阈值的旧工具结果剪掉头尾保留,省掉它们在后续每个请求里的整段重放。两个开关互相独立。

- 证据链(docs/ORACLE-ANALYSIS.zh.md + docs/PROPOSAL-resultpack.zh.md):97 个真实会话实测,工具结果重放暴露 2.77B–7.53B 字符(≈692M–1.88M token),96% 的结果字节来自 bash/read/run_code;反事实仿真显示 todo 边界覆盖 79% 的暴露,且偿还门在所有配置下提升净收益 12–32%。
- 偿还门:剪枝会重写前缀(KV 缓存击穿一次),只有当会话预计还要跑 boundaryPruneMinRemaining(默认 50)个以上请求时才 fire——预计长度用 workspace 历史先验 + 超过后 ×2 外推(有界乐观),长会话不会被挡死。
- 诚实激活面:goal=0 且无 todo 推进的会话天然不触发(无边界不剪,零副作用);全量干跑 77 会话激活 38%。pruner 服务缺席即自动禁用并打一条日志。
- 边界源:默认 todo 完成推进 / goal 终态事件;可选空闲步数边界 (boundaryPruneIdleCadence,默认 0=关)——覆盖约 62% 从不动 todo/goal 的会话(侦察:eval/recon-idle-boundary.mjs;反事实:eval/simulate-idle-cadence.mjs 显示 cadence 50 时节省 +85%),仍受偿还门约束。
- 阈值覆盖:boundaryPruneThresholdChars(默认 0=宿主 8192)。设为 2048 并配更小保留边缘(boundaryPruneHeadChars/boundaryPruneTailChars)可更早开剪——T2048+小边缘在真实语料上首剪节省是宿主默认的 2.4 倍(10.14M vs 4.23M 字符)。降阈值在 resultPack 开启时安全(被剪中段有归档兜底)。
- 默认开:boundaryPrune: true;false 是 opt-out。自适应闸门(boundaryPruneAdaptive: true,默认开)让 Context Ledger 在该 workspace 真实剪枝后净收益为负(≥3 次)时自动停剪——证据只能关、不会自行开。启动日志出现 engram boundary-prune (todo @N reqs): pruned …;被账本否决时出现 engram boundary-prune: ledger gate holds for …。

ResultPack L2(结果归档 + result_recall,默认开)

让 BoundaryPrune 剪枝无损:每个被剪工具结果的被移除中段,按与记忆写入相同的规则脱敏后归档到 ~/.dsh/engram/resultpack/<callId>.json,并在剪枝节点尾部追加 handle——[middle archived: result_recall(<callId>) 可取]——
模型知道完整结果就在一步之遥。result_recall(callId, offset?) 按页返回
归档中段(渐进披露,与 [ENGRAM] → engram_detail 同一哲学,也是 SoL-Pi 机制③ ObservationPack 的完整形态)。

- 构造性无损:L1 剪掉的是重放、L2 保留的是可随时取回的原文——「模型回头找被剪结果」的失败模式归零。
- 成本受控:归档在记忆域之外(大块内容永远不会撑大 domain JSON 或 [ENGRAM] 索引);保留期 resultPackRetentionDays(默认 7 天)按 mtime 清理,搭 GC 同一节奏。
- 自我禁用:只开 L2 不开 L1 将无内容可归档,故不注册工具并打一条警告。
- 默认开:resultPack: true(与默认开启的 L1 配对,保证默认剪枝无损);关掉后 L1 仍可工作,但被剪中段不可召回。开启时启动日志出现 engram result-pack enabled: result_recall(callId, offset) registered。

激活遥测(SoL-Pi M15/C13)

机制必须可证明在激活,休眠机制默认关。/api/dsh-engram/stats(以及 GUI overview)现在返回 activation: { boundaryPrune, recall }:

- boundaryPrune——BoundaryPrune 真实 fire 的累计持久记录(fires / prunedResults / charsRemoved / byKind todo|goal|step / lastFire),每次剪枝落盘到 ~/.dsh/engram/boundary-prune.status.json,与 resultPack 无关。
- recall——召回漏斗的注入腿:实际进过会话的 [RECALL] 块数量(recall-injections.json),与从 session log 已统计的另一端(engram_recall 查询 → 命中 → engram_detail 下钻)闭合,可看到注入→查询→下钻的完整激活。

Context Ledger(净收益账本)

把「省 token」变成可核对的账。每个模型请求记一笔:注入的 [ENGRAM]/[RECALL] 与 [ESR] 块按存活请求数计入成本;被 BoundaryPrune 剪掉的结果按后续请求数计入节省;每次剪枝按 (1 − ledgerCacheDiscount) 计一次前缀重写罚金(KV 缓存击穿)。
GET /api/dsh-engram/stats 与 /overview 返回 ledger:

- totals — requests / injectedChars / avoidedChars / rewriteCount / rewriteChars / netChars / netTokens(net = avoided − injected − rewriteChars × (1 − discount));
- byWorkspace / byDay — 按工作区、按天的同一口径,byDay 倒序;
- openSessions — 仍在累计、尚未落盘的会话数(进程重启最多丢 flushEvery 个请求的账)。

GUI「遥测」页签直接画这块账本(净 token 大数字 + 节省/注入/重写罚金三卡 + 近 14 天红绿柱),记忆概览页也多一张「上下文净 token」卡。落盘在 ~/.dsh/engram/ledger.json(原子写、按天滚动、 delta 落盘),进程内只做整数累加,热路径零 I/O、零模型调用。默认 ledgerEnabled: true、 ledgerCacheDiscount: 0.1;关掉 = 不挂监听、不写文件。

节省的两条入账腿(v0.7.1 补齐,此前只记成本所以净值恒负):

1. L1 边界剪枝——toolResultPruner 剪掉的 chars,按后续请求数计入(需要有 host 平面的 toolResultPruner;DSH ≥ 0.2.0 的 web profile 把该行移进 agent preset 的 isolate realm,本 bundle 的 cordis.patch.yml 会重新启用 host 行,状态快照里 boundaryPrune.armed 可核对);
2. 压缩(Context GC)——每次 checkpoint 用「被替换区间可见字符数 − checkpoint 字符数」入账,host 平面与 web 预设域(经 engramLedger 服务桥接)走同一个账本。

口径诚实:token 按 chars / 4 估算,不含 provider 实际计费与模型质量差;某工作区还没发生过剪枝/压缩时「节省」就是 0,净值等于注入成本 + 重写罚金(负值正常),遥测页会直接提示这一点。

JIT 推送(v0.6-C)

拉取式召回被实证休眠(112 会话仅 24 次 engram_recall、8 次 engram_detail),所以 v0.6 增加按事件推送:三个确定性事件,命中才推、每次一行、每会话最多 3 行(jitMaxLines,每行 jitMaxLineChars=220):

| 事件 | 触发条件 | 推什么 |
| 错误复发 | 同一 cmd: 签名第二次失败,且已有 error/procedure 记忆 | 该记忆的 id + 一行摘要 + engram_detail 下钻 |
| 文件焦点 | 同一文件连续第二次编辑,且有 filePath/dir 锚定记忆 | 优先 preference,其次 fact/decision |
| 目标完成 | goal complete/block 且有 active ESR 任务 | 前两个未闭环任务 + esr_status 提示 |

- append-only:推送行一旦出现就不再改写/撤回,前缀每次推送只变一次;每次推送按 estimatePrefixChars + 行长度 记一次重写罚金(Context Ledger 的 rewriteCount / rewriteChars),净收益可在遥测页直接看。
- 无命中沉默:没有可指向的记忆/任务就不推——宁可不推,也不灌噪声。
- 零 LLM、纯规则;jitEnabled:false 一键关,[JIT] section 为空时不占任何 token。
- 尚未做:resume brief(隔天继续)与「预计剩余请求不足时跳过推送」的显式 ROI 门——当前靠 3 行硬预算 + 账本观测。

会话纪要 + 恢复简报(v0.7-A/D)

- episode:会话结束(session/disposed)时,用零 LLM 规则把事件日志压成一条紧凑纪要——改过哪些文件、跑过哪些命令、哪些失败后修复(cmd×N→fixed)、git commit 的书面主题、仍挂着的 todo。只在有实质内容时生成(纯 ls 会话不写);永不进 [ENGRAM] 索引;每个 workspace 只保留最近 20 条,超出自动归档。
数据源全部是已有事件(tool/call 的 arguments、tool/result、todo/write),不调模型。
- 恢复简报:新会话第一次组装时,取最近 7 天最新的 episode,注入 ≤240 字符的 [RESUME] 行 + engram_detail <id> 指针;与 [ENGRAM]/[RECALL] 一起按会话冻结。
没有近期 episode 就不注入——隔天继续项目时不用重新交代背景。
- 设置键:episodeEnabled、resumeBriefEnabled(均默认开), episodeMaxPerWorkspace / resumeBriefMaxAgeDays / resumeBriefMaxChars 可调。

实体页 + 实体卡(v0.7-B)

esr_node 从「图上的点」升级成可读的页:对一个实体(package / repo / 服务 / bug…)
聚合当前真相、时间线、未闭环任务、冲突与关系。

- 实体卡(索引内,默认开):[ENGRAM] 为活跃实体(有锚定记忆或未闭环任务)渲染一行,最多 2 张: [ENT] dsh-engram · package · 3 mem · 1 open · truth: … #id · page: engram_detail(entity="dsh-engram") 这些实体的平铺记忆行不再重复(真相由卡片承载);其余实体仍列在 nodes: 一行。
- 完整页(按需下钻):engram_detail(entity="<name|id>") 返回 truth / timeline / open loops / conflicts / relations 五段,带 memory id 与证据缺口;没有注册节点但有记忆锚定的名字也会生成虚拟页。
- 纯函数、确定性排序:preference 排在 truth 最前,superseded 旧真相不进 truth,冲突按无序对去重(#a vs #b)。
- 设置键 entityCardsEnabled(默认开);entityCardsMax / entityCardChars 可调。

可重验证锚点 + 分层 TTL(v0.7-C)

「把过去当真相」是记忆插件最大的可信度问题。v0.7 给带 filePath 的记忆加锚点:

- 写入时记录 {path, mtimeMs, size, hash?}(≤256KB 的文件存 sha256,大文件只存 stat),落盘在记忆行的 anchor 字段;相对路径按 workspace 解析为绝对路径。
- 会话启动时做一次有界重验证(最多 40 条):文件缺失/改动 → 记忆打 stale 标签; [ENGRAM] 索引与实体页 truth 不再自动展示它,但不删除——engram_detail 仍可重取,实体页 timeline 会标 · stale。
- 分层 TTL:preference / procedure 永久;episode 默认 14 天;error 默认 60 天;其余按 expireDays(默认 180 天)。真正删除仍只发生在 GC 的归档(不硬删)。
- 设置键:anchorRevalidate(默认开)、episodeTtlDays、errorTtlDays。
- 未做:离线「巩固任务」(episode 抽象/合并)——确定性规则只能做 rollup,不是语义抽象;在没有证据前不引入,留到 v0.8 与 playbook 一起考虑。

自动捕获策略

捕获是确定性、离线的——只看到工具结果,从不看对话本身。什么会被记录成一条记忆:

| 工具结果 | 行为 | 信号 |
| git commit … -m "提交信息" | 记录——书面提交信息就是这条记忆 | 0.55 |
| git merge / rebase / cherry-pick / tag / checkout -b | 记录(里程碑) | 0.5 |
| git push / git stash / 无 -m 的 commit | 跳过——操作回显,不是决策 | — |
| 写入/编辑关键配置与文档路径 | 记录 | 0.3 |
| 读取配置路径 | 记录 | 0.3 |
| 反复出现的工具错误 | 记录(按消息去重) | 0.25 |

显式 engram_store 的记录不受上述规则约束(按会话限流)。

谁能拿到 [ENGRAM] 索引行(这才是真正进提示词的部分): signal >= minIndexSignal 或 hits >= promoteHits 或 kind === "task",再由 indexMaxLines / indexMaxChars 封顶。另有一道额外的闸保持管道干净:
自动捕获的 git 命令回显——文本里嵌着 shell 命令链(git push: cd … && …)——
即使信号超阈值也不进索引,直到被召回命中晋升为止。其余条目安静地躺在存储里,按需用 engram_recall / engram_detail 取用——「检索到 ≠ 注入」。

注入块

模型实际看到的内容(每个会话渲染一次,然后冻结):

ESR 操作协议(静态,每轮逐字节相同)
1. 动工前:esr_ready 看可认领工作;esr_status 拿实时状态。
2. 多步工作 → esr_task(draft 起步);动工 → esr_claim;收工 → esr_close(三证齐)。
state 是唯一真相:拿不准 state 就 call esr_status。

[ENGRAM] workspace: symbolic-index · 2 memories · 1 task(s) active · 0 links [D] 06-18 Decided: use sqlite-vec for retrieval #a2331d87 [T] 06-18 Retrieval upgrade — ACTIVE · gap: artifact, evaluation, memory_ref #tsk_8b26 drill: use [RECALL] below · engram_detail <id> (full record) · engram_recall <query> (more) · esr_task/esr_node/esr_link (work)

[RECALL] recall · 1 hit(s) · first msg: retrieval
- [D] 06-18 Decided: use sqlite-vec for retrieval upgrade #a2331d87 ×2
context: use above · engram_detail <id> · engram_recall <query>

[ESR] tasks: 1 active / 1 stable
- tsk_0d: Retrieval upgrade — ACTIVE · gap: artifact, evaluation, memory_ref
- closed: tsk_9a (RAG eval) · +1 snapshot from session start — WILL NOT auto-refresh; call esr_status for live state this-session actionables (frozen) promote: 2 pending todo(s) vs 1 ESR task(s) — esr_task(name="…") #suggest-promote

前缀:[D] 决定 · [E] 错误 · [P] 流程 · [F] 事实 · [I] 洞察 · [H] 交接 · [T] 任务。
被反复使用(hits >= promoteHits)的流程记忆升格为「实证经验」——前缀变 [P✓] 且稳定排在索引块最前(每组内部仍按新旧排序,块保持确定性);这是我们对 TencentDB Agent Memory「Skill= 经过验证的可执行经验」的零 LLM 对应物。
入选规则遵循「自动捕获策略」(信号阈值 / 命中晋升 / git 回显守卫),并按配置的行数与字符预算封顶。
id 通过 engram_detail 取完整记录。工作区没有任务时,[ESR] 仍会渲染一行点名 esr_task/esr_close,让机制对模型保持可见,而不是整体消失。

[RECALL](会话启动自动召回,autoRecallOnStart)按会话首条消息对工作区做确定性 BM25 召回,把命中前 autoRecallLimit 条注入,受 autoRecallMaxChars 字符预算约束;无命中或空工作区时不渲染。
它是一条提示而不是全文灌入——模型该把它当成"这条相关,可能需要用 engram_detail 看细节"。
纯读、不 bump 命中、superseded 过期真相不占槽位;随 [ENGRAM] 一起按会话冻结。

配置

默认值以 token 为优先;可通过 profile 补丁(~/.dsh/profiles/web/cordis.patch.yml)或 Web 配置卡片覆盖任意键:

- id: engram
config:
autoCapture: true # 零 LLM 工具结果捕获 autoCapturePreferences: true # 零 LLM 用户纠正/偏好捕获(prefer/avoid/switch-to 句式) preferenceCapturePerSession: 8 # 每会话偏好记忆条数上限 boundaryPrune: true # ResultPack L1:todo/goal 边界剪掉超阈值旧工具结果(默认开) boundaryPruneAdaptive: true # 账本否决:该 workspace 净收益为负(≥3 次剪枝)时自动停剪 boundaryPruneThresholdChars: 0 # 阈值覆盖(0=宿主 8192;如 2048 可更早开剪) boundaryPruneHeadChars: 0 # 覆盖时的保留头部(0=宿主 4096) boundaryPruneTailChars: 0 # 覆盖时的保留尾部(0=宿主 1024) boundaryPruneIdleCadence: 0 # 空闲步数边界(0=仅事件边界;覆盖无 todo/goal 会话) resultPack: true # ResultPack L2:剪掉的中段脱敏归档 + result_recall 按页召回(默认开,须与 L1 同开) resultPackRetentionDays: 7 # 归档保留天数(mtime 清理,走 GC 节奏) sessionSearch: true # engram_recall 也可对历史会话 FTS recallScope: workspace # 召回范围:workspace(严格工作区隔离,默认)| global(可选跨工作区召回,带 [W:<ws>] 来源标记) autoRecallOnStart: true # 会话启动自动召回:按首条消息注入 [RECALL] 块(false=回到纯拉取) autoRecallLimit: 3 # [RECALL] 最多注入条数(少而准) autoRecallMaxChars: 700 # [RECALL] 字符预算 autoCapturePerSession: 40 indexMaxLines: 12 # [ENGRAM] 行数上限 indexMaxChars: 700 # [ENGRAM] 字符上限(token 预算) minIndexSignal: 0.4 # 低于此信号的自动捕获不进索引(git 命令回显即便超阈值也不进,直到命中晋升) promoteHits: 3 # ……直到被召回这么多次才进索引 expireDays: 180 # 记忆 TTL(0 = 永不过期) maxMemoriesPerWorkspace: 2000 gcEnabled: true # 定时记忆 GC gcIntervalHours: 24 # 回收节奏 gcStableRetentionDays: 120 # 超过此天数的 stable 任务离开 [ESR] gcReplacesCompaction: false # Context GC:接管 DSH 自动 compact(默认关=宿主 LLM 压缩;true=opt-in 指针摘要) gcNarrative: true # 无锚轮次走 scoped LLM 叙事;false=纯机械(零 LLM) jitEnabled: true # JIT 推送:错误复发/文件焦点/goal 完成时推一行已有记忆 episodeEnabled: true # 会话纪要:结束时零 LLM 生成 episode(不进索引,供恢复简报) resumeBriefEnabled: true # 恢复简报:新会话注入最近 7 天最新 episode entityCardsEnabled: true # 实体卡:[ENGRAM] 为活跃实体渲染一行真相 + 任务 + 下钻指针 anchorRevalidate: true # 会话启动重验证 filePath 锚点(改动/缺失 → stale,不进索引/truth) episodeTtlDays: 14 # episode TTL(preference/procedure 永久,error 60 天,其余 expireDays) ledgerEnabled: true # Context Ledger:注入成本/剪枝节省/前缀重写罚金 → 净 token ledgerCacheDiscount: 0.1 # 缓存折价:重写按 (1−0.1) 计罚金;0.1 = 命中只付 10% autoWebCompaction: false # web 预设自动改写(默认关;开启需显式确认,可用 revert 还原) boundaryPruneMinRemaining: 50 # 偿还门:预计剩余请求数低于此值不剪 engramIndexOrder: 40 # systemPrompt section 顺序(位于 tools 段之前) esrOrder: 41

开发

npm test # 381 个测试:核心 + Web API + GC + Context GC + ESR 触发 + Context Ledger + JIT(node:test)
npm run build:client

仓库结构:lib/(宿主半边:store / capture / index-block / tools / api / settings)、 client/(浏览器半边,TSX + build.mjs)、test/(node:test)。

故障排查

Web 界面一打开就停在 “Failed to load plugins”,loader 报错形如:

failed to apply loader entry … (@linxin666/dsh-client-ui-web-ui-settings): keyed slot "settings.plugin.item" requires options.key

原因:DSH 自 0.1.0-rc.7 起把配置卡槽位 settings.plugin.item 声明为按 settings 命名空间键控(卡片用自身编辑的命名空间作 key 注册——dsh-engram 的配置卡正是用 key: "dsh-engram" 这样注册的)。@linxin666/dsh-web-ui-all 0.2.0 之前的 dsh-client-ui-web-ui-settings 向该槽位注册分组卡片时没有提供 key;而 loader 只要有一个 entry 失败就会中止整个启动流程,于是 GUI 一直卡在失败页。

修复方式:

- 正确修复——升级全家桶:@linxin666/dsh-web-ui-all@^0.2.x。0.2 系列已把自身设置面从键控槽位迁出,改为一级 settings.section(上游正是为这个报错做的修复)。
- 临时解阻:在已安装的 node_modules/@linxin666/dsh-client-ui-web-ui-settings/lib/client.js 中给那个 settings.plugin.item 注册补上 key: "web-ui-plugins",然后重启 dsh web。(在按命名空间键控的派发下,分组卡片只是不显示,不影响页面其他部分。)

相关项目

- symbolic-index — 原始跨会话记忆插件(5 信号 RRF 融合、sqlite-vec、Dream Engine)。
- pi-esr — 项目全周期的证据驱动任务状态;这里的闭环协议是它的简化形态。

许可

MIT
预览分享卡片长什么样 ↓skepsun/dsh-engram
分享 · DeepSeek Harness Hub
skepsun/dsh-engram

为 DeepSeek Harness 设计的极简长期记忆插件,融合了

这个插件是你维护的吗?
作者本人可认领并核对本条目;认领通过后,详情页会显示「作者已认领」标记。
认领条目 →
回到顶部