图解 Pi AgentPi Agent, Illustratedv1.1.0
包Packages· durable
演示Slides
Infra已完成Donepackages/durable · v1.1.0

durable@earendil-works/pi-durable

持久化的会话、任务与文档运行时。Durable conversation, task, and document runtime for Pi.

1.1.0
版本version
67
源文件src files
20,404
源码行src LOC
49
测试文件test files
743
测试用例test cases
11
导出路径export paths
依赖Depends onchordpi-ai被依赖Used byenv

这个包是什么What this package ispi-durable 是一个可持久化、可恢复的 agent 运行时:会话的一切——转录条目、类型化文档、任务、排队的提交——都通过一条原子提交线写进存储;模型调用、工具调用和压缩都是每一步都存检查点的任务,进程崩溃后从最后一个检查点继续。UI 需要的一切都是已提交的状态,用 chord 的复制状态观察。pi-durable is a persistent, resumable agent runtime: everything about a conversation — transcript entries, typed documents, tasks, queued submissions — is written to storage through one line of atomic commits; model calls, tool calls and compactions are tasks that checkpoint every step, so after a crash a process continues from the last checkpoint. Everything a UI needs is committed state, watched as chord replicated state.

共 20,404 行 TypeScript,其中 harness(运行 agent 的部分)7,015 行。依赖 chord、pi-ai、diff 与 typebox。导入它的包:coding-agent(17 个文件)、env(3 个文件)——coding-agent 的用法都在 src/experimental/ 下;env 实现了它的 ExecutionEnv。20,404 lines of TypeScript, 7,015 of them in the harness (the part that runs agents). Depends on chord, pi-ai, diff and typebox. Imported by coding-agent (17 files), env (3 files) — coding-agent's uses are all under src/experimental/; env implements its ExecutionEnv.

01Infra

模块结构Module structure#

一句话结论Takeaway自下而上:根目录的核心类型(Storage、TaskState、条目与文档定义)、session/(唯一的提交线与观察)、harness/(调度器和三个内置任务,最大的一块)、storage/(Memory、SQLite、JSONL 与 Cloudflare 后端)、env/ 与 tools/(执行环境与 read / write / edit / bash),以及给自定义后端用的 testing/ 一致性套件。Bottom up: core types at the root (Storage, TaskState, entry and document definitions), session/ (the single commit line and observation), harness/ (the scheduler and three built-in tasks, the biggest part), storage/ (Memory, SQLite, JSONL and Cloudflare backends), env/ and tools/ (execution environments and read / write / edit / bash), and testing/ conformance suites for custom backends.

