0
0
0

会话持久化与状态管理

2026-08-14
2026-09-07
会话持久化与状态管理
文章摘要
|

入门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. 架构分层

查询层
持久化层(存到硬盘)
运行时内存
维护层
Compaction
会话压缩
SessionStats
统计
SessionTitle
标题生成
SessionProjection
投影
SessionQuery
查询引擎
全文搜索
SessionPersistence
持久化服务
persistence-jsonl
JSONL 文件存储
persistence-sqlite
SQLite 数据库
Session
内存中的事件列表
SessionStore
会话管理器

内存层: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 属性——从原始数据"算"出各种视图。

事件日志
投影: 消息列表
投影: Token 统计
投影: 会话摘要
投影: 自定义视图

代码位置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

完整事件日志
压缩引擎
选择压缩范围
AI 生成摘要
用摘要替换
精简后的事件日志
abstract class CompactionEngine extends Service {
  async compact(options: CompactionOptions): Promise<CompactionResult>
  async pruneToolResults(options: PruneOptions): Promise<PruneResult>
}

7. 会话生命周期

Agent引擎持久化SessionStore用户Agent引擎持久化SessionStore用户loop[每次交互]创建会话存到硬盘绑定到 Agent发消息追加事件刷到硬盘关闭会话标记完成内存清理

要点总结

  • 事件溯源 = 不存当前状态,只存"发生了什么",像 Git 只追加不修改
  • Session = 内存中的事件列表,不可修改
  • 消息派生 = AI 看到的内容从事件日志"算"出来,不是直接存的
  • Surface 机制 = 控制哪些事件"浮出"水面给 AI 看到
  • 两种存储:JSONL(简单可靠)和 SQLite(支持索引)
  • 会话压缩 = 把历史事件合并成摘要,解决上下文超限

进阶阅读

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或者给予支持!