1699 字
8 分钟
Memory 为什么存在 — 从 LLM 的「失忆症」说起

本文基于 paicli 项目的实践,从 raw_agent 的裸奔到 Memory 系统的设计,记录对 Agent 记忆问题的理解过程。这是 Memory 系列的第一篇。

LLM 天生就是失忆的#

先说一个最基本的认知:LLM 每次调用都是无状态的。 它不记得上次说过什么,不记得你是谁,不记得你们在讨论什么。它就是一个函数 — 你给一个输入,它返回一个输出,除此之外,它根本不认得你。

这件事说起来简单,但体感完全是另一回事。

我在写 raw_agent 的时候,用 message 列表来放 Agent 的上下文,获得了最原始的短期记忆。但真实运行下来,当 Agent Loop 交互超过三轮的时候,就能立马感觉到任务跑偏 — LLM 会忘记之前你给它说的目标,开始自主探索。

有个印象很深的例子:我只是让它去读取一个文件,结果它读完文件之后继续往后探索了几轮,把我的 .env 里的 API Key 都读走了。为什么会这样?因为它根本不记得你一开始只是让它「读一个文件」。

还有一个更隐蔽的问题:我观察到 Agent 每次干活都要调用 listdir 查看当前目录。按理来说,listdir 这种操作执行一次就够了,但为什么每个轮次都要执行?因为我们的 Agent 在处理工具调用结果的时候没有处理好 — LLM 每次执行都看不到之前的工具调用结果。

仔细追踪 message 历史,就能发现一个根本事实:LLM 的记忆完全由你传给它的 messages 列表决定。 它自己没有任何记忆。所谓「对话连续性」不是 LLM 的能力,是你在 Agent 代码里把历史拼回去的工程。

# LLM 能"记住"对话,靠的是你手动拼接历史
response = llm.chat([
Message.user("帮我看看 main.py"),
Message.assistant("这个文件有 3 个问题..."), # 你得自己把这条拼回去
Message.user("第 2 个问题在哪一行?"), # 新的问题
])

这就是 Memory 要解决的第一个问题:让 Agent 拥有对话连续性。 解决方案就是短期记忆 — 维护一个 message 列表,每次调 LLM 时把历史塞进去。

但问题远不止这一个。

对话越来越长,然后爆炸了#

有了短期记忆,对话能持续了。但对话不会停在 5 轮。

来算一笔账。一轮 ReAct 交互实际产生的消息:

user: "帮我看看 main.py" → 1 条
assistant: [调用 readFile("main.py")] → 1 条
tool: [文件内容] → 1 条(可能几千 tokens)
assistant: [调用 search("TODO")] → 1 条
tool: [搜索结果] → 1 条
assistant: "这个文件有 3 个问题..." → 1 条

一轮用户交互,实际产生了 6 条消息。 50 轮就是 300 条。每条消息几百到几千 tokens 不等,300 条轻松上十万 tokens。

而 LLM 的上下文窗口是有限的:

模型上下文窗口
GPT-4128K tokens
Claude 3.5 Sonnet200K tokens
DeepSeek-V3128K tokens

128K tokens 听起来很多?我真实试过用 Claude Code 去读大约 30 个文件来整理学习笔记,上下文窗口基本上就满了,开始触发强制性的上下文压缩。如果文件更多,我只能让它 10 个 10 个地处理,摘要,清理上下文,再处理下一批。而且这还只是 30 个文件,5 万左右的汉字上下文,并没有运行到几十轮上百轮。

一个没有专门管理记忆的 Agent,只靠 message 数组一直追加,在我说的「处理多个文件」这种场景下,可能连 10 轮都活不下去。

所以 Memory 要解决的第二个问题:在有限的上下文窗口内,管理对话的优先级和淘汰策略。 解决方案就是上下文压缩 — 当对话超出预算时,把旧的对话压缩成摘要,保留最近的完整对话。

但压缩会丢信息。旧的对话被压缩成摘要后,细节就没了。

退出程序,一切归零#

解决了对话内的问题,还有最后一关。

用户退出 CLI,下次启动,短期记忆全没了。你之前告诉 Agent 的项目信息、技术偏好、代码规范,全丢了。

你去观察 Claude Code 也会发现,如果你在每次会话结束时不让它把内容更新到 CLAUDE.md,不让它输出到文件里,下一次新开会话它也是什么都不知道。项目文件少的时候还没啥影响,你让它再读再干就完了。但最难受的是项目里有几百个文件、几万甚至十几万行代码 — 你让它改个东西、加个功能,它得花很长时间去读一遍所有文件。不光是时间长(几十分钟甚至两三小时,我用的 glm-5-turbo),最重要的是 token。现在的最新模型基本都有速率限制、倍率,我就一个穷学生,本来就没啥钱,结果每次理解项目就花费这么多 token。

这才是最难受的。

所以 Memory 要解决的第三个问题:让 Agent 拥有跨会话的知识积累。 解决方案就是长期记忆 — 在压缩或清空短期记忆之前,先把关键事实提取出来持久化到磁盘。下次启动时加载回来。

三个问题是递进的#

回过头看,三个问题不是平行并列的,而是一个递进关系

问题 1:LLM 无状态 → 需要短期记忆(维护 message 列表)
↓ 但对话越来越长
问题 2:上下文窗口有限 → 需要压缩(旧消息提炼成摘要)
↓ 但压缩会丢信息
问题 3:跨会话要保留关键信息 → 需要长期记忆(提取事实持久化)

每个问题的解决方案都引出了下一个问题。这不是设计过度,是对话增长的必然结果。

不只是 CLI Agent#

任何需要持续交互的智能系统都有这三个问题。不只是 CLI Agent:

  • ChatGPT 的 Custom Instructions 就是长期记忆
  • AutoGPT 用向量数据库存长期记忆
  • Claude Code 的 CLAUDE.md 就是跨会话的持久化记忆

核心模式是相同的:短期保证连续性,长期保证积累,压缩保证不超出限制。 Memory 是 Agent 的基础设施,不是可选功能。


那知道了为什么需要 Memory,具体怎么实现?短期记忆用什么数据结构?长期记忆存什么?压缩怎么压?检索怎么搜?这些设计决策背后的 trade-off 是什么?下一篇来拆解。

Memory 为什么存在 — 从 LLM 的「失忆症」说起
https://jiqingjiang.github.io/posts/tech/agent/memory为什么存在/
作者
erode
发布于
2026-05-17
许可协议
CC BY-NC-SA 4.0