请AI折腾了一个小东西,叫Hashes Memory。是给AI工作流加的一套长期记忆。主要接入Codex,也供Qoder使用。
原因很简单。我和AI聊了那么多,让它看过论文、读过代码、分析过项目,也纠正过它不少次。按理说,它应该越来越了解我的工作:什么已经做完了,什么方案失败过,我习惯怎么写东西,哪些科研参数不能随便动。
于是我问了一句:
每次开一个新对话,你到底知不知道我们之前做过什么?
如果每次都要重新讲背景、重新读材料、重新解释同一个决定,那这个“助手”的交接工作实在不够格啊。
最可恨的是,这些“重新来一次”浪费的token都是真金白银啊。
读过,不等于以后还记得
这次把系统翻出来检查,发现需要先分清几件事。
同一个对话里,AI 可以利用已经进入上下文的内容。对话很长时,早期内容可能被压缩成摘要,一些细节也会丢失。
新开一个对话,情况就不同了。以前的完整对话不会因为属于同一个项目,就全部自动放到新对话里。能接上的,取决于当前提供的项目文件、规则、可用的历史摘要,以及实际检索到的记忆。
“我让它读过这篇论文”和“它以后还能找到当时的分析”,中间还差一个保存与检索的过程。更不能因为它读过材料,就认为这些内容已经训练进模型。
这也是 Hashes Memory 要补的地方。
它是我让Codex协助搭建的一套本地工具。不是某个模型自带功能的名字,也不是一种让模型永久记住一切的特殊开关。
它把值得留下的东西保存成文件,再提供一个让AI查找这些文件的入口。需要工作时,先翻一下笔记。
笔记本里放什么
目前用的是 Markdown、SQLite 和一个本地 MCP 服务。
Markdown 保存真正的记忆内容,既能直接打开,也能放进 Obsidian 阅读。SQLite 负责检索,是从 Markdown 生成的索引;索引丢了可以重建。MCP 则提供给 Codex、Qoder 一个统一的调用入口,例如查询项目简报、搜索历史决定、写入候选记忆。
大致的目录是这样:
Mem/
├── 00-Inbox 等待审核的候选
├── 10-Profile 个人偏好
├── 20-Projects 项目记忆
├── 30-Knowledge 方法、文献与参考知识
├── 40-Workflows 可复用工作流
└── 90-System 协议、模板与验证记录
保存的内容,我更关心下面三类。
第一类是习惯。例如中文优先,先读现有文件,再做最小修改;科研解释不能把推测写成结论。这些东西不应该每个项目都重新交代。
第二类是项目交接。现在做到哪一步,本轮完成了什么,验证过什么,还有什么未知,下一步先看哪个文件。这一层最直接决定了新对话能不能接上工作。
第三类是科研知识。某篇材料与我的问题有什么关系,两个方法为什么选了其中一个,一次实验的结论适用于什么条件,以及某个失败为什么暂时不值得重试。
这里尤其需要保留来源。一个“这个方案不行”的结论,过几个月看起来可能毫无用处。它是在什么数据、参数、环境下不行?失败发生在哪一步?如果环境变了,是否值得再试?
这些限定条件,往往比那一句结论更值钱。
当然,现在这三类内容的积累程度还不一样。个人习惯和项目入口已经有了一部分,很多论文理解和研究结论还没有系统整理进去。目录建好了,只能说明有地方放,不能说明里面已经装满知识。
真正的问题,是笔记有没有继续更新
这次检查出了一个挺典型的问题。
今天查看时,Hashes Memory最新的记录还停在7月15日。工具能连接,查询也能返回结果,但后面的工作没有持续进入这套记忆库。
拿项目SWED来说,项目文档已经记录了路线B在8月14日冻结,进入十年月级诊断扩展的阶段;旧记忆还停留在更早的收拢工作。
如果新任务只读旧记忆,它可能回答得很熟练,却是在熟练地介绍过去。
于是这次先拿SWED做试点,补了一份新的交接,另外整理了方法边界、CPU 优化与GPU 失败条件、冻结规则三条知识候选。
交接里也保留了不确定的地方。例如两个项目文档对十年运行范围的表述没有完全同步,服务器任务当前是否已经启动,本轮没有检查。这些都要写出来,不能替它们编一个整齐的答案。
现在的做法是:实质工作结束时,让 AI 留一份短交接,记录“摘要、已完成、验证、未完成、下一步、来源”。新任务可以先读取最新交接,但它仍被标为未审核工作记录。
科学结论和长期偏好则单独进入候选区,经过确认后才成为正式记忆。新的结论替代旧结论时,旧记录也保留下来,能看清前后变化。
这样,交接可以及时,结论又不至于越记越玄乎。
会不会为了省 token,反而读更多 token?
这是我紧接着担心的另一件事。
如果每次开始工作,都把所有项目、所有偏好、所有历史记录读一遍,那记忆库越大,开场越贵。整理了半天,最后给自己装了个自动复读机。
所以启动时只给一份短简报:个人规则、当前项目交接,以及预算内的少量正式记忆。遇到具体问题,再搜索相关条目;摘要不够用,才打开完整内容和来源。
在记忆库里执行 SQLite 查询,本身不需要模型 token。返回给模型的文字会占用上下文,后续读原文也会产生消耗。“存在本地”并不意味着交给在线 Codex 的内容没有 token 成本,也不意味着这些片段始终只留在电脑上。
这次 SWED 试点给启动简报设了约 1500 个估算 token 的预算。实测一份简报返回 1806 个字符,按当前估算方法约为 1374 token。
这里得强调一下:这是估算器的结果,不是模型账单上的精确 token 数。Codex 自己的系统提示、工具说明、项目规则和后续文件读取,也不包含在这个数字里。
同一个对话已经读过简报后,还可以只检查版本有没有变化。内容没变,就复用上下文里那一份。这次测试中,确认未变化只返回了 182 个字符。
我希望知识库可以慢慢变大,但每次开始工作的那份交接仍然很短。
日常最直接的用法,就是让 Codex:
先读取 SWED 的项目简报,说明已经完成什么、还有哪些未知,再继续当前任务。
也可以在 Mem 目录里直接检查:
.venv/bin/junmem brief --project swed
.venv/bin/junmem search "冻结" --project swed
关键是“没有找到记录”不能直接解释为“以前没有做过”。确实需要补查时,再去读项目文件或定位旧对话。
目前跨独立 MCP 进程恢复交接已经测试通过,相关代码测试和历史检索回归也通过了。但这仍然不能保证每一个真实任务都会老老实实写交接:客户端没有加载新规则、工具调用失败,或者任务被强制中断,都可能漏写。
这一点还得在实际使用中继续观察。
我对Hashes Memory的期待倒没有多复杂。下次打开一个项目,希望AI能先说清楚上次留下了什么,而不是兴冲冲地把第一步重新做一遍。
毕竟,我自己已经够容易忘事了。
Comments