欢迎光临
我们一直在努力

从一到无穷大 #87:AgentState 的产品定位与设计取舍

在这里插入图片描述

最近我的一个工作重点是负责 Agent 存算分离中的 AgentState 存储,以支持国内某头部AI产品。我之前讨论过一条更彻底的部署路线:如果会话、任务进度,以及继续执行需要的文件和制品都已在云端持久化,Agent Loop——调用模型、选择工具、处理结果的循环——也可以由云端托管,本机只保留交互入口和受控的 Tool Endpoint。用户关掉电脑,云端仍能推进不依赖本机的任务;换到手机或另一台电脑,也能查看同一任务的已提交状态和制品。任务的生命周期由云端服务管理,不再跟着一台用户设备起停。

这条路线把用户设备必须在线的要求,转成了服务端的长期存储、计算与恢复成本。存储成本尤其需要正视:会话历史、制品、版本和索引都在积累。本文要讨论的 AgentState 存储,就是这种持续托管架构中的逻辑状态服务;文件和制品由 AgentFS、Agent Bucket 或云盘承接。状态上云也可以服务本地 Agent Loop,但只有执行调度一并脱离本机,才有上面那种不依赖本机在线的任务连续性。

业界相近的产品也逐渐多起来了:Turso AgentFS、TiDB Cloud Filesystem、腾讯云 COS Agent Bucket、火山引擎 TOS Agent Bucket,以及 AWS 的 strands-dynamodb-storage。[1][2][3][4][5] 它们都在保存 Agent 使用或产生的数据,接管的对象却不同,有的保存工作目录,有的保存会话与执行状态,有的从消息中抽取长期记忆。

已有一些文章直接讨论了这个问题。Anthropic 在 2026 年 4 月的 Managed Agents 架构文章里,把持久 Session 日志、调用模型与工具的 Harness、执行代码的 Sandbox 拆开,让 Harness 故障后可以读取会话事件接续。[6] Pekka Enberg 在同年 1 月的个人博客里,也从临时计算和跨机迁移出发,提出对象存储上的分离式 AgentFS,不过他选择把文件、KV 与工具审计记录打包;文末明确,这还是设计方向[7] 。它们讨论的是相近的恢复问题,选择的产品边界却不同。

我想从第一性原理出发,把 AgentState 存储要解决的问题想清楚:当用户离线、换设备,或者执行实例被回收时,云端至少要保留哪些事实,才能继续推进同一任务,并让各端看到一致的进度和制品版本? 这要求同时回答恢复、跨实例接续、上下文组装和记忆检索需要什么数据,以及顺序与一致性、网络读写延迟、存储成本、租户隔离和数据生命周期会施加什么约束。保存一份消息文件只是其中一个动作,还没有回答整个问题。

下面先剖析本机一条 Codex 会话,把每类数据落到具体路径、JSONL 记录和 SQLite 表上,再讨论哪些数据应该从计算中剥离、由什么产品承接。本地目录布局是用来辨认数据职责的样本,不代表 Codex 已经把这些数据部署成远端的存算分离服务。

1. 本地 Codex Session 的数据剖析

这次用本机开发个人产品 SignalBrief 的会话作样本。把子 Agent 算进来,它其实是一棵会话树:2026 年 9 月 5 日检查时,父会话下面有 13 条直接子 Session,其中一条又派生出 4 条孙 Session,共 18 条 Session。每条都有自己的 rollout JSONL,文件合计 203,364,885 Bytes,约 193.94 MiB,共 23,484 行。

统计范围Session 数JSONL BytesJSONL 行数
父 Session 1 120,824,841 15,188
直接子 Session 13 68,478,120 6,425
孙 Session 4 14,061,924 1,871
合计 18 203,364,885 23,484

这里相加的是 18 个文件的字节长度,不是去重后的信息量:子 Session 会继承上下文,文件中存在重复记录。这个数字也没有计入共享 SQLite、Shell 辅助文件和 Workspace,不能当成整个任务的存储占用。后面的 JSONL 类型、行号和代码修改示例仍取自父 Session 的 15,188 行,避免把不同文件的记录混在一起。

1.1 Workspace、独属会话文件与共享 Session State

父会话的工作目录是 /Users/lizhaolong/Desktop/Exercise/,来自 JSONL 的 session_meta.cwd,也与父会话全部 47 条 turn_context.cwd 及 SQLite 中的 threads.cwd 一致。其余 17 条 Session 的首条 Header 和 threads.cwd 也都指向这个目录。本节实际剖析的项目在它的子目录 /Users/lizhaolong/Desktop/Exercise/SignalBrief/。本例的 Workspace 就是这个 Exercise/ 路径,不是 sessions/<session-id>/workspace/;18 条 Session 没有因此各自多出一份项目目录,项目文件也不因被一个 Session 使用就变成该 Session 独属的数据。

会话数据放在另一处:/Users/lizhaolong/.codex/。下面只展示父会话、子会话 final_code_review 和它的子会话 report_parser_review 三份 JSONL,其余 15 份的完整路径见本地样本证据:

/Users/lizhaolong/
├── .codex/ ← Codex 用户状态与配置目录
│ ├── sessions/2026/08/28/
│ │ ├── rollout-2026-08-28T14-17-21-01a04704-067c-7653-80d2-52f0d2a372b0.jsonl
│ │ │ ↑ 父 Session
│ │ ├── rollout-2026-08-28T14-36-45-01a04715-cb48-7071-92cd-e88518ccead6.jsonl
│ │ │ ↑ 子 Session:final_code_review
│ │ └── rollout-2026-08-28T16-41-18-01a04787-d137-7231-adfd-a8e70b8d5af9.jsonl
│ │ ↑ 孙 Session:report_parser_review
│ ├── state_5.sqlite ← 多 Session 共享,保存 Session State
│ ├── thread_history_1.sqlite ← 多 Session 共享,保存轮次与历史投影
│ ├── shell_snapshots/ ← Shell 环境脚本,另于会话记录保存
│ │ └── 01a04704-067c-7653-80d2-52f0d2a372b0.1787897841720599000.sh
│ ├── memories/ ← 本次检查为空
│ ├── logs_2.sqlite ← 共享日志库
│ └── config.toml ← 用户级配置
└── Desktop/Exercise/ ← 会话 CWD / Workspace
└── SignalBrief/ ← 本节检查的项目,不是私有 Session 目录
├── Sources/SignalBriefCore/FileStore.swift
├── Tests/SignalBriefCoreTests/ResilienceCoverageTests.swift
├── Docs/ARCHITECTURE.md
├── Package.swift
├── Packaging/Info.plist
├── Skills/signalbrief-summary/SKILL.md
├── .git/
├── .build/
│ ├── workspace-state.json
│ └── build.db
└── dist/SignalBrief.app/Contents/MacOS/SignalBrief

