十二Agent共享记忆架构设计
生产级12 Agent舰队如何实现跨运行记忆持久化,四层记忆结构(事件/状态/文档/语义)配合纪律规则,避免Agent间信息丢失。
完整中文译文
我们在生产环境中运行着十二个自主 AI Agent。它们生成销售线索、解析投标文件、监控合规、审查代码,并彼此监督。它们运行在 cron 调度器上,共用同一套代码库——直到我们真正把记忆功能做好之前,每次运行之间它们什么都记不住。
随便问一个 Agent"我们上次在做什么?"得到的只会是一片空白。每次运行都是一个新的上下文窗口、一个新的身份、一次新的失忆。我们的某个 Agent 第一次丢失外部线索(供应商申请、邮件序列、一个待处理的发现)时,我们用惨痛的教训明白了一件事:"我会记住的"根本不是一套记忆架构。
这是我们构建的记忆栈——四层结构,每层有明确的职责,外加一条让整个系统得以运转的纪律规则。它让 Agent 能够在几天后接续一条线索,说出:"这里是我们上次停下的地方,这是之后发生的变化,这是当前的阻碍。"
引发这一切的事件:一个 Agent 曾向外部平台提交了一份集成简报。一周后,团队成员问起进展。Agent 检查了它的上下文——没有。它检查了文件——没有。那次提交发生在已经不复存在的会话中,没有在任何持久化的地方写入状态。
损失不只是丢了一份简报。真正的问题在于虚假信心。Agent 相信那条线索已经处理了。实际上没有。"这种方案在失效之前看起来是有效的——而一旦失效,损害已经造成。"
三样东西缺失了:
- 持久化状态——线索只存在于一个已终结的上下文窗口中
- 召回路径——没有办法搜索过去会话实际做过什么
- 提交纪律——没有规则强制在行动发生的时刻写入状态
每缺失一样,就变成了一层。
第 0 层 - 热记忆:Agent 知道什么
每个 Agent 启动时都会收到一个紧凑、精选的记忆块,注入到其系统提示词中。这是 Agent 的"已知"层:身份、指挥链、环境特性、硬规则、活跃线索。
纪律的核心是精简。热记忆是稀缺资源——每一轮、每一个 token 都在消耗成本。所以它只存放:
- 一个月后仍有意义的事实(偏好、约定、硬规则)
- 当前状态指针(账本在哪里、哪条线索是活跃的)
- 永远不存任务日志、运行结果或已完成工作的记录
# MEMORY (hot tier - injected every turn)
- Current: 4,460 / 10,000 chars - compact facts only
- [COMPANY] websites: [TEST_DOMAIN] = TEST; [PROD_DOMAIN] = MAIN
- State files live in ~/.state/ - one file per external thread
# USER PROFILE
- Communicates in short, direct messages
- Wants decisions -> reasoning -> action steps, not essays
任何更大的内容,或者保存期短于一个月的东西,都会遭到驱逐。去哪了?下一层。
第 1 层 - 状态文件:Agent 做了什么
每个外部线索在磁盘上对应一个状态文件。一线索一文件,顶部有 CURRENT STATUS 行,在任何状态变更行动的同一轮次中更新。
# Thread: [EXTERNAL_THREAD_NAME]
CURRENT STATUS: SUBMITTED - awaiting validation
- [DATE] - Application form completed
- [DATE] - Content pack attached, terms signed
- [DATE] - Pre-requisite course completed
- [DATE] - Marketing blackout active until confirmed
Next action: follow up if no contact within 2 weeks
为什么是文件而不是单纯靠记忆?因为文件可搜索、可对比,且在上下文死亡后依然存活。记忆是 Agent 知道的东西;状态文件是 Agent 做过的事情——持久化、可验证的记录。当一个全新的上下文窗口启动时,它第一件事就是读取状态文件和实时系统,再去断言任何事情。
让这一切运作起来的规则——提交规则:
任何在外部系统中产生状态变更的操作(邮件发送、权限授予、发现提交、凭证签发)必须在同一轮次提交到状态文件。永远不要"稍后记下来"。"稍后"就是线索死去的地方。
这是一条纪律,不是一个功能。它多一次文件写入。它节省的是整场调查。
第 2 层 - 会话搜索:说过什么
状态文件记录行动。但一半重要的事情是对话——决策、推理、被拒绝的备选方案。为此,我们将每个会话索引到一个可搜索的消息存储中。
主力是 SQLite + FTS5。每个 Agent 会话的每条消息都进入一个虚拟表;召回时做全文查询,返回会话、命中的内容以及周围上下文。
-- Every turn lands here, indexed for recall
CREATE VIRTUAL TABLE IF NOT EXISTS messages USING fts5(
session_id,
role, -- 'user' | 'assistant' | 'tool'
content,
tokenize = 'porter'
);
-- Discovery: "which session dealt with X?"
SELECT session_id, snippet(messages, 3, '[', ']') AS hit
FROM messages
WHERE messages MATCH 'memory AND (prune OR recall)'
ORDER BY rank
LIMIT 5;
-- Scroll: once you find the session, read the window around the hit
-- (session_id, anchor_message_id) -> +/-N messages of context
召回模式有三种形状,用对模式很重要:
- Discovery - 在所有会话中搜索主题("哪个会话处理了记忆修剪?")
- Scroll - 找到会话后,在某条消息前后走动,重建目标→行动→解决的全过程
- Browse - 完全不需要查询,只是"我们最近在做什么?"用于状态检查
一次查询就能重建整条线索:目标(最初的消息)、决策(命中点附近)、解决(最后的消息)。我们称之为书挡模式——抓住前三条、命中窗口、后三条,你就以极低的 token 成本拿到了 90% 的上下文。
第 3 层 - 断言前验证:实际做了什么
最后一层是防止自我欺骗的那一层。Agent 会自我报告。自我报告会说谎——不是出于恶意,而是因为上下文过期、部分读取,以及幻觉出的信心。
规则:永远不要在没有证据的情况下相信自我报告。如果 Agent 声称"邮件已发送",邮件必须能在已发文件夹中看到。如果声称"仓库已推送",提交必须存在于远程。如果声称"服务在线",健康检查必须返回 200。
# Anti-pattern: assert from memory
# if agent_says("email sent"): proceed()
# Pattern: verify the artifact, then assert
import requests, subprocess
def verify_email_sent(sender, subject_fragment):
# check the real sent folder, don't trust the claim
out = subprocess.run(
["himalaya", "-a", sender, "envelope", "list"],
capture_output=True, text=True,
).stdout
return subject_fragment in out
def verify_repo_pushed(owner, repo, sha):
r = requests.get(f"https://api.github.com/repos/{owner}/{repo}/commits/{sha}")
return r.status_code == 200
assert verify_email_sent("[EMAIL_ACCOUNT]", "[SUBJECT_FRAGMENT]")
assert verify_repo_pushed("narko4u", "agent-memory-playbook", "HEAD")
这不是偏执——这是"记得住的 Agent"和"知道的 Agent"之间的区别。没有验证的记忆只是多了几步的自信幻觉。
让这一切运转起来的纪律规则
四层结构,一条规则:同一轮次提交、断言前验证,永远不要让一条线索只存在于上下文窗口中。
每个 Agent 运行遵循的实际检查清单:
- 启动时 - 回答任何关于过去的问题之前,先读状态文件 + 热记忆
- 行动时 - 在外部变更的同一轮次写入状态文件
- 召回时 - 搜索会话存储,不要猜测;用书挡模式,不要重放整个线程
- 声称时 - 断言成功之前验证产物(已发文件夹、远程提交、实时 HTTP)
- 记忆时 - 保持热记忆精简;驱逐到状态文件和搜索,永远不要让提示词膨胀
上下文窗口是一次性的;状态文件是永恒的。为上下文死亡做设计——它总会在线程中途发生。
"我会记住的"不是记忆架构。第一次失败代价是一场调查;纪律的代价是一次文件写入。
热记忆是预算,不是日记。系统提示词中的每个字符每一轮都在消耗。无情地精选。
全文搜索优于完美结构。我们先试了嵌套文件夹和分类法。最终 FTS5 全局获胜——搜索可以扩展,结构只会腐朽。
书挡召回优于重放。前三条 + 命中窗口 + 后三条消息以约 10% 的 token 成本重建一条线程。
自我报告需要证据。"验证产物,再断言"把虚假信心变成了可核查的事实。
提交规则是拱顶石。第 0-2 层是架构;第 3 层和那条规则是文化。两者缺一不可。
这个模式可以直接复制到任何 Agent 栈中。最小可用版本:
# 1. The store - SQLite + FTS5
sqlite3 agent-memory.db \
"CREATE VIRTUAL TABLE messages USING fts5(session_id, role, content);"
# 2. The state file - one per external thread
mkdir -p ~/.state
# 3. The discipline - a one-line commit after every external action
# (in whatever language your agent acts in)
echo "- $(date -Iseconds) - action taken, logged same-turn" >> ~/.state/thread.md
就这样。三个活动部件,一条规则。它能从笔记本上的单个 Agent 扩展到运行在 cron 调度器上的十二个 Agent 集群——而这正是我们运行它的环境。