Agent Engineering 101|06|Session Store
长任务 Agent 需要持久状态。mini-agent-harness 用 JSONL entry tree 保存消息、active leaf 和 summary entry。
核心结论
配套代码仓库: github.com/llm-101/mini-agent-harness
读完本文,你应该能回答
- 长任务 Agent 为什么不能只依赖内存里的 messages 数组?
- 什么状态必须在进程退出后继续存在?
- active leaf / session entry 解决了什么问题?
- session store 如何支撑 continue、resume 和 compact?
本篇在系列中的位置
- 上一篇:05 Messages and Events 区分了状态事实和过程事实。
- 本篇:本文说明长任务 Agent 如何把状态写成可恢复的 session。
- 下一篇:07 Tree History 会把线性会话扩展成可分支历史。
贯穿例子
本系列会反复使用同一个任务来连接各章:
用户说:“帮我修复这个 repo 里的 failing tests。”
在这个任务里,Session Store 保证进程中断后仍然知道已经读过哪些文件、跑过哪些测试、失败堆栈是什么、当前停在哪一步。读者要关注的是:长任务 Agent 的连续性来自持久状态,而不是一次模型调用的记忆。
- Session Store 是 Agent 的持久状态层。它把每条消息保存为 entry,并用 parentId 连接成 active branch。
- 内存里的 messages 数组无法支持进程重启、长任务恢复、分支、压缩和审计。Agent 需要一个简单但明确的状态事实源。
- 在
mini-agent-harness中,Session Store 的重点不是数据库选型,而是明确哪些运行时事实必须落盘。
定义
Session Store 是 Agent 的持久状态层。它把每条消息保存为 entry,并用 parentId 连接成 active branch。
为什么要单独看这一层?
只要任务会跨多轮、跨工具、跨进程,状态就不能停留在内存数组里。Session Store 的作用,是把“还能继续工作”变成一个工程能力。

边界
这一层的职责可以拆成几个稳定部分:
- JSONL File:每行一个 SessionEntry,易读、易调试
- MessageEntry:保存 AgentMessage 与 parentId
- SummaryEntry:保存 compact 后的旧历史摘要
- activeLeafId:当前会话分支的叶子节点
- appendMessage:写入新消息并移动 active leaf
- messagesForActiveBranch:从 leaf 回溯并重建上下文消息
这一层的边界可以用一个问题检验:如果它的内部实现变化,模型适配、工具执行、状态存储和产品外壳是否都不需要跟着重写?如果答案是否定的,说明这个边界还没有真正收束变化。
代码锚点
本篇主要对应这些模块:
src/session/jsonl-session-store.tssrc/session/session-tree.tssrc/cli/export-session.ts
阅读代码时建议先看类型,再看运行路径。
类型定义告诉你这一层暴露什么 contract;运行路径告诉你这个 contract 在 Agent 执行中何时被消费、何时被写回、何时被产品层看见。
运行流程

一次典型执行可以概括为:
- Load:读取 JSONL
- Append:createMessageEntry
- Persist:重写文件
- Branch:getBranch
- Context:messagesForActiveBranch
- Durability:进程退出后恢复
- Auditability:状态可读
- Compaction Ready:summary entry 可插入
这里最容易被忽略的是“中间态”。生产级 Agent 不是只关心最终答案;它还要在运行过程中展示进度、捕获错误、记录 usage、允许取消,并把可恢复状态写回 session。
读者抓手:哪些状态必须活过进程重启
| 状态 | 是否必须持久化 | 原因 |
|---|---|---|
| 用户输入和 assistant 回复 | 是 | 恢复任务需要知道已经说过什么 |
| tool call 和 tool result | 是 | 下一轮推理依赖真实执行结果 |
| active leaf | 是 | 恢复时要知道当前分支停在哪里 |
| streaming text delta | 通常否 | 它是过程信号,最终 message 才是事实 |
| spinner / UI selection | 否 | 这是产品展示状态,不应进入 runtime core |
| summary entry | 是 | compaction 后的连续性依赖它 |
Session Store 的边界越清楚,Agent 越容易从失败、中断和重启中恢复。
可迁移伪实现:会话持久化
下面的伪代码是机制抽象,不对应真实 API 或文件结构。它只用来说明这一层的控制点:
await session.load();
await session.appendMessage(userMessage);
const branch = session.messagesForActiveBranch();
// branch becomes the source for ContextBuilder
这个草图的价值在于说明控制点,而不是提供可复制的库代码。
真正的工程实现还要处理错误、取消、并发、token 预算、日志、权限、序列化和 provider 差异。
工程原则
将这一层从 Agent 系统中拆出来,通常带来四个直接收益。
第一,可替换。
外部系统、模型 provider、工具集合或产品外壳变化时,核心运行时不必整体重写。
第二,可观测。
边界清晰后,事件、trace、usage、错误和状态迁移都有稳定落点。
第三,可恢复。
只要状态写入 session,运行时就可以在进程重启、工具失败或长任务中断后继续推理。
第四,可治理。
权限、脱敏、审批、路径保护和执行策略可以放在稳定 hook 或 runtime boundary 上,而不是只靠 prompt 约束。
和 Agent Harness 的关系
Agent = Model + Harness 这个公式的重点,不是把模型之外的所有东西都称为“工程杂活”。
相反,它提示我们:模型之外存在一套必须被设计的运行时系统。
Session Store 就是这套系统中的一个切面。它不替代模型能力,也不替代产品体验;它让模型能力可以被组织成可执行、可观察、可恢复、可治理的任务流程。
小结
Session Store 的核心价值,是把一类容易扩散的复杂性收束到明确边界中。
在 mini-agent-harness 中,这个边界被刻意写得较小,方便阅读和教学。但它对应的问题并不小:只要一个 Agent 要长期运行、调用工具、管理上下文、支持 UI、保存状态并处理失败,这个边界就会出现。
下一步可以继续沿着系列计划,把这些边界组合成完整 Agent Harness:模型边界、工具边界、状态边界、上下文边界、扩展边界和产品外壳边界。