SQLite 中保存的也是 Session State,父子关系就保存在这里。 /Users/lizhaolong/.codex/state_5.sqlite 的 threads 表中,这棵树对应 18 行,每行用 id 区分 Session,用 rollout_path 指向它自己的 JSONL,另存 cwd、approval_mode、archived、history_mode 等配置和生命周期状态。同一个数据库的 thread_spawn_edges 表保存 17 条父子关系,由 parent_thread_id 和 child_thread_id 连接这些 Session。

目录树中的三份文件就是一个例子:父 Session 派生出 /root/final_code_review,后者再派生出 /root/final_code_review/report_parser_review。这里的 agent_path 是 Agent 的逻辑名称,不是磁盘目录;三个 JSONL 实际并排放在同一个日期目录下,父子关系并不靠文件夹嵌套表达。

thread_history_1.sqlite 把一条 Session(thread_id)拆成多个 Turn,每轮再包含消息、工具执行等 Item。关联和定位所需的字段如下,省略时间、错误等字段:

CREATE TABLE thread_turns (
thread_id TEXT NOT NULL,
turn_id TEXT NOT NULL,
status TEXT NOT NULL,
rollout_ordinal INTEGER NOT NULL,
rollout_byte_offset INTEGER,
rollout_end_ordinal INTEGER,
rollout_end_byte_offset INTEGER,
first_user_item_id TEXT,
final_agent_item_id TEXT,
PRIMARY KEY (thread_id, turn_id)
);

CREATE TABLE thread_items (
thread_id TEXT NOT NULL,
turn_id TEXT NOT NULL,
item_id TEXT NOT NULL,
item_type TEXT NOT NULL DEFAULT '',
item_json TEXT NOT NULL,
rollout_ordinal INTEGER NOT NULL,
PRIMARY KEY (thread_id, turn_id, item_id)
);

Item 用 (thread_id, turn_id) 关联所属 Turn,再由 item_id 区分;Turn 的 first_user_item_id 和 final_agent_item_id 分别指向本轮首条用户 Item 与最终助手 Item。两个表都在 (thread_id, rollout_ordinal) 上建有唯一索引,用于按记录顺序分页。

rollout_ordinal 是 JSONL 中从 0 开始的记录序号,对应文件第 ordinal + 1 行。以父 Session 的第一轮为例,下面三条记录的 turn_id 相同:

记录SQLite 定位字段序号JSONL 行号
Turn 开始:task_started thread_turns.rollout_ordinal 1 2
用户 Item:item_completed thread_items.rollout_ordinal 10 11
Turn 结束:task_complete thread_turns.rollout_end_ordinal 801 802

字节偏移从文件头的 0 开始计数。这一轮的 rollout_byte_offset=49316、rollout_end_byte_offset=2392962,对应字节区间 [49316, 2392962):开始偏移指向第 2 行开头,结束偏移指向第 802 行结束后的首字节。Item 表不存字节偏移;读取原始记录时,可以先通过 state_5.sqlite.threads.rollout_path 找到 JSONL,再从所属 Turn 的起始字节向后读取,按 Item 的序号定位。只读分页历史则可直接取 item_json。

另有 thread_history_projection_state,以 thread_id 为主键,保存 next_rollout_ordinal INTEGER NOT NULL 和 next_rollout_byte_offset INTEGER NOT NULL,标记历史投影下一次从哪里继续读取 JSONL。

接口说明见 App Server 文档[8],完整 DDL 与核对结果见样本证据。

1.2 JSONL 中到底存了什么

JSONL 是 UTF-8 文本,每行一个 JSON 对象。顶层 type 决定这一行是哪一类记录,payload 保存实际字段;response_item 和 event_msg 的 payload.type 还会继续区分子类型。同一个 JSONL 同时装着消息、控制事件和上下文材料,不能把每行都当成一条聊天消息。

JSONL 顶层类型父 Session 行数主要记录什么
session_meta 1 会话身份与创建时元信息
response_item 7,521 消息、工具调用、工具结果等协议项
event_msg 7,450 任务与 Item 的生命周期、Token 统计
turn_context 47 某一轮使用的模型与执行配置
world_state 49 指令、Skills、环境等上下文配置
compacted 21 压缩后的替换历史与窗口关系
inter_agent_communication_metadata 99 Agent 通信的附加控制标记

下面每一种都给一个本机例子。代码块为方便阅读展开成多行,原文件中仍是一条记录占一行;1.2.2 的调用与返回保留完整字段,仅在代码字符串内部用 … 省略内容;其余示例按用途摘录字段。compacted 的数组保留全部 7 项,但只展示各项的类型和角色,不展开指令、消息正文或内部压缩内容。

1.2.1 session_meta:会话身份与工作目录

第 1 行:

{
"type": "session_meta",
"payload": {
"id": "01a04704-067c-7653-80d2-52f0d2a372b0",
"cwd": "/Users/lizhaolong/Desktop/Exercise",
"originator": "Codex Desktop",
"cli_version": "0.148.0-alpha.21"
}
}

它说明这份文件属于哪条会话、会话创建时的工作目录是什么、由哪个客户端版本产生。id 可以与 SQLite 的 threads.id 对齐,cwd 则把会话和 /Users/lizhaolong/Desktop/Exercise 这个工作目录联系起来。其用途是识别和定位会话,以及解释历史数据的版本条件;它没有保存工作目录内的源码,也不代表每轮都使用创建时的配置。

