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 | System Prompt |
过滤 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 | tool_call_id |
它用于幂等、审计和崩溃恢复,通常不进入 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 |
| 只想把对话续接交给 Provider | Server-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. 五个框架为何没有同一个答案
| 案例 | 当前默认或主要路径 | 说明 |
|---|---|---|
| Pi | Coding CLI 默认 JSONL;Agent Core 可选 SQLite | 产品默认与框架 Backend 可以不同 |
| OpenClaw | 持久会话每 Agent SQLite;Incognito 在内存 | 旧 JSONL 留在迁移、归档和诊断边界 |
| Hermes | state.db + WAL + FTS | 多进程、搜索和 In-place Compaction 推动 SQLite |
| LangGraph | 可替换 Checkpointer 与 Store | SQLite 适合本地;PostgreSQL 面向生产共享状态 |
| OpenAI Agents SDK | Session Protocol + 多种 Backend | SQLite、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-first、Hermes Session Storage
4. 当前教学代码为什么仍用 JSONL
本专栏代码的存储需求是逐步长出来的:
1 | 02_rember.py |
代码分别见 02_rember.py、03_context.py 和 04_tool_reliability.py。
当前仍是本地单进程、单 Writer,主要操作是追加、顺序回放和人工观察。迁移 SQLite 会增加 Schema 与 Migration,却没有解决新的真实问题。
出现下面任一需求时,SQLite 才开始值得:
1 | 多个本地进程需要共享 Session |
如果进一步变成多主机、多 Worker,SQLite 又可能不够,此时才考虑 PostgreSQL 或其他服务。
5. 最小决策树
1 | 本地单 Writer,只追加和回放? |
最后记住两句话:数据库只是“纸条放在哪里”,不是记忆机制本身;JSONL 不是低级,SQLite 也不是高级,访问模式不同而已。