深色模式
03 · pi-durable — 会话 / 任务 / 文档 持久化运行时
包路径:
packages/durable(registry 名@earendil-works/pi-durable) 定位:把 agent 的会话、任务、文档以可持久化、可观察、可回放的方式落库。依赖chord的 replicated state 与 delta。 配套文档:docs/spec.md、docs/pico-v5-handoff.md、docs/pico-v5-chord-usage.md、docs/chord-delta-findings.md(建议配合阅读)。
1. 目录结构与关键文件(67 个 src 文件)
| 目录 / 文件 | 作用 |
|---|---|
src/session/session.ts | Session 内核:单条 mutation 线、文档 tracker 缓存、已提交状态才可观察 |
src/session/transaction.ts | Transaction(提交内的读写) |
src/session/observation.ts | 已提交状态的观察源(state / watch) |
src/session/forks.ts observation.ts transaction.ts | fork / 观察 / 事务 |
src/storage/jsonl/* sqlite/* | 两种存储后端(JSONL 与 SQLite,含 cloudflare) |
src/storage/scan.ts memory.ts | 扫描与内存后端 |
src/harness/harness.ts | Harness:在 Session 之上加会话句柄、注册表、任务调度 |
src/harness/agent.ts compaction.ts scheduler.ts submissions.ts task-graph.ts live.ts inbox.ts usage.ts provider.ts view.ts context.ts | 22 个文件:durable 版 agent loop / 压缩 / 任务调度 / 提交队列 / 任务图 |
src/tools/* | 内置工具:bash / edit / edit-diff / read / write / image / path-utils / file-mutation-queue |
src/env/* | 执行环境:decode / node / node-watch / line-scan |
src/testing/* | 存储 / 环境 conformance 测试、benchmark、runner |
src/documents.ts entries.ts tasks.ts ids.ts truncate.ts types.ts | 文档 / 条目 / 任务 / ID / 截断 / 类型定义 |
2. 两层内核:Session 与 Harness
Session(session/session.ts)
createSession(storage) → SessionImpl。核心特征:
- 单条 mutation 线:所有 commit / read 都经
#enqueue,串成Promise链(#tail),保证同一时刻只有一个变更在跑,多读派生看到一致状态。 - 已提交状态才可观察:只有 committed state 对外可见;commit 回调、storage 落盘、adoption、publication 都在"线"上排队,listeners 之后才跑。
- 文档模型(document):
snapshot(token, ...)/documentState(token, ...)/watchDoc(token, ...)按 token 定位文档(session / conversation / task 三级作用域 + family key)。 - 回放(rewind):
snapshotAsOf(token, conversationId, atEntryId, context)能读到某个 entry 之前的历史文档状态——这是"session 是树"的持久化落地。 - 提交流程
#runCommit:构造Transaction→ 变更 →tx.settleSuccess()拿到 writes →storage.commit(writes)落盘(一旦 admission 就不受取消影响)→tx.adopt(seq)采纳到内存 tracker →#publish通知 commit listeners。 - 毒化(poison):storage 已提交但 adoption 失败会标记 poison,之后拒绝新操作,要求重开 Session。
Harness(harness/harness.ts)
Harness.open(storage, options, context) → HarnessImpl extends SessionImpl。在 Session 之上增加:
- 会话句柄
ConversationImpl:agent()/configure()/submit()/compact()/reset()/fork()/abort()/context()/entries()/viewState()/watch()。 - 内置文档:每次创建/分叉会话时自动建
pi.live/pi.inbox/pi.usage/pi.provider/pi.agent(conversationCreated钩子)。 - 任务调度
TaskScheduler+Submissions+TaskGraphView:把"提交一个输入"变成"调度一个任务",任务可运行/排队/放置/终止。 - 注册表校验:
open时检查BUILTIN_TASKS是否都在 registry 中,缺则报错。 - 调用绑定
boundConversation:任务/工具调用的每个操作先校验 invocation 并在其 signal 下运行,invocation 结束后自动 reject(已 admission 的工作仍持久)。
3. 与 chord 的关系
session.ts 与 harness.ts 都从 @earendil-works/chord 引入 replicatedState、track(delta)、Context、withoutAbortSignal 等。文档值在内存中以 delta tracker 维护(track(value)),已提交变更以 Op(不可变 delta) 推进观察者(observedOperations)。也就是说:durable 的"文档 + 观察 + 回放"能力,底层全部由 chord 的 replicated state + delta 提供。
4. 存储后端
storage/jsonl/:每条 entry 一个 JSONL 文件,适合本地会话。storage/sqlite/:database.ts/migrations.ts/storage.ts/node.ts/cloudflare.ts,支持服务端/无服务器环境。storage/memory.ts:测试用。testing/storage-conformance.ts:存储后端必须通过的 conformance 测试(自定义后端需实现)。
5. 阅读清单(建议顺序)
docs/spec.md—— 整体规格(先看)。src/session/session.ts——createSession/commit/snapshot/watchDoc/snapshotAsOf/#runCommit/#publish。src/session/transaction.ts+observation.ts—— 事务与观察模型。src/harness/harness.ts——Harness.open/ConversationImpl/conversationCreated/boundConversation。src/harness/scheduler.ts+submissions.ts+task-graph.ts—— 任务调度三件套。src/harness/compaction.ts—— 压缩如何插入 summary entry 而不丢原 entry。src/storage/sqlite/storage.ts或jsonl/storage.ts—— 落地格式。
6. 自测
- Session 的"单条 mutation 线"解决了什么问题?为什么 read 也要上这条线?
snapshotAsOf能回放历史,靠的是 session 的什么结构?- 一个
compact()提交后,原 assistant 消息去哪了?模型下次请求看到的是什么? - Harness 的
pi.agent/pi.live/pi.inbox文档分别承载什么?