1.2.2 response_item:消息与工具调用的实际载荷

这一类既能装用户/助手消息,也能装工具调用和返回结果。以第 608 行修改源码的工具调用为例:

{
"timestamp": "2026-08-28T06:43:13.609Z",
"ordinal": 607,
"type": "response_item",
"payload": {
"type": "custom_tool_call",
"id": "ctc_0b1c123529f9a957016a912dec814487d092da5171f6115854",
"status": "completed",
"call_id": "call_M6e9gYIj4hN8LGTAAfKh5HyW",
"name": "exec",
"input": "const patch = \\"*** Begin Patch\\\\n*** Update File: /Users/lizhaolong/Desktop/Exercise/SignalBrief/Sources/SignalBriefCore/FileStore.swift\\\\n@@\\\\n-import Foundation\\\\n+import Darwin\\\\n+import Foundation\\\\n…\\\\n*** End Patch\\";\\ntext(await tools.apply_patch(patch));\\n",
"internal_chat_message_metadata_passthrough": {
"turn_id": "01a04704-0836-7f70-a491-f9fea964963f",
"create_time": 1787899366.823917
}
}
}

payload.input 仍是一个字符串,里面是调用 tools.apply_patch 的 JavaScript;… 只省略字符串内部的部分 Patch,外层记录和 payload 的字段全部保留。第 610 行随后记录同一个 call_id 的完整返回:

{
"timestamp": "2026-08-28T06:43:13.684Z",
"ordinal": 609,
"type": "response_item",
"payload": {
"type": "custom_tool_call_output",
"id": "ctco_01a0471b-b694-7c32-a03e-51f9e31af833",
"call_id": "call_M6e9gYIj4hN8LGTAAfKh5HyW",
"output": [
{
"type": "input_text",
"text": "Script completed\\nWall time 0.0 seconds\\nOutput:\\n"
},
{
"type": "input_text",
"text": "{}"
}
],
"internal_chat_message_metadata_passthrough": {
"turn_id": "01a04704-0836-7f70-a491-f9fea964963f",
"create_time": 1787899393.684788
}
}
}

id 区分两条记录,call_id 将调用与返回配对,Metadata 中的 turn_id 标明所属轮次。这里的 Script completed 是工具脚本的完成回执,不等于应用测试通过。工具结果保存在 JSONL 中,被修改后的源码字节则保存在 Workspace 中。

读这一类记录时还要看 payload.type。custom_tool_call、custom_tool_call_output 与普通 message 承担的角色不同,虽然顶层都叫 response_item。

1.2.3 event_msg:任务进度与运行事件

第 2 行记录一轮任务开始:

{
"type": "event_msg",
"payload": {
"type": "task_started",
"turn_id": "01a04704-0836-7f70-a491-f9fea964963f",
"model_context_window": 258400
}
}

turn_id 标识这一轮;第 802 行的 task_complete 记录同一轮结束。这里的 model_context_window 是该次运行记录的窗口值,不是已经消耗的 Token 数,也不能当作所有版本模型的通用上限。

这类数据用来表达任务生命周期、Item 完成状态和用量变化,便于展示与追踪执行进度。样本中还有 item_completed、token_count 和 thread_goal_updated。它与 response_item 可以描述同一次动作:一条保存工具协议项,另一条记录对应 Item 完成,所以不能将两边的条目数相加,当成不同动作数。

1.2.4 turn_context:单轮模型与执行配置

第 9 行:

{
"type": "turn_context",
"payload": {
"turn_id": "01a04704-0836-7f70-a491-f9fea964963f",
"cwd": "/Users/lizhaolong/Desktop/Exercise",
"model": "gpt-5.6-sol",
"approval_policy": "never",
"effort": "xhigh"
}
}

这里的 turn_id 与前面的 task_started 一致,说明这些参数对应哪一轮。model 和 effort 记录模型选择与推理配置,cwd 记录执行目录,approval_policy 记录当时的审批策略;它们是历史样本值,不是本文建议采用的权限设置。

其用途是给消息和工具动作补上当时的执行条件。只有会话开头的 session_meta,不能解释后续轮次可能发生的模型或配置变化;这些材料对恢复逻辑上下文和排查行为差异都有意义。

1.2.5 world_state:更大范围的上下文配置

第 8 行的字段摘录:

{
"type": "world_state",
"payload": {
"full": true,
"state": {
"model": "gpt-5.6-sol",
"environments": {
"current_date": "2026-08-28",
"timezone": "Asia/Shanghai"
},
"skills": {
"includeInstructions": true
},
"realtime": {
"active": false
}
}
}
}

这条记录带有 full: true 标记,配置集中在 payload.state 中;示例只展示其中一部分。日期、时区、Skills 指令开关和实时会话开关,说明它描述的是 Agent 当时使用的上下文配置。原记录还包含指令、Skills 内容和权限相关材料。

turn_context 给出某轮使用的参数,world_state 则记录更大范围的上下文输入。它可以帮助检查当时有哪些环境与指令条件,也为重建这些条件提供材料。名称中的 world 不代表整台机器:这里没有保存进程堆、打开的连接或文件系统的完整字节。

1.2.6 compacted:压缩历史与窗口接续

第 833 行:

{
"type": "compacted",
"payload": {
"message": "",
"replacement_history": [
{"type":"message","role":"user"},
{"type":"message","role":"developer"},
{"type":"message","role":"developer"},
{"type":"message","role":"developer"},
{"type":"message","role":"user"},
{"type":"message","role":"user"},
{"type":"compaction"}
],
"window_number": 1,
"previous_window_id": "01a04704-067c-7653-80d2-530016a613cb",
"window_id": "01a04733-cd91-7e40-adb8-bc2e7637a693"
}
}

这条记录的 message 虽然是空字符串,replacement_history 却有 7 项:既有保留的消息项,也有一个 compaction 项。读取压缩结果不能只盯着 message 字段。 这里省略了各项的内容字段,不能将展示出来的类型和角色当成全部压缩内容。

