会话与持久化
Session 的存储、恢复、分叉与过期。
一句话理解:会话(Session)是一段对话历史的容器;设计会话要回答五个问题:存在哪、怎么恢复、能否分叉、何时过期、恢复后世界是否已经变了。
用户周一下午让销售分析 Agent 做了一份“华东区 8 月销售下滑分析”,周二上午回来说:“接着昨天那个,把 SKU 维度也拆一下。”这句话能不能被正确响应,取决于会话设计:
- 昨天的对话还在吗?存在用户浏览器、应用服务器,还是模型厂商那边?
- 用户同时开着三个分析会话,“昨天那个”指哪个?
- 恢复之后,Agent 还记得昨天查过的数据,但数据仓库昨晚刚回灌了 8 月数据,旧结论还成立吗?
会话看起来只是“把聊天记录存下来”,但它牵涉存储位置、成本、合规和正确性。这一课讲清楚会话的存储方式、四种基本操作,以及最容易被忽略的陷阱。
模型本身不记得你
大多数模型 API 本质上是无状态的:每一次请求,模型只看到你这次发过去的内容。所谓“多轮对话”,是应用把历史消息一起发送的结果。
画出来就是这样:
所以“会话”是 Harness 或平台提供的能力,而不是模型的能力。问题只在于 谁来保存这份历史:
| 保存方 | 做法 | 代表 | 优点 | 代价 |
|---|---|---|---|---|
| 你的应用 | 自己把消息存进数据库,每次请求带上 | 直接调用模型 API | 完全掌控数据位置与格式 | 存储、截断、压缩都要自己做 |
| SDK / 框架 | 框架提供会话对象和存储后端 | Claude Agent SDK Sessions、OpenAI Agents SDK Sessions、LangGraph Checkpointer | 开箱即用,可换存储后端 | 需要理解框架的存储语义 |
| 模型厂商 | 厂商在服务端保存,请求时只传 ID | OpenAI Responses API 的 previous_response_id 与 Conversations API |
请求体小,跨设备方便 | 数据保存在厂商侧,受其保留策略约束 |
| 托管 Agent 平台 | 平台保存完整会话与运行环境 | Claude Managed Agents 的 Session | 连沙箱状态一起保存 | 数据与控制权都在平台侧 |
几个值得记住的细节(均来自官方文档):
- Claude Agent SDK 默认把会话记录写到本地磁盘(Claude Code 的项目目录下,以 JSONL 文件保存)。本地文件只能在同一台机器上恢复;要在容器、Serverless 或多台机器间恢复,需要接入自定义的会话存储适配器,把记录同步到你自己的后端。
- OpenAI Agents SDK 的 Sessions 提供多种实现:SQLite、Redis、SQLAlchemy、MongoDB 等,还有可以包裹任意会话、提供加密与按时间过期能力的
EncryptedSession。 - OpenAI Responses API 使用
previous_response_id串联时,响应对象默认保存 30 天;Conversations API 中的会话对象不受这 30 天期限约束。另外要注意,即使用 ID 串联,链上所有历史输入仍然按输入 Token 计费。 - LangGraph 用
thread_id标识一个会话线程,Checkpointer 把线程的图状态保存为检查点,官方提供内存、SQLite、Postgres 等实现。
四种基本操作
| 操作 | 含义 | 典型场景 |
|---|---|---|
| 继续 | 在当前会话追加新一轮 | 同一次使用中的连续追问 |
| 恢复 | 按 ID 找回某个历史会话继续 | 隔天回来、进程重启、从会话列表选择 |
| 分叉 | 复制历史,开出一个新分支 | 尝试不同方案,又不想污染原会话 |
| 结束 / 过期 | 会话不再可恢复,或被删除 | 超过保留期、用户删除、合规清理 |
以 Claude Agent SDK 为例,“继续”会找到当前目录下最近的会话;“恢复”需要传入具体的会话 ID(适合一个用户有多个会话、或多用户的应用);“分叉”会生成新的会话 ID,原会话历史保持不变。
最大的陷阱:会话恢复不等于世界恢复
Claude Agent SDK 的文档里有一句特别值得产品经理记住的话:会话持久化的是 对话,不是文件系统;分叉的是对话历史,不是文件。如果分叉出来的 Agent 修改了文件,这些修改是真实发生的,其他在同一目录工作的会话也看得见。要回滚文件改动,需要单独的文件检查点功能。
推广到所有 Agent,这意味着:
| 恢复了什么 | 没有恢复什么 |
|---|---|
| Agent 说过什么、查过什么、当时得出的结论 | 外部系统此刻的真实状态 |
| 当时工具返回的数据 | 数据此后发生的变化 |
| 当时做出的决定 | 其他人或其他会话在此期间做的改动 |
回到开头的销售分析例子:周二恢复会话时,Agent 的上下文里还躺着周一查询的 8 月数据。如果数据仓库昨晚回灌过,Agent 基于旧数据继续拆 SKU,就会得出前后矛盾的结论。
这个时间差可以画成两条线:
应对方法:
- 恢复时注入时间差:告诉模型“距离上次交互已过去 18 小时,涉及数据的结论请重新查询确认”。
- 关键数据带时间戳:工具返回结果时附上查询时间,便于模型判断是否过期。
- 写操作前强制重新读取:用 Hooks 或程序校验保证,而不是依赖模型自觉(见 Hooks 与生命周期)。
- 超过一定时间提示新建会话:与其恢复一个很旧、很长的会话,不如带着摘要开一个新会话。
会话设计的其他关键决策
会话归属与隔离
多用户产品里,会话 ID 必须和用户、租户绑定,恢复时校验归属。不能出现“知道会话 ID 就能恢复别人的会话”。团队共享的会话要单独设计可见范围。
会话变长之后
会话越长,每次请求的成本和延迟越高,模型的注意力也越分散(见 上下文腐化)。常见对策是限制加载的历史条数、自动压缩旧历史(见 长任务的上下文管理)。例如 OpenAI Agents SDK 允许通过会话设置限制取回的历史条数,并提供了一个包装器,基于 Responses API 自动压缩长会话。
配置变化后的旧会话
Agent 上线新版本后,工具列表、系统指令都可能变了,旧会话里引用的工具可能已经不存在。需要决定:旧会话是按旧配置恢复、按新配置恢复,还是提示用户新建。托管平台 Claude Managed Agents 的做法是让会话可以固定引用 Agent 配置的某个版本,便于分阶段发布。
保留期与删除
会话里往往包含用户数据和业务数据。要明确默认保留多久、用户能否删除、删除后是否同时清理由它派生的记忆与产物。一个容易踩的坑:在 Claude 和 ChatGPT 的产品说明中,删除某段对话都不会自动删除从中产生的记忆,需要单独删除记忆。详见 记忆的隐私与可控。
并发
同一个会话在两个浏览器标签页里同时提交,会导致历史交错。常见做法是:同一会话同一时刻只允许一个运行中的请求,其余请求排队或拒绝。
常见误区
- “模型会记住我们之前聊过的内容。” 模型不记得,记得的是保存并重新发送历史的那一层。
- “会话存得越久越好。” 越旧的会话越可能基于过期数据,恢复它反而可能误导 Agent,还会增加合规风险。
- “分叉就能安全地试错。” 分叉只隔离对话。如果分支里执行了写操作,现实世界已经被改变了。
产品经理检查清单
- 会话数据存在哪里?是否满足数据所在地与保留期要求?
- 用户如何找到并恢复某个历史会话?多个会话之间如何区分?
- 恢复时,Agent 会重新确认哪些业务数据?
- 会话过长时如何处理:截断、压缩,还是提示新建?
- 删除会话会一并删除哪些派生数据(记忆、产物、日志)?
- 同一会话的并发请求如何处理?
- Agent 发布新版本后,旧会话的恢复策略是什么?
练习
为「销售分析 Agent」设计会话方案,用时约 40 分钟,产出一页文档,回答:
- 选择哪种保存方式(自己存、框架、厂商、托管平台),为什么?
- 会话列表页每个会话显示哪些信息,帮助用户识别“昨天那个”?
- 恢复一个超过 24 小时的会话时,Agent 的第一步应该做什么?写出你会注入给模型的提示。
- 保留期、删除范围与并发策略分别是什么?
延伸阅读
- Work with sessions:Claude Agent SDK 的继续、恢复、分叉与跨主机恢复。
- OpenAI Agents SDK:Sessions:各类会话存储实现与历史条数限制。
- OpenAI:Conversation state:Responses API 的三种会话状态管理方式与保留期。
- LangGraph Persistence:Checkpointer、
thread_id与 Store。
下一步
会话记住了“聊了什么”,但对长任务来说,更重要的是“做到哪了”。下一课「任务状态与 Checkpoint」讲如何记录进度、中间结果和已执行的动作。