dsh-ledger-memory
项目级记忆,核心是台账:台账的结构由 AI 按项目类型自行决定,插件只负责检测、注入、回写、归档与 Git 配合。记忆按语义分开放 —— 活跃台账是当前状态、回合流水只追加、关键决策/目标树/经验教训各是一卡一文件(另有卡片目录的多维索引表),根本原则只有用户本人能改。压缩方式只有一种(分流后压缩):压缩前先让 AI 把这一段分成三个桶 —— 留上下文的压缩后逐字注回、以后要查的落进项目 _stash/、已被取代的丢掉。对话建在装着多个项目的总工作区上时,可以把它锁定到某个子项目(锁记在插件自己的家,跨重启保持;不在你的项目里留任何标记)。另有活动日志 L0–L4,其中 L4 逐字存档用户原话
安装
dsh plugin --profile web add dsh-ledger-memory
沙箱测试结果
| 阶段 | 结果 |
|---|---|
| 1 安装 | ✓ |
| 2 列装 | ✓ |
| 3 挂载 | ✓ |
| 4 启动 | — |
四阶段在隔离沙箱里跑:安装(有网)→ 列装 → 挂载 → 启动(后三步断网)。
README
dsh-ledger-memory(台账记忆)
一个面向 DSH(DeepSeek Harness) 的宿主侧插件,为每个项目建立独立、可回溯、可追查的长期记忆载体。
插件管理的对象并非对话记录,而是项目自身的记忆载体 —— 台账:可追溯、只追加、按周归档。
围绕台账还提供活动日志、上下文压缩与跨会话交接能力。
不预设任何模板。 台账的文件划分、命名与字段结构,全部由模型在初始化时依据项目类型自行确定;
插件仅承担检测、注入、回写、归档与 Git 协作等流程性职责。
解决的问题
问题 · 处理方式
新会话无法接续前一会话的项目进度 · 会话开始时注入活跃台账全文;内容未变更则不重复注入,以避免破坏 prompt 缓存
单个回合的产出未能沉淀 · 回合结束时将「已完成 / 受阻 / 下一步」写回活跃台账
台账持续增长、难以完整阅读 · 超出阈值时将最旧的已完成条目移入 归档目录/YYYY-MM/weekN,只追加、不删除
细节在上下文压缩中被抹去 · 压缩之前将用户原话、报错原文、变更文件清单持久化为逐字胶囊,压缩后与台账一并注回
新会话缺少承接信息 · 生成交接单,并把指针推入新会话的首轮注入,无需使用者主动查询
需要核查台账变更来源与时间 · 台账末尾自动维护变更登记;完整历史保存在只追加的活动日志中
仅为一次性排查、不构成项目 · ledger_note 生成问题记录(现象 → 根因 → 结论 → 处理过程),不建立台账
上下文压缩机制
插件默认启用全量压缩;增量(分段)压缩默认关闭,可按需手动开启。
阶段 · 触发条件 · 行为
① 保底 · 全量 · 水位达到 70%(内核默认 80%,插件在其之前触发) · 执行全量压缩,同时保留指向原始内容的指针:用户原话、报错原文、变更文件清单与重读入口
② 收尾 · 交接 · 切换会话时 · 由交接单承载,使新对话无缝接续
③ 可选 · 增量 · 需手动开启(默认关闭) · 仅压缩最旧一段,削减上涨区间的约一半
关于默认关闭的增量压缩:该分支曾为主要路径,现已默认关闭且不再按固定节奏调用。
依据是实测取舍 —— 压缩幅度较浅的摘要大概率会被再次压缩(≤5 万 token 的浅层压缩中 72.7% 会被二次压缩),
层级增加会导致清晰度衰减。因此当前以全量压缩为主路径,单次压缩更深。
deltaTriggerPoints(触发涨幅)与 deltaRemovePoints(单轮最大压缩点数)两项配置仅在启用增量压缩时生效。
压缩遵循一条固定约束:先记录台账,再执行压缩 —— 尚未落盘的进展将成为唯一副本。
该约束由周期判定("自上次压缩以来是否落盘")保证,而非"是否已发出要求"
(后者会使模型被阻塞:历史上曾出现拦截近 4 小时、压缩零次的情况)。
收尾硬门(hardMaintenanceGate)默认开启:未落盘则不压缩;关闭后回退为
"拦 3 次或超 20 分钟即放行"的旧行为。
安装
需要 Node.js ≥ 22.19。
# 走 npm npm install dsh-ledger-memory # 或走 DSH 自己的插件命令 dsh plugin add dsh-ledger-memory
装完重启客户端才生效 —— 内核对已加载的插件不会因文件改动自动重载。
升级(★ 如果"界面显示有新版本,却怎么装都是旧的",看这里)
从 1.0.0 起,正常升级不需要做任何特殊操作 —— ^1.0.0 允许整段 1.x,
所以 1.1、1.2 都在范围内,能自己爬上去。
若从更早的 0.x 版本升级上来且恰好受阻,那是两个叠加的原因:
1. 依赖被写成 ^0.1.0 这种旧范围。 0.x 的插入符语义是
^0.1.0 = >=0.1.0 <0.2.0 —— 不允许跨次版本,所以天花板永远是 0.1.1。
(1.x 没有这个问题:^1.0.0 的天花板是 2.0.0。)
2. pnpm-workspace.yaml 里可能留着一行过期的豁免(pnpm 装成功时会自己往这里追加):
minimumReleaseAgeExclude:
- dsh-ledger-memory@0.1.0 # ← 这行就是枷锁
★ 卸载重装治不好它 —— 卸载动不了这个文件。这也正是"反复卸载重装仍是旧版"的原因。
修法:
cd <DSH_HOME>/profiles/<profile> cp package.json package.json.bak # 备份 pnpm add dsh-ledger-memory@1.0.0 # ★ 关键就这一步:按精确版本装 # 然后重启客户端
★ 为什么"按精确版本装"这一步就能破局(实测):
pnpm 会把那一行自己改成 - dsh-ledger-memory@0.1.0 || 1.0.0(追加而非替换),
于是新版本进了候选集,解析就取其中最新的 ⇒ 一步到位。
★ 建议顺手把那行清掉(不是必需,但更干净):删掉
pnpm-workspace.yaml 里 - dsh-ledger-memory@… 那一行,以后就不会再有歧义。
自查(不需要任何额外文件):打开
<DSH_HOME>/profiles/<profile>/pnpm-workspace.yaml,看有没有
- dsh-ledger-memory@ 开头的那一行。有 ⇒ 就是它;没有 ⇒ 问题在依赖范围(上面第 1 条)。
本仓库里另有一个更省事的只读诊断:node tools/_probe-install-version.mjs。
⚠️ 它只在本仓库里有、不在 npm 包里(发布包内只有lib/、README.md、
LICENSE、cordis.patch.yml与package.json—— 整个tools/目录都不发)——
所以从 npm 装的用户请用上面那条手工自查。
使用方式
安装后无需额外配置。在一个项目目录中开始工作,插件将依次执行:
1. 检测到尚未建立台账时,注入一条提示,要求模型向使用者确认是否初始化(插件不会自行创建);
2. 使用者确认后,模型依据项目类型设计台账结构并完成登记;
3. 此后每个会话自动加载台账;到达水位阈值(或使用者主动要求)时,插件提示模型回写并归档。
关于「自动」的边界:插件不会自行写入台账或执行归档 —— 它负责在适当时机向模型
注入指令,由模型调用ledger_write/ledger_archive完成。
这是有意设计:台账内容需要判断(哪些值得保留),而插件不具备该判断能力。
因此模型忽略指令时,该回合不会落盘 —— 插件会在后续步骤再次要求(闸门机制)。
回合末的四选一卡片默认关闭(turnEndOffer),可在设置中开启。
手动命令
在对话里敲 /ledger(不带参数会弹一张可点的卡片),或用子命令:
/ledger status 看状态(有没有台账、用的哪个文件、几行、Git、日志开关) /ledger show 把当前活跃台账全文打出来 /ledger init 初始化台账(由 AI 为这个项目设计一套结构) /ledger update 让 AI 把本次会话的进展写回台账 /ledger archive 把最旧的已完成条目移进归档目录 /ledger compact 立刻做一次全量压缩(保留指针) /ledger light 轻量压缩 · 只压**上一个回复**那一段(★ 需显式使用,不是常规动作) /ledger log 查活动日志 /ledger handoff 写一份交接单 /ledger principles 看根本原则;`pending` 列提案;`approve <id>` 批准(仅限使用者本人批准) /ledger mode 看/切换压缩模式(steady / surgical) /ledger lock 把本场对话锁到某个项目目录(总工作区里用)
/ledger principles 是唯一仅限使用者本人执行的子命令:模型可以提议,但批准必须由使用者完成。
会话建立于总工作区时:锁定到子项目
若会话建立在包含多个项目的总工作区上,插件无法判断本次工作针对哪个项目,
默认将全部操作落在当前目录。此时可通过锁定指定目标:
/ledger lock 20-DSH插件/04-其余插件/10.项目台账
锁定后,本场对话的全部工具调用均落在该目录,无需每次单独指定。
/ledger lock 不带参数可查看当前状态,/ledger lock off 解除锁定。
会话首轮弹出的卡片中也提供该选项。
锁定状态会保持:重启或重新打开后依然有效,每场对话独立记录,互不影响。
记录保存在 DSH 自身的数据目录(plugin-data/)中,不写入工作区:
不创建注册表项,也不在目标目录留下任何标记 —— 项目目录不会因此新增文件。
若当前目录自身已有台账,仍以当前目录为准 —— 锁定仅接管"没有台账"的目录。
若锁定的目标目录被删除或移动,该锁定自动失效(回退为按工作目录解析),
后续操作不会指向不存在的路径。
设置面板
设置 → 项目台账(台账 / 压缩 / 日志),共 43 项配置,分为四页:常规、注入、扩展记忆、高级。主要可调项包括:配置优先级、压缩水位、
收尾硬门、回合末卡片、活动日志各档开关、活跃台账行数上限、变更登记保留条数、
水位提醒档位、逐字胶囊的采集范围与 ledger_recall 单次回读上限、文件锚定的权重阈值等。
关于「谁说了算」:该项决定设置面板与项目清单(.dsh-ledger.json)冲突时以哪一方为准。
默认值为「以设置面板为准」,即面板的修改对所有项目立即生效(含已初始化的既有项目)。
受该项管辖的是 6 项:mode、blockInjection、fileAnchor、maxActiveLines、
changeRegisterMax、logLevels;其余配置项始终以面板为准。
若同一代码仓库内不同子项目需要不同上限(例如大型项目 800 行、小型项目 150 行),
可将该项切换为「以项目清单为准」。
面板提供「推送到所有对话」操作:执行后所有对话在下一回合重新读取当前设置。
压缩流程:单一方式(分流后压缩)
此前的两种方式(按位置压缩 / 按内容分流压缩)已合并。分流现为每次压缩的标准步骤,
不再是可选项 —— 设置中的 mode 仅为兼容既有项目清单而保留,取值不影响行为。
合并原因:分流(ledger_triage)决定「哪些内容可被压缩、各自如何处理」,
而标记为「留在上下文」的部分会在压缩后逐字注回 —— 这是"指针得以完整保留"的前提。
按位置压缩无法识别哪些内容确实不可丢失。
分流将当前区间划为三个桶:
- 留(
keep):下一轮若缺失即会导致错误的内容(当前状态、未验证假设、既定约束)。压缩后逐字注回。 - 落(
stash):后续可能需要查阅、但不应占用上下文的内容(判断与依据、已排除的路径、关键文件位置)。
写入记忆目录的 _stash/ 目录(V3 下位于 <项目名>-ledger-memory/ 内),永不注入,按需检索。
- 丢(
drop):已被取代的内容,仅记录到日志。
keep 设有条数上限(单次与累积各一项)——保留过多等同于未压缩;
超出时按模型给出的顺序截断,并如实报告截断条数。
分流为每回合义务:每条回复结束前需完成落盘(ledger_write)与分流(ledger_triage)。
收尾硬门默认开启 —— 未完成时该回合的压缩不会发生(宁可让水位上涨至 78% 触发强制压缩,
也不保留"未落盘即压缩"的窗口)。注入指令的措辞按硬门状态分为两套,二者不自相矛盾。
二十二个工具
以下工具由模型在对话中调用,也可要求模型调用:
工具 · 作用
ledger_status · 报告这个项目的台账状态(只读)
ledger_read · 读台账 / 决策记录 / 归档 / 卡片
ledger_write · 写活跃台账、决策记录、流水或原则
ledger_init · 登记台账结构(四条硬约束:一个活跃台账 / 一份独立决策记录 / 一个只追加归档目录 / 行数上限)
ledger_archive · 把旧条目移进只追加的归档
ledger_conform · 检查既有项目的台账是否符合预期,能修的
…(truncated)
以上内容来自 npm 上的 README,我们只做格式化,不改内容。
版本
npm 上共发布:24,最后更新 2026-10-27