previous_window_id 与 window_id 连接压缩前后的上下文窗口,replacement_history 则保存替换历史,为接续压缩后的上下文提供持久化材料。它与原始事件历史有不同用途:旧记录仍留在本样本的 JSONL 中,后续模型却可以使用压缩后的视图,而不必携带全部旧事件。

1.2.7 inter_agent_communication_metadata:通信控制标记

第 78 行很短:

{
"type": "inter_agent_communication_metadata",
"payload": {
"trigger_turn": false
}
}

紧随其后的第 79 行才是消息记录,属于 response_item,这里只展示收发双方:

{
"type": "response_item",
"payload": {
"type": "agent_message",
"author": "/root/workspace_discovery",
"recipient": "/root"
}
}

这组记录把通信的附加标记与消息本身分开保存。第 78 行只包含 trigger_turn: false,第 79 行包含 author、recipient 和未在此展开的 content。从字段与相邻记录看,trigger_turn 表达通信是否触发新轮次的控制意图;本文没有进一步验证调度器的完整处理流程。

因此,这类 Metadata 本身既不是消息正文,也不是完整的多 Agent 调用链。要理解谁向谁发了什么,需要结合相邻的 agent_message 读取,不能凭 Metadata 的名称假设其中已经带齐了发送方、接收方和消息内容。

七类记录共用一个 JSONL,却服务于不同需求:消息与工具载荷保留任务历史,事件与配置记录运行条件,Compaction 保留当前上下文的替换材料。样本中 2,086 条 exec 调用有对应的 2,086 条输出,另外还有 5,000 条 item_completed 与 2,369 条 token_count;15,188 行既不是用户消息数,也不是模型调用次数。

这份 JSONL 还出现了 143 段内嵌图片 Data URL,编码文本合计约占文件字节的 45.7%,且未按图片去重。JSONL 是结构化容器,里面也可能塞着多模态大对象。把文本字段做结构化压缩、全文和向量检索,与把图片字节外置并保存稳定引用,是两个需要分别处理的动作。

1.3 Workspace 中的格式、内容与实际修改

下面全部是本机存在的文件。为了避免混淆,表中区分了当前源码、测试与文档,以及能够从它们生成的构建文件;这些文件的当前内容可能已经包含该会话后续轮次的修改。

实际路径(相对 SignalBrief/)格式实际内容对 Agent 的数据角色
Sources/SignalBriefCore/FileStore.swift Swift / UTF-8 文件存储、JSON 读写、flock 进程间互斥 可编辑源码
Tests/SignalBriefCoreTests/ResilienceCoverageTests.swift Swift / UTF-8 XCTest、模拟 HTTP 数据、Sitemap 与容错测试 测试源码
Docs/ARCHITECTURE.md Markdown / UTF-8 模块、调度、采集与持久化设计 技术文档
Package.swift Swift Package Manifest 模块、Target 与构建声明 构建输入
Packaging/Info.plist XML Property List Bundle ID、版本、最低系统版本 打包输入
Skills/signalbrief-summary/SKILL.md Markdown 报告生成的任务指令 可版本化指令文件
.git/ Git 对象与索引 Commit、Tree、Blob 和暂存状态 文件版本历史
.build/workspace-state.json JSON SwiftPM 的依赖、Artifact 与格式版本 构建工具元数据
.build/build.db SQLite 构建记录 可重建的构建状态
dist/SignalBrief.app/Contents/MacOS/SignalBrief Mach-O arm64 编译后的 macOS 可执行文件 构建制品

更具体一点,这条会话的第 608 行提交了 FileStore.swift 的修改,把文件操作包进进程间互斥区。下面是其中一个 Diff 片段:

public func saveSources(_ sources: [SourceDefinition]) throws {
– try write(sources.sorted { $0.name.localizedStandardCompare($1.name) == .orderedAscending }, to: sourcesURL)
+ try withExclusiveLock {
+ try write(sources.sorted { $0.name.localizedStandardCompare($1.name) == .orderedAscending }, to: sourcesURL)
+ }
}

第 668 行又修改了 Docs/ARCHITECTURE.md:持久化方案从 actor 串行化更新为 actor 加 POSIX flock,并加入 report-checkpoints.json。代码补丁和文档补丁的工具结果分别位于第 610、670 行;当前工作目录的源码与文档也都能读到这套机制。

同一次修改留下了两种有用的数据:rollout 记录修改请求、Patch、调用关系和返回结果,Workspace 保存可以继续编译、测试和编辑的文件。Patch 依赖原文件版本,工具输出也可能截断,因此不能用一份聊天历史替代项目文件的持久化。

.build/workspace-state.json 的当前内容则很小:

{"object":{"artifacts":[],"dependencies":[],"prebuilts":[]},"version":7}

这里的 workspace-state 是 SwiftPM 的术语,保存的是构建工具状态;它不包含 Codex 的用户目标、模型上下文或下一步动作。同样,FileStore.swift 中的 report-checkpoints.json 是被开发应用的业务状态文件名,也不能据此当成 Codex 的 Checkpoint。数据角色要看谁读它、靠它继续什么工作,文件名只能提供线索。

1.4 运行中的内存与 Trace

Codex 运行时还会在进程内存中持有当前上下文、待处理调用、连接和执行中的任务。这些对象并不都能在上面的目录树里找到。Trace Span 也在运行时产生,记录请求、工具执行、耗时和错误;配置 OpenTelemetry 后,可通过 Processor / Exporter 导出到外部观测系统。Batch Processor 会暂存待导出的 Span,Trace 被接收后才进入后端的持久存储。[9][10][11]

所以这里把 Trace 理解为运行时生成、可导出的观测数据就够了,不再给它加一套与 AgentState 平级的任务状态分类。更准确地说,它起初是内存中的遥测对象,但并非永远只能在内存里:Codex 也有本地日志,本机就存在 logs_2.sqlite。本次只确认用户级 config.toml 没有显式配置 [otel],没有据此断言 Desktop 不上报任何遥测,也没有修改导出配置。

2. AgentState 的三类数据与 Codex 落盘位置

