生产级12 Agent舰队如何实现跨运行记忆持久化,四层记忆结构(事件/状态/文档/语义)配合纪律规则,避免Agent间信息丢失。
我们在生产环境中运行着十二个自主 AI Agent。它们生成销售线索、解析投标文件、监控合规、审查代码,并彼此监督。它们运行在 cron 调度器上,共用同一套代码库——直到我们真正把记忆功能做好之前,每次运行之间它们什么都记不住。
随便问一个 Agent"我们上次在做什么?"得到的只会是一片空白。每次运行都是一个新的上下文窗口、一个新的身份、一次新的失忆。我们的某个 Agent 第一次丢失外部线索(供应商申请、邮件序列、一个待处理的发现)时,我们用惨痛的教训明白了一件事:"我会记住的"根本不是一套记忆架构。
这是我们构建的记忆栈——四层结构,每层有明确的职责,外加一条让整个系统得以运转的纪律规则。它让 Agent 能够在几天后接续一条线索,说出:"这里是我们上次停下的地方,这是之后发生的变化,这是当前的阻碍。"
引发这一切的事件:一个 Agent 曾向外部平台提交了一份集成简报。一周后,团队成员问起进展。Agent 检查了它的上下文——没有。它检查了文件——没有。那次提交发生在已经不复存在的会话中,没有在任何持久化的地方写入状态。
损失不只是丢了一份简报。真正的问题在于虚假信心。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
任何更大的内容,或者保存期短于一个月的东西,都会遭到驱逐。去哪了?下一层。
每个外部线索在磁盘上对应一个状态文件。一线索一文件,顶部有 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 做过的事情——持久化、可验证的记录。当一个全新的上下文窗口启动时,它第一件事就是读取状态文件和实时系统,再去断言任何事情。
让这一切运作起来的规则——提交规则:
任何在外部系统中产生状态变更的操作(邮件发送、权限授予、发现提交、凭证签发)必须在同一轮次提交到状态文件。永远不要"稍后记下来"。"稍后"就是线索死去的地方。
这是一条纪律,不是一个功能。它多一次文件写入。它节省的是整场调查。
状态文件记录行动。但一半重要的事情是对话——决策、推理、被拒绝的备选方案。为此,我们将每个会话索引到一个可搜索的消息存储中。
主力是 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
召回模式有三种形状,用对模式很重要:
一次查询就能重建整条线索:目标(最初的消息)、决策(命中点附近)、解决(最后的消息)。我们称之为书挡模式——抓住前三条、命中窗口、后三条,你就以极低的 token 成本拿到了 90% 的上下文。
最后一层是防止自我欺骗的那一层。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 运行遵循的实际检查清单:
上下文窗口是一次性的;状态文件是永恒的。为上下文死亡做设计——它总会在线程中途发生。
"我会记住的"不是记忆架构。第一次失败代价是一场调查;纪律的代价是一次文件写入。
热记忆是预算,不是日记。系统提示词中的每个字符每一轮都在消耗。无情地精选。
全文搜索优于完美结构。我们先试了嵌套文件夹和分类法。最终 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 集群——而这正是我们运行它的环境。