Agent 存储:JSONL、SQLite 与数据库

“Agent 应该使用 JSONL 还是 SQLite?”这个问题少了最重要的前半句:准备存什么,谁会读写,崩溃后要恢复到哪里?

Transcript、Checkpoint、长期记忆和 Artifact 的生命周期并不相同。把它们统称为“Agent Memory”,再比较文件扩展名,很容易得到一张看似整齐、实际无法指导设计的优缺点表。

本课先把数据分盒,再讨论 Backend。

1. 同一个 Session 里混着八类数据

1.1 Session 元数据与 Transcript

Session 元数据保存会话 ID、模型、创建时间、当前分支和生命周期。Transcript 保存实际发生的 User、Assistant、Tool Call 与 Tool Result。

Transcript 是“发生过什么”的事实来源,却不等于下一轮全部发送给模型。

1.2 Prompt View

Prompt View 是本次真正给模型看的派生列表:

1
2
3
4
5
System Prompt
+ Compaction Summary
+ 最近原文
+ 当前轮次
+ 按需召回的长期记忆

过滤 Prompt View 不应顺手删除 Transcript。它通常只存在于内存,下一轮可以重新构造。

1.3 两种 Checkpoint

Compaction Checkpoint 为上下文窗口服务,记录摘要、保留边界和最近原文。

Workflow Checkpoint 保存工作流某一步的完整状态。LangGraph 用 thread_id 关联一系列 Graph State Checkpoint,让中断恢复、Human-in-the-loop 和 Time Travel 成为可能。LangGraph Checkpoint

二者都叫 Checkpoint,恢复对象却不同:一个恢复模型上下文,一个恢复工作流执行状态。

1.4 Tool Execution Ledger

Ledger 记录工具执行事实:

1
2
3
4
5
tool_call_id
execution_id
idempotency_key
approved → running → succeeded / failed / unknown
result 或错误证据

它用于幂等、审计和崩溃恢复,通常不进入 Prompt。Transcript 可以只保留一条最终 Tool Result,Ledger 则要保留多次执行尝试。

1.5 长期 Memory / Store

长期记忆跨 Session 保存偏好、规则和项目知识。它需要 namespace、更新、删除、过滤与召回,不等于“保存很久的聊天记录”。LangGraph 也把单 Thread 的 Checkpointer 与跨 Thread 的 Store 分成两个系统。LangGraph Persistence

1.6 Artifact、索引与缓存

大型日志、文件、图片和二进制输出属于 Artifact。Transcript 只放状态、小预览、路径与 Hash,完整内容留在文件系统或对象存储。

FTS、向量索引和缓存属于可重建数据。索引可以从 Transcript 或 Memory 重建,不应反过来成为唯一事实来源。

2. 访问模式决定 Backend

需求更合适的起点
本地、单 Writer、顺序追加与人工检查JSONL
同机事务、索引、筛选、FTS、有限并发SQLite
多 Worker 共享、低延迟、TTL、协调Redis,另定持久化策略
多主机、多写者、强约束、复杂查询PostgreSQL
只想把对话续接交给 ProviderServer-managed Conversation/Response ID

2.1 JSONL:顺序事实流

JSONL 一行一条 Event,适合追加、Tail、导出和人工检查。Pi Coding Agent CLI 默认就是每 Session 一个 JSONL,Entry 通过 id/parentId 组成树。Pi Session Format

代价也很直接:多 Writer 需要自己加锁,跨多行原子提交需要额外协议,任意字段查询通常要扫描或另建索引,损坏行和 Schema 迁移也由应用处理。

JSONL 不是“不可靠”,只是把数据库替你做的工作留给应用。

2.2 SQLite:同机事务与查询

SQLite 把 Schema、事务、唯一约束、索引和 FTS 放进一个本地数据库。WAL 可以让 Reader 与 Writer 并行,但所有进程必须在同一主机,而且同一时刻仍只有一个 Writer。SQLite WAL