这里按 Session State、JSONL 会话记录和 Memory 组织 AgentState 的存储职责。文件和制品仍归 Workspace / 文件存储,Trace、Log 与 Eval 归观测和评估系统。JSONL 是 Codex 保存会话记录的格式,云端服务可以保留同样的事件结构,而不必把每条会话实现成一个文件。

下表的 rollout 指 1.1 节列出的 JSONL,每条 Session 各有一份;SQLite 与 memories/ 均位于 /Users/lizhaolong/.codex/。

大类子类本机位置
Session State 会话关联关系 state_5.sqlite → thread_spawn_edges
Session State 会话元数据 state_5.sqlite → threads
Session State 轮次与控制状态 thread_history_1.sqlite → thread_turns
JSONL 会话记录 原始消息与执行事件 rollout → response_item / event_msg
JSONL 会话记录 Context 配置 rollout → turn_context / world_state
JSONL 会话记录 Summary 与压缩历史 rollout → compacted
Memory 独立维护的记忆记录 memories/(本次检查为空)

Session State 保存会话当前的关系、配置和进度,JSONL 保存这些状态变化的经过;同一事实可以同时有事件记录和当前状态投影。下面按这三类细分。

2.1 Session State:关联关系、元数据与控制状态

2.1.1 会话关联关系

需要保存 Session 自身的 ID、父子关系和分支来源,才能知道哪些子 Agent 属于同一个任务。Codex 的 state_5.sqlite.thread_spawn_edges 用 parent_thread_id、child_thread_id 连接会话;前面的父、子、孙 Session 就靠这些记录组成会话树。

2.1.2 会话元数据

这部分描述会话的归属和运行配置。Codex 的 state_5.sqlite.threads 保存 id、cwd、rollout_path、权限、归档状态、历史模式等信息。其中 rollout_path 把一条 Session 的状态行连到它的 JSONL。

远端服务还需要用元数据表达 User / Tenant、Agent、Workspace 的归属与访问权限。

2.1.3 轮次与控制状态

这部分回答任务进行到哪里、某轮是否完成、恢复后有哪些动作待处理。Codex 的 thread_history_1.sqlite.thread_turns 保存 Turn ID、状态、起止时间和 JSONL 起止位置;thread_history_projection_state 另外保存历史投影读到哪里。任务目标和状态变化的事件则保留在 JSONL 的 thread_goal_updated、task_started、task_complete 中。

更完整的恢复协议还可能保存 Pending Action、审批、重试次数和幂等键。LangGraph 用 Checkpoint 保存 Graph State,Temporal 用 Workflow History 重建执行状态;它们提供的恢复语义不能直接套到本机 Codex 的这几张表上。[12][13]

状态存下来以后,还需要 Runtime / 调度器负责故障检测、重新执行和唤醒等待中的任务。Restate 对 Checkpoint 与 Durable Execution 的区分也是这个意思。[14] 对 AgentState 存储来说,要明确哪些状态已经提交、哪个执行实例有权更新;自动恢复由存储协议和运行协议一起完成。

2.2 JSONL 会话记录:原始事件、Context 配置与 Summary

2.2.1 原始消息与工具调用历史

response_item 保存用户和助手消息、工具调用参数与返回结果,event_msg 保存任务与 Item 的生命周期等执行事件。前面的第 608、610 行就是一组工具调用及返回,通过 call_id 配对。thread_history_1.sqlite.thread_items.item_json 是这些历史项的分页投影,也归在会话记录这一类。

12-Factor Agents 建议把 Agent Workflow 表达为可序列化的 Thread/Event,并从事件推导执行状态。[15] 因而这类存储需要保留消息结构、Session 内的顺序、调用与结果的关联,以及分支历史。

工具调用还会修改 Workspace 中的文件。例如,第 608 行记录修改 Sources/SignalBriefCore/FileStore.swift 的请求和 Patch,第 610 行记录工具返回;修改后的文件内容保存在 /Users/lizhaolong/Desktop/Exercise/SignalBrief/。动作及返回是会话记录,文件路径是记录中的引用,文件字节由 Workspace 保存。 引用可随消息或工具结果保存。

如果换到另一台 Sandbox,本地路径还需要映射到稳定的制品 URI 和版本。至于发邮件、创建云资源等操作,恢复时可能遇到操作已成功、结果尚未落盘的窗口,需要通过幂等键或外部操作状态避免重复执行。[16]

2.2.2 Context 配置

turn_context 保存某轮实际使用的模型、CWD、权限和推理配置;world_state 保存指令、Skills、环境等上下文配置。它们和消息保存在同一份 JSONL,说明当时是在什么条件下做出决策的。

会话元数据中的默认配置与某一轮使用过的配置可以不同。恢复历史任务时,需要能取回当时的配置或其版本,不能只加载当前最新的 System Prompt、Tool Schema 或 Skill Set。

2.2.3 Summary 与压缩后的上下文

这里的 Summary 用于压缩当前会话的上下文。Codex 样本中的对应对象是 compacted:第 833 行的 replacement_history 保存替换历史,并带有前后 Context Window 的关联标识;它不一定是一段纯文本摘要。

原始记录保存发生过的消息与事件,压缩历史保留后续推理实际使用的材料。Anthropic 的 Context Engineering 讨论了从持续增长的历史中选择和压缩模型上下文的问题。[17] 因此,已经参与后续决策的 Summary 也应保存具体版本,和 Context 配置一起归入会话记录,而不必单列一种 State。

这里保存的是应用能获得、框架能导出的 Context。Armin Ronacher 提醒过,模型 Provider 还可能有不透明的内部状态;保存了消息和 Summary,不等于已经具备跨模型迁移能力。[18]

2.3 Memory:独立创建、维护和检索的记忆

Memory 保存准备在后续任务中复用的事实、偏好、经验或规则,可以按 User、Project、Agent 等范围共享。它和只为当前会话压缩窗口的 Summary 用途不同。LangGraph 区分 Thread 内的短期状态与跨 Thread 的 Memory Store,Google ADK 也把 MemoryService 与 SessionService 分开。[19][20]

