为什么最先消失的是最旧的行
除非你主动要求,否则 OpenHands 中的持久化记忆是关闭的。官方文档明确指出:该功能是“选择性加入且默认关闭的”,如果不开启,“智能体将保留现有的基于 AGENTS.md 的引导,且提示词保持不变”。你可以通过在智能体的 AgentContext 上将 load_memory 设置为 true 来启用它。
一旦启用,会有两个层级。用户层级位于 ~/.openhands/memory/,保存“适用于所有项目的知识和偏好”;项目层级位于 <workspace>/.openhands/memory/,保存“特定于当前仓库的知识”。每个层级都包含一个 MEMORY.md——被描述为“持久事实的精选索引。这是唯一注入到提示词中的文件”——以及带日期的每日日志,这些日志“绝不会自动注入;当 MEMORY.md 指向它们时,智能体会根据需要使用其文件工具读取它们”。
这种划分是重要的架构,也是淘汰(eviction)机制的来源:
"大小预算:合并后的索引上限约为 6,000 个字符。当超出预算时,将从每个超出预算的层级顶部丢弃整行(最旧的内容)——部分行绝不会保留,层级标题始终保留,并且在任何丢失行的层级标题下都会出现截断通知。请保持索引的精选状态。"
仔细阅读这段话,因为其中包含了四个不同的行为。
上限是针对两个层级合并后的索引,因此你的个人偏好和项目事实会竞争同一个预算。一个冗长的用户层级文件会蚕食项目层级所能承载的空间。
行是从顶部丢弃的,文档将顶部解释为“最旧的内容”。由于智能体会在学习过程中不断追加内容,文件的顶部就是它记录的最早的内容。大多数上限机制会淘汰最新的内容或导致写入失败。而这一个会最先移除你的基础内容。
丢弃是以行为粒度且干净利落的——“部分行绝不会保留”——所以你永远不会得到一个改变了意思的半句话。很好的设计,这意味着单行长内容要么全部保留,要么全部丢弃。
而且确实有信号:“在任何丢失行的层级标题下都会出现截断通知。”只要你留意,一切都不是悄无声息的。问题是,在正常的开发会话中,没有人会去查看注入的系统提示词块。
对于运行多个工具的人来说,值得注意的一点是:6,000 个字符与 Devin Desktop 为其全局规则文件记录的预算相同。在 6,000 个字符左右,是几个厂商独立决定一个始终加载的文件不再值得其成本的临界点,无论你使用哪种工具,这都是一个值得内化的有用数字。
人们尝试的其他替代方案
提高预算。 第一直觉,但文档并没有将其作为一个可调节的旋钮——上限被描述为该功能的一个属性。即使它是可调的,出于 how much memory you should give an AI agent 中阐述的原因,一个更大的始终加载的块是一个更糟糕的权衡,而不是更好的选择。
每隔几周手动重写 MEMORY.md。 这行得通,也是“保持索引精选”所要求的。但这也是一项没有触发机制的苦差事,所以它通常只会发生一两次,然后就停止了,文件又会重新膨胀。
将所有内容移入 AGENTS.md 以防被淘汰。 这很诱人,但它误读了文档中设定的分工。智能体被指示“保留 AGENTS.md 用于针对在仓库中工作的任何智能体的指令——记忆则是为了记录智能体自己学到的东西”。将学到的历史记录倾倒进指令文件中,会给你带来一个换了名字的、始终加载的长文件,而且完全没有淘汰通知。
将重要事实放入技能(skill)中,以便按需加载。 一个合理的直觉,但碰到了错误的机制,原因在 why agent skills aren't memory 中有解释。技能回答的是“我如何完成这项任务”。而被拒绝的架构方案并不是一项任务。
提交 .openhands/memory/ 并将其视为团队文档。 文档明确允许这样做——“项目团队甚至可以提交 .openhands/memory/ 来共享智能体学到的知识”——这确实很有用。但它使淘汰问题变得更糟而不是更好,因为现在几个人的智能体都在针对同一个 6,000 字符的上限向共享索引追加内容。
还有一个更深层的原因导致这些方法都不太奏效,那就是人们忽略了这句话:
"设计上不可信:注入的块被包裹在<UNTRUSTED_CONTENT>中。记忆文件通常是由智能体编写的,但任何有权访问工作区或仓库的人都可以编辑或提交它们(克隆的仓库可能会附带.openhands/memory/MEMORY.md),因此智能体被告知它们可能包含提示词注入,并将其视为未经验证的提示,绝不能作为权威指令。"
智能体被指示将其自身的记忆视为未经验证的提示。这是正确的安全姿态——克隆的仓库确实可以附带记忆文件——这也为你解决了一个设计问题。任何必须被遵守的内容都不能存在于记忆中,因为记忆明确是不具权威性的。记忆是用于智能体可能觉得有用的上下文。指令属于 AGENTS.md,在其中它们针对任何智能体并作为指令被读取。
因此,这两个记录在案的属性结合成了一条规则:记忆索引是一个小型的、非权威的、顶部易失的指针文件。如果将其视为更多,它会在以下两个方面之一让你失望。
解决方案:保持索引为指针,而非存储
三个步骤。前两步只需十分钟;第三步是防止问题再次发生的方法。
步骤 1:将 MEMORY.md 转换为指针索引
文档已经告诉你了预期的形状——MEMORY.md 是“持久事实的精选索引”,智能体被告知“将长细节放入每日日志中”,并且当“MEMORY.md 指向它们时,智能体使用其文件工具按需读取日志”。
因此,索引中的每一行都应该很短,并且应该指向某个地方。每个事实占一行,其表述方式应让智能体既知道什么是真实的,又知道细节在哪里。散文段落、代码示例和长篇解释应移至带日期的日志文件中,在这些文件中,它们仅在需要时才被读取,并且不占用预算成本。
这样做之后,6,000 字符的上限就不再是限制了。一百个单行指针可以轻松容纳;而十几个段落则不行。
步骤 2:重新排序索引,使顶部可丢弃
因为淘汰会从顶部移除行,而顶部是最旧的内容,所以文件的按时间顺序排列直接对你不利。通过将顺序改为按语义排列来解决这个问题。
将你不想丢失的事实放在每个层级索引的底部——那些关于架构、约束和长期决策的事实。将临时的操作笔记放在顶部。现在,当超出预算时,被丢弃的行就是你本来就会修剪的那些行。
然后在不同层级之间重新平衡。因为上限是合并计算的,一个充斥着个人偏好的臃肿的 ~/.openhands/memory/MEMORY.md 会直接占用项目事实的空间。保持用户层级仅用于真正的跨项目偏好,并让项目层级拥有足够的空间。
在修改时,检查每个层级标题下是否有截断通知。如果存在,说明你已经丢失了行,而每日日志就是你找回它们内容的地方——这也是先执行步骤 1 的一个很好的理由。
步骤 3:分离目前共享同一个文件的三种内容
该索引现在保存着三种需要不同归宿的内容。
指令——必须遵守的事情——根据文档自身的分工,属于 AGENTS.md。它们不是记忆,且记忆不具权威性。
智能体学到的操作细节——环境奇特问题、不稳定的测试、实际起作用的命令——恰好属于它所在的地方:索引中的一个指针行,以及每日日志中的细节。这就是该功能的用途。
项目决策及其原因——为什么拒绝了队列库、合规性约束实际要求什么、三月份尝试了什么但没有成功——这些都不属于上述两者。它们不是指令,所以放 AGENTS.md 是错误的。它们不能是易失的或非权威的,所以放记忆索引也是错误的。而且它们需要能够被你团队使用的每个工具回答,而不仅仅是开启了 load_memory 的那一个。
在 MemoryLake 中进行设置
MemoryLake 是这第三类内容的归宿。它在任何单一智能体之外保存决策及其原因,没有始终加载的预算竞争,并通过 MCP 或 API 回答有关它们的问题。你的 MEMORY.md 保持为一个简短的指针索引,OpenHands 继续完全按照文档进行维护;持久的推理则存在于字符上限无法触及的地方。
步骤 1:创建 API 密钥
生成密钥并在大约三十秒内发出你的第一次请求。在执行上述步骤 2 之前完成此操作,这样在重新排序索引时,你就有地方可以移动每个决策。