durable 的模块、子路径与使用者durable modules, subpaths and users
← 左右滑动查看,点击放大 →← swipe to scroll, tap to enlarge →
pi-durable modules (20,404 lines of TypeScript)核心类型Core typessrc/*.ts · 1,786Sessionsession/ · 2,009Harnessharness/ · 7,015存储后端Storage backendsstorage/ · 3,221执行环境Execution envenv/ · 2,250内置工具Built-in toolstools/ · 1,313一致性测试套件Conformance suitestesting/ · 2,81011 个导入路径与依赖11 import paths and dependencies1,107types.tsStorage · TaskState · 记录Storage · TaskState · records208documents.tsdefineDoc34entries.tspi.user … pi.compaction8tasks.tsdefineTask1,042transaction.ts一次原子提交one atomic commit569session.ts唯一的提交线the single commit line301observation.tsview · watch1,435scheduler.ts任务调度与恢复task scheduling & recovery681generation.tspi.generation 任务the pi.generation task509tool.tspi.tool 任务the pi.tool task453compaction.tspi.compaction 任务the pi.compaction task437harness.tsHarness.open() · root()425events.tswatchEvents()934sqlite/storage.tsSQLite 核心SQLite core845jsonl/storage.tsJSONL 核心JSONL core864memory.tsMemoryStorage327index.tsExecutionEnv 接口the ExecutionEnv interface1,226node.tsNodeExecutionEnv522node-watch.ts文件监视file watching500edit-diff.tsedit 的匹配与差异edit matching & diffs216read.tsread 工具the read tool25index.tsCodingTools:4 个工具CodingTools: 4 tools38runner.tsregister…Conformance()1,662storage-conformance.ts存储用例storage cases511env-conformance.ts环境用例environment cases./env/env/node/tools/storage/memory/storage/jsonl/storage/jsonl/node/storage/sqlite/storage/sqlite/cloudflare/storage/sqlite/node/testingchord ^1.1.0pi-ai ^1.1.0diff 8.0.4typebox 1.3.27依赖:deps:被导入:coding-agent 17 个文件 · env 3 个文件imported by: coding-agent (17 files) · env (3 files)sessionharness存储storageenv

要点Key points

  • 11 个导入路径让宿主只加载需要的部分:例如 ./storage/sqlite 与 ./storage/jsonl 的可移植核心不依赖 Node API,可以跑在 Bun 或 Cloudflare Durable Object 上。11 import paths let a host load only what it needs: the portable cores ./storage/sqlite and ./storage/jsonl, for example, need no Node APIs and run on Bun or in Cloudflare Durable Objects.
  • 最大的文件是 harness/scheduler.ts:它拥有所有活任务的调度、恢复、所有权与中止。The largest file is harness/scheduler.ts: it owns scheduling, recovery, ownership and abort for every live task.
  • 包根会加载 TypeBox(工具参数校验用 pi-ai 的 validateToolArguments());README 给出的代价约 23 MB 峰值 RSS,tree-shaken 打包后约 4 MB。The package root loads TypeBox (tool arguments are validated with pi-ai's validateToolArguments()); the README puts the cost at about 23 MB of peak RSS, about 4 MB tree-shaken.

工作原理How it works

  1. const harness = await Harness.open(storage, { models, registry }, ctx)。const harness = await Harness.open(storage, { models, registry }, ctx).
  2. const root = await harness.root(ctx, { agent }),然后 root.submit({ type: "input", content }, ctx)。const root = await harness.root(ctx, { agent }), then root.submit({ type: "input", content }, ctx).
  3. await (await …submit(…)).wait(ctx) 得到 done 或带原因的 unanswered。await (await …submit(…)).wait(ctx) gives done or unanswered with a reason.

关键文件Key files · v1.1.0

文件File符号Symbol作用Role
packages/durable/src/index.tsL39export …包入口package entry
packages/durable/src/harness/harness.tsL411HarnessHarness.open()Harness.open()
packages/durable/src/types.tsL1015Storage存储接口the storage interface
packages/durable/src/session/session.tsL59SessionImpl提交线the commit line
packages/durable/src/harness/scheduler.tsL174TaskScheduler任务调度task scheduling
02Infra

公共 APIPublic API#

一句话结论Takeaway11 个导入路径(另有 ./package.json)。根入口从 22 个模块重新导出 37 个值和 155 个类型:createSession、Harness、defineTask、defineDoc 与扩展 API;执行环境、工具与各存储后端各有自己的子路径。11 import paths (plus ./package.json). The root re-exports 37 values and 155 types from 22 modules: createSession, Harness, defineTask, defineDoc and the extension API; the execution environment, tools and each storage backend have their own subpaths.

导入路径Import path目标Target说明Notes
@earendil-works/pi-durable./dist/index.js会话、Harness、任务、文档、扩展与类型sessions, the Harness, tasks, documents, extensions and types
@earendil-works/pi-durable/env./dist/env/index.jsExecutionEnv 接口(env 包实现它)the ExecutionEnv interface (implemented by env)
@earendil-works/pi-durable/env/node./dist/env/node.js本机 Node 实现the local Node implementation
@earendil-works/pi-durable/tools./dist/tools/index.js内置工具built-in tools
@earendil-works/pi-durable/storage/memory./dist/storage/memory.js内存存储in-memory storage
@earendil-works/pi-durable/storage/jsonl./dist/storage/jsonl/index.jsJSONL 存储JSONL storage
@earendil-works/pi-durable/storage/jsonl/node./dist/storage/jsonl/node.jsJSONL 的 Node 文件后端the Node file backend for JSONL
@earendil-works/pi-durable/storage/sqlite./dist/storage/sqlite/index.jsSQLite 存储SQLite storage
@earendil-works/pi-durable/storage/sqlite/cloudflare./dist/storage/sqlite/cloudflare.jsCloudflare Durable Object SQLite 后端the Cloudflare Durable Object SQLite backend
@earendil-works/pi-durable/storage/sqlite/node./dist/storage/sqlite/node.jsNode SQLite 后端the Node SQLite backend
@earendil-works/pi-durable/testing./dist/testing/index.js测试运行器与一致性套件the test runner and conformance suites
@earendil-works/pi-durable/package.json./package.json包元数据package metadata

package.json · exportsL8

常用导出Key exports

模块Module常用导出Key exports用途Use
packages/durable/src/session/session.tscreateSession在 Storage 之上打开会话open a session over a Storage
packages/durable/src/harness/harness.tsHarness带模型、工具与扩展的会话a session with model, tools and extensions
packages/durable/src/tasks.tsdefineTask可恢复的分阶段任务resumable, phased tasks
packages/durable/src/documents.tsdefineDoc defineDocFamily持久化文档durable documents
packages/durable/src/harness/define.tsdefineTool defineExtension工具与扩展tools and extensions
packages/durable/src/harness/inbox.tsInboxDoc收件箱文档the inbox document
packages/durable/src/storage/memory.tsMemoryStorage内存存储in-memory storage

根入口 index.ts 的全部导出(按来源模块)Every root index.ts export, by source module

展开全部 22 个来源模块Show all 22 source modules37 values155 types
来源模块Source module值导出Value exports类型导出Type exports
packages/durable/src/documents.tsdefineDoc defineDocFamily—
packages/durable/src/entries.tsAssistantEntry CompactionEntry defineEntry ResetEntry SystemEntry ToolResultEntry UserEntry—
packages/durable/src/errors.tsConversationBusy ReadAfterWrite StorageRejected—
packages/durable/src/harness/agent.tsAgentDoc configure DEFAULT_COMPACTION_POLICY DEFAULT_PROGRESS_POLICY DEFAULT_RETRY_POLICY—
packages/durable/src/harness/compaction.tsCompactionTaskCompactionCheckpoint CompactionInput SummaryRequest
packages/durable/src/harness/define.tsdefineExtension defineTool hook section wrapSection wrapTool—
packages/durable/src/harness/events.tswatchEventsAgentEvent AgentEventStream MessageChange SnapshotEvent
packages/durable/src/harness/generation.tsGenerationTaskGenerationCheckpoint GenerationInput GenerationResult
packages/durable/src/harness/harness.tsHarness—
packages/durable/src/harness/inbox.tsInboxDocInboxItem InboxState
packages/durable/src/harness/live.tsLiveDocCompactionStatus LiveState ToolSlot
packages/durable/src/harness/provider.tsProviderDocProviderState
packages/durable/src/harness/registry.tscreateRegistry—
packages/durable/src/harness/task-graph.ts—TaskGraph TaskGraphNode TaskGraphState TaskGraphWatch
packages/durable/src/harness/tool.tsToolTaskToolTaskCheckpoint ToolTaskInput ToolTaskResult
packages/durable/src/harness/types.ts—Agent AgentChange AgentState AnyTask CompactionHooks CompactionPolicy CompactionReason CompactionResult ContextView Conversation +41
packages/durable/src/harness/usage.tsUsageDocUsageState
packages/durable/src/harness/view.ts—ConversationView
packages/durable/src/session/session.tscreateSession—
packages/durable/src/storage/memory.tsMemoryStorage—
packages/durable/src/tasks.tsdefineTask—
packages/durable/src/types.tsROOT_CONVERSATION_IDCheckpointInfo CommitChange CommitPublication CommonDocDefinition ContextEdit ConversationDocFamilyToken ConversationDocToken ConversationId ConversationOwnership ConversationQuery +69

由构建脚本解析 export { … } from 语句生成。Generated by parsing the export { … } from statements at build time.

03Infra

概念模型The model#

一句话结论TakeawayHarness 打开一份存储;Session 是唯一的原子提交线,提交之前什么都不会被看到。一个 Conversation 由不可变的条目(pi.user、pi.assistant、pi.system、pi.tool-result、pi.reset、pi.compaction)、类型化的文档(pi.agent、pi.provider、pi.live、pi.inbox、pi.usage 与你自己的)、任务和提交组成;它用什么跑(模型、扩展、工具、cwd)存在 pi.agent 文档里,按名字对照 Registry 解析。A Harness opens one storage; the Session is the single line of atomic commits, and nothing is shown before it is stored. A Conversation is made of immutable entries (pi.user, pi.assistant, pi.system, pi.tool-result, pi.reset, pi.compaction), typed documents (pi.agent, pi.provider, pi.live, pi.inbox, pi.usage and your own), tasks and submissions; what it runs with (model, extensions, tools, cwd) is stored in the pi.agent document by name and resolved against the Registry.

概念模型:一切都是提交到存储里的记录The model: everything is a record committed to storage
← 左右滑动查看,点击放大 →← swipe to scroll, tap to enlarge →
The model: everything is a record committed to storage一个 Conversation(句柄不持有状态,按 id 比较)One Conversation (handles hold no state; compare by id)Entry:不可变记录Entries: immutableDocument:类型化 JSONDocuments: typed JSONTask 与 SubmissionTasks & submissionsAgent:会话用什么跑Agent: what a conversation runs with宿主程序Host programsubmit · wait · viewState · watchHarness一份打开的存储 + 运行 agent 的机制one open storage + the machinery that runs agentsRegistry本进程安装的 extension(不存储,可随时变)extensions this process installed (not stored, may change)Session:唯一的原子提交线Session: the single line of atomic commits提交前什么都不会被看到nothing is shown before it is storedpi.userpi.systempi.assistantpi.tool-resultpi.resetpi.compactionpi.agentpi.providerpi.livepi.inboxpi.usageapp.*pi.generationpi.toolpi.compaction用户输入user input提示词变化prompt changes模型回答model answer工具结果tool result新上下文起点new context start摘要summary模型 · 工具 · cwdmodel · tools · cwdsessionId运行中的生成与工具running work排队的提交queued submissions花费spend你自己的your own调用模型calls the model一次工具调用one tool call做摘要summarizesTask 每一步都存检查点,A task checkpoints every step,重启后从最后一步继续。so a restart continues from it.Submission:交给会话的A submission: input or an entry输入或条目,可以 wait()。handed over; you can wait().要么全存,要么全不存all of it or nonemodel · thinkingLevel模型与思考强度model and thinkingextensions · tools按名字存储stored by nameinstructions最后渲染的提示词段rendered as the last sectioncwd环境由它构建the env is built from it存在 pi.agent 文档里;每次使用Stored in the pi.agent document;时对照 Registry 解析。resolved against the registryat each use.扩展被卸载后,选它的会话只是After an extension is uninstalled,暂时拿不到它,名字仍保留。conversations just stop getting it;the stored name stays.settings:每次使用时读取,从不存储settings: read at every use, never stored条目与会话entries & conversations文档documents任务tasks扩展与 agentextensions & agent

要点Key points

  • conversation.commit(tx => …) 可以在一次原子写入里追加条目、改文档、建任务——要么全部存下,要么全不存。conversation.commit(tx => …) can append entries, edit documents and create tasks in one atomic write — all of it is stored or none of it.
  • Conversation 句柄不持有状态,按 id 比较;模型只看到最新一个 pi.reset 之后的条目,旧条目仍留在存储里。A Conversation handle holds no state; compare by id. The model sees entries from the newest pi.reset on; older ones stay in storage.
  • Registry 不被存储:扩展被卸载后,选它的会话只是暂时拿不到它,名字仍保留,重新安装就恢复。The Registry is not stored: after an extension is uninstalled, conversations that select it just stop getting it; the name stays and reinstalling restores it.
  • settings(重试、压缩、进度间隔、排队模式…)每次使用时读取、从不存储,所以可以用 getter 让它实时生效。settings (retry, compaction, progress intervals, queue modes…) are read at every use and never stored, so getters make them live.

工作原理How it works

  1. defineEntry("app.note")、defineDoc({ kind, version, scope, history, fork, initial }) 定义你自己的记录。defineEntry("app.note") and defineDoc({ kind, version, scope, history, fork, initial }) define your own records.
  2. root.configure({ model, tools, cwd, … }, ctx) 在一次提交里改 pi.agent;null 把字段清回宿主默认。root.configure({ model, tools, cwd, … }, ctx) changes pi.agent in one commit; null clears a field back to the host default.
  3. harness.createConversation() / root.fork(entryId, …) 建更多会话;分叉拿到新的 provider 会话 ID。harness.createConversation() / root.fork(entryId, …) create more conversations; a fork gets a fresh provider session ID.

关键文件Key files · v1.1.0

文件File符号Symbol作用Role
packages/durable/src/entries.tsL15UserEntry …内置条目built-in entries
packages/durable/src/documents.tsL54defineDoc()文档定义document definitions
packages/durable/src/session/transaction.tsL194Transaction一次提交one commit
packages/durable/src/harness/agent.tsL42AgentDocpi.agentpi.agent
packages/durable/src/harness/agent.tsL56resolveSettings()settings 默认值settings defaults
04Infra

回答一次输入Answering one input#

一句话结论Takeawaysubmit(input) 先提交一个 pi.user 条目,再由内置的 pi.generation 任务调用模型:它在提示词或工具变化时写 pi.system,写下带工具调用的 pi.assistant,为每个调用建一个归它所有的 pi.tool 任务并等待;工具结果写成 pi.tool-result 后,下一个 generation 接手,直到模型给出最终回答,submission 变为 done。submit(input) first commits a pi.user entry; the built-in pi.generation task then calls the model: it writes pi.system when the prompt or tools changed, writes the pi.assistant with tool calls, creates one owned pi.tool task per call and waits; once results are written as pi.tool-result, the next generation takes over until the model answers and the submission is done.

回答一次输入:条目与任务按时间排开One answered input, as entries and tasks over time
← 左右滑动查看,点击放大 →← swipe to scroll, tap to enlarge →
One answered input, as entries and tasks over timepi.generation 的检查点阶段pi.generation checkpoint phasesharness/generation.ts进程崩溃之后After a crashharness/tool.tssubmit(input)pi.userpi.generation第 1 轮turn 1pi.systempi.assistant工具调用tool callspi.tool #1pi.tool #2pi.tool-result × 2pi.generation第 2 轮turn 2pi.assistant回答answerwait() → donepreparerequestretrypolltools宿主Host任务Tasks提交的条目Committed entriespi.system 只在提示词或工具变化时写入pi.system only when the prompt or tools changedgeneration 拥有并等待它们owned and awaited by the generation组装请求;需要时先等一次压缩build the request; wait for a compaction if needed流式调用模型(部分回答每 100 ms 提交)stream the model (partials committed every 100 ms)退避后重试:最多 3 次,基数 2000 msback off and retry: up to 3, base 2000 ms延迟的回答:默认每 5000 ms 轮询deferred answers: poll every 5000 ms by default拥有本轮的 pi.tool 任务并等待own this round's pi.tool tasks and waitpi.tool 先提交意图(phase "execute"),再运行 execute()。pi.tool commits its intent (phase "execute") before execute() runs.replay: "safe"重新打开时重跑rerun on reopen默认 "unsafe"default "unsafe"模型收到 interrupted 错误结果:the model gets an interrupted error:"Tool … was interrupted and may have partially run"其余工作保持 pending;resume()(或下一次 submit / wait)启动调度器,Everything else stays pending; resume() (or the next submit / wait) starts the从最后的检查点继续。scheduler, which continues from the last checkpoints.同一个 requestId 的重复 submit 返回原来的 submission。A resubmit with the same requestId returns the existing submission.条目entries任务tasks产生produces作为输入feeds

要点Key points

  • generation 的检查点阶段:prepare → request → retry(退避重试,默认最多 3 次)→ poll(延迟回答,默认每 5000 ms)→ tools。Generation checkpoint phases: prepare → request → retry (back-off, up to 3 by default) → poll (deferred answers, every 5000 ms by default) → tools.
  • pi.tool 在运行 execute() 前先提交意图。崩溃后只有声明 replay: "safe" 的工具会重跑;其余给模型一个 interrupted 错误结果,附带已提交的输出。pi.tool commits its intent before execute() runs. After a crash only tools declared replay: "safe" rerun; others give the model an interrupted error result with the output committed so far.
  • 部分回答和工具输出默认最多每 100 ms 提交一次,所以崩溃最多丢这么一个窗口。Partial answers and tool output are committed at most every 100 ms by default, so a crash loses at most that window.
  • 上下文过长被拒时,generation 先压缩再重试一次;reserveTokens 之上的请求会等一次压缩完成。When a provider rejects a request for being too long, generation compacts and retries once; above reserveTokens, the next request waits for a compaction.

工作原理How it works

  1. 重启:用同一个存储 Harness.open(),harness.root(ctx) 拿到同一个根会话,harness.resume() 启动调度器。Restart: Harness.open() on the same storage, harness.root(ctx) returns the same root, harness.resume() starts the scheduler.
  2. 提交时带 requestId:重启后同一个请求再提交会拿到原来那个 submission,不会提交两次。Submit with a requestId: after a restart the same request returns the existing submission instead of submitting twice.
  3. harness.submission(id) 按 ID 重新拿到一个 submission,再 wait()。harness.submission(id) reacquires a submission by ID; then wait().

关键文件Key files · v1.1.0

文件File符号Symbol作用Role
packages/durable/src/harness/generation.tsL115GenerationTaskpi.generationpi.generation
packages/durable/src/harness/generation.tsL51GenerationCheckpoint5 个阶段the 5 phases
packages/durable/src/harness/tool.tsL50ToolTaskpi.toolpi.tool
packages/durable/src/harness/tool.tsL107interrupted崩溃后的工具a tool after a crash
packages/durable/src/harness/agent.tsL19DEFAULT_RETRY_POLICY重试默认值retry defaults
packages/durable/src/harness/harness.tsL238resume()启动调度器starting the scheduler
05Infra

任务:状态机与所有权Tasks: state machine and ownership#

一句话结论Takeaway任务状态只有 5 种:pending(可调度)、running(一次内存中的调用)、waiting(等 on 里的任务,策略 failFast 或 allSettled)、completing(结果已定,等它拥有的工作结束)、terminal(永久结果)。结果也只有 5 种:completed、failed、aborted、orphaned、faulted。每个任务都有主人:它的会话,或另一个任务。A task has only 5 states: pending (eligible), running (one in-memory invocation), waiting (on the tasks in on, policy failFast or allSettled), completing (outcome decided, waiting for owned work) and terminal (a permanent receipt). Outcomes are also 5: completed, failed, aborted, orphaned, faulted. Every task has an owner: its conversation, or another task.

Task:可恢复的状态机与所有权Tasks: a resumable state machine with ownership
← 左右滑动查看,点击放大 →← swipe to scroll, tap to enlarge →
Tasks: a resumable state machine with ownershipTaskOutcome所有权:每个任务都有主人Ownership: every task has an ownerpending可被调度eligible to runrunning一次内存中的调用one in-memory invocationwaitingon: [...] · policycompleting结果已定,等下属工作outcome heldterminal永久结果permanent receipt会话conversationpi.generationpi.toolsubagent子会话child conv提交 waitingcommit waitingon 都结束all of on done没有下属工作时直接结束straight to terminal when nothing below is live重启后,runningAfter a restart, running显示为 pendingshows as pending每次状态转换都在 Session 的Every transition is serialized提交线上串行完成。on the Session's commit line.活状态:pending · running ·Live: pending · running ·waiting · completingcompleted带 resultcarries resultfailed实现主动提交的失败failure the task committedaborted走了 abort 协议handled abortorphaned定义或迁移不可用definition / migration missingfaulted运行时检测到违约runtime contract failure· 中止调用会中止子会话;父会话要等子会话空闲才算空闲。· Aborting the call aborts the child; the parent is idle only once the child is.· 中止自下而上:先中止它拥有的工作,再运行它自己的 abort 处理。· Abort runs bottom-up: owned work first, then the task's own abort handler.· { background: true } 的任务是边界:它拥有的工作不受父中止影响,也不让父忙碌。· A { background: true } task is a boundary: its work survives the parent's abort and keeps it idle.· 等待策略:failFast(第一个失败就中止其余)或 allSettled。· Join policy: failFast (the first failure aborts the rest) or allSettled.harness.taskGraph() 把所有活任务显示为一个 Chord state(例:24-child-tasks.ts)harness.taskGraph() shows every live task as one Chord state (example: 24-child-tasks.ts)README:子任务README: child tasks// 子任务:创建后进入 waiting// child tasks: create, then waitpay: async (task, runtime, ctx) => {await runtime.commit(async tx => {const ids = [...] // tx.createTask(…)return { status: "waiting",checkpoint: { phase: "decide", ids },on: ids, policy: "failFast" };}, ctx);},转换transition恢复 / 捷径resume / shortcut重启restartcompleting

要点Key points

  • 每次状态转换都在 Session 的提交线上串行完成;重启后 running 的任务显示为 pending,直到再次运行。Every transition is serialized on the Session's commit line; after a restart, tasks that were running show as pending until they run again.
  • orphaned 表示定义或迁移不可用(例如扩展没装回来);faulted 是运行时发现的违约,如未捕获的异常或没有持久进展。orphaned means the definition or migration is unavailable (e.g. the extension was not reinstalled); faulted is a contract failure the runtime detects, such as an uncaught throw or no durable progress.
  • 中止自下而上:先中止它拥有的工作,等那些结束后才运行它自己的 abort 处理——每个任务只撤销自己的效果。Abort runs bottom-up: owned work is aborted first and the task's own abort handler runs only once that is done — each task undoes its own effects.
  • { background: true } 的任务是边界:它拥有的工作不受父中止影响,也不让父会话忙碌(subagent 的后台模式就靠它)。A { background: true } task is a boundary: its work survives the parent's abort and does not keep the parent busy (background subagents rely on it).

工作原理How it works

  1. defineTask({ name, version, initial, phases, abort, migrate? }) 定义任务:每个阶段返回下一个状态(running / waiting / terminal);abort 在中止标记后必须提交一个终态结果。defineTask({ name, version, initial, phases, abort, migrate? }) defines a task: each phase returns the next state (running / waiting / terminal); abort must commit a terminal outcome after an abort mark.
  2. 在 runtime.commit() 里 tx.createTask(Kind, input, { ownership: { kind: "task", taskId } }) 建子任务,再返回 { status: "waiting", on, policy }。Inside runtime.commit(), tx.createTask(Kind, input, { ownership: { kind: "task", taskId } }) creates children; then return { status: "waiting", on, policy }.
  3. harness.taskGraph(ctx) 把所有活任务显示为一个 chord state;harness.inspect() 报告被缺失定义阻塞的任务。harness.taskGraph(ctx) shows every live task as one chord state; harness.inspect() reports tasks blocked by a missing definition.

关键文件Key files · v1.1.0

文件File符号Symbol作用Role
packages/durable/src/types.tsL486TaskState5 种状态the 5 states
packages/durable/src/types.tsL449TaskOutcome5 种结果the 5 outcomes
packages/durable/src/harness/scheduler.tsL54LIVE_STATUSES活状态live states
packages/durable/src/tasks.tsL4defineTask()定义任务defining a task
packages/durable/src/harness/task-graph.tsL62TaskGraphView任务图the task graph
06Infra

会话忙时的提交Submitting to a busy conversation#

一句话结论Takeawayrun 进行时会话是忙的。这时的输入进入 pi.inbox 文档:whenBusy: "steer" 在当前工具轮之后放入并加入正在跑的工作;默认的 followUp 在 run 回答后放入并开启下一个 run;"reject" 直接抛 ConversationBusy。{ type: "write", entry } 只追加条目,不问模型。A conversation is busy while a run is going. Input then goes into the pi.inbox document: whenBusy: "steer" is placed after the current tool round and joins the running work; the default followUp is placed when the run answers and starts the next run; "reject" throws ConversationBusy. { type: "write", entry } only appends an entry without asking the model.

会话忙时的提交:steer、followUp、reject 与 writeSubmitting to a busy conversation: steer, followUp, reject and write
← 左右滑动查看,点击放大 →← swipe to scroll, tap to enlarge →
Submitting to a busy conversation: steer · followUp · reject · write它们什么时候被放进转录When they are placed into the transcriptpi.inbox 文档The pi.inbox documentharness/inbox.ts撤回、重置与幂等Withdraw, reset and idempotencyroot.submit(x, ctx)会话正在跑一个 runa run is in progresswhenBusy: "reject"立即抛出 ConversationBusythrows ConversationBusy at oncewhenBusy: "steer"进 pi.inbox,mode steerinto pi.inbox as steer默认:followUpdefault: followUp进 pi.inbox,mode followUpinto pi.inbox as followUp{ type: "write", entry }排队写条目,不问模型queue an entry, no model call第 1 轮turn 1工具轮tool round第 2 轮turn 2回答answersteer:当前工具轮之后,加入正在跑的工作steer: after the current tool round, joins the running workfollowUp:run 回答后放入,followUp: placed when the run开启下一个 runanswers; starts the next runsteeringMode / followUpMode 默认 "one-at-a-time":每轮放一条;"all":一次全放。steeringMode / followUpMode default "one-at-a-time": one per turn; "all": every queued item at once.run 失败时,排队项留在 inbox,等下一次提交时按先后放入。If a run fails, queued items stay until the next submission places them, oldest first.{ items: [{ id, mode: "steer", content },{ id, mode: "followUp", content },… writes and resets queue here too] }UI 从 view.docs["pi.inbox"] 直接画出排队区。A UI draws its queue straight from view.docs["pi.inbox"].submission.abort(ctx)撤回仍在排队的提交withdraw a queued submissionroot.abort(ctx)撤回排队输入(写入保留),中止当前工作withdraw queued inputs (writes stay), abort workroot.reset(note?, ctx)忙时像写入一样排队queued like a write while busyrequestId同一 ID 重复提交 → 原来那个 submissionsame ID again → the existing submissionsteerfollowUpwritereject

要点Key points

  • steeringMode / followUpMode 默认 "one-at-a-time":每轮放一条;设为 "all" 时一次放入全部排队项。steeringMode / followUpMode default to "one-at-a-time": one per turn; "all" places every queued item at once.
  • run 失败时排队项留在 inbox,等下一次提交时按先后放入。If a run fails, queued items stay in the inbox until the next submission places them, oldest first.
  • root.abort(ctx) 撤回排队的输入(排队的写入保留)、中止当前工作的所有任务,并在会话空闲时返回。root.abort(ctx) withdraws queued inputs (queued writes stay), aborts every task of the current work and resolves once the conversation is idle.
  • reset() 忙时也像写入一样排队;在工具轮中放入时会结束当前 run。工具可以用 control: { handoff } 请求同样的事。reset() is queued like a write while busy; placed during a tool round, it ends the current run. A tool can request the same with control: { handoff }.

工作原理How it works

  1. root.submit({ type: "input", content, whenBusy: "steer" }, ctx)。root.submit({ type: "input", content, whenBusy: "steer" }, ctx).
  2. UI 直接从 view.docs["pi.inbox"] 画出排队区;submission.abort(ctx) 撤回一条。A UI draws its queue straight from view.docs["pi.inbox"]; submission.abort(ctx) withdraws one.
  3. 内部:admitSubmission() 判断忙闲 → 写入 inbox;每个轮次边界 prepareBoundary() / applyBoundary() 决定放入哪些。Internally: admitSubmission() checks busy → writes the inbox; at each turn boundary prepareBoundary() / applyBoundary() decide what is placed.

关键文件Key files · v1.1.0

文件File符号Symbol作用Role
packages/durable/src/harness/submissions.tsL148admitSubmission()接收提交admitting a submission
packages/durable/src/harness/inbox.tsL16InboxDocpi.inboxpi.inbox
packages/durable/src/harness/inbox.tsL65applyBoundary()轮次边界turn boundaries
packages/durable/src/harness/inbox.tsL124withdrawQueuedInputs()abort 时撤回withdrawal on abort
packages/durable/src/harness/types.tsL160QueueMode排队模式queue modes
07Infra

文档与观察Documents and watching#

一句话结论Takeaway文档是和条目一起提交的类型化 JSON。内置的 5 个都按会话存放:pi.agent(可回溯,分叉时取分叉点的值)、pi.provider(每个会话一个 UUIDv7 的 sessionId,转给 pi-ai 用于提示缓存与会话亲和)、pi.live、pi.inbox、pi.usage。UI 用 viewState() 拿到 entries + docs 的只读 chord state,或用 watch() 拿到每次提交的精确 op。Documents are typed JSON committed together with entries. The 5 built-in ones are per conversation: pi.agent (rewindable; a fork takes the value at the fork entry), pi.provider (one UUIDv7 sessionId per conversation, forwarded to pi-ai for prompt caching and session affinity), pi.live, pi.inbox and pi.usage. A UI gets entries + docs as a read-only chord state with viewState(), or each commit's exact ops with watch().

文档与观察:UI 需要的都是已提交的状态Documents and watching: everything a UI needs is committed state
← 左右滑动查看,点击放大 →← swipe to scroll, tap to enlarge →
Documents and watching: everything a UI needs is committed state内置文档(scope: conversation)Built-in documents (scope: conversation)三种观察方式Three ways to watchpi.agentpi.providerpi.livepi.inboxpi.usageviewState(ctx)只读 Chord state:entries + docsread-only Chord state: entries + docswatch(ctx)同一个视图 + 每次提交的精确 Chord opthe same view + each commit's exact Chord opswatchEvents(…)coding-agent 风格事件(实验性)coding-agent style events (experimental)文档document内容holdshistoryfork模型 · 扩展 · 工具 · cwdmodel · extensions · tools · cwdrewindableasOfsessionId = UUIDv7latestinitial运行中的生成、工具、压缩running generation, tools, compactionslatestinitial排队的提交queued submissionslatestinitial按模型 / 工具的花费spend per model / toollatestinitialpi.agent 可回溯:分叉拿到分叉点那一刻的 agent;其余分叉时从初始值开始。pi.agent is rewindable: a fork gets the agent as of the fork entry; the rest start fresh.你自己的文档(README)Your own document (README)const Todos = defineDoc<{ items: string[] }>({kind: "app.todos", version: 1,scope: "conversation",history: "latest", // 或 "rewindable"history: "latest", // or "rewindable"fork: "initial", // "current" | "asOf"initial: () => ({ items: [] }),});await root.commit(async tx =>(await tx.doc(Todos, root.id)).items.push("write docs"), ctx);每次相关提交后更新updated after every commit that touches it慢消费者最多 100 帧,之后换成一帧完整视图slow consumers: 100 frames, then one whole-view frame落后超过 100 批 → 一个新的 snapshot 事件more than 100 batches behind → a fresh snapshot event迟到或重连的客户端从当前视图开始,不会重放历史;部分回答与工具输出默认最多每 100 ms 提交一次。Late or reconnecting clients start from the current view, nothing is replayed; partials and tool output commit at most every 100 ms.文档documents观察watching

要点Key points

  • history: "rewindable" 的文档可以用 snapshotAsOf() 读旧值;fork 决定分叉时的初值:initial、current 或 asOf。Documents with history: "rewindable" can be read as of the past with snapshotAsOf(); fork decides a fork's start value: initial, current or asOf.
  • 慢的 watch() 消费者最多积压 100 帧,之后整队换成一帧完整的最新视图;迟到或重连的客户端从当前视图开始,不重放历史。A slow watch() consumer keeps at most 100 undelivered frames, then they become one frame with the whole newest view; late or reconnecting clients start from the current view, nothing is replayed.
  • watchEvents()(实验性)把提交转换成 coding-agent 风格的事件(message_start、tool_execution_start…);落后超过 100 批时收到一个新的 snapshot 事件。watchEvents() (experimental) turns commits into coding-agent style events (message_start, tool_execution_start…); more than 100 batches behind, it delivers a fresh snapshot event.
  • HarnessOptions.conversationCreated(tx, conversation) 在每个创建或分叉会话的提交里运行,适合给每个会话放上你自己的文档。HarnessOptions.conversationCreated(tx, conversation) runs in every commit that creates or forks a conversation — the place to give every conversation your own documents.

工作原理How it works

  1. const view = await root.viewState(ctx); view.subscribe(v => render(v))。const view = await root.viewState(ctx); view.subscribe(v => render(v)).
  2. const w = await root.watch(ctx); render(w.value); w.start(async (value, ops) => send(ops))——把 op 发给远端客户端重放。const w = await root.watch(ctx); render(w.value); w.start(async (value, ops) => send(ops)) — ship ops to a remote client to replay.
  3. 自己的文档:(await tx.doc(Todos, root.id)).items.push("…");读:harness.snapshot(Todos, root.id, ctx)。Your own document: (await tx.doc(Todos, root.id)).items.push("…"); read: harness.snapshot(Todos, root.id, ctx).

关键文件Key files · v1.1.0

文件File符号Symbol作用Role
packages/durable/src/harness/view.tsL78ConversationViewsviewState / watchviewState / watch
packages/durable/src/session/observation.tsL15MAX_PENDING_WATCH_FRAMES100 帧100 frames
packages/durable/src/session/observation.tsL155CommittedWatch精确 op 的观察exact-op watching
packages/durable/src/harness/events.tsL140watchEvents()agent 事件agent events
packages/durable/src/harness/live.tsL64pi.live运行中的工作running work
packages/durable/src/harness/provider.tsL13pi.provider会话身份session identity
08Infra

存储后端Storage backends#

一句话结论Takeaway所有后端实现同一个 Storage 接口:commit(writes, ctx) 原子地写入一批 StorageWrite(8 种:conversation、entry、task、submission 与 document 的 create / copy / change / retire),返回单调递增的 Seq;其余都是按 ID 读取或分页扫描。Memory 不持久化;SQLite 是一个 WAL 模式的数据库文件;JSONL 是一个目录里的追加文件;Cloudflare 版本用一个 Durable Object 的 SQLite。Every backend implements one Storage interface: commit(writes, ctx) atomically writes a batch of StorageWrites (8 kinds: conversation, entry, task, submission, and document create / copy / change / retire) and returns an increasing Seq; everything else is reads by ID or paged scans. Memory persists nothing; SQLite is one WAL-mode database file; JSONL is append-only files in a directory; the Cloudflare one uses a Durable Object's SQLite.

一个 commit(writes) 接口,四种存储后端One commit(writes) interface, four storage backends
← 左右滑动查看,点击放大 →← swipe to scroll, tap to enlarge →
Storage: one commit(writes) interface, four backendsStorageWrite 的 8 种类型The 8 StorageWrite types读取接口Read interfaceMemorySQLite (Node)JSONL (Node)Cloudflare DOSession把一次事务变成一组写入turns a transaction into writesStorage.commit(writes, ctx) → Seq全部写入或全部不写;Seq 单调递增all writes or none; Seq increasesconversationentrytasksubmissiondocument.createdocument.copydocument.changedocument.retireconversation()scanConversations()entry()scanEntries()findLatestHeadMarker()task()scanTasks()submission()submissionByRequest()scanSubmissions()document()findDocument()scanDocuments()mintId()close()文档还有修订历史(rewindable)documents also keep revisions每个查询都分页(limit + cursor);同一时刻只能有一个进程拥有一个存储,没有跨进程锁。Every scan is paged (limit + cursor); one process owns a storage at a time, with no cross-process locking.MemoryStorage什么都不持久化;测试与示例nothing persisted; tests and examples从包根导入exported from the package root与其他后端跑同一套一致性测试runs the same conformance suiteopenNodeSqliteStorage(file)一个数据库文件one database fileWAL · synchronous = NORMALwal_autocheckpoint 1000 · busy 5000 ms崩溃不丢提交;断电可能丢最新一个survives crashes; power loss may drop the newestopenNodeJsonlStorage(dir, ctx)一个目录里的追加文件append-only files in a directorymain.jsonl:每次提交追加一行标记main.jsonl: one marker line per commit{format:1, type:"commit", seq, writes}task-<id>.jsonl / doc-<id>.jsonl 旁路文件task-<id>.jsonl / doc-<id>.jsonl sidecars{ fsync: true }openDurableObjectSqliteStorage(…)一个 Durable Object 的 SQLiteone Durable Object's SQLite提交 = 它的存储事务commits = its storage transactions8 张表:8 tables:durable_metadatarecord_idsconversationsentriestaskssubmissionsdocumentsdocument_revisions可移植核心 /storage/sqlite、/storage/jsonl 不依赖 Node:可跑在 Bun 或 Durable Object 上portable cores /storage/sqlite and /storage/jsonl need no Node APIs: Bun, Durable Objects提交commit实现implements

要点Key points

  • SQLite:journal_mode = WAL、synchronous = NORMAL、wal_autocheckpoint 1000 页、busy 超时 5000 ms——提交能挺过进程崩溃,断电可能丢最新一个。SQLite: journal_mode = WAL, synchronous = NORMAL, wal_autocheckpoint 1000 pages, a 5000 ms busy timeout — commits survive process crashes; power loss may drop the newest.
  • JSONL:每次提交在 main.jsonl 追加一行 { format: 1, type: "commit", seq, writes } 标记;任务与文档的大内容写进 task-<id>.jsonl / doc-<id>.jsonl 旁路文件;{ fsync: true } 在标记前刷盘。JSONL: each commit appends one { format: 1, type: "commit", seq, writes } marker to main.jsonl; task and document payloads go to task-<id>.jsonl / doc-<id>.jsonl sidecars; { fsync: true } flushes before the marker.
  • 同一时刻只有一个进程拥有一个存储,没有跨进程锁。One process owns a storage at a time; there is no cross-process locking.
  • 自定义后端可以用 registerStorageConformance() 跑共享的一致性套件,任何 Vitest / Jest 兼容的 runner 都行。A custom backend can run the shared suite with registerStorageConformance() under any Vitest- or Jest-compatible runner.

工作原理How it works

  1. openNodeSqliteStorage("./session.sqlite") 或 openNodeJsonlStorage(dir, ctx),交给 Harness.open()。openNodeSqliteStorage("./session.sqlite") or openNodeJsonlStorage(dir, ctx), passed to Harness.open().
  2. 非 Node 环境:给可移植核心一个异步的 SqliteDatabase 外观(exec / run / get / all / transaction / close)或一个 FileSystem。Outside Node: give a portable core an async SqliteDatabase facade (exec / run / get / all / transaction / close) or a FileSystem.
  3. Cloudflare:openDurableObjectSqliteStorage(ctx.storage)。Cloudflare: openDurableObjectSqliteStorage(ctx.storage).

关键文件Key files · v1.1.0

文件File符号Symbol作用Role
packages/durable/src/types.tsL708StorageWrite8 种写入the 8 writes
packages/durable/src/storage/memory.tsL236MemoryStorage内存memory
packages/durable/src/storage/sqlite/node.tsL205openNodeSqliteStorage()SQLite(Node)SQLite (Node)
packages/durable/src/storage/sqlite/migrations.tsL28entriesSQLite 表结构SQLite schema
packages/durable/src/storage/jsonl/storage.tsL241JsonlStorageJSONLJSONL
packages/durable/src/storage/sqlite/cloudflare.tsL137openDurableObjectSqliteStorage()CloudflareCloudflare
09Infra

扩展、钩子与重载Extensions, hooks and reload#

一句话结论TakeawayHarness 运行的代码(内置任务之外)都来自安装在 Registry 里的命名扩展。一个扩展可以带 tools、sections(系统提示词段)、hooks(挂在 GenerationTask、ToolTask、CompactionTask 上)、wraps(按名字装饰工具或段)和 tasks。默认每个会话按安装顺序选用全部扩展。Code the Harness runs, other than its built-in tasks, comes from named extensions installed in a Registry. An extension may bring tools, sections (system-prompt sections), hooks (on GenerationTask, ToolTask, CompactionTask), wraps (decorating a tool or section by name) and tasks. By default every conversation selects every installed extension, in install order.

扩展:工具、提示词段、钩子、包装器与任务Extensions: tools, sections, hooks, wraps and tasks
← 左右滑动查看,点击放大 →← swipe to scroll, tap to enlarge →
Extensions: tools · sections · hooks · wraps · tasksdefineExtension({ name, … })钩子挂在三个内置任务上Hooks attach to the three built-in tasksBUILTIN_TASKS系统提示词The system promptharness/prompt.ts重载ReloadtoolssectionshookswrapstasksGenerationTaskpi.generationToolTaskpi.toolCompactionTaskpi.compactiondefineTool:TypeBox 参数defineTool: TypeBox paramssection(name, fn)hook(Task, {…}):见右hook(Task, {…}): see rightwrapTool / wrapSection自己的持久任务your own durable tasks同名工具:后安装的覆盖先安装的Same-name tool: the later extension winsbeforeRequest替换一次请求的消息replace one request's messagesafterResponse看到回答observe the answeronYield{ continue } 让 run 继续{ continue } keeps the run goingafterTools一轮工具都结束后after a round's toolsbeforeTool{ block } 或改写参数{ block } or rewrite argsafterTool替换结果replace the resultbeforeCompact拒绝或自给摘要decline or supply a summary钩子只在选中该扩展的会话里生效:configure({ extensions: { add: [Guard] } })Hooks act only in conversations that select their extension: configure({ extensions: { add: [Guard] } })按顺序渲染所选扩展的段;instructions 最后渲染。Selected extensions' sections render in order; instructions last.<cwd>\n/work/repo\n</cwd> ← section("cwd", i => i.env?.cwd)段与工具变化存成转录里按位置的 pi.system 条目,Section and tool changes are stored as positional pi.system只重发变化的部分 → provider 提示缓存保持命中。entries; only changes are resent → prompt caches stay warm.每次都变的段(如当前时间)会破坏缓存。A section that changes every time (e.g. the time) defeats that.registry.install(ext):同名就原地替换,一步完成。registry.install(ext): same name replaces in place, in one step.已开始的工作保留它拿到的代码:Work that started keeps the code it took:· 运行中的工具调用用旧实现跑完· a running tool call finishes on the old code· 每个任务阶段开始时解析一次钩子和 agent· each task phase resolves hooks and agent once· 下一个阶段 / 请求 / 调用用新代码· the next phase / request / call uses new code重启后重新安装同样的扩展,其 pending 任务就会继续。After a restart, install them again; their pending tasks resume.扩展extensions内置任务与钩子built-in tasks & hooks提示词prompt重载reload

要点Key points

  • 钩子:generation 有 beforeRequest、afterResponse、onYield({ continue } 让 run 继续)、afterTools;工具有 beforeTool({ block } 或改参数)、afterTool;压缩有 beforeCompact。Hooks: generation has beforeRequest, afterResponse, onYield ({ continue } keeps the run going), afterTools; tools have beforeTool ({ block } or rewrite arguments), afterTool; compaction has beforeCompact.
  • 段与工具的变化存成转录里按位置的 pi.system 条目,只重发变化的部分,让 provider 的提示缓存保持命中;每次都变的段会破坏缓存。Section and tool changes are stored as positional pi.system entries, and only changes are resent, keeping provider prompt caches warm; a section that changes every time defeats that.
  • 同名工具后安装的覆盖先安装的;wrapTool() 装饰最后胜出的那个。工具结果带 control: { terminate: true } 且整轮都这样时,run 不再请求模型就结束。A later extension's same-name tool replaces an earlier one; wrapTool() decorates whichever won. When every result of a round returns control: { terminate: true }, the run ends without another model request.
  • 重载:同名 install() 原地替换。已开始的工作保留它拿到的代码;每个任务阶段开始时从 Registry 解析一次钩子与 agent,下一阶段才用新代码。Reload: install() with an installed name replaces in place. Started work keeps the code it took; each task phase resolves hooks and the agent once at its start, and the next phase uses the new code.

工作原理How it works

  1. defineExtension({ name, tools: [defineTool({ name, parameters, execute })], sections: [section("cwd", i => i.env?.cwd)] })。defineExtension({ name, tools: [defineTool({ name, parameters, execute })], sections: [section("cwd", i => i.env?.cwd)] }).
  2. registry.install(ext);只想在部分会话生效时,用 configure({ extensions: { add: [Ext] } }) 选上。registry.install(ext); to limit it to some conversations, select it with configure({ extensions: { add: [Ext] } }).
  3. @earendil-works/pi-durable/tools 的 CodingTools 一次装上 read、write、edit、bash,它们只通过调用的执行环境访问文件与进程。CodingTools from @earendil-works/pi-durable/tools installs read, write, edit and bash at once; they touch files and processes only through the call's environment.

关键文件Key files · v1.1.0

文件File符号Symbol作用Role
packages/durable/src/harness/define.tsL6defineExtension()定义扩展defining an extension
packages/durable/src/harness/define.tsL29hook()钩子hooks
packages/durable/src/harness/registry.tsL112createRegistry()Registrythe Registry
packages/durable/src/harness/registry.tsL10BUILTIN_TASKS三个内置任务the three built-in tasks
packages/durable/src/harness/prompt.tsL66planSystemEntries()按位置的系统条目positional system entries
packages/durable/src/tools/index.tsL22CodingTools内置工具built-in tools
10Infra

扩展点Extension points#

defineExtension({ tools, sections, hooks, wraps, tasks })把功能打包成一个可安装、可重载的扩展。Package functionality as an installable, reloadable extension.
defineTool({ parameters, execute, replay })TypeBox 参数;replay: "safe" 让崩溃后重跑。TypeBox parameters; replay: "safe" reruns after a crash.
hook(Task, { … })观察或调整内置任务:beforeTool、onYield、beforeCompact…Observe or adjust built-in tasks: beforeTool, onYield, beforeCompact…
defineDoc / defineEntry / defineTask你自己的文档、条目和持久任务。Your own documents, entries and durable tasks.
Storage实现 commit + 读取接口,用一致性套件验证。Implement commit + the read interface, verify with the conformance suite.
ExecutionEnvFileSystem + exec:例如 env 包的 RemoteExecutionEnv。FileSystem + exec: e.g. the env package's RemoteExecutionEnv.
HarnessOptions.env / settings每次调用构建环境;实时读取的运行策略。Build the environment per call; run policy read live.
11Infra

测试Tests#

一句话结论Takeaway测试覆盖会话与 Harness、任务的恢复与迁移、文档、收件箱、压缩与各个存储后端;存储与执行环境另有可复用的一致性套件(./testing),env 包用同一套件验证远程实现。Tests cover sessions and the Harness, task recovery and migration, documents, the inbox, compaction and every storage backend; storage and the execution environment also have reusable conformance suites (./testing), which the env package runs against its remote implementation.

另有 29 个测试文件未列出(共 49 个,743 个用例)。29 more test files not listed (49 files, 743 cases in total).

用例数 = 源码中 it( / test( 的出现次数(静态统计,不运行测试)。Cases = occurrences of it( / test( in the source (static count; tests were not run).

12Infra

交叉链接Cross-links#