摘要: GPT-6 官方明确说 persistent agent 能连续干几小时的活,提示缓存命中还能省最多九成的输入 token 成本------成本被官方补贴了,工程问题没人替你解决。长会话里 Agent 的"记忆"分三层:会话状态、工作上下文、跨会话记忆。本文按会话中、会话变长、会话结束三个阶段,把每一层该放哪、该留什么、该丢什么讲清楚,文末附一张可直接勾选的长会话设计检查清单。
文章目录
-
- [开头:Agent 能跑一整天的活了,然后呢?](#开头:Agent 能跑一整天的活了,然后呢?)
- 会话、上下文、记忆,先把概念对齐
- 阶段一:会话进行中,状态放哪
- 阶段二:上下文满了,怎么压缩
- 阶段三:会话结束,记忆怎么留
- 产出物:长会话设计检查清单
- 边界:有些场景根本不需要长会话
- 总结
开头:Agent 能跑一整天的活了,然后呢?
9 月 22 号 OpenAI 发了篇东西,讲 GPT-6 的提示缓存优化(Better prompt caching for GPT-6),我盯着里面一句话看了半天:persistent agent 现在可以连续工作几个小时 ,做代码库重构、做长文档调研那种活。缓存命中率上去了,输入 token 最多能省九成。翻译一下:官方把长会话的成本问题补贴了,让你敢让 Agent 跑久一点。但成本只是第一道坎。我第一次让 Agent 跑长任务是在沙箱里搭的一堆假订单------原计划是让它一口气处理完,别中间停下来问我。结果跑到后半程,它开始用自己默认的习惯做事,开头交代的规矩全当没听见。我当时第一反应是"模型又犯蠢了",排查到后面才发现,问题出在我没替它管好记忆。
这篇把我在那轮演练里踩出来的三个决策点讲清楚:状态放哪、上下文怎么压缩、记忆怎么留。每个都给边界,别照着抄完就以为万事大吉。
会话、上下文、记忆,先把概念对齐
长会话翻车,多半是这三个词混着用导致的。先把定义对齐,后面全部按这个口径说,不然容易绕晕。
- 会话状态:Agent 当前这一步在干什么、手里有什么数据。比如处理到第几条、当前订单的 id 是什么。
- 上下文窗口:每次请求发给模型的全部内容,指令、工具定义、历史对话都塞在里面。窗口是有限的,GPT-6 给了更大的窗口,但再大也有头。
- 记忆:我理解是跨会话还能用上的东西,比如"金额一律按分返回"这种约定、用户偏好、业务规则。
三个东西的寿命不一样。会话状态跟着这次会话走,上下文窗口每次请求都会重建(历史还在,只是越来越挤),记忆是要跨越会话留下去的。
| 维度 | 会话状态 | 上下文窗口 | 跨会话记忆 |
|---|---|---|---|
| 生命周期 | 单次会话内 | 每次请求,随窗口滚动 | 跨会话/跨天 |
| 典型内容 | 处理到第几条、当前数据 | 指令、工具、历史对话 | 约定、偏好、规则 |
| 存放位置 | 内存/外部存储 | 随请求发送给模型 | 外部存储/数据库 |
| 丢了会怎样 | 任务中断,可重试 | 细节丢失,Agent 按默认习惯办事 | 每次会话从头学 |
表 1 会话状态、上下文窗口、跨会话记忆的对比
判断标准就一句话:凡是丢了会让 Agent 办错事的信息,都不该只活在上下文窗口里。 这条后面反复用到。
阶段一:会话进行中,状态放哪
先说最容易踩的坑:单轮能完的事,别上状态。这条看着像废话,但很多人一上来就把 Agent 做成"带状态"的,结果单轮问答也背着全套历史,白白增加出错的面积。
我之前给 Agent 配过一个问答工具,让它根据用户问题查资料回答,一次一问,问完就完。那种场景给 Agent 塞一套状态管理,纯属给自己找事------每次请求都带全量上下文,答完就丢,干净利落。
真正需要管状态的是长链路任务:批量处理几百条数据、分步骤生成内容、跟外部系统打多轮交道。这种任务里 Agent 自己记不住"处理到第几条",它的工作记忆每次请求都在重置。
我的做法比较土:用一个 JSON 文件当状态层,Agent 每处理一条,就把进度和当前结果写回去。
javascript
// state.js ------ 一个极简的会话状态层(Node 25.8.2 实测)
// 为什么看这段:长任务里 Agent 的"进行到哪了"不该靠它自己记,
// 而是外部持久化,重启不丢、可对账。
const fs = require('fs');
const STATE_FILE = './agent-session.json';
function loadState() {
if (!fs.existsSync(STATE_FILE)) {
return { processed: 0, agreements: null, results: [] };
}
return JSON.parse(fs.readFileSync(STATE_FILE, 'utf8'));
}
function saveState(state) {
fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
}
text
// 运行结果(Node 25.8.2 实测):第一条处理完后的状态文件
{
"processed": 1,
"agreements": "金额按分返回;库存字段禁止修改",
"results": [
{ "id": "O-1001", "amountInCents": 12990, "status": "ok" }
]
}
注意看 agreements 这个字段,它是这个方案的核心:把开头跟 Agent 约定好的规矩,直接写进状态文件,而不是只存在于对话开头那几轮里。后面阶段二会讲为什么这条救了我的命。
这里有个明显的边界:状态全塞一个 JSON 文件,扛不住并发和多 Agent 同时写。真上线我会换数据库或 Redis,演练阶段图省事用文件够了。你要是做高并发长任务,别学这个,但思路是一样的。
阶段二:上下文满了,怎么压缩
长任务跑起来,上下文窗口会被中间输出快速灌满。历史对话不能全删------Agent 后面要用前面的结论。这时候只有两条路:压缩,或者换会话。
先说换会话这条路,很多人容易忽略:如果任务能拆成独立小步骤,步骤之间只共享少量数据,那就别硬撑一个长会话。 每个步骤新开会话、只把必要数据传过去,上下文干净,翻车面也小。我后来复盘才发现,当初那个订单任务本来可以拆,是我图省事硬让一个会话从头跑到尾。
拆不动的时候才谈压缩。压缩常见三种,各有各的坑:
| 压缩策略 | 怎么做 | 保留什么 | 最容易丢什么 | 适合场景 |
|---|---|---|---|---|
| 直接截断 | 最早的对话滚出窗口 | 最近的内容 | 早期指令、开头的约定 | 早期内容真没用了 |
| 摘要压缩 | 把历史对话喂给模型,产出摘要 | 事件主干 | 数字、单位、禁止项这类细节 | 叙事型任务 |
| 结构化压缩 | 把关键信息抽成结构化字段 | 状态、结果、约定 | 推理过程和中间讨论 | 有明确数据边界的任务 |
表 2 三种压缩策略对比
我的经验是,摘要压缩是最大的坑 :AI 摘要是会丢细节的,而且丢的恰恰是"金额按分返回"这种看起来不起眼、丢了就要命的信息。OpenAI 自己的缓存指南也印证了这一点------他们专门提醒开发者用 append-only 的方式维护上下文,新指令追加在末尾覆盖旧的,而不是去修改中间内容,就是为了保持上下文稳定、别把前面的东西弄丢。
所以压缩之前,先做一件事:把不可丢的信息钉死在状态里 。就是阶段一那个 agreements 字段。上下文随便压,只要 Agent 每步都能从状态文件里读到关键约定,摘要丢多少都不怕。这个效果我在演练里实测过:没钉约定之前,跑到后半程金额单位错了九十多条;把约定写进状态文件重跑,从头到尾零漂移------完整对账脚本和数字在另一篇演练复盘里有,这篇不重复贴。
这个策略的边界也讲清楚:钉死约定只解决"约定丢失",解决不了"模型对约定的理解漂移"。Agent 读到了约定但执行时打折,那是另一层问题,需要靠验证环节兜。
阶段三:会话结束,记忆怎么留
长任务跑完,会话就结束了,但 Agent 干活的规矩不能跟着会话一起结束。这时候要决定哪些东西值得留到下次会话。
我的取舍标准很简单:留规则,不留过程。 业务约定、输出格式、禁止操作,这些是规则,留;中间某一步具体怎么算出来的,那是过程,下次基本用不上,删。收尾这段用一个归档脚本搞定:
javascript
// archiveRules.js ------ 会话结束后:规则入库,过程丢弃
// 为什么看这段:收尾不是把整个会话原样存下来,而是只提取规则层,
// 下次会话载入的也是这份规则,不是几 MB 的历史对话。
const fs = require('fs');
const session = JSON.parse(fs.readFileSync('./agent-session.json', 'utf8'));
const rulesArchive = {
savedAt: '2026-09-28',
agreements: session.agreements, // 规则,留
resultCount: session.results.length, // 只留数量,过程结果丢弃
lastId: session.results.at(-1)?.id // 断点续跑用,留个锚
};
fs.writeFileSync('./rules.json', JSON.stringify(rulesArchive, null, 2));
text
// 运行结果(Node 25.8.2 实测):archiveRules.js 产出的 rules.json
{
"savedAt": "2026-09-28",
"agreements": "金额按分返回;库存字段禁止修改",
"resultCount": 428,
"lastId": "O-1428"
}
留多久、放哪,看信息敏感度:
- 纯业务规则(金额单位、字段命名):放项目配置或状态文件,长期留。
- 带用户数据的(用户偏好、历史操作):放数据库,按业务合规要求定保留期。
- 敏感的(token、密钥、用户 PII):别留在任何 Agent 可读的上下文里,交给专门的密钥管理。
这段说得直白点:记忆不是越多越好。我之前见过有人给 Agent 配了个"无限记忆",把几年对话全存进去,结果每次请求都背着几 MB 的历史,贵不说,Agent 反而被无关老对话带偏。
ASCII 图是长会话生命周期的完整样子:
会话开始 → 阶段一:状态初始化(约定写入 agreements)
↓
任务循环:读状态 → 干活 → 写状态
↓
上下文窗口滚满 → 阶段二:先钉死约定 → 再压缩
↓
任务完成 → 阶段三:规则入库,过程丢弃
↓
下次会话 → 载入 agreements,不用从头教
图 1 长会话生命周期:约定从开头写进状态,压缩不丢,结束入库
产出物:长会话设计检查清单
把上面的决策整理成一张能勾选的表,我演练完之后就一直按这个检查:
| 检查项 | 具体动作 | 勾选 |
|---|---|---|
| 1 单轮够用? | 任务能一次问答解决就别上状态 | ☐ |
| 2 需要外部状态吗 | 长链路任务才需要,先确认拆不掉 | ☐ |
| 3 约定有没有钉死 | 关键约定写进状态文件,不只活在对话里 | ☐ |
| 4 压缩策略选对了吗 | 数据型任务用结构化,别用摘要 | ☐ |
| 5 压缩前先固化 | 先钉不可丢信息,再让上下文滚 | ☐ |
| 6 留规则不留过程 | 会话结束只入库规则,过程丢弃 | ☐ |
| 7 敏感信息隔离 | token/PII 不进 Agent 可读上下文 | ☐ |
| 8 能拆就拆 | 步骤独立就拆会话,别硬撑长会话 | ☐ |
表 3 长会话设计检查清单
这套清单只覆盖"记忆与上下文"这个维度。长会话还有别的坑------成本监控、任务取消与恢复、超时重试------这篇不展开,真要上线得单独过一遍。
清单落地之后怎么验收?我自己验证的方式是三遍对账:跑一遍长任务,用对账脚本盯漂移点;把约定钉进状态文件重跑,看漂移是否消失;再删掉状态文件重跑,看漂移是否回来。这三步和同一轮演练的复盘文里用的脚本是一套,读者可以照搬------比"我觉得没问题"可靠得多。
边界:有些场景根本不需要长会话
写到最后必须泼一盆冷水。
长会话不是越久越好。多数日常任务根本用不到长会话:问答、单次生成、独立小任务,短会话干净利落,反而省事。硬把简单任务拉成长会话,等于给自己引入状态管理、压缩策略、记忆持久化一整套复杂度,收益为零。
适合长会话的,是那种必须在一个上下文里连续推理、中间结果互相依赖的任务------批量处理共享约定的数据、多步骤长文档、跨系统协调。判断标准就一条:拆开会话后,要不要频繁地把前面的结论手动搬过来?要,就别拆。
我见过不少团队一上来就追求"Agent 跑一整天",其实他们那个场景,拆成几十个短任务反而更稳。先想清楚你的任务配不配长会话,再谈长会话怎么设计。
总结
回头看那轮演练,翻车的不是我让 Agent 跑得久,是我让它的记忆全活在上下文窗口里,而窗口是会滚、会压、会丢的。
三个决策点再压一遍:状态放外部,约定先钉死,记忆留规则不留过程。做到这三点,Agent 跑几小时还是跑一天,差别就没那么大了。