v1.1.0Pi v1.1.0 源码分析Analysis of Pi v1.1.0 · 2026-10-07

durabledurable

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

InfraInfrapi-ai

← → 翻页 · F 全屏 · O 目录 · T 明暗 · L 语言← → navigate · F fullscreen · O overview · T theme · L language

图解 Pi Agent · Pi v1.1.0Pi Agent, Illustrated · Pi v1.1.001
01 · 模块结构01 · Module structureInfra

模块结构Module structure

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

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.

图解 Pi Agent · Pi v1.1.0Pi Agent, Illustrated · Pi v1.1.002
02 · 概念模型02 · The modelInfra

概念模型The model

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

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.

图解 Pi Agent · Pi v1.1.0Pi Agent, Illustrated · Pi v1.1.003
03 · 回答一次输入03 · Answering one inputInfra

回答一次输入Answering one input

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

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 Agent · Pi v1.1.0Pi Agent, Illustrated · Pi v1.1.004
04 · 任务:状态机与所有权04 · Tasks: state machine and ownershipInfra

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

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

每次状态转换都在 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.

图解 Pi Agent · Pi v1.1.0Pi Agent, Illustrated · Pi v1.1.005
05 · 会话忙时的提交05 · Submitting to a busy conversationInfra

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

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

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.

图解 Pi Agent · Pi v1.1.0Pi Agent, Illustrated · Pi v1.1.006
06 · 文档与观察06 · Documents and watchingInfra

文档与观察Documents and watching

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

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.

图解 Pi Agent · Pi v1.1.0Pi Agent, Illustrated · Pi v1.1.007
07 · 存储后端07 · Storage backendsInfra

存储后端Storage backends

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

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.

图解 Pi Agent · Pi v1.1.0Pi Agent, Illustrated · Pi v1.1.008
08 · 扩展、钩子与重载08 · Extensions, hooks and reloadInfra

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

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

钩子: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 Agent · Pi v1.1.0Pi Agent, Illustrated · Pi v1.1.009
扩展点Extension pointspi-coding-agent

durable 的扩展点Where to plug into durable

1
defineExtension({ tools, sections, hooks, wraps, tasks })defineExtension({ tools, sections, hooks, wraps, tasks })

把功能打包成一个可安装、可重载的扩展。Package functionality as an installable, reloadable extension.

2
defineTool({ parameters, execute, replay })defineTool({ parameters, execute, replay })

TypeBox 参数;replay: "safe" 让崩溃后重跑。TypeBox parameters; replay: "safe" reruns after a crash.

3
hook(Task, { … })hook(Task, { … })

观察或调整内置任务:beforeTool、onYield、beforeCompact…Observe or adjust built-in tasks: beforeTool, onYield, beforeCompact…

4
defineDoc / defineEntry / defineTaskdefineDoc / defineEntry / defineTask

你自己的文档、条目和持久任务。Your own documents, entries and durable tasks.

5
StorageStorage

实现 commit + 读取接口,用一致性套件验证。Implement commit + the read interface, verify with the conformance suite.

图解 Pi Agent · Pi v1.1.0Pi Agent, Illustrated · Pi v1.1.010
←
1 / 10