会话持久化与状态管理

入门06:会话持久化与状态管理
这是 Harness 保存聊天记录的机制。简单说就是:"不存当前状态,只存发生了什么"——就像 Git 只记录每次 commit 的变更,不保存最终结果。
1. 事件溯源是啥?(用余额的例子讲)
传统方式存数据:
数据库里:用户余额 = 100元
下次改:直接覆盖成 200元
事件溯源方式:
事件1: 2024-01-01 充值 50元 → 余额变成 50
事件2: 2024-01-02 消费 20元 → 余额变成 30
事件3: 2024-01-03 充值 70元 → 余额变成 100
区别在哪? 传统方式只存"当前值",事件溯源存"所有发生过的事"。
好处(用前端视角理解):
- 完整历史:就像 Git 的
git log,能看到每一步操作 - 可回溯:可以回到任意时间点,就像
git checkout - 可分叉:从某个历史点创建新分支,就像
git branch - 永不丢失:只追加不修改,就像
git commit --amend是不允许的
2. 架构分层
内存层:SessionStore
就像 Vuex 的 store——管理所有会话的生命周期,但状态是"算"出来的,不是直接存的。
代码位置:packages/core/session/src/index.ts
class SessionStore extends Service {
create(): Session // 创建新会话
fork(session: Session): Session // 分叉(类似 git branch)
// ...
}
持久化层:SessionPersistence
就像 localStorage + 数据库的结合体——把内存中的事件写到硬盘。
代码位置:packages/session/session-persistence/src/index.ts
abstract class SessionPersistence extends Service {
abstract write(sessionId: string, events: SessionEvent[]): Promise<void>
abstract read(sessionId: string): Promise<SessionEvent[]>
abstract list(): Promise<SessionInfo[]>
abstract delete(sessionId: string): Promise<void>
}
两种存储方式
JSONL 后端(packages/session/session-persistence-jsonl/):
- 每个会话 = 一个
.jsonl文件 - 每行 = 一个 JSON 格式的事件
- 追加写入,不支持随机删除
- 简单、可靠、人类可读
SQLite 后端(packages/session/session-persistence-sqlite/):
- 存在 SQLite 数据库里
- 支持索引和复杂查询
- 适合大量会话
3. 消息派生机制
这是关键:AI 看到的对话历史不是直接存的,而是从事件日志"算"出来的。
graph LR
subgraph "事件日志(不可修改)"
E1[用户: "帮我搜索"]
E2[回合开始]
E3[AI: "好的,我搜一下"]
E4[步骤开始]
E5[工具: 搜索结果]
E6[步骤结束]
E7[AI: "这是结果"]
E8[回合结束]
end
subgraph "派生消息(AI 看到的)"
M1[用户: "帮我搜索"]
M2[AI: "好的,我搜一下"]
M3[工具: 搜索结果]
M4[AI: "这是结果"]
end
E1 --> M1
E3 --> M2
E5 --> M3
E7 --> M4
注意看:回合开始、步骤开始、步骤结束、回合结束 这些事件不会传给 AI。只有 用户消息、AI 回复、工具结果 这些"表面"事件才会。
Surface(表面)机制
就像数据库的 View(视图)——不是所有字段都暴露给用户,只暴露需要的。
// 这个事件会"浮出"水面,AI 能看到
session.append('user/message', data, { surface: true })
// 这个事件不会出现在 AI 的上下文中
session.append('turn/start', data, { surface: false })
4. 会话投影
就像 Vue 的 computed 属性——从原始数据"算"出各种视图。
代码位置:packages/session/session-projection/src/index.ts
class SessionProjection {
build(events: SessionEvent[]): Projection // 从零构建
update(projection: Projection, newEvents: SessionEvent[]): Projection // 增量更新
}
5. 会话查询
就像数据库查询——按条件搜索、过滤、追溯。
代码位置:packages/session/session-query/
class SessionQueryEngine extends Service {
readEvents(sessionId: string): Promise<SessionEvent[]> // 读事件
search(query: string): Promise<SearchResult[]> // 全文搜索
filterEvents(filter: EventFilter): Promise<SessionEvent[]> // 条件过滤
trace(eventId: string): Promise<EventTrace[]> // 追溯事件链
}
AI 自己也能查历史会话:
| 工具名 | 干啥的 |
|---|---|
session_event_read | 读会话事件 |
session_event_search | 搜索会话事件 |
session_event_trace | 追溯事件链 |
session_search | 搜索会话 |
session_trace | 追溯会话 |
6. 会话压缩
聊天时间长了,事件太多,上下文窗口装不下怎么办?压缩!
就像 Git 的 git rebase——把一堆小 commit 合并成一个。
代码位置:packages/compaction/compaction/src/index.ts
abstract class CompactionEngine extends Service {
async compact(options: CompactionOptions): Promise<CompactionResult>
async pruneToolResults(options: PruneOptions): Promise<PruneResult>
}
7. 会话生命周期
要点总结
- 事件溯源 = 不存当前状态,只存"发生了什么",像 Git 只追加不修改
- Session = 内存中的事件列表,不可修改
- 消息派生 = AI 看到的内容从事件日志"算"出来,不是直接存的
- Surface 机制 = 控制哪些事件"浮出"水面给 AI 看到
- 两种存储:JSONL(简单可靠)和 SQLite(支持索引)
- 会话压缩 = 把历史事件合并成摘要,解决上下文超限
进阶阅读
- 核心 Session 源码:
packages/core/session/src/ - 持久化服务:
packages/session/session-persistence/src/ - 查询引擎:
packages/session/session-query/src/ - 名词速查:附录-AI 概念名词速查手册
- 下一步:07-沙箱与安全机制