我把 Crush 当作日常编码助手用了一阵子了,通过 Lemonade Server 接本地模型,我是真心喜欢。我已经适应了代理式编码流程,而我关于“把代码交给本地 LLM 靠不靠谱”的心得,正好说明了我怎么分配哪个模型干什么活。但有一件事一直在默默折磨我:每次会话都从零开始。周二刚踩的坑?周三就忘光了。每次开新会话,前十分钟我都在跟那个帮我搭项目的工具重新解释我自己的项目。我不喜欢这样。
于是我花了一个周末给 Crush 加上持久记忆,一开始想法很清爽:本地模型不会自己使用记忆工具,所以你把明确的指令写进上下文文件,就完事了。结果这个想法一到我的 30B 模型面前就翻车了。我反而学到了更扎心、也多少让人有点不安的东西:本地模型会把你的规则逐字复述给你,然后完全无视它们。要让记忆真正管用,得换一套完全不同的打法——而这一路上的翻车比成功更有启发。
两个记忆服务器胜过一个
数据库存事实,一堆 Markdown 存知识
架构分两层,故意让它们性格不一样,两者都通过 MCP 与 Crush 通信。MCP Memory Service 是情景层:带 sqlite-vec 扩展的 SQLite、本地 ONNX 嵌入、语义搜索,还有语义去重这种贴心功能——我的模型想重复存同一条事实,它直接给拒了。Basic Memory 是策展层:每条笔记都是文件夹里的纯 Markdown 文件,我可以像对待其他文件一样 grep、编辑、rsync 到 NAS。事实进数据库,知识进笔记,而我这颗囤数据的心,终于有了一个真正读得懂的记忆库。
两个服务器还没派上用场,底层管道先给了个下马威。Crush 运行在我 Windows 机器上的 WSL2 Ubuntu 里,而 Lemonade 从 Windows 侧提供模型,默认情况下这座桥压根不存在。我第一次用 curl 调 API 的时候,直接卡死不动了,因为 WSL2 默认的 NAT 模式给 Linux 分了个自己的小网络,在那里 localhost 根本指不到东西。
显而易见的修复方法是错误的:将 Lemonade 绑定到 0.0.0.0 会将推理 API 以及其内部控制端点暴露给整个局域网,就为了省一个配置步骤,把这么多信任交出去,实在不划算。正确的修复方法是在 .wslconfig 中加两行——networkingMode=mirrored——之后 localhost 双向可用,Lemonade 永远不会离开回环地址,整个安全问题就直接不存在了。有个经验教训要提醒一下:该更改只有在完全执行 wsl --shutdown 后才会生效,而 Docker Desktop 会偷偷把虚拟机占着不关,让你以为改了没用。
安装环节是这个周末又添新伤的地方。基础 pip 包安装顺利、跑起来也没问题,却会静默降级为哈希伪嵌入——毫无语义价值的相似度表演——除非你安装 [sqlite-ml] 附加包。更糟的是,HTTP 健康端点全程报告“健康”;只有更严格的 CLI 状态检查才承认嵌入是假的。要信就信最严格的检查,因为那些看着友好的检查会骗你。
然后是上下文伏击。Crush 的起始消息——系统提示、51 个工具模式、上下文文件——总计约 23,600 个 token,而 llama.cpp 的默认上下文是 8,192。你以为是一块 RTX 5090 跑得慢,实际上是连续四次一模一样的拒绝;根本不是慢的问题,是全部在报错。把 ctx_size 设置持久化之后,同一块显卡的提示处理速度超过每秒 4,200 个 token,生成速度为 223。Agent 场景的工作负载会打破那些为聊天场景调好的假设,而默认值恰恰就是按聊天场景调的。
我的模型引用了规则,然后无视了它们
背诵不等于遵守,而我证明了这一点
这是所有记忆指南都略过不提的部分。一切配置好、验证完毕之后,我把一份记忆协议写进了 CRUSH.md。其中包括强制性的会话启动搜索、立即保存规则等等;然后让模型把它复述一遍。它一字不差地把这条指令背了出来,连全大写都没落下。接着我打开一个新会话,说我已经准备好开始工作,然后就看着它列目录、读代码库、甩给我一个任务菜单,一次记忆调用都没做。指令就在上下文中。它们只是没拿到话语权;真正说了算的是 Crush 自带的探索项目系统提示词,我的协议就是个附录,在跟正文抢戏。
我加码了两次——先把规则前置,然后把它改写成响应格式契约——结果两次都败下阵来。而且越试越离谱。有一次,当被要求回忆时,模型读取了项目文件,并把它们作为“相关记忆”呈现出来,这是在来源上睁眼说瞎话。
另一次,它把记忆搜索委托给了 Crush 的任务子代理,而该子代理根本没有 MCP 访问权限——于是,一个没有记忆工具的代理给我写了一篇四段式的哲学文章,谈论记忆的概念,然后说任务搞定了。我最喜欢的失败是最小的那个:让它用项目目录名去搜,它搜的是“项目目录名”这几个字。指令是当文字来执行的,不是当意思来理解的。
最终奏效的并非更优美的文字——而是更管用的通道。触发词在上下文文件里定义,但由我在用户发言中调用:“recall”执行搜索,“note this”保存事实,“document this:”写入精心整理的笔记。用户发言指令优先于环境上下文,合规率一夜之间从几乎从不执行变成了基本每次都执行。
当然,敲六个字母的仪式感,确实不如一个能自动记忆的智能体那么神奇。但它也确实有效,而我后来测到的不对称现象确实有意思:模型在没人提示的情况下主动保存的频率出奇地高——它自己主动存了一条带标签的测试结果——但它从来没有在没人提示的情况下回忆过。保存是它自己愿意的;回忆必须得有人叫它。
护栏比指令更重要
五十一条规则,意味着有很多种发挥创意的路子
两个记忆服务器意味着我的 Qwen3-Coder 现在每个会话要面对 51 个 MCP 工具,其中包括一排真正的危险货架:delete_note、delete_project、memory_cleanup,以及一整套我压根没要求过的 schema 接口。协议用一份禁止触碰清单把这些隔开了,但那个周末让我彻底明白了,一道用文字写成的围栏到底值几个钱。
所以真正的执行保障在于 Crush 的权限系统:六个被开了绿灯的工具——存储、搜索以及四个笔记操作——被列入白名单,无需提示即可运行,而其他所有操作都会卡在人工审批弹窗那一步。上下文文件说的是别做;配置文件说的是没我点头做不了。常见情况用文字说明,破坏性操作则设硬性关卡。
另一道保险靠的是习惯,不是配置:核实记忆操作是否真的发生了。真正的回忆在 Crush 的记录里能看到工具调用的块;而虚构的回忆则只会显示声称成功的文字。在被骗过一次之后,我现在会瞥一眼那些工具调用块,就像你瞥一眼文件复制进度条一样,而且我会告诉任何构建这类系统的人也要这样做——因为最要命的失败模式不是智能体遗忘了,而是它假装记得。
一个会记笔记的健忘 agent,胜过一个聪明绝顶却失忆的 agent
两天下来,回报正如标题所承诺的那样:Crush 会直接开一个会话,用的还是我当初通过 MCP 把 Claude 接到 Proxmox 集群时那套工具配置,把上次踩过的坑翻出来,并从我们中断的地方继续。无需重新解释,无需重新发现已解决的问题,也没有 API 开销——整个技术栈都是本地的,记忆存储用 rsync 备份,不到一秒就搞定。它并非完美无缺。回忆质量取决于保存时的措辞好坏;偶尔某条记忆读起来像是心不在焉的金鱼写的,而触发仪式意味着这种魔力是靠一套固定流程撑起来的,不是自然而然冒出来的。但每次都能生效的"有套路的魔法",胜过从来不生效的"自然魔法",而且差距不是一点半点。
热门跟贴