本机 /Users/lizhaolong/.codex/memories/ 在这次检查时为空,没有可展示的独立记忆结果。下面区分的是记忆模块可以采用的两种创建方式,不是断言这个 Codex 样本已经产生了它们。

2.3.1 模型与 Harness 主动维护的记忆

模型根据任务过程、用户要求或反馈判断哪些信息值得保留,再由 Harness 提供的写入工具和权限规则完成创建或更新。例如,用户明确要求记住一个项目约定,Agent 可以把它写入记忆文件、数据库或 Memory 服务。

这条路径由 Agent 主动决定写什么,不需要等待统一的消息抽取流程。LangGraph 的 Memory 文档也讨论了在执行路径中由 Agent 决定写入记忆的方式。[19] 存储侧需要保存记忆内容、作用域和更新版本;如果是从某段对话得出的判断,还应关联来源。

2.3.2 独立抽取模块生成的记忆

另一条路径由独立 Memory 模块读取原始消息与工具历史,完成抽取、合并、去重和索引。抽取可以同步触发,也可以在会话结束后或后台增量运行;Agent Loop 无须对每一条记忆分别发出写入调用。LangGraph 也讨论了后台生成记忆的方式。[19]

两条路径可以共用 Memory 存储,但应区分创建者、来源和版本:抽取规则或模型升级后,派生结果可以在授权范围内重算,用户显式维护的内容则需要单独处理冲突。

Microsoft 的《Guarding AI memory》强调记忆的来源、访问隔离,以及用户审阅、修改和删除的能力。[21] 对产品设计来说,原消息、派生记忆和检索索引的删除规则要协同,避免已删除或已撤权的信息在下一次抽取时重新出现。

3. Agent 衍生业界产品形态与数据种类的对应关系

第二节拆的是 AgentState 内部的数据。比较产品时,把 Session State 与 JSONL 会话记录合并为 AgentState 一列,业界已有独立的 Memory 产品,因此单列比较。Memory 仍属于广义 AgentState,表格分列不意味着它必须独立成为产品。

Workspace 包含工作目录和制品;观测与评估包括 Trace、Log 和 Eval;执行层包含 Runtime、Sandbox 与执行编排,承接计算和隔离职责,不另算一种数据。AgentState 列中的会话记录包含消息、工具历史、Context 配置和 Summary,不要求其他产品使用 JSONL 文件格式;观测与评估列也不要求每项产品同时覆盖三类能力。

表格对应截至 2026 年 9 月 5 日已核对的公开资料。— 表示资料未建立对应关系,不表示公司没有此类产品;† 表示可承载状态的存储原语,需要框架或应用补充状态结构与恢复协议。进入某一列表示覆盖其中相应数据,不等于提供了该列的全部能力。

公司WorkspaceAgentStateMemory观测与评估执行层
AWS [5][22][23][24] S3 / EFS / Session Storage Strands Session Manager + DynamoDB Storage AgentCore Memory AgentCore Observability / CloudWatch AgentCore Runtime [25]
Google Cloud [20][26][27][28][29] ADK ArtifactService / GCS Agent Platform Sessions / ADK Session State Memory Bank Cloud Trace Agent Runtime [30]
Microsoft Azure [31][32][33][34] Hosted Session Filesystem / Azure Storage Foundry Conversation Foundry Memory Store Application Insights Foundry Hosted Agents
Alibaba Cloud [35][36] AgenticFS / AgenticSpace AgentRun Conversation History / State AgentRun Long-term Memory AgentRun Runtime / Sandbox [37]
Tencent Cloud [3][38] COS Agent Bucket / Space TencentDB Agent Memory Tencent CLS Agent Sandbox(AGSX)[39]
Volcano Engine [4][40] TOS Agent Bucket / ObjectSet OpenViking Session Viking 记忆库 / OpenViking AgentKit Runtime / Sandbox [41]
Cloudflare [42][43][44][45][46] R2 / Sandbox Directory Backup Agent Conversation State / Durable Objects† / Workflows Agent Memory Workers / Agents / Sandbox SDK [47]
Oracle Cloud [48] Files API / Containers Conversations Project Memory Enterprise AI Applications / Code Interpreter
IBM [49] Orchestrate Chat History Orchestrate Agent Memory watsonx Orchestrate(托管 Agent)[50]
Huawei Cloud [51] SFS Turbo AgentArts Memory AgentArts Runtime
PingCAP [2][52] TiDB Cloud Filesystem TiDB Cloud Starter†
Turso [1] AgentFS VFS AgentFS KV† AgentFS Tool Call Audit agentfs run(本地 Sandbox)[53]
LangChain [12][19][54][55] LangGraph Thread State / Checkpointer LangGraph Store LangSmith Observability / Evaluation LangSmith Deployment / Sandboxes [56][57]
Temporal [13] Workflow History Workflows / Workers(执行编排)
Daytona [58] Sandbox Filesystem / Volumes Daytona Sandbox

表中最容易混淆的是 AgentState 列。Conversation / Chat History 产品主要覆盖会话记录,能否保存审批、Pending Action 或完整 Checkpoint,需要继续查接口;Temporal 的 Workflow History 支持执行恢复,但模型消息要由应用按需纳入。TiDB Labs 则明确把可查询的 Structured Task Status 放在 Cloud Starter,Filesystem 保存文件并提供语义搜索。[13][52]

4. 四类数据对应的产品职责

4.1 Workspace:工作目录与制品存储

SignalBrief 中修改后的 FileStore.swift、ARCHITECTURE.md 和构建产物,都需要保存文件内容。AgentFS 面向运行中的工作目录,提供 Path、文件读写、Rename 以及不同程度的 POSIX 兼容;Agent Drive / Agent Bucket 更侧重用户空间、制品访问、权限、版本与生命周期。两者覆盖同一类数据,但访问方式和管理重点不同。[1][2][3][4]

例如,Agent 可以在挂载目录里持续修改代码,用户则从云盘查看已提交的报告。产品也可以把这两个入口放在一起。腾讯 COS 的 Space、火山 TOS 的 ObjectSet、阿里 AgenticFS 的 AgenticSpace,都提供了按用户或租户管理文件空间的边界。[3][4][35]

