Agent SDK 在容器内运行时默认会把 Session Transcripts、CLAUDE.md memory files、Working-directory artifacts 三类状态写入容器本地文件系统,且未挂载任何持久化卷的前提下,容器重启、缩容、跨节点迁移都会让这三类状态全部丢失——不存在「自动同步到控制面」的例外。生产环境若把 Agent 当作有状态服务跑,就必须把状态显式外置到对象存储、分布式数据库或 PV 之上。
一、为什么状态外置是 Agent 落地的第一道坎
把 Agent 容器当作「短命脚本沙箱」是多数团队初期的假设,但只要让 Agent 跨会话记忆用户偏好、跨重启续接长任务,这一假设就会塌方。容器本地文件系统本质是 Pod 生命周期的一部分,沙箱结束即回收;K8s 调度器在节点压力、镜像升级、Deployment 滚动时都可能把 Pod 漂移到新节点,旧节点上的 /root/... 路径不会跟着走。
因此在生产侧,主流云厂商和开源 Agent 框架都把状态拆成两层:进程内短期状态由容器本地承载,跨进程跨节点的长期状态由外置存储承载。Agent SDK 默认行为只覆盖前者,这是设计选择,不是缺陷——但选型阶段必须看清。
二、三类状态与默认路径
下面把容器本地磁盘上会被默认写入的三类状态列清楚,并指出默认路径。多数 Agent SDK(如 Claude Agent SDK、Letta Agent SDK、AgentScope Java SDK)都遵循类似约定,仅命名与目录略有差异。
| 状态类型 | 默认路径(容器内) | 包含什么 | 生命周期 |
|---|---|---|---|
| Session Transcripts | ~/.claude/projects/<encoded-cwd>/*.jsonl 或 ~/.config/agent/sessions/<session-id>/messages/* |
每条 user / assistant / tool 消息、工具返回值、Token 用量 | 与 Session 绑定 |
| CLAUDE.md memory files | ~/.claude/CLAUDE.md、./CLAUDE.md(项目级) |
用户偏好、项目约定、工具说明、被显式写入的”长期记忆” | 跨 Session 累积 |
| Working-directory artifacts | 当前工作目录下由工具写出的文件(./.claude/...、./output/...) |
Agent 在执行过程中生成的中间产物、报告、补丁 | 与任务绑定 |
Session Transcripts 是会话级追加日志,写盘频率高、增长快;CLAUDE.md memory files 是跨会话的”长期记忆”,由用户或 Agent 显式维护;Working-directory artifacts 则是任务执行落地产物。三者默认都落在容器内文件树中,区别只在于目录与写入时机。
三、容器重启、缩容、跨节点迁移的丢失判定
三类状态对生命周期事件的反应并不一致,但默认配置下都是”丢”。下表把常见事件与状态丢失的对应关系列清楚。
| 生命周期事件 | Session Transcripts | CLAUDE.md memory | Working-directory artifacts |
|---|---|---|---|
| 容器内进程崩溃重启(同一 Pod) | 全部丢失 | 全部丢失 | 全部丢失 |
| Pod 重建(Deployment 滚动升级) | 全部丢失 | 全部丢失 | 全部丢失 |
| 节点缩容 / 节点驱逐 | 全部丢失 | 全部丢失 | 全部丢失 |
| 跨节点迁移(重新调度到新节点) | 全部丢失 | 全部丢失 | 全部丢失 |
| 镜像版本回滚 | 全部丢失 | 全部丢失 | 全部丢失 |
判定逻辑很直接:只要这些文件落在容器文件系统的可写层(Container Runtime 的 overlayfs 上层),而没有显式挂载 PersistentVolume(PV)或 emptyDir 跨容器共享,它们就与容器同生共死。Docker/K8s 的回收策略不区分”用户文件”和”系统文件”,全部一并清空。
四、让状态真正”活下来”的落地步骤
要让三类状态在容器生命周期之外可用,必须主动外置。下面给出一组可执行的步骤,覆盖存储介质选择、挂载方式、写入路径改造与失败回滚。
- 选存储介质:长会话日志与 memory 用对象存储(S3 / OSS / COS),高频读写用分布式块存储或文件系统(CephFS / JuiceFS),避免落到本地盘;
- 挂载 PV/CSI:在 Pod spec 中声明
volumes与volumeMounts,将存储介质挂到 Agent SDK 默认的写入目录(如~/.claude、当前工作目录); - 改造写入路径:把 SDK 的”工作目录”和”用户主目录”通过环境变量(如
HOME、CLAUDE_HOME)指向挂载点,避免硬编码/root; - 增加初始化脚本:用 initContainer 或启动脚本在容器启动时把对象存储中的 memory 快照预拉到本地目录,结束时把增量回写;
- 接入版本控制与回收策略:memory 文件加语义版本与变更审计;对象存储设置生命周期规则防止无止境增长。
# 示例:把 ~/.claude 挂到 PersistentVolumeClaim
apiVersion: v1
kind: Pod
metadata:
name: agent-pod
spec:
volumes:
- name: agent-home
persistentVolumeClaim:
claimName: agent-home-pvc
containers:
- name: agent
image: agent-sdk:latest
env:
- name: HOME
value: /agent-home
volumeMounts:
- name: agent-home
mountPath: /agent-home
五、易踩的三个坑
- 以为 K8s ConfigMap 能存长状态:ConfigMap 总大小 1MB 上限,且 update 是整体替换,不适合追加式日志;
- 把 memory 写到镜像层:每次重建镜像就会丢;必须在运行时写入可写层或外置存储;
- 共享同一 PV 给多 Pod:若多个 Agent 实例共享同一
~/.claude路径,memory 工具的并发写入会互相覆盖,需要在应用层加文件锁或迁移到对象存储。
把外置存储与并发控制两件事做扎实,状态丢失的隐患才算真正关掉。
常见问题(FAQ)
Q1:把 memory 放进 Git 仓库能不能解决丢失?
可以解决持久化,但解决不了并发与版本回滚,且仓库会随 memory 增长而膨胀,不推荐做主存储。
Q2:Session Transcripts 是否必须长期保留?
不必全保留,建议按”近 N 天全量 + 超出后降采样归档”的策略,控制存储成本。
Q3:跨节点迁移时如何做到 Agent 无感知?
把状态外置到共享存储后,Pod 漂移时新节点挂载同一份目录即可,业务代码无需感知节点切换。