Agent 会忘事。
用户昨天告诉它"我们的 Grafana 在 monitor.internal:3000",今天再问,它一脸茫然。这不是 LLM 的智力问题——是架构问题。LLM 本身是无状态的,每次对话都是一张白纸。把跨会话的知识保留下来,就是记忆系统的职责。
先看整体演进路径:

从单文件到分片索引,再到主动蒸馏和熔断保护——每一步被真实问题驱动。
—
为什么不用向量数据库
稍微了解 RAG 的人,第一反应是向量库。我们没选这条路,原因很直接:
团队规模决定了技术选型。 TeamAgent 服务的是几十人的内部团队,不是 C 端产品。向量库意味着引入独立的存储服务(Chroma、Milvus),运维复杂度直接上一个台阶。
Go 生态的向量搜索不成熟。 Python 有 LangChain、LlamaIndex 的完整工具链,但 TeamAgent 是纯 Go。Go 里的向量搜索要么依赖 CGO(hnswlib 绑定),要么是还在早期的纯 Go 实现。引入这类依赖会让部署变得脆弱。
YAGNI。 一个活跃用户一年积累的上下文,用纯文本存也就几十 KB。用向量库是拿大炮打蚊子。
Markdown 文件零依赖。os.ReadFile / os.WriteFile,没有服务依赖,git 版本管理,文本编辑器就能排查问题。
最终结论:纯 Markdown 文件 + 精心设计的注入策略,这个规模下够用,而且更可靠。
—
V1:单文件 MEMORY.md
最初的实现非常简单——一个 MEMORY.md,每次会话全量注入 system prompt:
workspace/memory/MEMORY.md用户说"记住",Agent 调用工具追加内容。缺点逐渐暴露:
记忆越多,token 浪费越大。 用户 A 只关心 Grafana 和部署流程,但每次对话都要把用户 B 的监控配置一起塞进去。MEMORY.md 膨胀到 2000 字后,每次对话的起步成本就很高。
没有结构。 所有信息混在一个文件里,更新一条记忆需要 LLM 理解整个文件再重写,容易引入错误。
没有历史。 记忆被覆盖就永久消失,无法追溯"这个结论什么时候写进来的"。
—
V2:INDEX.md + 分片
反思问题的根源:一个大文件干了两件事——存储所有记忆,同时每次全量注入。解法:分离索引和内容。
workspace/memory/├── INDEX.md ← 每次都注入,每条 ≤150 字符├── HISTORY.md ← 纯追加日志,永不覆盖└── shards/├── general.md├── grafana.md├── deployment.md└── *.md
INDEX.md 是关键。它只存每个分片的一行摘要,格式固定:
- [grafana](shards/grafana.md) — Grafana 在 monitor.internal:3000- [deployment](shards/deployment.md) — 服务部署用 kubectl
每条摘要硬限 150 字符,INDEX.md 极小(通常不超 20 行),无脑注入每次对话。
按需加载。 LLM 看到索引后,判断哪个分片相关,主动调用 memory 工具读取完整内容。大多数对话只消耗 INDEX.md 的 token,只在需要时才拉完整分片。
save_memory 工具
LLM 自己判断什么值得记。工具定义三个动作:save、read、list。
content 是全量替换,不是追加。LLM 每次保存要提供分片的完整最新版本。这看似低效,但避免了增量叠加导致的内容矛盾——旧的错误信息不会"幽灵化"地留在分片里。
shard 由 LLM 自由命名。它理解谈话内容,能判断"这件事属于 Grafana 配置还是部署流程"。与其在代码里硬编码分类规则,不如把这个判断交给最擅长它的那一层。
—
整合时机:被动 + 主动
被动触发:token 达到预算 80%。 消息历史的估算 token 超过预算的 80% 时触发整合。问题来了:飞书场景里用户跟 Agent 一次对话往往只有几问几答,消息体量小,根本到不了阈值。等用户下次来聊,上一轮的有用信息早就丢了。
主动蒸馏:EvolutionRunner 每日 pipeline。 每晚午夜执行一次。扫描所有 session,找出自上次整合后新增消息超过 10 条的 session,把工具执行证据提取出来蒸馏成结构化记忆写入分片。
Agent 睡觉的时候,它在消化白天学到的东西。第二天用户来聊,Infra 的信息已经被梳理好了。
—
"No Execution, No Memory"
这是整个记忆系统最重要的原则。
LLM 会撒谎。 不是故意的,但它会自信地描述它"做了"但其实没做的事。如果把 LLM 的规划意图写进记忆("我打算用 kubectl 部署这个服务"),下次对话时 Agent 会把规划当事实,越走越偏。
规则:只有工具执行成功并返回了结果,才把结论写入记忆。 整合代码只收集 role=tool 的成功响应作为证据。LLM 的 planning、intent、对话中的猜测——统统不记录。
这条规则防止记忆系统被 LLM 的幻觉污染。一旦污染,后续所有基于这段记忆的推理都可能出错,而且很难排查。
—
降级保护
整合随时可能失败——网络抖动、LLM 超时、API 限流。失败了不能直接丢,否则那段信息永久丢失。
第一层:追加到 HISTORY.md。 整合失败时,把原始消息追加到 HISTORY.md。这个文件是 append-only log,从不修改已有内容。即使失败无数次,原始记录永远在那里。
第二层:连续 3 次失败后熔断。 表明整合服务本身出了问题,继续尝试只会浪费资源。走降级路径,等下一轮 pipeline 重试。
HISTORY.md 有三重角色:整合操作留痕、整合失败兜底、蒸馏原材料。它从不注入 system prompt,也从不被直接查询。它是原始日志,不是记忆。
—
回顾整个演进路径:从问题出发,不从技术出发。单文件够用就先用,出现 token 浪费时才拆分片,被动整合覆盖不到短对话时才加主动蒸馏。每一步都被真实问题驱动。最关键的决策是分离索引和内容——和数据库索引是一回事,读路径必须快,写路径可以慢一点。
可靠性靠降级,不靠保证。整合可以失败,失败了有兜底,连续失败了有熔断。系统对失败是弹性的,而不是试图消灭失败。
夜雨聆风