会话中保存的路径把动作和文件联系起来,文件侧负责实际字节。工具调用里出现了 Patch,不能替代工作目录的完整版本;有了文件系统,也仍需有人维护任务目标、会话历史和控制状态。

4.2 AgentState:会话关系、历史与当前上下文

对应 Codex,就是共享 SQLite 中的 Session State,以及各 Session 的 rollout JSONL。前者描述会话关系、元数据和控制进度,后者保留消息、工具调用与结果、用过的 Context 配置和 Summary。新执行实例读取这些数据,才能知道任务进行到哪里,并组装下一轮模型输入。

产品接口因此围绕 Session 创建与查询、事件追加、历史分页、分支、状态提交和恢复读取展开。会话中的 call_id、Turn ID、顺序与版本需要保留,不能只存成一段可搜索的聊天文本。全文或向量检索适合找相关历史,恢复某一轮则需要读取确定的状态和事件范围。

AgentState 存储保存继续任务所需的逻辑记录,Runtime / 调度器负责拿着这些记录继续执行。 Checkpoint 若要支持可靠接续,还需要约定提交位置、待执行动作、当前写入者,以及外部操作的重试规则;数据库里有一行状态,不会自动完成故障接管。[12][14][16]

4.3 Memory:可以并入 AgentState

Memory 保存供后续任务复用的事实、偏好和经验,需要维护作用域、来源、版本、冲突、过期和删除。[19][20][21] 这些需求可以由 AgentState 存储承接。创建和维护记忆可以是一个独立模块,但并不因此需要一个独立产品、一支独立团队。

我们的产品立项初期也想做 Memory,中间看到公司内不下五个 Memory 项目已经死掉了。我对这一年独立 Memory 产品潮的判断是走歪了,核心问题在 Eval:记忆抽出来了、召回也准确,究竟有没有让 Agent 把任务做得更好?这个问题没有回答,抽取和检索能力就还不足以证明产品价值。

我在之前的 Memory 评估文章里也讨论过,评估必须跟着场景走。RCA 要检查旧经验有没有帮助验证当前故障,而不是让 Agent 更相信上一次的结论;办公任务除了文件和数值是否正确,还涉及用户对表达、格式和偏好的主观感受。需要拿无记忆版本做对照,看任务结果、耗时、Token 和旧经验带来的误导。对覆盖 RCA、办公等多类任务的通用 Agent Loop,我不认为长期记忆应该默认开启,至少要先证明当前场景的收益。这里关闭的是跨任务的长期记忆,不是当前会话历史和上下文。

这也是我更愿意把 Memory 交给垂类 Agent Loop 团队的原因:他们掌握真实任务、工具和用户反馈,能决定什么值得记、什么时候取、什么时候不用,也能为最终效果负责。独立 Memory 团队如果拿不到这层反馈,Eval 就很容易停在抽取和召回指标上。它还要承担抽取、演化、Embedding、存储、检索和持续评估的成本;全量抽取一旦开启,成本就随消息量增长,不会等到业务收益得到证明才发生。

所以我的产品选择是:记忆策略和 Eval 由 Agent Loop 团队负责,通用的持久化、版本、权限与检索由 AgentState 提供。Agent Loop 可以自己通过模型与 Harness 写入,也可以调用独立的抽取模块,不必从头实现每项算法。就存储产品形态而言,我倾向于 Memory 最终会并入 AgentState;即使保留单独的 Memory API,也可以是 AgentState 中的一组接口,不必再维护一套独立存储。

4.4 Trace / Log / Eval:执行诊断与质量评估

Trace 记录调用链、耗时和错误,Log 保存运行诊断或审计记录,Eval 保存评估样本、评估器及其版本、实验结果和评分。AWS AgentCore 的遥测存入 CloudWatch,Google Agent 遥测可进入 Cloud Trace,Foundry Trace 存入关联的 Application Insights;OpenAI Agents SDK 也提供 Tracing 能力。[24][29][34][59]

Eval 不一定只是运行时上报的数据。LangSmith 既支持对线上执行记录做评估,也支持维护数据集、运行离线实验、比较不同版本,还可以把失败 Trace 转成后续回归用例。[55] 因而这一类产品不仅接收遥测,也管理用于诊断和改进 Agent 的数据。

同一次工具调用可以同时存在于 JSONL 和 Trace 中:前者参与任务历史与接续,后者方便排查耗时和失败。它们通过 Session ID、Call ID、Trace ID 关联,可以使用不同的保留策略。若评分进入了 Agent 的决策流程,参与决策的评分值与评估版本也需要进入会话记录,不能只留在评估后台。

5. AgentState 上云后的任务接续与成本

5.1 云端 Agent Loop 与本机工具

状态的保存位置与 Agent Loop 的运行位置,是两个独立选择。本地 Loop 配合云端 AgentState,可以持久保存任务并支持跨机接续;若产品目标是用户关机后仍继续工作,就需要云端 Loop 与调度器接管执行,本机只保留交互入口和受控的 Tool Endpoint。Anthropic 的 Managed Agents 将 Harness、Session 和 Sandbox 解耦,为这种部署提供了相近的架构依据。[6]

各端读取同一任务的 AgentState 和制品,就可以查看同一份进度与成果,无须各自跑一个 Loop。本机离线时,云端继续不依赖本地资源的步骤;需要本地浏览器、桌面应用或未上传文件时,再进入等待。等待审批或外部事件期间,也可以保存状态、释放计算,条件满足后再唤醒。[14]

本地 Loop 更容易直接使用现成工具,也保留数据不上云的选择;云端 Loop 则让任务持续运行不再依赖用户设备在线,但执行和存储费用也由云端持续承担。这是部署方式的取舍,独立 AgentState 存储应当支持两种方式。

5.2 会话恢复依赖的文件版本

假设 Agent 生成了 report.md,JSONL 已记录成功,但报告还在即将回收的 Sandbox 里。换台机器后,新 Loop 能读到报告已生成的消息,却拿不到报告。消息里只有 /workspace/report.md 也不够:新 Sandbox 的同一路径未必有同一份文件。

