Repository navigation
Releases: dpkg-s/ai-memory-template
Release list
v3.3.0 — 记忆条目自带权级,闸门落在服务端
本版把「哪条记忆、谁能读」从提示词层面的约定,变成服务端的硬闸门。
新增
- 每条记忆可以声明权级 —— 写入时可选
security(内容多敏感)与clearance(谁能读)。不写就落在默认档(internal/member),旧条目行为完全不变。 - 远程端点按身份分档 ——
/mcp为主人档,/mcp/member与/mcp/guest为受限档。受限档上越权读取返回的结果与「该条不存在」逐字节一致,不会通过错误信息泄露存在性。 - 带过滤条件的检索更快 ——
memory_search/memory_list现在先在索引里筛候选、再读正文。实测scope=project的查询只读 46.7% 的条目,mem_type=experience只读 19.0%。 - 安装器底栏「接入 AI 工具」 —— 已装过的机器可从这一步直接跳到接入,不必重走选目录流程。
升级方式
- 安装器用户:下载附件
ai-memory-setup.exe运行,记忆库与已连接的 AI 工具配置不会被覆盖。 - 源码用户:更新后重启 MCP 服务。
需要注意
- 建议删掉一次索引文件(
memory_index_*.db)让它重建 —— 旧索引缺六个检索列,不重建也能用,只是没有提速。 - 若你在用受限端点(
/mcp/member等),升级后写工具在这些端点上直接不可用,这是设计意图。
v3.1.2 — 安装器可直接从 Release 下载(本版无功能变更)
本版没有功能变更 —— MCP 服务端与安装脚本的行为和 v3.1.0 完全一致。唯一的差别是分发方式:安装器现在随 Release 一起提供。
新增
- Windows 安装器可以直接下载了 —— 此前
ai-memory-setup.exe只放在仓库里,Release 页面看不到,换台机器或帮别人装都得先自己从源码打一份。
升级方式
已安装的用户不需要动任何东西。要重装或换机器,直接下载附件运行:
ai-memory-setup.exe
它会引导完成「安装 → 选记忆库 → 连接 AI 工具」,已有的记忆库与客户端配置不会被覆盖。
不受影响
server.py、setup_vault.py、setup_link.py、ai_memory_setup.py一行未改- 已连接的 AI 客户端配置不变
- 记忆库目录、
rules/、.ai-memory-install.json不变
破坏性变更
无。
校验
| 附件 | 用途 |
|---|---|
ai-memory-setup.exe |
Windows 快捷安装器(图形界面) |
a52ca45373f8bfe21bfdbebc9b21afe91f40d7fe3f9c14f4961e8d2a7a3e3b1f ai-memory-setup.exe
Windows 上可以用 Get-FileHash ai-memory-setup.exe -Algorithm SHA256 比对。本版尚未提供 .sha256 文件,下一版起自动附带。
v3.1.1 — 本版无功能变更(Docker 部署入口迁至 ai-memory-docker)
本版没有功能变更 —— server.py、安装器、各 setup_*.py 的行为与 v3.1.0 完全相同,安装器也没有重新打包。
只有用 Docker 部署的人需要看一下
控制台镜像的构建定义原先放在 Web 控制台仓里,现在统一收进专门的容器仓 ai-memory-docker。此前照着控制台仓文档 docker build 的,改成:
git clone --recurse-submodules https://github.com/dpkg-s/ai-memory-docker.git
cd ai-memory-docker
docker compose up -d --build
镜像内容没有变化,已经跑着的那套不用重新构建也能继续用。
不需要动的
- 用安装器装的:无需重装
- 用
python server.py或 MCP 客户端的:命令、路径、行为都不变 - 记忆库目录与已有配置:不受影响
破坏性变更
无。
校验
| 附件 | 用途 |
|---|---|
ai-memory-setup.exe |
Windows 快捷安装器(图形界面) |
与 v3.1.0、v3.1.2 是同一个文件:
a52ca45373f8bfe21bfdbebc9b21afe91f40d7fe3f9c14f4961e8d2a7a3e3b1f ai-memory-setup.exe
v3.1.0 — 安装器升级为记忆库管家:能选记忆库、能给 AI 工具接上
安装器不再只是「把程序拷到某处」—— 它现在管两件以前最容易出错的事:接错库和接不上。
新增
vault子命令 —— 选对记忆库:vault list扫描候选并打分排序(有记忆索引.md、条目多、是 git 仓、同目录有server.py的分数更高),vault use <路径>指定、vault path查看、vault init <空目录>建新库。- 它解决的是一个静默翻车点:程序拿不到
AI_MEMORY_DIR时会回落到默认路径,那里如果是空的,AI 会安然连上一个空库,界面一切正常,没人会发现。
- 它解决的是一个静默翻车点:程序拿不到
link子命令 —— 把 AI 工具接上:覆盖 21 个客户端,逐个判定「已连接 / 待创建 / 未连接 / 需确认 / 需修正(配置漂移)/ 需手工 / 未安装」。- 只增删
ai-memory这一个条目,其它配置原样保留;写前备份、原子替换、写后回读,解析失败即回滚。 - 各客户端用各自的 Python 环境是正常状态,不算「配置漂移」—— 只有参数与记忆库路径不一致才算。
- 只增删
- 交互菜单:无参数启动进入仪表盘(程序 / 版本 / 记忆库 / 已配客户端数),分为「安装 · 记忆库 · AI 连接 · 自检」四个区。
- 安装器图形版随 Release 提供 —— 装完直接能选版本、选记忆库、连工具,不用记子命令。
升级方式
覆盖安装即可。已有的记忆库、AI 客户端配置都不会被覆盖;装完可以用菜单里的「自检」看当前状态。
破坏性变更
无。MCP 工具面与记忆库格式不变。
校验
| 附件 | 用途 |
|---|---|
ai-memory-setup.exe |
Windows 快捷安装器(图形界面) |
a52ca45373f8bfe21bfdbebc9b21afe91f40d7fe3f9c14f4961e8d2a7a3e3b1f ai-memory-setup.exe
v3.0.0 — 阈值配置纳入 MCP,可远程访问与容器部署,附带 Web 控制台
这是 v3 的第一个版本。工具面只增不改:新增第 25 个工具 memory_config,其余 24 个的行为与 2.5.0 完全一致;不碰新工具的话,python server.py 的 stdio 用法与 2.5.0 无差别。
新增
memory_config(第 25 个 MCP 工具)—— 运行时阈值的读写通道:get给出当前生效值、每个键的来源(环境变量 / 配置文件 / 默认值)与中文说明;set整状态写入,必须提交全部 8 个键(少交的键会被静默写回默认值,所以直接拒绝),写前备份、原子替换,只改memory.toml,绝不碰任何.md。- 可选远程访问 —— 同一个
server.py、同一份记忆库,既能本机 stdio 给单个 AI 用,也能由serve_http.py起一个 HTTP 端点给局域网多台机器共用。- 监听非回环地址时必须显式写
AI_MEMORY_HTTP_ALLOWED_HOSTS,否则拒绝启动 —— 这是防 DNS rebinding 的必要摩擦,不提供关掉的开关。
- 监听非回环地址时必须显式写
- 可选容器部署 —— 镜像里只有 Python +
mcp+ 程序文件,记忆库从卷挂进来,升级只换镜像 tag、数据不动;非 root 运行。 - 可选 Web 控制台 —— 独立分发,本版配套 1.1.0:浏览器里看库、改阈值、检查更新。
升级方式
装过安装器的,覆盖安装即可;源码使用的,更新后 pip install -r requirements.txt。
源码使用的旧方式有个变化:Web 控制台已独立分发,源码目录里不再包含 web/、serve_web.py、web_console.py。要控制台请用安装器,或从控制台的下载页获取。
破坏性变更
- 源码目录结构变化:上述控制台文件不再在源码根目录(没有删除,移到了独立分发的控制台里)。2.x 时从源码起控制台的,需要换用新入口。
- MCP 工具面与记忆库格式不变,已连接的 AI 客户端无需改配置。
v2.5.0 — mcp 依赖下限 1.27 → 1.30
本版要点
运行时依赖下限由 mcp>=1.27 提升至 mcp>=1.30,上界 mcp<2 保持不变。
上游 1.x 线已进入维护模式(此后仅收安全修复),1.30.0 是该线的最后一版 —— 停在 1.27.2 等于停在维护线的更早位置。本版只做这一处实质改动,并同步散落在安装脚本与文档里的版本引用。
升级前请注意
- 若你的环境锁定了
mcp<1.30,安装会失败。先执行pip install -U "mcp>=1.30,<2"再重跑安装脚本。 - 上界仍是
<2:2.x 目前不可用。迁移成本不在那两行 import,而在InitializationState已被移除 ——memory_restart依赖它做会话恢复补丁,在 2.x 上会失效。
实测依据(隔离 venv,非推断)
mcp.server.fastmcp、mcp.server.session.InitializationState、mcp.types.ToolAnnotations在 1.30.0 上全部在位;FastMCP.__init__参数 27 → 30,新增session_idle_timeout/max_sessions(对将来的 HTTP 远程化是直接的 DoS 面收敛);- 全量回归 25 套 / 1000 断言全绿、退出码全 0(含会话恢复补丁的 26 项验证)。
文档一致性修正
- README 测试计数统一为 1000(原正文 964 / 徽章 998,三处互不一致);
- README 徽章版本号由滞后的
2.4.1更新为2.5.0; - 测试套件名
隐藏索引与迁移正名为索引命名与迁移(v2.4.2 已把索引改回可见名)。
升级方式
重新运行 install.sh / install.ps1,或手动升级依赖:
pip install -U "mcp>=1.30,<2"Full Changelog: v2.4.2...v2.5.0
v2.4.2 — 索引恢复可见名 + 条目带摘要 + 多行标量转义修复
[2.4.2] - 2026-10-12
本版修掉一个**「工具全绿、人却点不开」的断链**,并把索引从「标题清单」升级为「带摘要的 MOC」。
记忆索引.md 在 2.4.0 前后被改成了隐藏名 .记忆索引.md。问题在于 Obsidian 是按文件名(stem)解析双链的 —— 隐藏名让全库 25 篇引用 [[记忆索引]] 的笔记在 Obsidian 里全部点不开;而服务端 memory_rebuild_links 的死链检查恰好把 title 也收进了「已知条目」集合,于是它从不认为这些是死链:工具报「0 死链」,人在 Obsidian 里却一个都点不开。这是「判据选错了载体」的典型 —— 只有把判据落到 stem 上才会暴露。本版把索引恢复为可见名,索引从此可点击、也能被 vault 的全文检索命中。
另外,索引此前只渲染 [[stem|title]],读到 270+ 个标题仍不知道哪一篇是自己要找的。本版在每条索引行尾追加 frontmatter 的 summary(上限 60 字、空白压平),一次读入即可初筛。顺带修掉一个会静默丢内容的 YAML 转义缺陷。
Changed
- 索引文件名由
.记忆索引.md回到记忆索引.md(撤销此前的隐藏化)。迁移是反向且一次性的:_drop_legacy_index()现在删的是隐藏名残留,且调用点被提到枚举条目之前 —— 否则首轮重建会把即将被删的.记忆索引.md也渲染成一行索引,凭空造出一条指向不存在文件的死链。 - 索引条目行追加
summary:索引由「标题清单」升级为「带摘要的 MOC」。summary同时纳入内容签名,否则「只改摘要」不会触发重建。 memory_search的recent-first从名义变成事实:此前实现是sorted(..., reverse=True),实际按文件名倒序,与文档口径不符;现改为按(updated, 文件名)倒序。
Fixed
- frontmatter 多行值被静默截断:
yaml_quote_scalar的「需要加引号」判据不含换行 ⇒ 含\n的值会按裸多行标量写出,从第二行起被解析器当成「不含:的行」直接跳过。实测:显式传入两行summary,读回只剩第一行,且全程无任何报错。现判据纳入控制空白,转义侧补写\n/\r/\t(解析端本就支持这些转义)⇒ 读-改-写逐字节稳定。
Tests
- 新增
tests/test_index_name.py(47 断言):可见名常量面、写入后根目录恰一份且隐藏名消失、索引仍被枚举与检索、迁移反向 + 人工同名文件双条件保护(title 与 tags 同时不符即保留,绝不误删数据),以及可点击性判据 —— 断言直接落在 stem 上(any(p.stem == target for p in _iter_md_files())),而不是落在服务端那套「已知条目」口径上。 - 删除
tests/test_hidden_index.py(语义整体作废);test_frontmatter_norm.py41 → 49 断言(新增多行标量转义往返);索引文件名相关断言随可见名更新。 - 全量回归:25 套 / PASS=1000 / FAIL=0(2.4.1 基线实测 976)。
- 证伪:用
git archive HEAD(2.4.1)快照 + 只替换测试文件 ⇒test_index_name.py47 项中 23 项确定性变红、test_frontmatter_norm.py6 项变红,证明新断言确实咬住了改动、不是恒绿装饰。
升级说明
无需手工干预:启动后首次写入即会自动把隐藏名生成物迁移为可见名,并把全库摘要补进索引。若你手工放过一份同名笔记(title 不是 记忆索引、或未带 moc / index / navigation 标签),迁移会跳过它 —— 那是数据,不是生成物。
Full Changelog: v2.4.1...v2.4.2
v2.4.1 — 自动归类命中既有目录 + 索引不丢条目 + Windows 抢锁不逃逸
[2.4.1] - 2026-10-12
本版是 2.4.0 目录化的一次校正:写入端自动归类当时只认 11 个写死的语义桶,遇到 vault 里既有的分类目录(故障/ 核验/ 经验/ 调研/ 排坑/ …)一律兜底成「其他」,等于把标题前缀语义本已清晰的笔记扫进垃圾桶;索引渲染又只遍历固定桶序,前缀命中的「目录名桶」不在序里,会静默丢条目(索引少链接却不报错)。两处均已修正,且旧调用形态的行为与改动前逐字一致。另顺带修掉一个 Windows 专属的并发崩溃:抢锁撞上 ERROR_ACCESS_DENIED 时异常会逃逸出 acquire_lock,把「没抢到锁」变成调用方崩溃。
Fixed
acquire_lock在 Windows 下抛PermissionError逃逸(并发写随机崩):open(path, "x")落到O_CREAT|O_EXCL—— POSIX 下目标已存在返回EEXIST(FileExistsError),而 Windows 下目标已存在且被其他进程持有时返回ERROR_ACCESS_DENIED(5),Python 抛的是PermissionError。代码只except FileExistsError,于是该异常直接逃逸出acquire_lock:调用方(memory_write等)拿到的是异常而非「抢锁失败」。同一陷阱也存在于回收侧 ——unlink删除「正被其他进程打开」的文件同样抛PermissionError,旧代码在except (JSONDecodeError, OSError)分支里再删一次,第二次照样逃逸。现改为:创建时一并捕获PermissionError;删除统一走新增的_unlink_quiet()(失败静默、下轮重试)。实测:10 进程并发写,脱链环境下 8 轮里 2 轮随机报.memory.lock的PermissionError(WinError 5/Errno 13),修复后同一循环不再复现。⚠️ 该缺陷只在 Windows 触发 —— POSIX 下open("x")恒为EEXIST、unlink不留占用,所以 ubuntu CI 上从未暴露。- 自动归类漏判「既有分类目录」:新增
_dir_prefix_hit()—— 标题第一个_之前那段若恰好是 vault 根下既有的一级目录,就直接以该目录为落点(故障_H29K_dnsmasq崩溃循环→故障/),不再兜底成「其他」。实施时实测:根目录 22 篇里 8 篇被兜底成「其他」,判准率仅 11/22。该判定刻意排在 tags 判定之前 —— tags 可能由_auto_suggest_tags()自动补出,可靠性低于写入方手写的标题前缀。四档策略都受此影响(warn下体现为建议落点变化,auto下才是真实落点变化);不搬迁任何存量条目,存量归位仍走一次性手工搬迁。 - 核心页被前缀规则误送进同名目录:全库核心页文件名用
_连接(Codex_身份档案),而CORE_PAGES里写的是空格形态(Codex 身份档案)⇒ 不归一化就漏判,Codex_身份档案会被送进Codex/。新增_core_variant()做_/空格等价归一化,核心页(含两种变体)一律留根。 - 索引渲染静默丢条目:渲染循环此前只遍历写死的 11 个桶序,而前缀命中会产生「目录名桶」(
故障/核验/ …),它们不在序里 ⇒ 这些桶下的条目不会出现在.记忆索引.md中,且不报任何错。改为由_bucket_order()生成渲染序:规范桶(仅有内容的)按固定序在前,其余桶按字典序附后。
Changed
_entry_bucket()与_suggest_dir()增加可选dirs参数:省略时内部取一次_known_dirs();传入None的旧调用形态整体跳过前缀判定,行为与改动前逐字一致。- 三处批量调用点(索引重建 /
memory_audit/memory_index_draft)改为在循环外取一次目录集复用 ——_known_dirs()要扫一遍 vault 根目录,逐篇调用等于把 250 次目录枚举压进一次索引重建。
Tests
tests/test_dir_policy.py:断言 51 → 62(+11),覆盖前缀命中落点、核心页_/空格变体豁免、dirs=None旧形态等价性、渲染序含「目录名桶」四项。tests/test_recovery.py:新增 R3f(36 断言,原 35)—— 打桩open让锁文件的所有访问都抛PermissionError,断言acquire_lock不逃逸、按抢锁失败返回False。该断言对旧实现确定性变红(实测输出acquired=RAISED:PermissionError),故是真判据而非恒绿装饰。
Full Changelog: v2.4.0...v2.4.1
v2.4.0 — 目录化 + 稳定标识与索引增量化 + 可脚本化与可配置
本版三条主线:目录化(记忆库不再只有扁平一层 —— 可建子目录、写入端还会按语义自动归位)、稳定标识与索引增量化(改名不再静默失联,索引不再每次全量重解析)、可脚本化与可配置(只读工具可出 JSON,8 个运行时阈值外置到
memory.toml并热重载)。
从 2.3.0 到 2.4.0:工具 23 → 24、测试 17 套 / 594 断言 → 25 套 / 964 断言、运行时依赖零新增。
🆕 新增能力
1. 多级目录:可建子目录,写入端还会自动归位
此前 21 处扫描都是非递归 glob("*.md") —— 把笔记放进子目录就等于脱离检索,这也是 2.2.0 明确否决目录分层的原因。本版把枚举层统一收口后正式支持目录:
-
_iter_md_files()成为唯一的.md枚举入口(os.walk+ 保留目录剪枝 + 按「深度 → relpath」确定性排序),21 处glob全部替换,只此一处可改枚举行为 -
RESERVED_DIRS(8 项:.archive/.trash/.git/.obsidian/ai-memory/__pycache__/.ruff_cache/.pytest_cache)内的.md不会被当记忆收录 -
memory_write("项目/某条目", ...)直接写入分类子目录;不含/的标题行为与旧版逐字节一致 -
写入端自动归类:新建且标题不含
/时,按 11 个语义桶映射到规范目录。四档策略AI_MEMORY_DIR_POLICY:warn(默认) —— 只给建议、不改落点auto—— 自动归类落盘strict—— 无目录可归时拒绝写入off—— 关闭
默认取
warn是刻意的:auto作默认时 18 套测试有 11 套失败,且两套启发式叠加会让落点对写入方不可预测。 -
三条边界:只作用于新建(更新绝不搬运,存量根目录条目不受影响);落盘目录不写进 frontmatter 的
title(title 恒为裸叶子名);标题已含/时完全尊重写入方 -
非法路径(保留目录、
./..段、盘符、绝对路径、层级 > 3)显式拒绝并说明原因,不做静默消毒
2. permalink 稳定标识 —— 改名不再静默失联
条目此前唯一身份是 title / 文件名 stem,文件一改名(手工重命名、搬迁、第三方同步器回灌),所有指向它的引用就静默失联(表现为「查不到」而非报错)。
permalink在首次写入时冻结为当时的文件名 stem,此后永不覆盖- 查找链升级为
permalink → title → stem三级 - 指向旧标识的双链不再被误报为死链
- 存量笔记带前缀 title 被再次写入时自动回正,无需迁移脚本
3. memory_schema_diff —— 结构对账(第 24 个工具)
只读工具,把「写入端承诺写出什么」与「磁盘上实际有什么」对账。契约来源就是代码(yaml_io.build_frontmatter 是唯一写出源),不另立 schema 文件(两份真相必然分叉)。
键分三层:core(title/tags/created/updated/tier/access_count,缺 = 真实缺陷)/ v2(type/confidence/verified/scope/status/schema_version,v1 缺属预期、声明 v2 却缺 = 写入端 bug)/ opt(按需写出)。
输出六节:核心键缺失 / v2 缺键 / v1 存量迁移清单 / 未知键(附行号)/ 可选键使用率 / 无 frontmatter 文件(单列一类且不计入缺陷数 —— README 这类普通 Markdown 本就不受结构契约约束)。
4. memory.toml —— 运行时阈值外置 + 热重载
把原先散落在各模块的 8 个阈值收口到 vault 根的可选 memory.toml:
[thresholds]
dup_title_jaccard = 0.6
dup_body_jaccard = 0.5
body_truncate_chars = 8000
lock_timeout = 15
heat_cold_max = 0.1
heat_demote_hot_max = 0.5
heat_hot_min = 3.0
heat_decay_lambda = 0.05- 优先级:环境变量
AI_MEMORY_CFG_<KEY>>[thresholds]区 > 顶层同名键 > 内置默认 - 默认值与改动前的硬编码值逐项相同 ⇒ 不建文件行为零变化
- 改动后 2 秒内热重载,无需重启客户端
- 配置写错时报错并给出行号与原文,不静默取默认值(静默退回会让「我明明改了却不生效」极难排查)
memory_stats新增「运行时阈值配置」小节:显示配置文件路径、8 个生效值(覆写项标← 已覆写)与最近一次解析错误
为什么不用
tomllib:它是 3.11+ 才进标准库,而项目仍需支持 Python 3.10。改为自写最小 TOML 子集解析器(与既有手写 YAML 解析同一条路线),对数组 / 内联表 / 多行字符串等不支持语法一律显式报错。
5. 四个只读工具支持 output_format="json"
memory_read / memory_list / memory_recent / memory_orphans 可传 output_format="json",脚本化调用不必再用正则扒字段。
- 范围即契约:报告型工具(
stats/doctor/audit/schema_diff/context/graph)刻意不给 —— 它们的产出是「分节标题 + 计数说明 + 提示语」的叙事,整体转 JSON 等于另立一套真相;写工具的返回值是操作回执,结构化无意义 - 非法取值报错而不静默回落
text:脚本化调用最怕「以为拿到 JSON、实际拿到一句中文」 - JSON 的
body不含中文截断说明(改由truncated/total_chars表达),脚本无需再正则剥中文;截断说明仍逐字节保留在文本形态里
6. 模块分层守卫(开发向)
原计划用 import-linter,但它要求声明 contracts 与打包元数据,而这个项目刻意零配置、零构建步骤。改为手写 AST 守卫(纯标准库):
叶子模块(locks / memory_config / memory_index / metrics / text_utils / yaml_io)不得 import server(含函数体内延迟 import 与 importlib 动态拉起);本地模块之间无环;叶子间依赖只能在显式白名单内且白名单不得腐烂;新增模块必须登记进清单。
⚠️ 行为变更
- 自动生成的 MOC 索引改名为隐藏文件
.记忆索引.md:生成物混在用户笔记里(既污染memory_list、又会被当成「一篇记忆」参与引用与检索)是长期噪声源。自带一次性保守迁移 —— 仅当旧文件的title恰为记忆索引且 tags 命中moc/index/navigation才判定为「确认的生成物」并删除,疑似人工文件一律跳过并告警。 - 索引增量化:
_iter_entries()改为两层缓存(整库指纹回放 + 逐文件按(size, mtime_ns)只重解析变化者),_refresh_index()增加内容签名短路。判据是「改 1 个文件只新增 1 次解析」(旧实现为 +全库)。⚠️ 刻意不做「桶级增量渲染」 —— 瓶颈在解析不在渲染。 memory_orphans的反链判定改用标识映射:正文里的[[X]]有三种合法写法(title/ 文件名 stem /permalink),原实现直接拿 target 当 key,导致「title ≠ stem」的条目全被算成孤立;现统一经映射解析。另:生成页的链接只是全库罗列而非真实引用,把它算作引用方会让孤立数恒为 0、工具直接失去意义,现予排除。- 安装脚本原先用 shell glob 复制模板文件,点文件会被漏掉 ⇒ 改为包含点文件的方式(
.记忆索引.md因此能正确安装)。
🐛 修复
- 条目缓存按
title为键导致同名文件互相污染元数据:缓存键改为 relpath,并拆出条目级缓存与(size, mtime_ns)指纹表。此前同名文件会让后读到的元数据覆盖前一个,且只有重启进程才能净化。 cleanup_residue.py的「活索引可 open」与「真实库对账」两项判据从未生效(随本版同批修正):脚本引用了sqlite3却没有 import ⇒ 两项最关键的验收判据恒报失败,属永远红的假判据 —— 比「永远绿的假判据」更隐蔽,因为它看起来像环境问题而被习惯性忽略。
📦 升级说明
- 向后兼容:不建
memory.toml、不改环境变量时,行为与 2.3.0 逐项一致(8 个阈值默认值未变、AI_MEMORY_DIR_POLICY默认warn不改落点)。 - 自动迁移:新增
permalink列与索引表结构变更由_migrate()自动完成(ALTER TABLE,幂等,二次连接不重复执行);存量笔记无需手工处理。 - 无需数据迁移脚本;存量笔记不改名、不搬迁。
- 依赖不变:
requirements.txt仍为mcp>=1.27,<2。
🧪 工程化
- 测试资产:17 套 / 594 断言 → 25 套 / 964 断言
- 本批新增:
test_folders.py(43) /test_title_path.py(42) /test_dir_policy.py(71) /test_index_cache.py(17) /test_permalink.py(25) /test_schema_diff.py(32) /test_memory_config.py(79) /test_hidden_index.py(33) /test_output_json.py(76) /test_import_layers.py(35) - 每项新增判据都做了可证伪验证(把产品改回旧行为,断言必须变红):B5 8/8 根针、B6 7/7、C1 7/7、A2 8/8。其中三次失败的证伪实验反而推动了设计修正 ——
- 依赖时钟/调度的判据不算判据;
- 同一语义若存在「索引快路径 + 全库兜底」两条实现,反证必须两条一起禁,否则快路径会把禁用效果整体掩盖、产出假绿;
test_*是独立脚本(退出码判据),用-m pytest会No module named pytest且 rc=1 无输出,极易误读成「断言红了」。
Full Changelog: v2.3.0...v2.4.0
v2.3.0 — memory_context 上下文组装 + memory_restart 原地热重启
Added
- memory_context —— 上下文组装(第 23 个工具):memory_graph 只列「出链 + 反链」,跨会话延续仍得把沿途正文一条条读出来(实测 3~5 次调用)。本工具把沿途内容直接组装成一段可喂给模型的 Markdown,一次调用拿到起点 + 周边。沿 [[wikilink]] 出链与反链 BFS 至 depth(0 = 只要起点),周边按 tier → 读取次数 → 更新时间 排序取前 max_related 条,每块标注来源与关系;timeframe 支持自然语言(last week / 2 days ago / 3d),只过滤周边、起点恒在。
- 三个设计要点都是实测踩出来的:max_chars 是整段预算(起点至多一半、其余按篇数均分),否则长起点会独吞预算、周边一篇都展不开;自动生成的 记忆索引.md 排除出周边(它正文就是全库标题列表);截断回退到行边界,不切开关系标注行。
- memory_restart —— 原地热重启(第 22 个工具):改完 server.py 或任一模块后让新代码立刻生效,不必重连客户端、不必重启应用。os.execv 替换进程映像并保留 stdin/stdout,会话不断。
- 三道安全闸:① 换进程前先在子进程预检新代码可导入、会话恢复补丁装得上,失败即放弃 ② 本次响应先经管道发出 ③ 新进程凭环境变量把既有会话视为已初始化,否则会以「初始化未完成」拒掉请求。
- 热度驱动的 tier 自动升降级(memory_heat_suggest(apply=False)):此前只输出建议字符串,改完还得手抄一遍 tool call。现支持 apply=True 真正写盘,同时保持默认只预览。
- 回收站闭环(.trash/):memory_delete 软删进的条目,现在可 memory_list(include_trash=True) 列出、memory_restore(title, source="trash") 取回、memory_delete(title, empty_trash=True) 清空。
- 冲突 / 重复主动防护(memory_write):写入后自动做疑似重复/冲突检测,命中时在返回值末尾追加非阻断提示(字符 bigram Jaccard 相似度,零依赖)。只提示、不改数据。
Changed - memory_search 长句检索策略:从纯子串匹配改为「精确零命中时才降级到 bigram 兜底」,长句召回实测 0.00 → 1.00;兜底结果显式标注「以下为 bigram 模糊匹配结果」,不伪装成精确命中。
- memory_heat_suggest 由「只预览」变为「可预览可执行」,预览与执行共用同一判据函数,不会漂移。
- memory_rebuild_links 改为只读校验:不再改写文件,只报告死链与计数。
- 跨进程缓存失效键升级:由「目录 mtime」改为「目录 mtime + 逐文件指纹」,Obsidian 保存、其它客户端写入、git 改文件都会令缓存失效(147 篇规模额外开销 1~3 ms)。
Fixed - 双 frontmatter 与摘要污染:调用方把带 frontmatter 的整篇当 content 传入时,与写入端自动生成的 frontmatter 拼接产出「双 frontmatter」,第二块退化成正文本垃圾并固化进 summary。全库体检命中 8 篇;现于写入入口与收口各剥一次,已写坏的条目下次写入即自愈。
- 冲突防护漏掉核心页:「新建『近期工作动态记录』而非更新原页」这类最典型的重复写入此前静默通过,现已纳入比对。
- 索引自动生成死链:索引与草稿直接输出 - [[{title}]],标题含空格即全成渲染死链且每次写入后重建会覆盖人工修复(148 篇库复现 36 条)。现 target 恒用文件名 stem、可读标题放别名位。
- 跨进程缓存失效在 Windows 上不生效:改写文件内容不更新目录 mtime,外部编辑永远触发不了缓存重建;改为文件级指纹后修复。
Removed - memory_index_draft 中从未被使用的错误别名计算(方向反了且未被使用),随索引死链修复一并删除。
Dependencies - 无变化(mcp>=1.27,<2;开发依赖仍为 pytest>=8.0 + ruff>=0.5)。
测试:15 套 / 506 断言,全绿(新增 test_context.py 59 断言、test_restart.py 26 断言等)。
完整变更:v2.2.0...v2.3.0