它适合桌面 Agent、CLI/UI/后台任务共享状态、Session 搜索和本地 Ledger。它不自动解决多主机共享、高写入争用、备份和 Migration。

2.3 Redis 与 PostgreSQL

Redis 适合共享短期状态、TTL、锁与任务流,但“在 Redis 里”不等于永久可审计。RDB、AOF、复制和故障切换都需要明确选择。Redis Persistence

PostgreSQL 适合多实例、多租户和多写者,能用事务、唯一约束和关系查询保护 Session、Checkpoint 与幂等键。它是否值得,仍取决于团队是否已经具备迁移、备份、监控和恢复能力。

2.4 服务端续接不是本地状态的替代品

OpenAI 可以用 Conversation ID 或 previous_response_id 续接对话;Agents SDK 也支持 OpenAI Conversations Session。这样能少管理一份客户端历史,但本地 Tool Ledger、Artifact、权限记录与业务 Memory 仍要单独设计。OpenAI Conversation State

3. 五个框架为何没有同一个答案

案例当前默认或主要路径说明
PiCoding CLI 默认 JSONL;Agent Core 可选 SQLite产品默认与框架 Backend 可以不同
OpenClaw持久会话每 Agent SQLite;Incognito 在内存旧 JSONL 留在迁移、归档和诊断边界
Hermesstate.db + WAL + FTS多进程、搜索和 In-place Compaction 推动 SQLite
LangGraph可替换 Checkpointer 与 StoreSQLite 适合本地;PostgreSQL 面向生产共享状态
OpenAI Agents SDKSession Protocol + 多种 BackendSQLite、Redis、SQLAlchemy、MongoDB 或服务端续接都可替换

Pi 很能说明问题:默认 Coding Agent CLI 继续使用 JSONL,同一仓库却提供 SQLite Session Backend、Migration、物化视图和 FTS。不能用一句“Pi 使用 JSONL”概括整个项目。Pi SQLite Backend

OpenClaw 与 Hermes 的 SQLite 也不是“把 JSONL 换个扩展名”。它们需要 Session 列表、并发写协调、全文搜索、软归档和压缩状态,因此数据库开始回本。OpenClaw Database-firstHermes Session Storage

4. 当前教学代码为什么仍用 JSONL

本专栏代码的存储需求是逐步长出来的:

1
2
3
4
5
6
7
8
02_rember.py
最新 Checkpoint JSON:summary + turns

03_context.py
append-only Session JSONL:Message + Compaction

04_tool_reliability.py
同一 JSONL 增加 Tool Execution Ledger

代码分别见 02_rember.py03_context.py04_tool_reliability.py

当前仍是本地单进程、单 Writer,主要操作是追加、顺序回放和人工观察。迁移 SQLite 会增加 Schema 与 Migration,却没有解决新的真实问题。

出现下面任一需求时,SQLite 才开始值得:

1
2
3
4
5
多个本地进程需要共享 Session
频繁查询所有 running / unknown Execution
用唯一约束保护 idempotency_key
跨 Session 搜索与筛选
多条状态需要同一事务提交

如果进一步变成多主机、多 Worker,SQLite 又可能不够,此时才考虑 PostgreSQL 或其他服务。

5. 最小决策树

1
2
3
4
5
6
7
8
本地单 Writer,只追加和回放?
├─ 是 → JSONL
└─ 否
├─ 同机,需要事务、索引或 FTS? → SQLite
├─ 多 Worker,需要低延迟或 TTL? → Redis,并单定持久化
├─ 多主机、多写者、强约束? → PostgreSQL
└─ 只想托管对话续接? → Server-managed
本地 Ledger、Artifact、Memory 仍需设计

最后记住两句话:数据库只是“纸条放在哪里”,不是记忆机制本身;JSONL 不是低级,SQLite 也不是高级,访问模式不同而已。

参考资料