一种做法是由 Harness 先把报告上传到文件服务,确认持久化,再把文件 ID、版本号和这一步的完成状态一起提交到 AgentState。假如记录的是版本 v3,恢复到这一步就读 v3,而不是后来被覆盖的最新版本。AgentState 可以不存文件内容,但必须能找到恢复任务所依赖的那一版文件。 这里要约定的是会话进度与文件版本的对应关系。

5.3 完整历史的存储与索引成本

前面的 Codex 样本,仅 18 条 Session 的 JSONL 就有约 193.94 MiB,包含重复历史和内嵌图片。全部上云后,原始记录、索引、Embedding 和数据传输都要计入成本。结构化存储便于压缩和按需读取,但不意味着每条工具输出都值得建全文和向量索引。

用于继续任务的 Session State、最近消息和 Summary 需要快速读取;旧历史是否建索引、大内容是否外置,可以按实际使用频率决定。产品需要同时控制保存完整历史的成本,以及新 Loop 取回上下文的耗时。

6. 结束语:我的产品思路

我现在更关心的是独立于 Runtime 和 Sandbox 的结构化 AgentState 存储:保存 Session 的关联、元数据和控制状态,保留原始消息、工具历史以及用过的 Context / Summary,支持结构化压缩、全文和向量检索。Memory 可以由模型与 Harness 主动维护,也可以由独立模块从这些原始记录中抽取。

这个方向不接管制品。文件交给 AgentFS、Agent Bucket 或云盘,状态里保存稳定引用;Trace、Log 和 Eval 由观测与评估系统管理,需要时通过 ID 关联。Memory 的存储并入 AgentState,是否开启、如何维护和评估,则由使用它的 Agent Loop 团队决定。

最终希望支撑的体验,还是开头说的:云端持续托管任务,本机按需提供工具,换设备后能看到同一份进度和制品。为此,存储需要明确保存什么、何时算提交、恢复要读哪个版本;持续执行则交给运行和调度系统。接下来需要权衡的是完整历史与索引的存储成本、恢复速度,检索方式以及这套接口能适配多少种 Harness。

7. 参考资料

[1] Turso: The Missing Abstraction for AI Agents — The Agent Filesystem

[2] TiDB Cloud Filesystem: The Workspace Your Agents Share

[3] 腾讯云对象存储:智能体桶优势

[4] 火山引擎对象存储:ObjectSet 概述

[5] AWS: Introducing strands-dynamodb-storage

[6] Lance Martin, Gabe Cemaj, Michael Cohen / Anthropic: Scaling Managed Agents — Decoupling the brain from the hands(2026-04-08)

[7] Pekka Enberg: Towards a Disaggregated Agent Filesystem on Object Storage(2026-01-11)

[8] Codex App Server

[9] Codex Advanced Configuration: Observability and Telemetry

[10] Codex Configuration Reference: otel.trace_exporter

[11] OpenTelemetry: Tracing SDK — Span Processors and Exporters

[12] LangGraph: Persistence

[13] Temporal: AI Agent Reference Architecture

[14] Giselle van Dongen / Restate: Agent checkpointing is far from production-grade resiliency(2026-06-15)

[15] 12-Factor Agents: Unify Execution State and Business State

[16] Keith Tenzer, Joshua Smith / Temporal: What is idempotency? And why it matters for durable systems

[17] Anthropic: Effective Context Engineering for AI Agents

[18] Armin Ronacher: LLM APIs are a Synchronization Problem(2025-11-22)

[19] LangGraph: Memory Overview

[20] Google ADK: Conversational Context — Session, State, and Memory

[21] Natalie Isak, Sarah Cooley / Microsoft: Guarding AI memory(2026-06-22)

[22] Amazon Bedrock AgentCore: File System Configurations

[23] Amazon Bedrock AgentCore: Memory

[24] Amazon Bedrock AgentCore: Observability

[25] Amazon Bedrock AgentCore: Host agents or tools with AgentCore Runtime

[26] Google ADK: Artifacts

[27] Google Cloud: Remember this — Agent State and Memory with ADK

[28] Google Cloud: Agent Platform Sessions Overview

[29] Google Cloud: Observability for AI Agent Developers

[30] Google Cloud: Agent Runtime

[31] Microsoft Foundry: Hosted agents in Foundry Agent Service

[32] Microsoft Foundry Agent Service FAQ

[33] Microsoft Foundry: Give a Hosted Agent Persistent Memory

[34] Microsoft Foundry Tracing and Data Handling

[35] Alibaba Cloud NAS: What is AgenticFS

[36] Alibaba Cloud AgentRun: Memory Store

[37] Alibaba Cloud: What is AgentRun?

[38] TencentDB Agent Memory:Memory 介绍

[39] 腾讯云:Agent 沙箱服务(AGSX)

[40] 火山引擎:Viking 记忆库与 OpenViking Service

[41] 火山引擎 AgentKit:智能体运行时与沙箱工具

[42] Cloudflare Agents: Store and Sync State

[43] Cloudflare Agents: Conversation State and Memory

[44] Cloudflare Agent Memory

[45] Cloudflare Sandbox SDK: Directory Backups

[46] Cloudflare Agents: Using Agents with Workflows

[47] Cloudflare Sandbox SDK: Overview

[48] Oracle Cloud: Enterprise AI Agents GA

[49] IBM watsonx Orchestrate: Memory Behavior and Limits

[50] IBM watsonx Orchestrate ADK: Importing and deploying agents

[51] 华为云 AgentArts:智能体运行时介绍

[52] TiDB Labs: Introduction to TiDB Cloud Filesystem

[53] Turso AgentFS: CLI Reference

[54] LangChain: On Agent Frameworks and Agent Observability

[55] LangSmith Evaluation

[56] LangSmith Deployment

[57] LangSmith Sandboxes

[58] Daytona: Persistence

[59] OpenAI Agents SDK: Tracing

赞(0)
未经允许不得转载:171主机测评 » 从一到无穷大 #87:AgentState 的产品定位与设计取舍
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址