如何为AI代理添加记忆:生产环境中的状态持久化模式
无状态代理——那种Worker退出时忘记一切的代理——适合一次性任务。一旦代理需要记住昨天发生的事情、识别回头客或基于之前的输出继续工作,就需要记忆。有三种模式:工作记忆(运行中的上下文,在KV中存活于一次运行期间)、情节记忆(发生了什么以及何时发生,一个可查询的日志)和语义记忆(你知道什么,通过向量搜索或结构化数据检索)。将正确的模式与正确的工作配对。
每周三。28,400+ 读者。纯干货。
✓ 请查收邮箱 — 点击确认链接以完成订阅。
✓ 订阅成功!
✓ 您已在订阅列表中。
目录
2026年6月更新。
TL;DR: 无状态代理——那种Worker退出时忘记一切的代理——适合一次性任务。一旦代理需要记住昨天发生的事情、识别回头客或基于之前的输出继续工作,就需要记忆。有三种模式:工作记忆(运行中的上下文,在KV中存活于一次运行期间)、情节记忆(发生了什么以及何时发生,一个可查询的日志)和语义记忆(你知道什么,通过向量搜索或结构化数据检索)。将正确的模式与正确的工作配对。
[运营者的观点] 我不止一次撞上无状态的墙。社交回复代理不断向它已经交谈过20次的客户自我介绍。每日简报代理连续四天标记同一个问题,因为它不记得昨天已经标记过了。添加正确类型的记忆解决了这两个问题。这就是我使用的方法。
为什么无状态代理持续失败
无状态代理每次运行只从你明确传递的内容开始:系统提示、用户消息以及在调用时获取的新数据。它不了解之前的运行、之前的用户或之前的决策。
对于一次性分类任务——读取评论、返回类别——无状态是正确的。它快速、便宜且可预测。
一旦你需要连续性,故障面就会出现:
- 面向客户的代理不认识客户的历史
- 内容代理推荐了它上周已经推荐过的文章
- 审核代理不断重新升级一个已解决的案例
- 每日简报无限期显示同一个过时警报
所有这些都是同一问题的症状:代理无法跨运行传递上下文。
三种记忆类型
我在生产中发现有用的框架:
- 工作记忆 — 代理在单次运行期间_当前_知道的内容。在调用生命周期内保存在KV或内存中。
- 情节记忆 — 发生了什么以及何时发生。代理在每次运行开始时读取的结构化日志,以便定位自己。
- 语义记忆 — 它对世界、客户或知识库的了解。在相关时通过结构化查询或向量搜索检索。
你不总是需要全部三种。我运行的大多数代理需要工作记忆+情节记忆。语义记忆最难构建,只有当知识库太大无法放入上下文窗口时才值得投入。
工作记忆:运行中的上下文
工作记忆是在一次代理运行期间存在的状态。最简单的形式是函数作用域中的变量。更有趣的形式是共享KV键,同一次运行中的子任务读写它。
我的社交回复代理使用工作记忆在处理一个队列消息中的一批评论时积累上下文。它在开始时从KV读取每个客户的最近对话历史,在处理过程中添加新上下文,并在结束时写回。
// workers/social-reply.ts
async function processComment(
comment: SocialCommentEvent,
env: Env
): Promise<void> {
// 从KV加载该客户的最近历史(工作记忆)
const historyKey = `customer:${comment.userId}:history`;
const rawHistory = await env.AGENT_KV.get(historyKey);
const history: ConversationTurn[] = rawHistory
? JSON.parse(rawHistory)
: [];
// 根据历史构建上下文感知的系统提示
const systemPrompt = buildSystemPrompt(history);
const response = await anthropic.messages.create({
model: "claude-opus-4-8",
max_tokens: 512,
system: systemPrompt,
messages: [{ role: "user", content: comment.text }],
});
const reply =
response.content[0].type === "text" ? response.content[0].text : "";
// 更新历史——保留最后10轮,TTL 30天
const updatedHistory: ConversationTurn[] = [
...history.slice(-9),
{ role: "assistant", content: reply, timestamp: comment.timestamp },
];
await env.AGENT_KV.put(historyKey, JSON.stringify(updatedHistory), {
expirationTtl: 60 * 60 * 24 * 30,
});
await postReply(comment, reply, env);
}注意两点。历史限制为10轮——使用滑动窗口,不要让它无限增长。TTL为30天:如果客户沉默一个月,历史过期,代理重新开始。两者都是有意为之的。
情节记忆:发生了什么以及何时
情节记忆是代理的日志。代理在每次新运行开始时读取的过去运行的结构化记录,以避免重复。
我的每日简报代理每天都显示相同的过时警报,因为每次运行都不知道之前已经标记了什么。解决方案:代理在生成简报之前读取的过去警报的结构化日志。
// workers/daily-brief.ts
interface AlertLogEntry {
id: string;
surfacedAt: string; // ISO时间戳
resolvedAt?: string;
summary: string;
}
async function buildDailyBrief(env: Env): Promise<void> {
const [emails, calendar, tasks] = await Promise.all([
fetchOvernightEmails(env),
fetchTodayCalendar(env),
fetchTopTasks(env),
]);
// 加载情节记忆:已经标记过什么
const rawLog = await env.AGENT_KV.get("brief:alert-log");
const alertLog: AlertLogEntry[] = rawLog ? JSON.parse(rawLog) : [];
// 只过滤近期未解决的警报
const sevenDaysAgo = new Date(
Date.now() - 7 * 24 * 60 * 60 * 1000
).toISOString();
const recentAlerts = alertLog.filter(
(e) => e.surfacedAt > sevenDaysAgo && !e.resolvedAt
);
const brief = await synthesizeBrief(
{ emails, calendar, tasks, recentAlerts },
env
);
// 用本次运行标记的新警报更新日志
const newAlerts: AlertLogEntry[] = brief.newAlerts.map((a) => ({
id: crypto.randomUUID(),
surfacedAt: new Date().toISOString(),
summary: a,
}));
const updatedLog = [...alertLog, ...newAlerts].slice(-100); // 保留最后100条
await env.AGENT_KV.put("brief:alert-log", JSON.stringify(updatedLog));
await writeToWorkspace(brief.content, env);
}代理现在知道它已经说过什么了。重复警报不会出现在简报中,直到底层问题改变。当我将警报标记为已解决时,它从活动列表中消失。
这个模式可以推广:任何产生决策、标记或建议的代理都受益于日志。日志便宜(KV中的几KB),回报高(不再有冗余输出)。
语义记忆:你知道什么
语义记忆是知识库。它在查询时回答”你对X了解什么?“,而不是预先将所有内容塞入系统提示。
最简单的形式是KV或数据库中的结构化查找。我的Pickleland预订代理在起草确认之前查找客户档案和场地偏好:
// workers/booking-agent.ts
interface CustomerProfile {
userId: string;
preferredCourts: string[];
experienceLevel: "beginner" | "intermediate" | "advanced";
specialNotes: string;
}
async function draftConfirmation(
booking: BookingEvent,
env: Env
): Promise<string> {
// 从KV获取客户档案(语义记忆——事实性知识)
const profileKey = `customer:${booking.userId}:profile`;
const rawProfile = await env.AGENT_KV.get(profileKey);
const profile: CustomerProfile | null = rawProfile
? JSON.parse(rawProfile)
: null;
const systemPrompt = profile
? `你起草个性化预订确认。该客户偏好${profile.preferredCourts.join("、")},是${profile.experienceLevel}级别的球员。${profile.specialNotes}`
: "你为匹克球场馆起草预订确认。";
const response = await anthropic.messages.create({
model: "claude-haiku-4-5-20251001",
max_tokens: 256,
system: systemPrompt,
messages: [
{
role: "user",
content: `为以下内容起草确认:${JSON.stringify(booking)}`,
},
],
});
return response.content[0].type === "text" ? response.content[0].text : "";
}对于更大的知识库——产品文档、支持知识库、任何太大无法放入上下文窗口的内容——你需要向量存储。工作流程是:嵌入查询,检索最相关的k个片段,将它们注入上下文。如果你已经在Workers上,Cloudflare Vectorize可以原生处理这个问题。对于更大的索引,我使用了Upstash Vector。选择取决于规模,而不是原则。
关于语义记忆的诚实说明:它是三者中最难构建和维护的。索引需要保持最新。检索质量参差不齐。从结构化查找开始——KV、D1中的表——只有在结构化方法无法覆盖所需知识面时才考虑向量搜索。
记忆决策框架
在向代理添加任何记忆之前,回答三个问题:
-
代理需要跨运行记忆吗? 如果每次调用都是真正独立的——翻译、分类、一次性生成——跳过记忆。无状态更简单、更便宜。
-
代理是否在重复自己或对自己的历史视而不见? 如果是,先添加情节记忆。这是最省力的修复,涵盖了大多数”代理一直做X”的投诉。
-
代理是否在不应该的时候将每个用户或实体一视同仁? 如果是,添加工作记忆(客户历史、用户档案)或语义记忆(搜索或检索系统)。
我在实践中最常见的错误:有人向一个代理添加了庞大的知识库(语义记忆),而该代理实际上因为没有情节记忆而失败——没有它已做过什么的日志。复杂性与问题不匹配。
我在生产中实际使用的内容
在30多个代理中:
- 所有代理至少有工作记忆——运行内的某种状态形式,即使只是上下文窗口本身。
- 大约一半有情节记忆——过去运行、决策或标记的日志。这几乎总是值得添加的。
- 三四个有真正的语义记忆,由向量存储支持。这些是针对大型动态知识库回答问题的代理。
Cloudflare KV是我用于工作记忆和情节记忆的默认存储。它快速、便宜,并且原生集成到Workers中——不需要额外的客户端,不需要单独的凭据。限制:KV最终一致,不适合高频写入。对于每秒写入状态多次的代理,我改用Durable Objects或D1数据库。
对于向量支持的语义记忆,我对小到中等索引(少于~10万个向量)使用Cloudflare Vectorize,对更大的内容使用Upstash Vector。两者都有一流的JavaScript客户端。
运营者的结论
只有当无状态行为导致真正的问题时才向代理添加记忆——重复输出、客户历史盲点、对过去决策的忽视。然后选择正确的层次:工作记忆用于运行中的上下文,情节记忆用于历史发生的事情,语义记忆用于你知道的内容。如果不确定,从情节记忆开始——它以最少的复杂性修复最常见的故障模式。在用尽结构化查找之前不要使用向量数据库。最好的记忆系统是让代理正确运行的最简单的系统。
相关: 我用来运行30多个生产代理的代理栈 · 事件触发代理与定时代理 · 我如何衡量AI代理是否真正有效
需要帮助为您的用例设计代理记忆吗? 联系我 — 我为运营团队设计生产代理系统。
每周三。28,400+ 读者。纯干货。
✓ 请查收邮箱 — 点击确认链接以完成订阅。
✓ 订阅成功!
✓ 您已在订阅列表中。
相关文章
如何构建你的第一个MCP服务器:实践指南
2026年更新。我用于构建和注册MCP服务器的精确TypeScript代码——stdio传输、工具定义,以及如何在30分钟内在Claude Desktop中测试它们。
AI Agents使用 Claude API 进行提示缓存:在不更换模型的情况下降低输入成本
如何使用 cache_control 将拥有大型稳定提示的智能体的 Claude API 输入成本降低多达 90%——前缀匹配的不变量、应该缓存什么、隐性失效因素,以及盈亏平衡的计算。
AI AgentsClaude Fable 5 初体验:一位运营者的视角
2026 年更新。来自一位运维 30 多个生产级智能体的人对 Claude Fable 5 的初体验——到底有什么不同、没人提醒你的成本陷阱,以及你是否应该切换。
将AI实战手册发送到您的邮箱
每周三。28,400+ 读者。纯干货。
请查收邮箱。
我们已向您发送确认邮件 — 点击其中的链接以完成订阅。如果一分钟内没收到,请检查垃圾邮件。
订阅成功。
欢迎 — 下一期很快就会送达您的邮箱。
您已在订阅列表中 — 每周三留意查收。