Repository navigation
Releases: Noelune/unified-agent-memory
Release list
v0.9.2 — npm 包内容修复(默认启用参与度层)
Fixed — npm 包内容(0.9.1 的 npm 产物不可用,请直接装本版)
- 0.9.1 的 npm tarball 缺失整个分层核心(app/domain/infra/surface 与钩子集成文件不在包里),装出来的包一跑就
ImportError。根因:files白名单仍是 0.8.8 时代的顶层匹配,且发布流程在打包修复之前的工作树上执行。0.9.2 从修正后的白名单发布,发布前对注册表产物做了内容核验(58 个分层文件 + 钩子文件 + 0 个字节码)与真实导入冒烟。 files白名单改为整棵core/unified_memory(否定模式排除字节码——.npmignore对files白名单目录实测不生效),并补上integrations/hooks与integrations/claude/hooks。- 0.9.1 的 git 仓库内容本身没有问题;只有 npm 产物坏。已装 0.9.1 的用户请升级到本版。
Verified
注册表 tarball 内容核验 + 解包导入冒烟通过;npm run check 完整链 exit 0(typecheck → drift → sync → JS 886 → Python 534 → setup 32 → build → node → drift:python)。
用户更新
npm install dsh-unified-agent-memory@0.9.2或 dsh 桌面端的插件更新。数据库自动迁移,无需手动操作。
v0.9.1 — 参与度层默认启用
Changed — 参与度层默认启用(不再是自愿开启)
- block ID 补写自动执行:每个 vault 一次性的
backfill-ids不再需要手动跑——memory sync、catchup(dsh 每小时补跑路径)与setup init在标记缺失时自动执行(marker 防重、幂等、只追加 ID 后缀、走唯一执行器,绝不进入检索读路径)。 - Claude Code 钩子默认安装:
setup init与新的setup hooks把三条钩子幂等追加进~/.claude/settings.json(首次写入自动备份;你的已有钩子原样保留;无法解析的 settings 绝不触碰;--no-hooks/hooks --remove随时退出)。手动粘片段仍是等价方式。 - 钩子队列默认排水:
catchup每次运行顺带digest --queue(session transcript → 事实抽取 → 提交区),失败只计报告、绝不拖垮补跑。 - 要退出:
setup hooks --remove+ 已打标记的 vault 不会再动。
Verified
Python 534 / JS 886 / setup 32 / bench 一致 / 快照 46 逐字节 / e2e 46;新增 8 例默认启用行为测试。
⚠️ 注意:本版本的 npm 产物不完整(发布流程事故),请直接安装 v0.9.2。git 仓库内容本身没有问题。
v0.9.0 — 分层地基、事实身份与参与度层
一次大版本累积(80 个提交,五个批次)
兼容性:数据库首次打开自动迁移 v10 → v11(只增不减——memories/embeddings/access_log 一行不动,自动留 .v<旧版本>.bak 备份;已在真实库副本上验证 164/130/848 守恒)。无需任何手动操作。
Added — 批次 A:止血(12 项修复 + 1 个现场迁移 bug)
- 迁移不再失忆:版本不符从"全表 DELETE"改为增量注册表 + 单事务 + 失败回滚 + 自动备份;并修掉一个会让所有真实 0.8.8 库升级直接崩溃的顺序 bug。
- 事实身份改为 occurrence 身份(跨文档同句不再静默合并、删一篇不丢另一篇)。
- 中文检索:三套词法表收敛为一张
fts_lex(ASCII 词 + CJK bigram)——旧表对中文恒 0 命中却拿最高权重。 - 哈希审计链全接入(
memory audit [--verify]);遗忘去毒(retrieved/used 拆分,只有被引用才强化);查询向量预算 0.8s→0.12s;图流门控;预算按价值密度装填。 - 完整清单见 BATCH-A-REPORT.md。
Added — 批次 B/C/D/E:地基、置信度、Claim Ledger、参与度层
- 四层架构(surface/app/domain/infra),依赖方向由架构测试钉住,六类白名单全部清零,规模预算门开启。
- 检索弃答:零支持的弱命中不再返回,
--json新增confidence。问不存在的东西得到空结果,而不是 8 条 0.03 分的弱匹配。 - 遗忘收敛:保护网从 20+ 关键词宽网(命中约 75% 事实、遗忘形同虚设)收敛为显式白名单(凭据/红线/端点/协作);可触达率 41.7% → 75%。旧事实现在会真的参与衰减评分——全部可回滚(进 记忆遗忘区/)。
- Claim Ledger(schema v11):每条事实一个永久 block ID(
^c-…,Obsidian 原生语法,改文字不改 ID);memory backfill-ids [--dry-run]幂等补写;memory search --as-of YYYY-MM-DD与memory explain <id>。 - 参与度钩子:
integrations/hooks/memory_hook.py平台无关钩子运行时(会话开始常驻包 / 每轮本地检索 / 会话结束排队沉淀)+ Claude Code 接入物 +memory digest --queue。 - README.md 不再进事实索引。
Verified
Python 526 / JS 886 / setup 32 / typecheck 0;bench 检索指标与 0.8.8 逐字段一致(五批次未触碰排序权重);CLI 快照逐字节;46 项真功能 e2e;真实库副本迁移守恒。
v0.8.8 — 两个进门就绊人的小地方
Fixed — 两个进门就绊人的小地方
preview 拒绝 --view。 view 是位置参数,文档和插件自己的 argv 构造都用那个形式,所以位置参数仍是唯一规范写法。但 --view 是读过 search --limit、doctor --strict 之后会顺手打出来的拼法,argparse 直接以 exit 2 顶回去:
$ memory preview --view pending
memory: error: unrecognized arguments: --view
现在它是别名,渲染同一个 view。两道闸门防止它变成第二个行为不同的入口:位置参数和 --view 给了不同 view 时报错并列出两者,不静默挑一个;一个都不给时列出四个可选 view,而不是把 None 传进 preview.build()。
顺带记一个坑:两者共用一个 dest 看着更整洁,但不工作——argparse 先解析可选、后解析位置,位置参数的默认值会把已解析的值冲掉。两个名字分开,在 cmd_preview 里汇合一次。
装好的包跑不了自己的测试。 files 没列 core/tests,.npmignore 里又写着 core/tests/,两处一起把它挡在外面:
$ python -m unittest discover -s core/tests
Ran 0 tests
于是"装了所以无法自证"成了必然。测试套件只用标准库,约 700 KB,没有任何依赖上的理由扣着不放。包从 104 条目增到 132,含 28 个测试模块,打包 545 KB。audit:package 仍然干净:入口是 core/tests/*.py 这个 glob,.log/.bak/.db/__pycache__ 进不来。
Tests
test_preview.py:别名与位置参数输出同一个 JSON;两种拼法冲突时非零退出并说明;都不给时列出四个 view。test/package-contents.test.mjs(新增):core/tests/*.py在files里、.npmignore没排除它、非源码(src/、scripts/、test/、node_modules、字节码)不随包走、入口保持*.pyglob 以免夹带 fixture。health.py的未归类计数不再把 markdown---分隔线当事实行(用与同一函数里判重复相同的谓词;回归测试在旧谓词下报4 != 3)。
Verified
core unittest discover 374 OK、node --test 24 OK、vitest 865 OK(38 文件)、typecheck 0 错误、audit:package 132 条目、drift:check 与 drift:python 均通过。
v0.8.7 — 括号留着,只删里面的废话
这一版在退什么
0.8.6 看到「不要写这种话」就以为是不能用括号,把六处正常的括注改成正文:
| 0.8.6 改成了 | 现在 |
|---|---|
dsh · 未识别 profile |
dsh(未识别 profile) |
已并存,两条都留下,写进 事实冲突.md |
已并存,两条都留下(写进 事实冲突.md) |
8 个孤儿索引库,是旧 vault 留下的,… |
8 个孤儿索引库(旧 vault 留下的),… |
空文档 |
(空文档) |
并入 · Agent 建议 |
并入(Agent 建议) |
没有对应的旧记录 |
(没有对应的旧记录) |
那不是删废话,是把废话挪出括号再加一个逗号,句子反而更长。全部退回。
真正删掉的
| 原来 | 现在 |
|---|---|
将发送到:一个新会话(不动你正在聊的这个) |
将发送到:一个新会话 |
已经是最新版本了,不用再装。 |
已经是最新版本了。 |
「一个新会话」已经说明了它不动当前会话,后面那半句是重复。
规则
括号照常使用。问题只在括号里塞了解释性、重复性的补充说明 —— 那种内容直接删掉,不要改成
正文里的另一句话。占位、标签、具体值照常留在括号里。
其它
只有文案变化,所以是 patch。装完要重启 DSH。
v0.8.6 — 把界面里那些括注式的自言自语删掉了
Changed — 把界面里那些括注式的自言自语删掉了
0.8.5 的徽标悬停提示写成了这样:
点击更新到 v0.8.5(只向 npm 查一个版本号,不上传任何本机信息)
用户的评价:
不要写这种傻逼的要死的话……一股子AI味,看起来贼蠢
对。括号里那句话不是给读者用的,是这个功能在替自己辩解:没有它,用户照样知道点一下会发生什么;
有了它,读者要先读一段产品宣言才能看到版本号。悬停提示只需要一个动词和一个版本号:
点击更新到 v0.8.5
同一批清掉的还有四处同样的写法:
| 原来 | 现在 |
|---|---|
将发送到:一个新会话(不动你正在聊的这个) |
将发送到:一个新会话,不动你正在聊的这个 |
dsh(未识别 profile) |
dsh · 未识别 profile |
已并存,两条都留下(写进 事实冲突.md) |
已并存,两条都留下,写进 事实冲突.md |
8 个孤儿索引库(旧 vault 留下的),… |
8 个孤儿索引库,是旧 vault 留下的,… |
(空文档) / (Agent 建议) / (没有对应的旧记录) |
空文档 / · Agent 建议 / 没有对应的旧记录 |
规则收进一句话:括注只放读者要用的具体值——版本号、路径、命令、可选方案;原因、免责、
功能动机、安抚话术一律不进括号。 已经写进本机记忆库的「我的偏好摘要」与「工程执行规则」,
后续会话会读到它。
为什么是 patch:改的是文案,行为、接口、记忆格式都没动。
v0.8.5 — 侧边栏「可更新」,点一下安装
这版解决什么
DSH 永远不会提醒第三方插件有新版本:updateAvailable() 读不到 pkg.installTarget 就直接返回
false,而这个字段只给官方按需目录里的包设置。两个第三方插件市场也都还停在 0.7.1。结果就是
0.8.x 对已经装过的人等于不存在——没有提示,也没有去查的理由。
现在面板自己说:
- 侧边栏插件图标旁渲染 「可更新」,点一下直接安装最新版。
- 状态四态:
可更新→更新中→重启生效;失败显示更新失败,悬停给出宿主原话,再点重试。 - 安装期间忽略重复点击(两次 pnpm 跑同一个 lockfile 只会更乱)。
- 点击同时打开面板——失败原因印在里面。一个点了没反应的按钮和一个坏掉的按钮长得一样。
安装不是插件自己干的
POST /api/dsh-unified-agent-memory/update 只调宿主的
pluginManager.installBundle('dsh-unified-agent-memory@版本', { saveExact: true }) —— 插件页和
dsh plugin 用的就是它。它自己会在失败/取消/无 bundle patch 时还原 package.json 与
pnpm-lock.yaml。这里不跑 pnpm、不碰 node_modules。
两个宿主细节:
- HMR 下会先被拒。桌面端
installBundle对「已是 profile 依赖」的包抛
already-installed。官方 UI 的做法是 remove → install,这里照做;宿主没有removeBundle
就如实报失败,绝不在背后覆写文件。 - 没有插件管理器的主机(headless / CLI)答
unsupported,提示手动更新。
隐私
安装这个 POST 的请求体是 {},不发任何本机信息。唯一的外网请求仍是那个早就存在的版本号
GET(每小时一次,registry.npmjs.org 不通才退到 npmmirror)。悬停提示就把这句话写给用户看。
装了之后
装完需要重启 DSH 才生效——运行中的插件不能替换自己。徽标会一直显示「重启生效」直到重启。
本版是 patch:没有对外契约变化(记忆文件格式、CLI 参数、既有 JSON 契约都没动),新路由是新增的。
0.8.4 — 面板不再把别人的队列摆给你看
0.8.4 — 面板不再把别人的队列摆给你看
0.8.3 把两个队列的说法分开了,但那一块本身就该消失。这一版是同一件事的下半截,把它删掉。
删掉的东西
待处理页里那个「待晋升的提交」列表——连同它的两个按钮一起去掉。那是 Agent 提交完、等下一次晋升任务处理的中间态,还没轮到人。每天晋升任务会自己合并重复、把判不了的移上来。不需要在 Agent 干完活之前替它做决定。
页签改名为 需要你裁决,一页只问一个问题:这条记忆到底该算哪一边?
现在的分工
| 队列 | 谁处理 | 在哪看 |
|---|---|---|
情境信息/事实冲突待裁决.md |
你 | 面板「需要你裁决」 |
Agent提交区/*.md |
每日晋升任务 | 健康页的待晋升计数 |
提交队列的积压是健康数字,不是待办事项,所以它只在健康页出现一次。徽标改成数冲突队列——一个会为「没人需要做的事」亮起来的徽标,会教会你看不见它,然后在真正要紧的那天失效。
死代码清理
面板不再调用 /dismiss 和 /accept,客户端里对应的 dismissItem / acceptItem、五个文案函数(previewEmptyText、dismissReasonText、acceptReasonText、acceptOutcomeText、inboxSummary)、三个工具函数(oldestFirst、relativeAge、noteDigest)全部删除,随附 46 个没有主人的测试用例。
两条服务端路由和 memory accept|dismiss CLI 保留不动——面板不调用它们,和下线路由是两件事,后者会动契约,另开一版。
验证
- 闸门全绿:typecheck / drift:check / vitest 830 / core 370 / setup 32 / build / node 21 / drift:python / sync:check
lib/client-ui.js144.1kb → 131.5kb- 产物核对(按
\uXXXX转义查):收录0 处、不采纳0 处、没有需要你裁决的事1 处、裁决队列读不到1 处 - 新增断言变异验证 5/5 CAUGHT
升级
升级后需要重启 DSH 才会看到新面板。
v0.8.3 — 待确认页:把两个队列的名字分开,并说清谁清它们
v0.8.3 — 待确认页:把两个队列的名字分开,并说清谁清它们
用户的原话:「每天 3 点才晋升,为啥现在这一页就有内容?」
机制没错,说法错了。 这一页装的是两个队列:
| 屏幕上的块 | 数据来源 | 什么时候出现 | 等谁 |
|---|---|---|---|
| 需要你裁决 | 情境信息/事实冲突待裁决.md |
晋升时发现同一件事两种说法才写 | 等你 |
| 待确认(旧名) | Agent提交区/*.md |
memory_submit 一写就出现 |
等下一次晋升 |
下面那块是晋升之前的收件箱,跟 3 点无关。但两块共用一页,页签叫「待确认」、
徽标只数下面那块,于是「还没到点处理的提交」看起来像「在等你确认的东西」——
而页面从不交代谁清、什么时候清。
这一版只改说法,不改行为
- 页签
待确认→待处理;块标题待确认→待晋升的提交;徽标 tooltip 同步为N 条待晋升的提交。 - 列表上方补一句说明:这批提交等每天的晋升任务处理,重复的会合并、判不了矛盾的移到上面的「需要你裁决」;现在就点「收录」/「不采纳」等于提前替晋升任务处理掉这一条——按钮是可选项,不是唯一出口。
- 刻意不写死
03:00:晋升时间属于各安装自己的 job 配置,写死对任何换过时间的安装都是错的。 - 空态、积压提示、健康页/统计页行标签、
not-a-submission拒绝说明一并扫干净。
读的还是同一个 preview pending / preview conflicts,按钮、路由、核心语义一字未动。
已知未修
页签徽标仍只数收件箱、不含冲突数——stats.totals 里没有冲突计数,要加需改 core 的 stats.build() 契约。
Full Changelog: v0.8.2...v0.8.3
v0.8.2
Patch release from an external audit. No new surface: six defects closed, all reproducible in an isolated vault, one report refused as not-a-defect.
Fixed
- A failed core rendered as an empty block.
runCore()answers a failure withoutput: ''and the diagnosis inerror;renderText()readoutputalone, so a missing interpreter, an uninitialised vault and a core traceback all reached the agent as a blank text block — the tool looked like it had nothing to say. It now falls back toerror, and still prefersoutputwhen both exist. - Read routes had no
Hostfence.guard()checked only that the caller was loopback; the DNS-rebinding fence lived inguardWrite().curl -H 'Host: evil.com' …/statusreturned 200, and/status,/preview,/doc,/searchhand back local absolute paths and note bodies. The fence moved intoguard();guardWrite()lost its duplicate. - A refused write exited 0.
dismiss/accept/advise/decideprinted the reason to stderr and returned normally, so a script read a refusal as success.notealready exited 1 on the same path.--jsondeliberately stays exit 0 —runCorediscards stdout from a failed subprocess, so the panel needsdata.reasonin a normal envelope. showrefused the spelling the vault writes.上下文索引.mdlinks notes as[[我的偏好摘要]]; the doc had to end in.mdto resolve.note_namedadds the suffix (never strips it), and the refusal reason is computed on the normalised name.用新deleted the old fact instead of retiring it.promotehas always moved a superseded line under## 已取代;decideerased it — so the note claimed the old value had never existed. Both now share onemove_to_superseded.- A ruled submission stayed in the inbox forever.
decidedequeued and wrote, but never archived, sodoctorkept counting it as pending work while the panel said the queue was clear.archive_settled_source()now archives — but only when every fact in that file is already in the adjudication log, because one ruling is not the whole file.
Not a defect
preview --view conflicts reporting unrecognized arguments: --view appears nowhere in the source, docs or history. The view has always been positional (preview conflicts). The report used a syntax this project never shipped.
Full check: typecheck, drift (JS + Python), 874 vitest, 370 core, 32 setup, 21 node — all green. Each fix was mutation-verified: reverting it turns the new test red.