步骤 2:上传你的第一批记忆
通读当前的索引和它指向的每日日志。每一个属于决策而非观察的条目,都要写下选择了什么、拒绝了什么以及原因。支持文档和文件也放在同一个地方。

步骤 3:连接你的 AI 和智能体
让 OpenHands、Claude、Codex 和你的其他智能体通过 MCP 或 API 进行访问。当其中任何一个需要知道项目为什么是这样时,答案会附带其推理一起送达,而不是在系统提示词中竞争空间。

这在实践中改变了什么
第一个改变是淘汰变得不再重要。一个将可丢弃内容排在顶部的指针索引只会丢失你本来就会修剪的内容,而且每个指针背后的细节仍然在智能体可以打开的日志文件中。
第二个改变是记忆功能能够很好地履行其本职工作。记录集成测试需要特定的环境变量正是智能体维护的存储应该做的事情,当它不被要求同时作为团队的决策记录时,它能把这件事做好。
第三个改变是“不可信内容”的框架不再是问题。一旦记忆中没有承重的内容,智能体将其视为未经验证的提示就完全是正确的——而确实需要具有权威性的内容则在 AGENTS.md 中,在其中它们作为指令被读取。
第四个改变是在团队规模下提交 .openhands/memory/ 变得安全。在 6,000 字符的预算下,多个智能体追加指针行是可持续的,而多个智能体追加段落则不可持续。当两个条目确实发生冲突时,你可以在某个地方解决它,而不是让字母顺序或时间顺序的偶然性来决定——memory conflict detection 就是为了捕获这个问题而存在的。
OpenHands 持久化记忆的最佳实践
一行、一事实、一指针。 索引在文档中被定义为精选索引。任何长于一行的内容都属于每日日志,并由索引指向它。
按重要性排序,而非按时间。 淘汰会从顶部移除行。将你可以承受丢失的内容放在那里。
在两个层级之间平衡预算。 上限是合并计算的,因此冗长的用户层级会默默地缩小你的项目层级空间。
检查截断通知。 它出现在任何丢失行的层级标题下方。这是你得到的唯一信号,如果你去留意,它就是可靠的。
绝不要将凭据放入记忆中。 智能体自身的指令说明绝不要记录机密或凭据。不要通过自己添加它们来破坏这一点,并定期进行审计——auditing what your AI remembers 涵盖了这一习惯。
跳过任何显而易见、容易重新发现的内容。 维护指令也是这么说的:目录列表和显而易见的命令会消耗预算,且无法带来任何信息。
完全将指令排除在记忆之外。 记忆在文档中被定义为未经验证的提示。如果必须遵守,它应该放在 AGENTS.md 中。
记住记忆是重新读取的,而不是存储在会话中。 文档指出,解析后的文本不包含在对话持久化和 API 负载中,并在每个会话中从磁盘重新读取,因此手动编辑文件将在下一次对话中生效。
结论
OpenHands 准确地记录了其持久化记忆:选择性加入且默认关闭、两个层级、仅注入 MEMORY.md、合并上限约为 6,000 个字符、从超出预算的层级顶部丢弃整行并在标题下显示截断通知,以及整个块被包裹在不可信内容标记中,智能体被告知将其视为未经验证的提示,而不是权威指令。每一个都是合理的决定。它们共同描述了一个小型的、易失的、建议性的指针文件——这是一个真正有用的东西,而不是保存项目推理的地方。
保持索引为单行指针,对其进行排序以使顶部是你能够承受丢失的内容,并分离出目前共享它的三种内容。指令放入 AGENTS.md。观察保留在记忆及其日志中,这正是该功能的设计初衷。决策及其原因则放入没有字符预算且没有过期限制的地方,因为这些是你在一年后仍然需要的内容——而且,正如 keeping less in agent memory 所论证的,一旦其余内容有了归宿,一个更小的始终加载的文件在各个维度上都更好。