CLI → AgentSession → agentLoop → pi-ai → tools → JSONL → TUI diff
CLI → AgentSession → agentLoop → pi-ai → tools → JSONL → TUI diff
你在终端里敲下一行「读取 README.md,用一句话告诉我这个项目做什么」之后,这串文字要穿过 26 道工序、跨 6 个 npm 包、在 3 层消息模型里变形,最后才变成屏幕上滚动的 token 和磁盘上的一行 JSONL。对照 earendil-works/pi 生产源码与 hahhforest/pi-textbook 的 15 个 checkpoint,把每一步的类层级、事件流、owner 分工、可视化全摊开。
After you type «read README.md and tell me what this project does in one sentence», that string passes through 26 stations, crosses six npm packages, morphs across three message layers, and only then becomes scrolling tokens on screen and one JSONL line on disk. Cross-reading earendil-works/pi production source with hahhforest/pi-textbook's 15 checkpoints — every step: class hierarchy, event flow, owner roles, and diagrams.
全文 26 章不是平铺的百科条目,而是一条固定主线 prompt从终端回车追到 JSONL 落盘的流水线。下面这张图是望远镜:告诉你 Phase 在哪、章节密度在哪、心脏区(runLoop + tools)在哪。读任何一章前,先看这里的 compass 条(每章顶部)确认自己在全局中的位置。
All 26 chapters are not a flat encyclopedia — they are a pipeline tracing one fixed through-line prompt from Enter to JSONL on disk. The diagram below is the telescope: where phases sit, where chapter density peaks, where the heart (runLoop + tools) lives. Before any chapter, check the compass bar at the top to locate yourself in the whole.
h2 章标题;正文小节为 C07.1 式编号;「深度细读」块内为 C07.4+,FAQ / Case Study 不再占用标题层级。
Heading convention: one h2 chapter title; body sections numbered C07.1; deep-dive block uses C07.4+; FAQ / Case Study use labels, not headings.
agent 产品战争里的第三条路
a third path in the agent product war
2025–2026 年的 coding agent 市场被两条路线撕成两半:密封产品(Claude Code、Cursor、Windsurf)和可组合 harness(Pi、OpenCode、aider 变体)。我们的主线 prompt——读取 README.md,用一句话告诉我这个项目做什么——在两种架构里走的路径完全不同:产品把中间层焊死在二进制里;harness 把每一站写成可测试的 TypeScript。
The 2025–2026 coding-agent market splits into sealed products (Claude Code, Cursor, Windsurf) and composable harnesses (Pi, OpenCode, aider variants). Our through-line prompt — read README.md and tell me what this project does in one sentence — takes completely different paths: products weld the middle layers into a binary; harnesses write every station as testable TypeScript.
| Claude Code | Cursor | Pi | |
|---|---|---|---|
| 定位 | 密封 CLI 产品 | 密封 IDE 产品 | 开源 harness |
| 会话格式 | 专有 | 专有 | JSONL 树 |
| 扩展 | MCP + 插件 | VS Code 生态 | TypeScript hooks |
| 可 fork | ✗ | ✗ | ✓ pi-mono |
| 主线可读 | 黑盒 | 黑盒 | agent-loop.ts 逐行 |
密封产品的复杂度藏在黑盒里:你不知道 steering 消息如何插队、compaction 的精确阈值、tool 并行策略。Pi 把这三件事写进 agent-loop.ts 和 agent-session.ts,用单元测试锁住行为。你可以 fork pi-mono 换工具、写扩展注入 lint、用 --mode rpc 嵌进 CI。
Sealed products hide complexity. Pi writes steering, compaction, and tool parallelism into agent-loop.ts and agent-session.ts, locked by tests. Fork pi-mono, swap tools, inject lint via extensions, embed with --mode rpc.
产品卖的是答案。
Harness 卖的是能问出下一个问题的显微镜。 Products sell answers.
Harnesses sell the microscope to ask the next question. Field Note · 10
prologue.ts)· 生产映射:packages/coding-agent/src/main.ts · packages/agent/src/agent-loop.ts
📘 pi-textbook checkpoint 00: 一次 README 读取的七个里程碑 (prologue.ts) · production: packages/coding-agent/src/main.ts · packages/agent/src/agent-loop.ts
| CP | 主题 | 教学 artifact | 生产映射 |
|---|---|---|---|
| cp 00 | 一次 README 读取的七个里程碑 | prologue.ts | packages/coding-agent/src/main.ts · packages/agent/src/agent-loop.ts |
| cp 13 | Composition Root | composition-root | packages/coding-agent/src/core/agent-session.ts · sdk.ts |
Claude Code 把编排藏在产品里;Pi 把编排写在 agent-loop.ts。迁移的第一步不是换 CLI,而是跑通 pi-textbook checkpoint 00 的七里程碑,建立事件心智模型。
Claude Code hides orchestration in the product; Pi writes it in agent-loop.ts. Migration starts not with a new CLI but checkpoint 00's seven milestones — building an event mental model.
用户输入「读取 README.md,用一句话告诉我这个项目做什么」后,packages/agent/src/agent-loop.ts 定义了此阶段的类型契约与副作用边界。
After «read README.md and tell me what this project does in one sentence», packages/agent/src/agent-loop.ts defines type contracts and side-effect boundaries here.
在 pi-mono 仓库打开该文件,搜索 export 与 AgentEvent——这是比读 README 更快的定位法。
In pi-mono, search export and AgentEvent — faster than README alone.
设计原则:核心循环(agent-loop)不 import TUI;本模块通过事件与回调与上游通信。
Principle: agent-loop never imports TUI; this module talks upstream via events/callbacks.
pi-textbook 对应 checkpoint 提供剥离 OAuth/TUI 后的最小可运行切片,建议先跑测试再读生产文件。
Matching textbook checkpoint offers a minimal slice without OAuth/TUI — run its test before production file.
agent-loop.ts 内的 async 函数通常是 IO 边界:磁盘(read/write JSONL)、网络(streamSimple)、或子进程(bash)。
async functions in agent-loop.ts are usually IO boundaries: disk, network, or subprocess.
成功路径与错误路径都必须 emit 事件,否则 TUI 会卡在 streaming 状态、JSONL 会缺 entry。
Success and error paths must emit events or TUI stalls streaming and JSONL misses entries.
length 截断时 agent-loop.ts 的 failToolCallsFromTruncatedMessage 批量失败 tool calls,防止执行损坏参数。
On length truncation, failToolCallsFromTruncatedMessage fails all tool calls to avoid corrupted args.
对照 packages/agent/test/agent-loop.test.ts 中的同名场景测试用例。
Compare with matching scenarios in packages/agent/test/agent-loop.test.ts.
AgentLoopConfig(packages/agent/src/types.ts)列出所有注入点:convertToLlm、getSteeringMessages、prepareNextTurn 等。
AgentLoopConfig in types.ts lists injection points: convertToLlm, getSteeringMessages, prepareNextTurn, etc.
AgentSession 在构造 config 时绑定本模块实现;换模式(print/rpc/interactive)不换 agent-loop。
AgentSession binds this module when building config; modes change, agent-loop does not.
fork 项目时优先替换本模块而非复制 agent-loop——这是 Pi 社区的实际 fork 模式。
When forking, replace this module first rather than copying agent-loop — the common community pattern.
--verbose 模式下 stderr 事件类型可回溯到 packages/agent/src/agent-loop.ts 的具体行。
Under --verbose, stderr event types trace to lines in packages/agent/src/agent-loop.ts.
① 主线 prompt 进入本章模块时,第一个被调用的 export 函数是什么?② 它 emit 的第一个 AgentEvent 类型是什么?③ 若在此层抛错,TUI 会卡在什么状态?④ 对应的 pi-textbook checkpoint 测试命令是什么?⑤ packages/agent/src/agent-loop.ts 中哪一行最值得打断点?
① First export called when through-line enters this module? ② First AgentEvent type emitted? ③ If this layer throws, what TUI state stalls? ④ Matching pi-textbook checkpoint test command? ⑤ Best breakpoint line in packages/agent/src/agent-loop.ts?
不要一次读完整个文件。用「从测试入手」法:先 npm test -- <matching-test>,看失败断言指向哪行,再读那一行上下游 30 行。生产 pi-mono 的测试覆盖率在 agent-loop 与 session-manager 最高——优先读测试。
Don't read whole files at once. «Test-first» method: run npm test -- <matching-test>, see which assertion fails, read ±30 lines around that line. Highest test coverage in agent-loop and session-manager — read tests first.
对照本文 TRACE 盒与 --verbose stderr:每个 event type 应在源码中有且仅有一处「决策点」——若找不到,说明事件在更上层(AgentSession)或更下层(pi-ai adapter)合并发出。
Cross-check TRACE box with --verbose stderr: each event type should have exactly one «decision point» in source — if missing, event is merged upstream (AgentSession) or downstream (pi-ai adapter).
进阶:用 git blame packages/agent/src/agent-loop.ts 看最近改动——Pi 的 harness 接口仍在活跃演进,注释可能落后于 AgentLoopConfig 类型定义。类型即文档。
Advanced: git blame packages/agent/src/agent-loop.ts for recent changes — Pi harness APIs still evolve; comments may lag AgentLoopConfig types. Types are the doc.
Station 00:harness 心智,尚未 read。
Station 00: harness mindset; read not yet.
跑 checkpoint 00 再对照 AgentSession.prompt()。
Run checkpoint 00 then compare AgentSession.prompt().
| CP | 主题 | 教学 artifact | 生产映射 |
|---|---|---|---|
| cp 00 | 一次 README 读取的七个里程碑 | prologue.ts | packages/coding-agent/src/main.ts · packages/agent/src/agent-loop.ts |
| cp 13 | Composition Root | composition-root | packages/coding-agent/src/core/agent-session.ts · sdk.ts |
CLI + 可嵌入库。
CLI plus embeddable libs.
闭源,无法审计 agentLoop。
Closed source.
克隆 pi 与 pi-textbook。
Clone pi and pi-textbook.
本章主线 prompt 经过此模块时,请在 pi-mono 打开 packages/agent/src/agent-loop.ts,搜索 export 与测试文件中的同名场景。对照 pi-textbook 对应 checkpoint 的 workshop 测试——先让测试绿,再读生产实现。
When the through-line prompt crosses this module, open packages/agent/src/agent-loop.ts in pi-mono, search export and matching test scenarios. Cross-read the pi-textbook checkpoint workshop test — green test first, then production.
建议命令:cd packages/agent && npm test -- agent-loop(路径因章而异)。测试文件是行为契约的executable spec,比注释更可靠。
Suggested: cd packages/agent && npm test -- agent-loop (path varies). Tests are executable specs of behavior contracts — more reliable than comments.
用 --verbose 跑一轮主线 prompt,把 stderr event 类型与源码中的 emit 调用逐行对齐——这是本文 C26 推荐的 trace 方法。
Run the through-line with --verbose, align stderr event types with emit calls in source — the trace method recommended in C26.
从回车到 JSONL 的全景图
from Enter to JSONL, the full map
按下回车之后,读取 README.md,用一句话告诉我这个项目做什么 不是直接发给 LLM——它要先变成 AgentMessage,穿过 AgentSession.prompt(),触发 agentLoop(),在 runLoop 的双环里转两圈 model stream,中间插一次 read 工具,最后才在 TUI 里滚动、在 JSONL 里落盘。
After Enter, read README.md and tell me what this project does in one sentence does not go straight to the LLM — it becomes an AgentMessage, passes through AgentSession.prompt(), triggers agentLoop(), spins two model streams in runLoop's twin loops with a read tool in between, then scrolls in the TUI and lands on disk as JSONL.
pi-textbook 用离线 ScriptedModel 固定了主线 prompt 的七步 trace。生产代码 agent-loop.ts 产生语义等价但事件更细的事件流。
pi-textbook fixes a seven-step trace for the through-line prompt with offline ScriptedModel. Production agent-loop.ts emits semantically equivalent but finer-grained events.
| # | type | owner | detail |
|---|---|---|---|
| 01 | user_message | user | 读取 README.md,用一句话告诉我… |
| 02 | model_start | model | turn=1 |
| 03 | assistant_message | model | stopReason=toolUse · call_1 |
| 04 | tool_start | loop | read(call_1) |
| 05 | tool_result | tool | README fixture |
| 06 | model_start | model | turn=2 |
| 07 | assistant_message | model | stopReason=stop |
七个里程碑是望远镜。
二十六个章节是显微镜。 Seven milestones are the telescope.
Twenty-six chapters are the microscope. Field Note · 10
站 01–06:shell 回车 → cli.ts → main.ts → createAgentSession() → prompt() 将「读取 README.md,用一句话告诉我这个项目做什么」变为 AgentMessage 并写 JSONL。
Stations 01–06: Enter → cli.ts → main.ts → createAgentSession() → prompt() turns «read README.md and tell me what this project does in one sentence» into AgentMessage + JSONL.
agentLoop emit agent_start/turn_start 后 runLoop 内环调用 streamAssistantResponse——第一次 owner=model 边界。
agentLoop emits agent_start/turn_start; inner runLoop calls streamAssistantResponse — first owner=model boundary.
典型第一次 stream:stopReason=toolUse,toolCall type=read path=README.md(agent-loop.ts 第203行 filter toolCall)。
Typical first stream: stopReason=toolUse, read README.md (agent-loop.ts line 203 filters toolCall).
owner=loop 站 07–11:executeToolCalls → packages/coding-agent/src/core/tools/read.ts createReadTool。
owner=loop 07–11: executeToolCalls → createReadTool in read.ts.
toolResult push 后 convertToLlm(messages) 转 Message[];第二次 streamAssistantResponse。
After toolResult, convertToLlm to Message[]; second streamAssistantResponse.
stopReason=stop 产出最终回答;message_update 驱动 TUI;message_end 写 JSONL。
stopReason=stop yields answer; message_update drives TUI; message_end writes JSONL.
turn_end 携带 toolResults;无 follow-up 时 agent_end。
turn_end carries toolResults; agent_end without follow-up.
01 user_message↔01–04;03 assistant(toolUse)↔06;04–05 tool↔07–12;07 stop↔15–18。
01 user↔01–04; 03 toolUse↔06; 04–05 tool↔07–12; 07 stop↔15–18.
站 19–22:扩展 hook、compaction 检查、TUI flush、leafId——短任务可能 noop。
19–22: extension hooks, compaction, TUI flush, leafId — may noop on short task.
C06↔03–05;C07↔05–08;C10↔09–11;C14↔14–16。
C06↔03–05; C07↔05–08; C10↔09–11; C14↔14–16.
验收:pi --verbose 事件序列应对齐 prologue.ts 类型顺序。
Acceptance: pi --verbose event types mirror prologue.ts order.
① 主线 prompt 进入本章模块时,第一个被调用的 export 函数是什么?② 它 emit 的第一个 AgentEvent 类型是什么?③ 若在此层抛错,TUI 会卡在什么状态?④ 对应的 pi-textbook checkpoint 测试命令是什么?⑤ packages/coding-agent/src/main.ts 中哪一行最值得打断点?
① First export called when through-line enters this module? ② First AgentEvent type emitted? ③ If this layer throws, what TUI state stalls? ④ Matching pi-textbook checkpoint test command? ⑤ Best breakpoint line in packages/coding-agent/src/main.ts?
不要一次读完整个文件。用「从测试入手」法:先 npm test -- <matching-test>,看失败断言指向哪行,再读那一行上下游 30 行。生产 pi-mono 的测试覆盖率在 agent-loop 与 session-manager 最高——优先读测试。
Don't read whole files at once. «Test-first» method: run npm test -- <matching-test>, see which assertion fails, read ±30 lines around that line. Highest test coverage in agent-loop and session-manager — read tests first.
对照本文 TRACE 盒与 --verbose stderr:每个 event type 应在源码中有且仅有一处「决策点」——若找不到,说明事件在更上层(AgentSession)或更下层(pi-ai adapter)合并发出。
Cross-check TRACE box with --verbose stderr: each event type should have exactly one «decision point» in source — if missing, event is merged upstream (AgentSession) or downstream (pi-ai adapter).
进阶:用 git blame packages/coding-agent/src/main.ts 看最近改动——Pi 的 harness 接口仍在活跃演进,注释可能落后于 AgentLoopConfig 类型定义。类型即文档。
Advanced: git blame packages/coding-agent/src/main.ts for recent changes — Pi harness APIs still evolve; comments may lag AgentLoopConfig types. Types are the doc.
站 01:「读取 README.md,用一句话告诉我这个项目做什么」进入 cli.ts。
Station 01: «read README.md and tell me what this project does in one sentence» enters cli.ts.
| CP | 主题 | 教学 artifact | 生产映射 |
|---|---|---|---|
| cp 00 | 一次 README 读取的七个里程碑 | prologue.ts | packages/coding-agent/src/main.ts · packages/agent/src/agent-loop.ts |
章含背景 C03–C05 与全景 C24–C25。
Chapters include background and landscape.
prologue.ts trace 约定。
prologue.ts trace convention.
对照 coding-agent package.json 版本。
Match coding-agent version.
本章主线 prompt 经过此模块时,请在 pi-mono 打开 packages/coding-agent/src/main.ts,搜索 export 与测试文件中的同名场景。对照 pi-textbook 对应 checkpoint 的 workshop 测试——先让测试绿,再读生产实现。
When the through-line prompt crosses this module, open packages/coding-agent/src/main.ts in pi-mono, search export and matching test scenarios. Cross-read the pi-textbook checkpoint workshop test — green test first, then production.
建议命令:cd packages/agent && npm test -- agent-loop(路径因章而异)。测试文件是行为契约的executable spec,比注释更可靠。
Suggested: cd packages/agent && npm test -- agent-loop (path varies). Tests are executable specs of behavior contracts — more reliable than comments.
用 --verbose 跑一轮主线 prompt,把 stderr event 类型与源码中的 emit 调用逐行对齐——这是本文 C26 推荐的 trace 方法。
Run the through-line with --verbose, align stderr event types with emit calls in source — the trace method recommended in C26.
Mario Zechner 与 earendil-works
Mario Zechner and earendil-works
pi-mono 是 Mario Zechner(Badlogic Games,libGDX 作者)在 earendil-works 组织下的 monorepo。它不是「又一个 ChatGPT 套壳」——而是一套可 fork 的 agent harness,npm 发布六个包。
pi-mono is Mario Zechner's (Badlogic Games, libGDX author) monorepo under earendil-works. Not another ChatGPT wrapper — a forkable agent harness published as six npm packages.
| Package | 职责 / Role | 主线 touchpoint |
|---|---|---|
| @earendil-works/pi-agent-core | agentLoop · types · StreamFn | C07–C13 |
| @earendil-works/pi-ai | Models · providers · EventStream | C14–C16 |
| @earendil-works/pi-tui | Terminal · diff render · Editor | C17–C18 |
| @earendil-works/coding-agent | CLI · AgentSession · tools | C06–C08 |
| @earendil-works/pi-protocol | RPC JSONL 协议 | C25 |
| pi-server | 远程会话 CBOR | C25 |
pi-textbook 把生产代码降维成 15 个 checkpoint 的可运行 workshop。每个 checkpoint 对应 pi-mono 里一个真实目录,但剥离了 OAuth、40+ provider、TUI 差分渲染等生产复杂度。
pi-textbook reduces production code into 15 runnable workshop checkpoints. Each maps to a real pi-mono directory, stripped of OAuth, 40+ providers, TUI diff rendering, etc.
| Checkpoint | 教学焦点 | 生产文件 |
|---|---|---|
| 00 | 一次 README 读取的七个里程碑 | packages/coding-agent/src/main.ts · packages/agent/src/agent-loop.ts |
| 01 | TypeScript 生存集 | — |
| 02 | EventStream | packages/ai/src/utils/event-stream.ts |
| 03 | Message IR | packages/agent/src/types.ts · packages/coding-agent/src/core/messages.ts |
| 04 | ScriptedModel | — |
| 05 | Provider Adapter | packages/ai/src/api/*.ts · packages/ai/src/models.ts |
| 06 | Tool Contract | packages/agent/src/agent-loop.ts · packages/coding-agent/src/core/tools/ |
| 07 | Agent Loop | packages/agent/src/agent-loop.ts |
用户输入「读取 README.md,用一句话告诉我这个项目做什么」后,pi-mono/package.json 定义了此阶段的类型契约与副作用边界。
After «read README.md and tell me what this project does in one sentence», pi-mono/package.json defines type contracts and side-effect boundaries here.
在 pi-mono 仓库打开该文件,搜索 export 与 AgentEvent——这是比读 README 更快的定位法。
In pi-mono, search export and AgentEvent — faster than README alone.
设计原则:核心循环(agent-loop)不 import TUI;本模块通过事件与回调与上游通信。
Principle: agent-loop never imports TUI; this module talks upstream via events/callbacks.
pi-textbook 对应 checkpoint 提供剥离 OAuth/TUI 后的最小可运行切片,建议先跑测试再读生产文件。
Matching textbook checkpoint offers a minimal slice without OAuth/TUI — run its test before production file.
package.json 内的 async 函数通常是 IO 边界:磁盘(read/write JSONL)、网络(streamSimple)、或子进程(bash)。
async functions in package.json are usually IO boundaries: disk, network, or subprocess.
成功路径与错误路径都必须 emit 事件,否则 TUI 会卡在 streaming 状态、JSONL 会缺 entry。
Success and error paths must emit events or TUI stalls streaming and JSONL misses entries.
length 截断时 agent-loop.ts 的 failToolCallsFromTruncatedMessage 批量失败 tool calls,防止执行损坏参数。
On length truncation, failToolCallsFromTruncatedMessage fails all tool calls to avoid corrupted args.
对照 packages/agent/test/agent-loop.test.ts 中的同名场景测试用例。
Compare with matching scenarios in packages/agent/test/agent-loop.test.ts.
AgentLoopConfig(packages/agent/src/types.ts)列出所有注入点:convertToLlm、getSteeringMessages、prepareNextTurn 等。
AgentLoopConfig in types.ts lists injection points: convertToLlm, getSteeringMessages, prepareNextTurn, etc.
AgentSession 在构造 config 时绑定本模块实现;换模式(print/rpc/interactive)不换 agent-loop。
AgentSession binds this module when building config; modes change, agent-loop does not.
fork 项目时优先替换本模块而非复制 agent-loop——这是 Pi 社区的实际 fork 模式。
When forking, replace this module first rather than copying agent-loop — the common community pattern.
--verbose 模式下 stderr 事件类型可回溯到 pi-mono/package.json 的具体行。
Under --verbose, stderr event types trace to lines in pi-mono/package.json.
显式事件总线 vs 隐式回调:Pi 选择前者,事件类型在 AgentEvent union 中版本化。
Explicit event bus vs implicit callbacks: Pi chooses the former; events versioned in AgentEvent union.
扩展应 subscribe 事件而非 monkey-patch 私有方法——ExtensionRunner 提供正规 hook 面。
Extensions should subscribe to events, not monkey-patch privates — ExtensionRunner provides hooks.
JSONL CURRENT_SESSION_VERSION = 3:改 entry schema 必须 bump version 并提供迁移。
JSONL CURRENT_SESSION_VERSION = 3: schema changes need version bump and migration.
对比 Claude Code:闭源栈无法确认 compaction 触发点;Pi 的 shouldCompact 在源码中可读。
vs Claude Code: closed stack hides compaction triggers; Pi's shouldCompact is readable.
① 主线 prompt 进入本章模块时,第一个被调用的 export 函数是什么?② 它 emit 的第一个 AgentEvent 类型是什么?③ 若在此层抛错,TUI 会卡在什么状态?④ 对应的 pi-textbook checkpoint 测试命令是什么?⑤ packages/agent/src/agent-loop.ts 中哪一行最值得打断点?
① First export called when through-line enters this module? ② First AgentEvent type emitted? ③ If this layer throws, what TUI state stalls? ④ Matching pi-textbook checkpoint test command? ⑤ Best breakpoint line in packages/agent/src/agent-loop.ts?
不要一次读完整个文件。用「从测试入手」法:先 npm test -- <matching-test>,看失败断言指向哪行,再读那一行上下游 30 行。生产 pi-mono 的测试覆盖率在 agent-loop 与 session-manager 最高——优先读测试。
Don't read whole files at once. «Test-first» method: run npm test -- <matching-test>, see which assertion fails, read ±30 lines around that line. Highest test coverage in agent-loop and session-manager — read tests first.
对照本文 TRACE 盒与 --verbose stderr:每个 event type 应在源码中有且仅有一处「决策点」——若找不到,说明事件在更上层(AgentSession)或更下层(pi-ai adapter)合并发出。
Cross-check TRACE box with --verbose stderr: each event type should have exactly one «decision point» in source — if missing, event is merged upstream (AgentSession) or downstream (pi-ai adapter).
进阶:用 git blame packages/agent/src/agent-loop.ts 看最近改动——Pi 的 harness 接口仍在活跃演进,注释可能落后于 AgentLoopConfig 类型定义。类型即文档。
Advanced: git blame packages/agent/src/agent-loop.ts for recent changes — Pi harness APIs still evolve; comments may lag AgentLoopConfig types. Types are the doc.
| CP | 主题 | 教学 artifact | 生产映射 |
|---|---|---|---|
| cp 13 | Composition Root | composition-root | packages/coding-agent/src/core/agent-session.ts · sdk.ts |
pi-mono/package.json。
pi-mono/package.json.
mock StreamFn 或 ScriptedModel。
mock StreamFn or ScriptedModel.
生产含 OAuth/TUI/40+ providers。
Production adds OAuth/TUI/40+ providers.
本章主线 prompt 经过此模块时,请在 pi-mono 打开 package.json,搜索 export 与测试文件中的同名场景。对照 pi-textbook 对应 checkpoint 的 workshop 测试——先让测试绿,再读生产实现。
When the through-line prompt crosses this module, open package.json in pi-mono, search export and matching test scenarios. Cross-read the pi-textbook checkpoint workshop test — green test first, then production.
建议命令:cd packages/agent && npm test -- agent-loop(路径因章而异)。测试文件是行为契约的executable spec,比注释更可靠。
Suggested: cd packages/agent && npm test -- agent-loop (path varies). Tests are executable specs of behavior contracts — more reliable than comments.
用 --verbose 跑一轮主线 prompt,把 stderr event 类型与源码中的 emit 调用逐行对齐——这是本文 C26 推荐的 trace 方法。
Run the through-line with --verbose, align stderr event types with emit calls in source — the trace method recommended in C26.
agent-core · ai · tui · coding-agent
agent-core · ai · tui · coding-agent
主线 prompt 读取 README.md,用一句话告诉我这个项目做什么 从 L6 进入,在 L5 循环,L4 调模型,L3 渲染,L2/L1 只在 RPC 模式参与。
Through-line prompt enters at L6, loops in L5, calls model at L4, renders at L3; L2/L1 only in RPC mode.
AgentMessage 在整个 agent-core 内流通;只在 streamAssistantResponse 调用点通过 convertToLlm 变成 pi-ai 的 Message[]。这条边界是 pi-textbook checkpoint 03 的核心教训。
AgentMessage flows through agent-core; only at streamAssistantResponse does convertToLlm produce pi-ai Message[]. Core lesson of textbook checkpoint 03.
| 层 | 可替换? | 例子 |
|---|---|---|
| agent-core | ✓ fork | 自定义 runLoop |
| pi-ai | ✓ | 换 provider adapter |
| coding-agent tools | ✓ | registerTool |
| JSONL format | △ | version 字段演进 |
types.ts)· 生产映射:packages/agent/src/types.ts · packages/coding-agent/src/core/messages.ts
📘 pi-textbook checkpoint 03: Message IR (types.ts) · production: packages/agent/src/types.ts · packages/coding-agent/src/core/messages.ts
composition-root)· 生产映射:packages/coding-agent/src/core/agent-session.ts · sdk.ts
📘 pi-textbook checkpoint 13: Composition Root (composition-root) · production: packages/coding-agent/src/core/agent-session.ts · sdk.ts
用户输入「读取 README.md,用一句话告诉我这个项目做什么」后,packages/agent/package.json 定义了此阶段的类型契约与副作用边界。
After «read README.md and tell me what this project does in one sentence», packages/agent/package.json defines type contracts and side-effect boundaries here.
在 pi-mono 仓库打开该文件,搜索 export 与 AgentEvent——这是比读 README 更快的定位法。
In pi-mono, search export and AgentEvent — faster than README alone.
设计原则:核心循环(agent-loop)不 import TUI;本模块通过事件与回调与上游通信。
Principle: agent-loop never imports TUI; this module talks upstream via events/callbacks.
pi-textbook 对应 checkpoint 提供剥离 OAuth/TUI 后的最小可运行切片,建议先跑测试再读生产文件。
Matching textbook checkpoint offers a minimal slice without OAuth/TUI — run its test before production file.
package.json 内的 async 函数通常是 IO 边界:磁盘(read/write JSONL)、网络(streamSimple)、或子进程(bash)。
async functions in package.json are usually IO boundaries: disk, network, or subprocess.
成功路径与错误路径都必须 emit 事件,否则 TUI 会卡在 streaming 状态、JSONL 会缺 entry。
Success and error paths must emit events or TUI stalls streaming and JSONL misses entries.
length 截断时 agent-loop.ts 的 failToolCallsFromTruncatedMessage 批量失败 tool calls,防止执行损坏参数。
On length truncation, failToolCallsFromTruncatedMessage fails all tool calls to avoid corrupted args.
对照 packages/agent/test/agent-loop.test.ts 中的同名场景测试用例。
Compare with matching scenarios in packages/agent/test/agent-loop.test.ts.
AgentLoopConfig(packages/agent/src/types.ts)列出所有注入点:convertToLlm、getSteeringMessages、prepareNextTurn 等。
AgentLoopConfig in types.ts lists injection points: convertToLlm, getSteeringMessages, prepareNextTurn, etc.
AgentSession 在构造 config 时绑定本模块实现;换模式(print/rpc/interactive)不换 agent-loop。
AgentSession binds this module when building config; modes change, agent-loop does not.
fork 项目时优先替换本模块而非复制 agent-loop——这是 Pi 社区的实际 fork 模式。
When forking, replace this module first rather than copying agent-loop — the common community pattern.
--verbose 模式下 stderr 事件类型可回溯到 packages/agent/package.json 的具体行。
Under --verbose, stderr event types trace to lines in packages/agent/package.json.
显式事件总线 vs 隐式回调:Pi 选择前者,事件类型在 AgentEvent union 中版本化。
Explicit event bus vs implicit callbacks: Pi chooses the former; events versioned in AgentEvent union.
扩展应 subscribe 事件而非 monkey-patch 私有方法——ExtensionRunner 提供正规 hook 面。
Extensions should subscribe to events, not monkey-patch privates — ExtensionRunner provides hooks.
JSONL CURRENT_SESSION_VERSION = 3:改 entry schema 必须 bump version 并提供迁移。
JSONL CURRENT_SESSION_VERSION = 3: schema changes need version bump and migration.
对比 Claude Code:闭源栈无法确认 compaction 触发点;Pi 的 shouldCompact 在源码中可读。
vs Claude Code: closed stack hides compaction triggers; Pi's shouldCompact is readable.
① 主线 prompt 进入本章模块时,第一个被调用的 export 函数是什么?② 它 emit 的第一个 AgentEvent 类型是什么?③ 若在此层抛错,TUI 会卡在什么状态?④ 对应的 pi-textbook checkpoint 测试命令是什么?⑤ packages/agent/src/agent-loop.ts 中哪一行最值得打断点?
① First export called when through-line enters this module? ② First AgentEvent type emitted? ③ If this layer throws, what TUI state stalls? ④ Matching pi-textbook checkpoint test command? ⑤ Best breakpoint line in packages/agent/src/agent-loop.ts?
不要一次读完整个文件。用「从测试入手」法:先 npm test -- <matching-test>,看失败断言指向哪行,再读那一行上下游 30 行。生产 pi-mono 的测试覆盖率在 agent-loop 与 session-manager 最高——优先读测试。
Don't read whole files at once. «Test-first» method: run npm test -- <matching-test>, see which assertion fails, read ±30 lines around that line. Highest test coverage in agent-loop and session-manager — read tests first.
对照本文 TRACE 盒与 --verbose stderr:每个 event type 应在源码中有且仅有一处「决策点」——若找不到,说明事件在更上层(AgentSession)或更下层(pi-ai adapter)合并发出。
Cross-check TRACE box with --verbose stderr: each event type should have exactly one «decision point» in source — if missing, event is merged upstream (AgentSession) or downstream (pi-ai adapter).
进阶:用 git blame packages/agent/src/agent-loop.ts 看最近改动——Pi 的 harness 接口仍在活跃演进,注释可能落后于 AgentLoopConfig 类型定义。类型即文档。
Advanced: git blame packages/agent/src/agent-loop.ts for recent changes — Pi harness APIs still evolve; comments may lag AgentLoopConfig types. Types are the doc.
| CP | 主题 | 教学 artifact | 生产映射 |
|---|---|---|---|
| cp 03 | Message IR | types.ts | packages/agent/src/types.ts · packages/coding-agent/src/core/messages.ts |
| cp 13 | Composition Root | composition-root | packages/coding-agent/src/core/agent-session.ts · sdk.ts |
packages/agent/package.json。
packages/agent/package.json.
mock StreamFn 或 ScriptedModel。
mock StreamFn or ScriptedModel.
生产含 OAuth/TUI/40+ providers。
Production adds OAuth/TUI/40+ providers.
本章主线 prompt 经过此模块时,请在 pi-mono 打开 packages/agent/package.json,搜索 export 与测试文件中的同名场景。对照 pi-textbook 对应 checkpoint 的 workshop 测试——先让测试绿,再读生产实现。
When the through-line prompt crosses this module, open packages/agent/package.json in pi-mono, search export and matching test scenarios. Cross-read the pi-textbook checkpoint workshop test — green test first, then production.
建议命令:cd packages/agent && npm test -- agent-loop(路径因章而异)。测试文件是行为契约的executable spec,比注释更可靠。
Suggested: cd packages/agent && npm test -- agent-loop (path varies). Tests are executable specs of behavior contracts — more reliable than comments.
用 --verbose 跑一轮主线 prompt,把 stderr event 类型与源码中的 emit 调用逐行对齐——这是本文 C26 推荐的 trace 方法。
Run the through-line with --verbose, align stderr event types with emit calls in source — the trace method recommended in C26.
No MCP · No sub-agents · No plan mode
No MCP · No sub-agents · No plan mode
Pi 的 README 明确列出故意不做的功能:No MCP · No sub-agents · No plan mode。这不是遗漏——是 harness 哲学的边界声明。
Pi's README explicitly lists intentionally omitted features: No MCP · No sub-agents · No plan mode. Not oversights — boundary statements of harness philosophy.
| 功能 | Claude Code | Pi | 替代路径 |
|---|---|---|---|
| MCP | 一等协议 | ✗ | TypeScript Extension API |
| Sub-agents | 内置 | ✗ | fork session + RPC |
| Plan mode | 专用 UI | ✗ | thinking level + 用户消息 |
| IDE 集成 | 原生 | 终端 only | 外部编辑器 |
| 权限弹窗 | GUI | 终端确认 | tool 级别 policy |
MCP 把工具发现协议化,但 Pi 选择 registerTool + npm/git 扩展包——类型安全、可测试、与 agent loop 同进程。代价是生态不互通 Claude Desktop 的 MCP 市场。
MCP protocolizes tool discovery; Pi chooses registerTool + npm/git extension packages — type-safe, testable, same process as agent loop. Cost: no interchange with Claude Desktop's MCP marketplace.
子 agent 本质是嵌套 agentLoop + 独立 context。Pi 用 session fork(JSONL parentSession)+ RPC 模式 达到类似效果,但不隐藏嵌套层。
Sub-agents are nested agentLoop + isolated context. Pi achieves similar via session fork (JSONL parentSession) + RPC mode without hiding the nesting.
少即是多:
每一层省略的功能,都是一层可观测性。 Less is more:
every omitted feature is a layer of observability gained. Field Note · 10
packages/coding-agent/README.md 第 498 行:No MCP。Mario 的博客解释:MCP 把工具发现协议化,但 Pi 选择 registerTool + Skills + npm/git 扩展包。
packages/coding-agent/README.md line 498: No MCP. Mario's blog: MCP protocolizes tool discovery; Pi chooses registerTool + Skills + npm/git extension packages.
对主线 prompt 的影响:模型调用 read 是 coding-agent 内置 tool,经 createReadTool 同进程执行——无需 MCP server 握手。
Through-line impact: model calls built-in read via createReadTool in-process — no MCP handshake.
代价:无法直接接入 Claude Desktop MCP 市场。收益:tool schema 在 TypeScript 中定义,validateToolArguments 在 agent-loop.ts 执行前校验。
Cost: no Claude Desktop MCP marketplace. Payoff: TypeScript tool schemas; validateToolArguments before execution.
扩展路径:examples/extensions/ 可添加 MCP;plan-mode/ 示例展示 extension 实现产品功能。
Extension path: examples/extensions/ can add MCP; plan-mode/ shows product features via extensions.
README 第 500 行:可用 tmux spawn 多个 pi 实例,或用 extension 自建,或安装第三方 pi package。
README line 500: spawn pi via tmux, build with extensions, or install third-party pi packages.
Pi 的替代是 session fork(SessionManager.fork,parentSession)+ RPC 模式(--mode rpc)。嵌套层不隐藏——JSONL 树可 diff。
Pi's alternative: session fork + RPC mode. Nesting visible in diffable JSONL tree.
主线 README 任务不需要子 agent:一次 read + 一次总结,单 agentLoop 足够。
README through-line needs no sub-agent: one read + one summary suffices.
packages/client 的 acquireSession exclusive lease 防止并发写同一会话。
packages/client acquireSession exclusive lease prevents concurrent session writes.
README 第 504 行:把计划写进文件、用 extension 实现、或安装 package。examples/extensions/plan-mode/ 提供 /plan 与 Ctrl+Alt+P。
README line 504: write plans to files, use extensions, or install packages. plan-mode/ provides /plan and Ctrl+Alt+P.
Pi 的 thinking level(ThinkingLevelSchema in protocol/schemas.ts)是模型侧推理,不是 UI plan 模式。
Pi's thinking level in protocol/schemas.ts is model-side reasoning, not UI plan mode.
对主线 prompt:模型可直接 toolUse read,无需先进入 plan 只读阶段。
Through-line: model can toolUse read directly — no plan-only phase.
每省略一个产品功能,就少一层黑盒。Pi 用 --verbose 事件 + 开源 agent-loop.ts 补偿 IDE 集成缺失。
Each omitted feature removes a black box. Pi compensates with --verbose events and open agent-loop.ts.
对比 Claude Code Plan 模式:用户看不见 plan 状态机;Pi plan-mode extension 仍走 ExtensionRunner 事件。
vs Claude Code Plan: users cannot see state machine; Pi plan-mode extension still emits via ExtensionRunner.
主线只需 read + summarize——恰好是 Pi 默认 tool 集的最小闭环。
Through-line needs only read + summarize — Pi default tool set minimal loop.
若 fork Pi 加 MCP:改动 ExtensionRunner + tool registry,不必 fork agent-loop.ts。
Forking to add MCP: change ExtensionRunner + tool registry, not agent-loop.ts.
① 主线 prompt 进入本章模块时,第一个被调用的 export 函数是什么?② 它 emit 的第一个 AgentEvent 类型是什么?③ 若在此层抛错,TUI 会卡在什么状态?④ 对应的 pi-textbook checkpoint 测试命令是什么?⑤ packages/agent/src/agent-loop.ts 中哪一行最值得打断点?
① First export called when through-line enters this module? ② First AgentEvent type emitted? ③ If this layer throws, what TUI state stalls? ④ Matching pi-textbook checkpoint test command? ⑤ Best breakpoint line in packages/agent/src/agent-loop.ts?
不要一次读完整个文件。用「从测试入手」法:先 npm test -- <matching-test>,看失败断言指向哪行,再读那一行上下游 30 行。生产 pi-mono 的测试覆盖率在 agent-loop 与 session-manager 最高——优先读测试。
Don't read whole files at once. «Test-first» method: run npm test -- <matching-test>, see which assertion fails, read ±30 lines around that line. Highest test coverage in agent-loop and session-manager — read tests first.
对照本文 TRACE 盒与 --verbose stderr:每个 event type 应在源码中有且仅有一处「决策点」——若找不到,说明事件在更上层(AgentSession)或更下层(pi-ai adapter)合并发出。
Cross-check TRACE box with --verbose stderr: each event type should have exactly one «decision point» in source — if missing, event is merged upstream (AgentSession) or downstream (pi-ai adapter).
进阶:用 git blame packages/agent/src/agent-loop.ts 看最近改动——Pi 的 harness 接口仍在活跃演进,注释可能落后于 AgentLoopConfig 类型定义。类型即文档。
Advanced: git blame packages/agent/src/agent-loop.ts for recent changes — Pi harness APIs still evolve; comments may lag AgentLoopConfig types. Types are the doc.
| CP | 主题 | 教学 artifact | 生产映射 |
|---|---|---|---|
| cp 12 | Resources + Extensions | resources-extensions | packages/coding-agent/src/core/extensions/ · resource-loader.ts |
| cp 13 | Composition Root | composition-root | packages/coding-agent/src/core/agent-session.ts · sdk.ts |
核心不做一等协议;社区 extension 可加。
Not first-class in core; community extensions can add it.
fork 显式 JSONL 树 + parentSession。
fork has explicit JSONL tree + parentSession.
examples/extensions/plan-mode/。
examples/extensions/plan-mode/.
本章主线 prompt 经过此模块时,请在 pi-mono 打开 packages/coding-agent/README.md,搜索 export 与测试文件中的同名场景。对照 pi-textbook 对应 checkpoint 的 workshop 测试——先让测试绿,再读生产实现。
When the through-line prompt crosses this module, open packages/coding-agent/README.md in pi-mono, search export and matching test scenarios. Cross-read the pi-textbook checkpoint workshop test — green test first, then production.
建议命令:cd packages/agent && npm test -- agent-loop(路径因章而异)。测试文件是行为契约的executable spec,比注释更可靠。
Suggested: cd packages/agent && npm test -- agent-loop (path varies). Tests are executable specs of behavior contracts — more reliable than comments.
用 --verbose 跑一轮主线 prompt,把 stderr event 类型与源码中的 emit 调用逐行对齐——这是本文 C26 推荐的 trace 方法。
Run the through-line with --verbose, align stderr event types with emit calls in source — the trace method recommended in C26.
cli.ts → main.ts → createAgentSession
cli.ts → main.ts → createAgentSession
你在 shell 里输入 pi 或 npx @earendil-works/coding-agent,启动链从 cli.ts 解析 argv,到 main.ts 选择 interactive/print/rpc 模式,最终调用 createAgentSession()。
You type pi or npx @earendil-works/coding-agent; boot chain parses argv in cli.ts, main.ts picks interactive/print/rpc mode, then createAgentSession().
冷启动时 ModelRegistry 读取 models.generated.ts,ResourceLoader 扫描 AGENTS.md 栈,SessionManager 创建新 JSONL 文件。用户尚未输入主线 prompt,但 harness 已就绪。
On cold boot ModelRegistry reads models.generated.ts, ResourceLoader scans AGENTS.md stack, SessionManager creates new JSONL. User hasn't typed the through-line prompt yet, but harness is ready.
composition-root)· 生产映射:packages/coding-agent/src/core/agent-session.ts · sdk.ts
📘 pi-textbook checkpoint 13: Composition Root (composition-root) · production: packages/coding-agent/src/core/agent-session.ts · sdk.ts
用户输入「读取 README.md,用一句话告诉我这个项目做什么」后,packages/coding-agent/src/cli.ts 定义了此阶段的类型契约与副作用边界。
After «read README.md and tell me what this project does in one sentence», packages/coding-agent/src/cli.ts defines type contracts and side-effect boundaries here.
在 pi-mono 仓库打开该文件,搜索 export 与 AgentEvent——这是比读 README 更快的定位法。
In pi-mono, search export and AgentEvent — faster than README alone.
设计原则:核心循环(agent-loop)不 import TUI;本模块通过事件与回调与上游通信。
Principle: agent-loop never imports TUI; this module talks upstream via events/callbacks.
pi-textbook 对应 checkpoint 提供剥离 OAuth/TUI 后的最小可运行切片,建议先跑测试再读生产文件。
Matching textbook checkpoint offers a minimal slice without OAuth/TUI — run its test before production file.
cli.ts 内的 async 函数通常是 IO 边界:磁盘(read/write JSONL)、网络(streamSimple)、或子进程(bash)。
async functions in cli.ts are usually IO boundaries: disk, network, or subprocess.
成功路径与错误路径都必须 emit 事件,否则 TUI 会卡在 streaming 状态、JSONL 会缺 entry。
Success and error paths must emit events or TUI stalls streaming and JSONL misses entries.
length 截断时 agent-loop.ts 的 failToolCallsFromTruncatedMessage 批量失败 tool calls,防止执行损坏参数。
On length truncation, failToolCallsFromTruncatedMessage fails all tool calls to avoid corrupted args.
对照 packages/agent/test/agent-loop.test.ts 中的同名场景测试用例。
Compare with matching scenarios in packages/agent/test/agent-loop.test.ts.
AgentLoopConfig(packages/agent/src/types.ts)列出所有注入点:convertToLlm、getSteeringMessages、prepareNextTurn 等。
AgentLoopConfig in types.ts lists injection points: convertToLlm, getSteeringMessages, prepareNextTurn, etc.
AgentSession 在构造 config 时绑定本模块实现;换模式(print/rpc/interactive)不换 agent-loop。
AgentSession binds this module when building config; modes change, agent-loop does not.
fork 项目时优先替换本模块而非复制 agent-loop——这是 Pi 社区的实际 fork 模式。
When forking, replace this module first rather than copying agent-loop — the common community pattern.
--verbose 模式下 stderr 事件类型可回溯到 packages/coding-agent/src/cli.ts 的具体行。
Under --verbose, stderr event types trace to lines in packages/coding-agent/src/cli.ts.
显式事件总线 vs 隐式回调:Pi 选择前者,事件类型在 AgentEvent union 中版本化。
Explicit event bus vs implicit callbacks: Pi chooses the former; events versioned in AgentEvent union.
扩展应 subscribe 事件而非 monkey-patch 私有方法——ExtensionRunner 提供正规 hook 面。
Extensions should subscribe to events, not monkey-patch privates — ExtensionRunner provides hooks.
JSONL CURRENT_SESSION_VERSION = 3:改 entry schema 必须 bump version 并提供迁移。
JSONL CURRENT_SESSION_VERSION = 3: schema changes need version bump and migration.
对比 Claude Code:闭源栈无法确认 compaction 触发点;Pi 的 shouldCompact 在源码中可读。
vs Claude Code: closed stack hides compaction triggers; Pi's shouldCompact is readable.
① 主线 prompt 进入本章模块时,第一个被调用的 export 函数是什么?② 它 emit 的第一个 AgentEvent 类型是什么?③ 若在此层抛错,TUI 会卡在什么状态?④ 对应的 pi-textbook checkpoint 测试命令是什么?⑤ packages/coding-agent/src/cli.ts 中哪一行最值得打断点?
① First export called when through-line enters this module? ② First AgentEvent type emitted? ③ If this layer throws, what TUI state stalls? ④ Matching pi-textbook checkpoint test command? ⑤ Best breakpoint line in packages/coding-agent/src/cli.ts?
不要一次读完整个文件。用「从测试入手」法:先 npm test -- <matching-test>,看失败断言指向哪行,再读那一行上下游 30 行。生产 pi-mono 的测试覆盖率在 agent-loop 与 session-manager 最高——优先读测试。
Don't read whole files at once. «Test-first» method: run npm test -- <matching-test>, see which assertion fails, read ±30 lines around that line. Highest test coverage in agent-loop and session-manager — read tests first.
对照本文 TRACE 盒与 --verbose stderr:每个 event type 应在源码中有且仅有一处「决策点」——若找不到,说明事件在更上层(AgentSession)或更下层(pi-ai adapter)合并发出。
Cross-check TRACE box with --verbose stderr: each event type should have exactly one «decision point» in source — if missing, event is merged upstream (AgentSession) or downstream (pi-ai adapter).
进阶:用 git blame packages/coding-agent/src/cli.ts 看最近改动——Pi 的 harness 接口仍在活跃演进,注释可能落后于 AgentLoopConfig 类型定义。类型即文档。
Advanced: git blame packages/coding-agent/src/cli.ts for recent changes — Pi harness APIs still evolve; comments may lag AgentLoopConfig types. Types are the doc.
站 06:packages/coding-agent/src/cli.ts 处理主线。
Station 06: packages/coding-agent/src/cli.ts on through-line.
| CP | 主题 | 教学 artifact | 生产映射 |
|---|---|---|---|
| cp 13 | Composition Root | composition-root | packages/coding-agent/src/core/agent-session.ts · sdk.ts |
packages/coding-agent/src/cli.ts。
packages/coding-agent/src/cli.ts.
mock StreamFn 或 ScriptedModel。
mock StreamFn or ScriptedModel.
生产含 OAuth/TUI/40+ providers。
Production adds OAuth/TUI/40+ providers.
本章主线 prompt 经过此模块时,请在 pi-mono 打开 packages/coding-agent/src/cli.ts,搜索 export 与测试文件中的同名场景。对照 pi-textbook 对应 checkpoint 的 workshop 测试——先让测试绿,再读生产实现。
When the through-line prompt crosses this module, open packages/coding-agent/src/cli.ts in pi-mono, search export and matching test scenarios. Cross-read the pi-textbook checkpoint workshop test — green test first, then production.
建议命令:cd packages/agent && npm test -- agent-loop(路径因章而异)。测试文件是行为契约的executable spec,比注释更可靠。
Suggested: cd packages/agent && npm test -- agent-loop (path varies). Tests are executable specs of behavior contracts — more reliable than comments.
用 --verbose 跑一轮主线 prompt,把 stderr event 类型与源码中的 emit 调用逐行对齐——这是本文 C26 推荐的 trace 方法。
Run the through-line with --verbose, align stderr event types with emit calls in source — the trace method recommended in C26.
唯一的中枢调度器
the single orchestration hub
AgentSession 是 Pi 的唯一中枢调度器——所有模式(interactive、print、rpc)共享它。主线 prompt 通过 prompt() 进入,内部调用 agentLoop() 并订阅事件写 JSONL。
AgentSession is Pi's single orchestration hub — all modes share it. Through-line prompt enters via prompt(), internally calls agentLoop() and subscribes to events for JSONL persistence.
| 职责 | 类/模块 | 主线时刻 |
|---|---|---|
| 编排 agentLoop | agent-session.ts | prompt() 调用时 |
| 持久化 | SessionManager | 每个 message_end |
| 扩展 | ExtensionRunner | 全程 hook |
| 压缩 | compaction/ | token 超阈值 |
| 模型切换 | ModelRegistry | model_change entry |
AgentSession 拥有 I/O 和持久化;agentLoop 是纯函数式循环,不知道 JSONL 和 TUI 的存在。测试 agent loop 不需要启动终端。
AgentSession owns I/O and persistence; agentLoop is a pure loop unaware of JSONL and TUI. Testing agent loop needs no terminal.
agent-loop.ts)· 生产映射:packages/agent/src/agent-loop.ts
📘 pi-textbook checkpoint 07: Agent Loop (agent-loop.ts) · production: packages/agent/src/agent-loop.ts
stateful-agent)· 生产映射:packages/agent/src/agent.ts
📘 pi-textbook checkpoint 09: Stateful Agent (stateful-agent) · production: packages/agent/src/agent.ts
| CP | 主题 | 教学 artifact | 生产映射 |
|---|---|---|---|
| cp 09 | Stateful Agent | stateful-agent | packages/agent/src/agent.ts |
| cp 13 | Composition Root | composition-root | packages/coding-agent/src/core/agent-session.ts · sdk.ts |
用户输入「读取 README.md,用一句话告诉我这个项目做什么」后,packages/coding-agent/src/core/agent-session.ts 定义了此阶段的类型契约与副作用边界。
After «read README.md and tell me what this project does in one sentence», packages/coding-agent/src/core/agent-session.ts defines type contracts and side-effect boundaries here.
在 pi-mono 仓库打开该文件,搜索 export 与 AgentEvent——这是比读 README 更快的定位法。
In pi-mono, search export and AgentEvent — faster than README alone.
设计原则:核心循环(agent-loop)不 import TUI;本模块通过事件与回调与上游通信。
Principle: agent-loop never imports TUI; this module talks upstream via events/callbacks.
pi-textbook 对应 checkpoint 提供剥离 OAuth/TUI 后的最小可运行切片,建议先跑测试再读生产文件。
Matching textbook checkpoint offers a minimal slice without OAuth/TUI — run its test before production file.
agent-session.ts 内的 async 函数通常是 IO 边界:磁盘(read/write JSONL)、网络(streamSimple)、或子进程(bash)。
async functions in agent-session.ts are usually IO boundaries: disk, network, or subprocess.
成功路径与错误路径都必须 emit 事件,否则 TUI 会卡在 streaming 状态、JSONL 会缺 entry。
Success and error paths must emit events or TUI stalls streaming and JSONL misses entries.
length 截断时 agent-loop.ts 的 failToolCallsFromTruncatedMessage 批量失败 tool calls,防止执行损坏参数。
On length truncation, failToolCallsFromTruncatedMessage fails all tool calls to avoid corrupted args.
对照 packages/agent/test/agent-loop.test.ts 中的同名场景测试用例。
Compare with matching scenarios in packages/agent/test/agent-loop.test.ts.
AgentLoopConfig(packages/agent/src/types.ts)列出所有注入点:convertToLlm、getSteeringMessages、prepareNextTurn 等。
AgentLoopConfig in types.ts lists injection points: convertToLlm, getSteeringMessages, prepareNextTurn, etc.
AgentSession 在构造 config 时绑定本模块实现;换模式(print/rpc/interactive)不换 agent-loop。
AgentSession binds this module when building config; modes change, agent-loop does not.
fork 项目时优先替换本模块而非复制 agent-loop——这是 Pi 社区的实际 fork 模式。
When forking, replace this module first rather than copying agent-loop — the common community pattern.
--verbose 模式下 stderr 事件类型可回溯到 packages/coding-agent/src/core/agent-session.ts 的具体行。
Under --verbose, stderr event types trace to lines in packages/coding-agent/src/core/agent-session.ts.
显式事件总线 vs 隐式回调:Pi 选择前者,事件类型在 AgentEvent union 中版本化。
Explicit event bus vs implicit callbacks: Pi chooses the former; events versioned in AgentEvent union.
扩展应 subscribe 事件而非 monkey-patch 私有方法——ExtensionRunner 提供正规 hook 面。
Extensions should subscribe to events, not monkey-patch privates — ExtensionRunner provides hooks.
JSONL CURRENT_SESSION_VERSION = 3:改 entry schema 必须 bump version 并提供迁移。
JSONL CURRENT_SESSION_VERSION = 3: schema changes need version bump and migration.
对比 Claude Code:闭源栈无法确认 compaction 触发点;Pi 的 shouldCompact 在源码中可读。
vs Claude Code: closed stack hides compaction triggers; Pi's shouldCompact is readable.
① 主线 prompt 进入本章模块时,第一个被调用的 export 函数是什么?② 它 emit 的第一个 AgentEvent 类型是什么?③ 若在此层抛错,TUI 会卡在什么状态?④ 对应的 pi-textbook checkpoint 测试命令是什么?⑤ packages/coding-agent/src/core/agent-session.ts 中哪一行最值得打断点?
① First export called when through-line enters this module? ② First AgentEvent type emitted? ③ If this layer throws, what TUI state stalls? ④ Matching pi-textbook checkpoint test command? ⑤ Best breakpoint line in packages/coding-agent/src/core/agent-session.ts?
不要一次读完整个文件。用「从测试入手」法:先 npm test -- <matching-test>,看失败断言指向哪行,再读那一行上下游 30 行。生产 pi-mono 的测试覆盖率在 agent-loop 与 session-manager 最高——优先读测试。
Don't read whole files at once. «Test-first» method: run npm test -- <matching-test>, see which assertion fails, read ±30 lines around that line. Highest test coverage in agent-loop and session-manager — read tests first.
对照本文 TRACE 盒与 --verbose stderr:每个 event type 应在源码中有且仅有一处「决策点」——若找不到,说明事件在更上层(AgentSession)或更下层(pi-ai adapter)合并发出。
Cross-check TRACE box with --verbose stderr: each event type should have exactly one «decision point» in source — if missing, event is merged upstream (AgentSession) or downstream (pi-ai adapter).
进阶:用 git blame packages/coding-agent/src/core/agent-session.ts 看最近改动——Pi 的 harness 接口仍在活跃演进,注释可能落后于 AgentLoopConfig 类型定义。类型即文档。
Advanced: git blame packages/coding-agent/src/core/agent-session.ts for recent changes — Pi harness APIs still evolve; comments may lag AgentLoopConfig types. Types are the doc.
站 07:packages/coding-agent/src/core/agent-session.ts 处理主线。
Station 07: packages/coding-agent/src/core/agent-session.ts on through-line.
| CP | 主题 | 教学 artifact | 生产映射 |
|---|---|---|---|
| cp 09 | Stateful Agent | stateful-agent | packages/agent/src/agent.ts |
| cp 13 | Composition Root | composition-root | packages/coding-agent/src/core/agent-session.ts · sdk.ts |
packages/coding-agent/src/core/agent-session.ts。
packages/coding-agent/src/core/agent-session.ts.
mock StreamFn 或 ScriptedModel。
mock StreamFn or ScriptedModel.
生产含 OAuth/TUI/40+ providers。
Production adds OAuth/TUI/40+ providers.
AgentSession.prompt(text) 首先构造 createUserMessage(text),类型为 AgentMessage 而非 pi-ai 的 Message。这是 checkpoint 03 与 09 的分水岭:在 agent-core 内永远操作 AgentMessage,只在 streamAssistantResponse 边界调用 convertToLlm。
AgentSession.prompt(text) first builds createUserMessage(text) as AgentMessage, not pi-ai Message. Checkpoint 03/09 watershed: AgentMessage inside agent-core; convertToLlm only at streamAssistantResponse.
随后 runLoop 把 userMsg 推入 pending queue,emit turn_start → message_start。若 SessionManager 已绑定,同一事件流会触发 JSONL append——因此 TUI 与磁盘是同一事件的两个订阅者,不是两条路径。
Then runLoop pushes userMsg to pending queue, emits turn_start → message_start. With SessionManager bound, the same stream triggers JSONL append — TUI and disk are two subscribers to one event bus, not two paths.
主线 prompt 的第一圈 model stream 在 agent-loop.ts 的 inner loop 启动;当 stopReason=toolUse 时,outer loop 不退出,而是进入 tool batch。读 README 的 read 在此执行——cwd 相对路径由 coding-agent 的 tool registry 解析。
First model stream for the through-line starts in agent-loop's inner loop; when stopReason=toolUse, outer loop continues into tool batch. read for README runs here — cwd-relative paths resolved by coding-agent tool registry.
从 ~/.pi 到项目根的指令叠加
instructions stacked from ~/.pi to project root
在主线 prompt 到达模型之前,ResourceLoader 把从 ~/.pi/AGENTS.md 到项目根目录的指令文件叠加进 system context。每一层目录的 AGENTS.md 追加规则。
Before the through-line prompt reaches the model, ResourceLoader stacks instruction files from ~/.pi/AGENTS.md to project root into system context.
模型看到的不是裸的「读取 README」——而是 system: [AGENTS.md 栈] + user: 读取 README.md…。工具 schema 也在 system 或 tools 参数里。
Model sees not bare «read README» but system: [AGENTS.md stack] + user: read README.md…. Tool schemas live in system or tools param.
resources-extensions)· 生产映射:packages/coding-agent/src/core/extensions/ · resource-loader.ts
📘 pi-textbook checkpoint 12: Resources + Extensions (resources-extensions) · production: packages/coding-agent/src/core/extensions/ · resource-loader.ts
用户输入「读取 README.md,用一句话告诉我这个项目做什么」后,packages/coding-agent/src/core/resource-loader.ts 定义了此阶段的类型契约与副作用边界。
After «read README.md and tell me what this project does in one sentence», packages/coding-agent/src/core/resource-loader.ts defines type contracts and side-effect boundaries here.
在 pi-mono 仓库打开该文件,搜索 export 与 AgentEvent——这是比读 README 更快的定位法。
In pi-mono, search export and AgentEvent — faster than README alone.
设计原则:核心循环(agent-loop)不 import TUI;本模块通过事件与回调与上游通信。
Principle: agent-loop never imports TUI; this module talks upstream via events/callbacks.
pi-textbook 对应 checkpoint 提供剥离 OAuth/TUI 后的最小可运行切片,建议先跑测试再读生产文件。
Matching textbook checkpoint offers a minimal slice without OAuth/TUI — run its test before production file.
resource-loader.ts 内的 async 函数通常是 IO 边界:磁盘(read/write JSONL)、网络(streamSimple)、或子进程(bash)。
async functions in resource-loader.ts are usually IO boundaries: disk, network, or subprocess.
成功路径与错误路径都必须 emit 事件,否则 TUI 会卡在 streaming 状态、JSONL 会缺 entry。
Success and error paths must emit events or TUI stalls streaming and JSONL misses entries.
length 截断时 agent-loop.ts 的 failToolCallsFromTruncatedMessage 批量失败 tool calls,防止执行损坏参数。
On length truncation, failToolCallsFromTruncatedMessage fails all tool calls to avoid corrupted args.
对照 packages/agent/test/agent-loop.test.ts 中的同名场景测试用例。
Compare with matching scenarios in packages/agent/test/agent-loop.test.ts.
AgentLoopConfig(packages/agent/src/types.ts)列出所有注入点:convertToLlm、getSteeringMessages、prepareNextTurn 等。
AgentLoopConfig in types.ts lists injection points: convertToLlm, getSteeringMessages, prepareNextTurn, etc.
AgentSession 在构造 config 时绑定本模块实现;换模式(print/rpc/interactive)不换 agent-loop。
AgentSession binds this module when building config; modes change, agent-loop does not.
fork 项目时优先替换本模块而非复制 agent-loop——这是 Pi 社区的实际 fork 模式。
When forking, replace this module first rather than copying agent-loop — the common community pattern.
--verbose 模式下 stderr 事件类型可回溯到 packages/coding-agent/src/core/resource-loader.ts 的具体行。
Under --verbose, stderr event types trace to lines in packages/coding-agent/src/core/resource-loader.ts.
显式事件总线 vs 隐式回调:Pi 选择前者,事件类型在 AgentEvent union 中版本化。
Explicit event bus vs implicit callbacks: Pi chooses the former; events versioned in AgentEvent union.
扩展应 subscribe 事件而非 monkey-patch 私有方法——ExtensionRunner 提供正规 hook 面。
Extensions should subscribe to events, not monkey-patch privates — ExtensionRunner provides hooks.
JSONL CURRENT_SESSION_VERSION = 3:改 entry schema 必须 bump version 并提供迁移。
JSONL CURRENT_SESSION_VERSION = 3: schema changes need version bump and migration.
对比 Claude Code:闭源栈无法确认 compaction 触发点;Pi 的 shouldCompact 在源码中可读。
vs Claude Code: closed stack hides compaction triggers; Pi's shouldCompact is readable.
① 主线 prompt 进入本章模块时,第一个被调用的 export 函数是什么?② 它 emit 的第一个 AgentEvent 类型是什么?③ 若在此层抛错,TUI 会卡在什么状态?④ 对应的 pi-textbook checkpoint 测试命令是什么?⑤ packages/agent/src/agent-loop.ts 中哪一行最值得打断点?
① First export called when through-line enters this module? ② First AgentEvent type emitted? ③ If this layer throws, what TUI state stalls? ④ Matching pi-textbook checkpoint test command? ⑤ Best breakpoint line in packages/agent/src/agent-loop.ts?
不要一次读完整个文件。用「从测试入手」法:先 npm test -- <matching-test>,看失败断言指向哪行,再读那一行上下游 30 行。生产 pi-mono 的测试覆盖率在 agent-loop 与 session-manager 最高——优先读测试。
Don't read whole files at once. «Test-first» method: run npm test -- <matching-test>, see which assertion fails, read ±30 lines around that line. Highest test coverage in agent-loop and session-manager — read tests first.
对照本文 TRACE 盒与 --verbose stderr:每个 event type 应在源码中有且仅有一处「决策点」——若找不到,说明事件在更上层(AgentSession)或更下层(pi-ai adapter)合并发出。
Cross-check TRACE box with --verbose stderr: each event type should have exactly one «decision point» in source — if missing, event is merged upstream (AgentSession) or downstream (pi-ai adapter).
进阶:用 git blame packages/agent/src/agent-loop.ts 看最近改动——Pi 的 harness 接口仍在活跃演进,注释可能落后于 AgentLoopConfig 类型定义。类型即文档。
Advanced: git blame packages/agent/src/agent-loop.ts for recent changes — Pi harness APIs still evolve; comments may lag AgentLoopConfig types. Types are the doc.
站 08:packages/coding-agent/src/core/resource-loader.ts 处理主线。
Station 08: packages/coding-agent/src/core/resource-loader.ts on through-line.
| CP | 主题 | 教学 artifact | 生产映射 |
|---|---|---|---|
| cp 12 | Resources + Extensions | resources-extensions | packages/coding-agent/src/core/extensions/ · resource-loader.ts |
packages/coding-agent/src/core/resource-loader.ts。
packages/coding-agent/src/core/resource-loader.ts.
mock StreamFn 或 ScriptedModel。
mock StreamFn or ScriptedModel.
生产含 OAuth/TUI/40+ providers。
Production adds OAuth/TUI/40+ providers.
本章主线 prompt 经过此模块时,请在 pi-mono 打开 packages/coding-agent/src/core/resource-loader.ts,搜索 export 与测试文件中的同名场景。对照 pi-textbook 对应 checkpoint 的 workshop 测试——先让测试绿,再读生产实现。
When the through-line prompt crosses this module, open packages/coding-agent/src/core/resource-loader.ts in pi-mono, search export and matching test scenarios. Cross-read the pi-textbook checkpoint workshop test — green test first, then production.
建议命令:cd packages/agent && npm test -- agent-loop(路径因章而异)。测试文件是行为契约的executable spec,比注释更可靠。
Suggested: cd packages/agent && npm test -- agent-loop (path varies). Tests are executable specs of behavior contracts — more reliable than comments.
用 --verbose 跑一轮主线 prompt,把 stderr event 类型与源码中的 emit 调用逐行对齐——这是本文 C26 推荐的 trace 方法。
Run the through-line with --verbose, align stderr event types with emit calls in source — the trace method recommended in C26.
AgentMessage ≠ Message
AgentMessage ≠ Message
Pi 维护两层消息模型:AgentMessage(agent-core 内部,可含自定义 role)和 Message(pi-ai LLM API,严格 user/assistant/tool)。convertToLlm 是唯一转换点。
Pi maintains two message layers: AgentMessage (agent-core, custom roles) and Message (pi-ai LLM API, strict user/assistant/tool). convertToLlm is the sole conversion point.
| 层 | 类型 | 谁能看 | 例子 |
|---|---|---|---|
| Transcript | AgentMessage[] | agent loop · JSONL | user · assistant · toolResult · custom |
| LLM API | Message[] | pi-ai providers | user · assistant · tool_result |
| TUI | RenderedMessage | 终端组件 | Markdown · tool 卡片 |
pi-textbook checkpoint 03 强调:transcript 是唯一真相源;LLM 请求是投影;TUI 是另一个投影。fork 会话时只复制 AgentMessage 链。
Textbook checkpoint 03: transcript is the single source of truth; LLM request is a projection; TUI is another. Session fork copies AgentMessage chain only.
types.ts)· 生产映射:packages/agent/src/types.ts · packages/coding-agent/src/core/messages.ts
📘 pi-textbook checkpoint 03: Message IR (types.ts) · production: packages/agent/src/types.ts · packages/coding-agent/src/core/messages.ts
本章 trace 只盯住一件事:同一条用户输入在 agent-core 内永远是 AgentMessage,直到 streamAssistantResponse 调用 convertToLlm 才投影成 pi-ai 的 Message[]。主线 prompt 的 user 消息、read 的 toolResult、最终 assistant 回答——三者形状不同,但 JSONL 里存的全是 AgentMessage 变体。
This trace tracks one thing: the same user input stays AgentMessage inside agent-core until streamAssistantResponse calls convertToLlm to project into pi-ai Message[]. User message, read toolResult, final assistant — different shapes, all AgentMessage variants in JSONL.
AgentLoopConfig 的 JSDoc 把契约写得很硬:convertToLlm must not throw——抛错会打断低层循环且不会产生正常 event 序列。这就是为什么 compaction 和 custom message 过滤都在 Message 层做投影,而不是在 agent-loop 里 try/catch 糊过去。
AgentLoopConfig JSDoc is strict: convertToLlm must not throw — throws break the low-level loop without a normal event sequence. Compaction and custom message filtering project at Message layer, not try/catch in agent-loop.
| # | location | 主线时刻 / through-line moment |
|---|---|---|
| 1 | AgentSession.prompt() | user AgentMessage: «读取 README.md,用一句话告…» |
| 2 | context.messages[] | canonical transcript 追加 user |
| 3 | transformContext? | compaction 可能在此剪枝旧消息 |
| 4 | convertToLlm() | → [{role:user, content:…}] 送 pi-ai |
| 5 | turn-1 assistant | toolUse(read) 仍在 AgentMessage 形状 |
| 6 | toolResult | read README 内容 · role=toolResult |
| 7 | turn-2 convert | user+assistant+toolResult → Message[] |
| 8 | turn-2 assistant | stopReason=stop · 最终回答 |
注意 streamAssistantResponse 注释原文:「This is where AgentMessage[] gets transformed to Message[] for the LLM.」——读 pi-mono 时搜索这句话,比搜索 convertToLlm 更快定位心脏。主线 prompt 第一次 convert 发生在 turn-1 model stream 前;第二次在 turn-2(tool result 已入 context)前。
Note streamAssistantResponse comment: «This is where AgentMessage[] gets transformed to Message[] for the LLM.» Search this phrase in pi-mono to locate the heart faster than convertToLlm. First convert before turn-1; second before turn-2 after tool result enters context.
role:custom 消息,观察 convertToLlm 是否过滤、JSONL 是否仍完整保存。
Exercise: in textbook cp03 add a role:custom message; observe convertToLlm filter vs JSONL full save.
用户输入「读取 README.md,用一句话告诉我这个项目做什么」后,packages/agent/src/types.ts 定义了此阶段的类型契约与副作用边界。
After «read README.md and tell me what this project does in one sentence», packages/agent/src/types.ts defines type contracts and side-effect boundaries here.
在 pi-mono 仓库打开该文件,搜索 export 与 AgentEvent——这是比读 README 更快的定位法。
In pi-mono, search export and AgentEvent — faster than README alone.
设计原则:核心循环(agent-loop)不 import TUI;本模块通过事件与回调与上游通信。
Principle: agent-loop never imports TUI; this module talks upstream via events/callbacks.
pi-textbook 对应 checkpoint 提供剥离 OAuth/TUI 后的最小可运行切片,建议先跑测试再读生产文件。
Matching textbook checkpoint offers a minimal slice without OAuth/TUI — run its test before production file.
types.ts 内的 async 函数通常是 IO 边界:磁盘(read/write JSONL)、网络(streamSimple)、或子进程(bash)。
async functions in types.ts are usually IO boundaries: disk, network, or subprocess.
成功路径与错误路径都必须 emit 事件,否则 TUI 会卡在 streaming 状态、JSONL 会缺 entry。
Success and error paths must emit events or TUI stalls streaming and JSONL misses entries.
length 截断时 agent-loop.ts 的 failToolCallsFromTruncatedMessage 批量失败 tool calls,防止执行损坏参数。
On length truncation, failToolCallsFromTruncatedMessage fails all tool calls to avoid corrupted args.
对照 packages/agent/test/agent-loop.test.ts 中的同名场景测试用例。
Compare with matching scenarios in packages/agent/test/agent-loop.test.ts.
AgentLoopConfig(packages/agent/src/types.ts)列出所有注入点:convertToLlm、getSteeringMessages、prepareNextTurn 等。
AgentLoopConfig in types.ts lists injection points: convertToLlm, getSteeringMessages, prepareNextTurn, etc.
AgentSession 在构造 config 时绑定本模块实现;换模式(print/rpc/interactive)不换 agent-loop。
AgentSession binds this module when building config; modes change, agent-loop does not.
fork 项目时优先替换本模块而非复制 agent-loop——这是 Pi 社区的实际 fork 模式。
When forking, replace this module first rather than copying agent-loop — the common community pattern.
--verbose 模式下 stderr 事件类型可回溯到 packages/agent/src/types.ts 的具体行。
Under --verbose, stderr event types trace to lines in packages/agent/src/types.ts.
显式事件总线 vs 隐式回调:Pi 选择前者,事件类型在 AgentEvent union 中版本化。
Explicit event bus vs implicit callbacks: Pi chooses the former; events versioned in AgentEvent union.
扩展应 subscribe 事件而非 monkey-patch 私有方法——ExtensionRunner 提供正规 hook 面。
Extensions should subscribe to events, not monkey-patch privates — ExtensionRunner provides hooks.
JSONL CURRENT_SESSION_VERSION = 3:改 entry schema 必须 bump version 并提供迁移。
JSONL CURRENT_SESSION_VERSION = 3: schema changes need version bump and migration.
对比 Claude Code:闭源栈无法确认 compaction 触发点;Pi 的 shouldCompact 在源码中可读。
vs Claude Code: closed stack hides compaction triggers; Pi's shouldCompact is readable.
① 主线 prompt 进入本章模块时,第一个被调用的 export 函数是什么?② 它 emit 的第一个 AgentEvent 类型是什么?③ 若在此层抛错,TUI 会卡在什么状态?④ 对应的 pi-textbook checkpoint 测试命令是什么?⑤ packages/agent/src/agent-loop.ts 中哪一行最值得打断点?
① First export called when through-line enters this module? ② First AgentEvent type emitted? ③ If this layer throws, what TUI state stalls? ④ Matching pi-textbook checkpoint test command? ⑤ Best breakpoint line in packages/agent/src/agent-loop.ts?
不要一次读完整个文件。用「从测试入手」法:先 npm test -- <matching-test>,看失败断言指向哪行,再读那一行上下游 30 行。生产 pi-mono 的测试覆盖率在 agent-loop 与 session-manager 最高——优先读测试。
Don't read whole files at once. «Test-first» method: run npm test -- <matching-test>, see which assertion fails, read ±30 lines around that line. Highest test coverage in agent-loop and session-manager — read tests first.
对照本文 TRACE 盒与 --verbose stderr:每个 event type 应在源码中有且仅有一处「决策点」——若找不到,说明事件在更上层(AgentSession)或更下层(pi-ai adapter)合并发出。
Cross-check TRACE box with --verbose stderr: each event type should have exactly one «decision point» in source — if missing, event is merged upstream (AgentSession) or downstream (pi-ai adapter).
进阶:用 git blame packages/agent/src/agent-loop.ts 看最近改动——Pi 的 harness 接口仍在活跃演进,注释可能落后于 AgentLoopConfig 类型定义。类型即文档。
Advanced: git blame packages/agent/src/agent-loop.ts for recent changes — Pi harness APIs still evolve; comments may lag AgentLoopConfig types. Types are the doc.
站 09:packages/agent/src/types.ts 处理主线。
Station 09: packages/agent/src/types.ts on through-line.
| CP | 主题 | 教学 artifact | 生产映射 |
|---|---|---|---|
| cp 03 | Message IR | types.ts | packages/agent/src/types.ts · packages/coding-agent/src/core/messages.ts |
packages/agent/src/types.ts。
packages/agent/src/types.ts.
mock StreamFn 或 ScriptedModel。
mock StreamFn or ScriptedModel.
生产含 OAuth/TUI/40+ providers。
Production adds OAuth/TUI/40+ providers.
本章主线 prompt 经过此模块时,请在 pi-mono 打开 packages/agent/src/types.ts,搜索 export 与测试文件中的同名场景。对照 pi-textbook 对应 checkpoint 的 workshop 测试——先让测试绿,再读生产实现。
When the through-line prompt crosses this module, open packages/agent/src/types.ts in pi-mono, search export and matching test scenarios. Cross-read the pi-textbook checkpoint workshop test — green test first, then production.
建议命令:cd packages/agent && npm test -- agent-loop(路径因章而异)。测试文件是行为契约的executable spec,比注释更可靠。
Suggested: cd packages/agent && npm test -- agent-loop (path varies). Tests are executable specs of behavior contracts — more reliable than comments.
用 --verbose 跑一轮主线 prompt,把 stderr event 类型与源码中的 emit 调用逐行对齐——这是本文 C26 推荐的 trace 方法。
Run the through-line with --verbose, align stderr event types with emit calls in source — the trace method recommended in C26.
outer follow-up · inner tool batch
outer follow-up · inner tool batch
runLoop 是 agent 的心脏:外环处理 follow-up 队列(用户在你以为结束时又发了一条),内环处理 tool batch 和 steering 注入。
runLoop is the agent heart: outer loop handles follow-up queue; inner loop handles tool batch and steering injection.
agent-loop.ts)· 生产映射:packages/agent/src/agent-loop.ts
📘 pi-textbook checkpoint 07: Agent Loop (agent-loop.ts) · production: packages/agent/src/agent-loop.ts
外环是耐心。
内环是纪律。 Outer loop is patience.
Inner loop is discipline. Field Note · 10
runLoop(agent-loop.ts:155)是 Pi 与 Claude Code 最大架构差异所在:不是「一次 completion」,而是外环 follow-up × 内环 tool batch。主线 prompt 固定走两圈内环迭代(turn-1 toolUse + turn-2 stop),外环通常只转一圈。
runLoop (agent-loop.ts:155) is Pi's biggest architectural difference from Claude Code: not one completion but outer follow-up × inner tool batch. Through-line prompt runs two inner iterations (turn-1 toolUse + turn-2 stop); outer loop usually one revolution.
| # | location | 主线时刻 / through-line moment |
|---|---|---|
| T0 | outer iter=1, inner iter=1 | pending=[] · stream turn-1 |
| T1 | inner iter=1 end | stopReason=toolUse · toolCalls=[read] |
| T2 | executeToolCalls | tool_execution_* · README content |
| T3 | inner iter=2 | context+=toolResult · stream turn-2 |
| T4 | inner iter=2 end | stopReason=stop · 最终回答 |
| T5 | inner exit | hasMoreToolCalls=false · pending=[] |
| T6 | outer check follow-up | 无 follow-up → break |
| T7 | agent_end | return newMessages[] |
内环第二次迭代时,currentContext.messages 已含:user prompt、turn-1 assistant(toolUse)、toolResult(README)。convertToLlm 把 toolResult 折叠成 provider 特定 tool_result 块——Anthropic 与 OpenAI 形状不同,但 agent-loop 不感知,这是 pi-ai adapter 的职责(C14)。
Second inner iteration: currentContext.messages has user, turn-1 assistant(toolUse), toolResult(README). convertToLlm folds toolResult to provider-specific blocks — agent-loop doesn't care, pi-ai adapter's job (C14).
C10 心脏止于 streamFunction 调用点——再往下就是 C14 的 pi-ai 层。下面这条桥接 trace 把同一次 turn-1 model stream从 agent-loop 追到 Anthropic SSE,是理解「双环」与「provider 抽象」接缝的关键。
C10 heart ends at streamFunction — below that is C14 pi-ai. This bridge trace follows the same turn-1 model stream from agent-loop to Anthropic SSE; the seam between twin loops and provider abstraction.
| # | location | 主线时刻 / through-line moment |
|---|---|---|
| 1 | agent-loop.ts:308 | streamFunction(model, llmContext, options) |
| 2 | agent.ts:222 | Agent.streamFunction ← sdk streamFn |
| 3 | sdk.ts:322 | modelRuntime.streamSimple(model, context, …) |
| 4 | model-runtime.ts:638 | prepareRequest → provider.streamSimple |
| 5 | models.ts:693 | applyAuth → apiKey/headers/baseUrl |
| 6 | compat.ts:275 | streamSimple → builtinProvider / resolveApiProvider |
| 7 | anthropic-messages.ts:584 | SSE → stream.push(start|toolcall_delta|done) |
| 8 | agent-loop.ts:317 | for await event → message_update → emit |
agent-loop.test.ts:搜索 two turn 或 toolUse,测试里的 event 序列应与 --verbose stderr 同构。
Cross-read textbook cp07 agent-loop.test.ts: search two turn or toolUse; test event sequence should match --verbose stderr.
| CP | 主题 | 教学 artifact | 生产映射 |
|---|---|---|---|
| cp 07 | Agent Loop | agent-loop.ts | packages/agent/src/agent-loop.ts |
用户输入「读取 README.md,用一句话告诉我这个项目做什么」后,packages/agent/src/agent-loop.ts 定义了此阶段的类型契约与副作用边界。
After «read README.md and tell me what this project does in one sentence», packages/agent/src/agent-loop.ts defines type contracts and side-effect boundaries here.
在 pi-mono 仓库打开该文件,搜索 export 与 AgentEvent——这是比读 README 更快的定位法。
In pi-mono, search export and AgentEvent — faster than README alone.
设计原则:核心循环(agent-loop)不 import TUI;本模块通过事件与回调与上游通信。
Principle: agent-loop never imports TUI; this module talks upstream via events/callbacks.
pi-textbook 对应 checkpoint 提供剥离 OAuth/TUI 后的最小可运行切片,建议先跑测试再读生产文件。
Matching textbook checkpoint offers a minimal slice without OAuth/TUI — run its test before production file.
agent-loop.ts 内的 async 函数通常是 IO 边界:磁盘(read/write JSONL)、网络(streamSimple)、或子进程(bash)。
async functions in agent-loop.ts are usually IO boundaries: disk, network, or subprocess.
成功路径与错误路径都必须 emit 事件,否则 TUI 会卡在 streaming 状态、JSONL 会缺 entry。
Success and error paths must emit events or TUI stalls streaming and JSONL misses entries.
length 截断时 agent-loop.ts 的 failToolCallsFromTruncatedMessage 批量失败 tool calls,防止执行损坏参数。
On length truncation, failToolCallsFromTruncatedMessage fails all tool calls to avoid corrupted args.
对照 packages/agent/test/agent-loop.test.ts 中的同名场景测试用例。
Compare with matching scenarios in packages/agent/test/agent-loop.test.ts.
AgentLoopConfig(packages/agent/src/types.ts)列出所有注入点:convertToLlm、getSteeringMessages、prepareNextTurn 等。
AgentLoopConfig in types.ts lists injection points: convertToLlm, getSteeringMessages, prepareNextTurn, etc.
AgentSession 在构造 config 时绑定本模块实现;换模式(print/rpc/interactive)不换 agent-loop。
AgentSession binds this module when building config; modes change, agent-loop does not.
fork 项目时优先替换本模块而非复制 agent-loop——这是 Pi 社区的实际 fork 模式。
When forking, replace this module first rather than copying agent-loop — the common community pattern.
--verbose 模式下 stderr 事件类型可回溯到 packages/agent/src/agent-loop.ts 的具体行。
Under --verbose, stderr event types trace to lines in packages/agent/src/agent-loop.ts.
显式事件总线 vs 隐式回调:Pi 选择前者,事件类型在 AgentEvent union 中版本化。
Explicit event bus vs implicit callbacks: Pi chooses the former; events versioned in AgentEvent union.
扩展应 subscribe 事件而非 monkey-patch 私有方法——ExtensionRunner 提供正规 hook 面。
Extensions should subscribe to events, not monkey-patch privates — ExtensionRunner provides hooks.
JSONL CURRENT_SESSION_VERSION = 3:改 entry schema 必须 bump version 并提供迁移。
JSONL CURRENT_SESSION_VERSION = 3: schema changes need version bump and migration.
对比 Claude Code:闭源栈无法确认 compaction 触发点;Pi 的 shouldCompact 在源码中可读。
vs Claude Code: closed stack hides compaction triggers; Pi's shouldCompact is readable.
① 主线 prompt 进入本章模块时,第一个被调用的 export 函数是什么?② 它 emit 的第一个 AgentEvent 类型是什么?③ 若在此层抛错,TUI 会卡在什么状态?④ 对应的 pi-textbook checkpoint 测试命令是什么?⑤ packages/agent/src/agent-loop.ts 中哪一行最值得打断点?
① First export called when through-line enters this module? ② First AgentEvent type emitted? ③ If this layer throws, what TUI state stalls? ④ Matching pi-textbook checkpoint test command? ⑤ Best breakpoint line in packages/agent/src/agent-loop.ts?
不要一次读完整个文件。用「从测试入手」法:先 npm test -- <matching-test>,看失败断言指向哪行,再读那一行上下游 30 行。生产 pi-mono 的测试覆盖率在 agent-loop 与 session-manager 最高——优先读测试。
Don't read whole files at once. «Test-first» method: run npm test -- <matching-test>, see which assertion fails, read ±30 lines around that line. Highest test coverage in agent-loop and session-manager — read tests first.
对照本文 TRACE 盒与 --verbose stderr:每个 event type 应在源码中有且仅有一处「决策点」——若找不到,说明事件在更上层(AgentSession)或更下层(pi-ai adapter)合并发出。
Cross-check TRACE box with --verbose stderr: each event type should have exactly one «decision point» in source — if missing, event is merged upstream (AgentSession) or downstream (pi-ai adapter).
进阶:用 git blame packages/agent/src/agent-loop.ts 看最近改动——Pi 的 harness 接口仍在活跃演进,注释可能落后于 AgentLoopConfig 类型定义。类型即文档。
Advanced: git blame packages/agent/src/agent-loop.ts for recent changes — Pi harness APIs still evolve; comments may lag AgentLoopConfig types. Types are the doc.
站 10:packages/agent/src/agent-loop.ts 处理主线。
Station 10: packages/agent/src/agent-loop.ts on through-line.
| CP | 主题 | 教学 artifact | 生产映射 |
|---|---|---|---|
| cp 07 | Agent Loop | agent-loop.ts | packages/agent/src/agent-loop.ts |
packages/agent/src/agent-loop.ts。
packages/agent/src/agent-loop.ts.
mock StreamFn 或 ScriptedModel。
mock StreamFn or ScriptedModel.
生产含 OAuth/TUI/40+ providers。
Production adds OAuth/TUI/40+ providers.
外环条件:while (true) 检查 follow-up 队列与 steering 注入。主线 prompt 首次进入时 follow-up 为空,但 steering 可能在 tool 执行中被用户插入——这是 Pi 与「一次性 completion API」的本质差异。
Outer loop: while (true) checks follow-up queue and steering. First through-line entry has empty follow-up, but steering may inject during tool execution — Pi's essential difference from one-shot completion APIs.
内环条件:while (hasMoreToolCalls || pendingMessages.length)。第一圈 model 返回 toolUse(read) 后,hasMoreToolCalls 为真,内环继续而不回到用户。第二圈 model 收到 tool result 后 stopReason=stop,内环退出,外环检查无 follow-up 后 agent_end。
Inner loop: while (hasMoreToolCalls || pendingMessages.length). After turn-1 toolUse(read), inner loop continues. Turn-2 stop ends inner loop; outer loop exits with agent_end when no follow-up.
理解双环的最快方法:在 pi-textbook checkpoint 07 的测试里给 ScriptedModel 固定两轮响应,对照 --verbose stderr 的 event 顺序——应看到两次 model_start 夹一次 tool_execution_*。
Fastest way to grok twin loops: fix two-turn responses in textbook cp07's ScriptedModel, compare with --verbose stderr — expect two model_start bracketing one tool_execution_*.
运行中插队与结束后续
mid-run injection and post-stop follow-up
Steering:运行中用户插入新消息(内环下一轮前注入)。Follow-up:agent 即将停止时队列里的后续消息(外环重启)。
Steering: user inserts mid-run (injected before next inner round). Follow-up: queued messages when agent would stop (outer loop restarts).
| 机制 | 触发时机 | 典型场景 |
|---|---|---|
| Steering | 内环每轮前 | 「别读 README 了,先 ls」 |
| Follow-up | 外环 would-stop | 批量任务第二条命令 |
| Abort | AbortSignal | Ctrl+C · 扩展取消 |
用户发出主线 prompt 后,模型开始 toolUse read;用户在 TUI 里输入 steering「先列出目录」。内环在 executeTool 前收到 pendingMessages,注入 user 消息,可能改变 tool 选择。
After through-line prompt, model starts toolUse read; user steers «list directory first». Inner loop receives pendingMessages before executeTool, may change tool choice.
stateful-agent)· 生产映射:packages/agent/src/agent.ts
📘 pi-textbook checkpoint 09: Stateful Agent (stateful-agent) · production: packages/agent/src/agent.ts
Steering 与 Follow-up 不是 UI 花活——它们在类型系统里是 AgentLoopConfig 的两个可选钩子:getSteeringMessages 与 getFollowUpMessages。AgentSession 维护 _steeringMessages / _followUpMessages 字符串队列,在 config 工厂里桥接到 agent-loop。
Steering and follow-up aren't UI gimmicks — they're optional hooks on AgentLoopConfig: getSteeringMessages and getFollowUpMessages. AgentSession maintains string queues bridged to agent-loop via config factory.
| # | location | 主线时刻 / through-line moment |
|---|---|---|
| 1 | turn-1 toolUse(read) | 模型已决定读 README |
| 2 | executeTool 前 | getSteeringMessages 返回 user「先 ls」 |
| 3 | pending 注入 | context += steer user · 无 model |
| 4 | stream turn-1b | 模型可能改调 bash/ls |
| 5 | 队列 UI | AgentSession splice steering 队列 |
关键时序:_handleAgentEvent 在 message_start(user) 时先于 emit 修改队列状态——保证 TUI 看到 steering 消息时侧边栏队列已更新。这是「事件驱动 UI」与「轮询队列」的接缝,也是 fork Pi 时最容易漏掉的细节。
Key timing: _handleAgentEvent mutates queue before emit on message_start(user) — TUI sees updated sidebar when steering message appears. Seam between event-driven UI and queue polling; easy to miss when forking Pi.
runLoop 内环每次迭代末尾调用 getSteeringMessages(agent-loop.ts 259 行)。返回的 AgentMessage[] 在下一次 streamAssistantResponse 之前 push 进 context。
Inner loop calls getSteeringMessages (line 259). AgentMessage[] pushed before next streamAssistantResponse.
agent.ts 475–480 行:steeringQueue.drain();skipInitialSteeringPoll 避免首轮重复 drain。
agent.ts 475–480: steeringQueue.drain(); skipInitialSteeringPoll avoids double drain.
AgentSession 1545 行 getSteeringMessages() 暴露只读副本给 TUI。
AgentSession line 1545 exposes read-only copy for TUI.
场景:read 执行中用户输入「先 ls」——steering 在 tool batch 完成后、下次 LLM 前注入。
Scenario: while read runs, user types «ls first» — steering injects after tool batch.
内环结束后外环检查 getFollowUpMessages(263 行)。非空则 pendingMessages 并 continue 外环。
Outer loop checks getFollowUpMessages (line 263). Non-empty continues outer loop.
agent.ts 482 行 followUpQueue.drain()。AgentSession._queueFollowUp(1407 行)路由后续用户输入。
agent.ts 482 followUpQueue.drain(); AgentSession._queueFollowUp routes post-turn input.
与 steering 区别:follow-up 在 agent would stop 时;steering 在 still running 时。
vs steering: follow-up when would stop; steering while still running.
AgentSession 维护 _steeringMessages / _followUpMessages;构造 AgentLoopConfig 时绑定 drain。
AgentSession maintains queues; binds drain when building AgentLoopConfig.
Agent 类封装有状态队列,供 SDK 与测试使用。
Agent class wraps stateful queues for SDK and tests.
interactive-mode.ts 4303 行合并队列状态渲染 footer。
interactive-mode.ts line 4303 merges queue state in footer.
types.ts 导出 QueueMode 控制排队策略。
types.ts exports QueueMode for queue policy.
agent-session-concurrent.test.ts 验证 extension steer。
concurrent test verifies extension steering.
abort:AgentSession.abort() 1561 行调用 agent.abort()。
Abort: AgentSession.abort() line 1561 calls agent.abort().
默认 README 任务不触发 steering;测试 mock getSteeringMessages 观察内环注入。
Default README task does not steer; mock getSteeringMessages to test injection.
① 主线 prompt 进入本章模块时,第一个被调用的 export 函数是什么?② 它 emit 的第一个 AgentEvent 类型是什么?③ 若在此层抛错,TUI 会卡在什么状态?④ 对应的 pi-textbook checkpoint 测试命令是什么?⑤ packages/agent/src/agent-loop.ts 中哪一行最值得打断点?
① First export called when through-line enters this module? ② First AgentEvent type emitted? ③ If this layer throws, what TUI state stalls? ④ Matching pi-textbook checkpoint test command? ⑤ Best breakpoint line in packages/agent/src/agent-loop.ts?
不要一次读完整个文件。用「从测试入手」法:先 npm test -- <matching-test>,看失败断言指向哪行,再读那一行上下游 30 行。生产 pi-mono 的测试覆盖率在 agent-loop 与 session-manager 最高——优先读测试。
Don't read whole files at once. «Test-first» method: run npm test -- <matching-test>, see which assertion fails, read ±30 lines around that line. Highest test coverage in agent-loop and session-manager — read tests first.
对照本文 TRACE 盒与 --verbose stderr:每个 event type 应在源码中有且仅有一处「决策点」——若找不到,说明事件在更上层(AgentSession)或更下层(pi-ai adapter)合并发出。
Cross-check TRACE box with --verbose stderr: each event type should have exactly one «decision point» in source — if missing, event is merged upstream (AgentSession) or downstream (pi-ai adapter).
进阶:用 git blame packages/agent/src/agent-loop.ts 看最近改动——Pi 的 harness 接口仍在活跃演进,注释可能落后于 AgentLoopConfig 类型定义。类型即文档。
Advanced: git blame packages/agent/src/agent-loop.ts for recent changes — Pi harness APIs still evolve; comments may lag AgentLoopConfig types. Types are the doc.
站 10–11:getSteeringMessages / getFollowUpMessages 桥接 AgentSession 与 runLoop。
Stations 10–11: steering/follow-up bridge AgentSession and runLoop.
| CP | 主题 | 教学 artifact | 生产映射 |
|---|---|---|---|
| cp 09 | Stateful Agent | stateful-agent | packages/agent/src/agent.ts |
可以;pendingMessageCount 1540 行返回两者之和。
Yes; pendingMessageCount sums both.
不立即;在当前 tool batch 完成后注入。
Not immediately; after tool batch.
通过 ExtensionActions queue 方法。
Via ExtensionActions queue methods.
本章主线 prompt 经过此模块时,请在 pi-mono 打开 packages/agent/src/agent-loop.ts,搜索 export 与测试文件中的同名场景。对照 pi-textbook 对应 checkpoint 的 workshop 测试——先让测试绿,再读生产实现。
When the through-line prompt crosses this module, open packages/agent/src/agent-loop.ts in pi-mono, search export and matching test scenarios. Cross-read the pi-textbook checkpoint workshop test — green test first, then production.
建议命令:cd packages/agent && npm test -- agent-loop(路径因章而异)。测试文件是行为契约的executable spec,比注释更可靠。
Suggested: cd packages/agent && npm test -- agent-loop (path varies). Tests are executable specs of behavior contracts — more reliable than comments.
用 --verbose 跑一轮主线 prompt,把 stderr event 类型与源码中的 emit 调用逐行对齐——这是本文 C26 推荐的 trace 方法。
Run the through-line with --verbose, align stderr event types with emit calls in source — the trace method recommended in C26.
parallel vs sequential · length 截断
parallel vs sequential · length truncation
模型返回 toolUse 后,executeTools 按 parallel 或 sequential 策略调度。read/write/edit/bash 各有 executor;结果超长时 truncateToolResult 截断。
After model returns toolUse, executeTools dispatches per parallel or sequential policy. read/write/edit/bash have executors; oversized results hit truncateToolResult.
| Tool | 并行安全? | 主线用例 |
|---|---|---|
| read | ✓ | README.md |
| bash | △ | 需确认 |
| write/edit | ✗ sequential | — |
tool-contract)· 生产映射:packages/agent/src/agent-loop.ts · packages/coding-agent/src/core/tools/
📘 pi-textbook checkpoint 06: Tool Contract (tool-contract) · production: packages/agent/src/agent-loop.ts · packages/coding-agent/src/core/tools/
coding-tools)· 生产映射:packages/coding-agent/src/core/tools/index.ts
📘 pi-textbook checkpoint 08: Coding Tools (coding-tools) · production: packages/coding-agent/src/core/tools/index.ts
主线 prompt 的第一次 tool call 几乎总是 read({"path":"README.md"})。executeToolCalls 根据 toolExecution 配置和 per-tool executionMode 选择并行或串行;read 默认并行安全。
Through-line's first tool call is usually read({"path":"README.md"}). executeToolCalls picks parallel vs sequential from toolExecution config and per-tool executionMode; read is parallel-safe by default.
| # | location | 主线时刻 / through-line moment |
|---|---|---|
| 1 | validateToolArguments | {"path":"README.md"} |
| 2 | resolveReadPathAsync | cwd/README.md → absolute |
| 3 | fsReadFile | UTF-8 buffer |
| 4 | truncateHead | ≤2000 lines / 256KB |
| 5 | tool_execution_end | isError=false |
| 6 | AgentMessage | role=toolResult · toolCallId=call_1 |
| 7 | inner loop iter 2 | context.messages += result |
若 README 超大,truncateHead 会在 tool result 末尾附加 [Showing lines … Use offset=N to continue.]——模型在 turn-2 可能只读到文件头部。主线 prompt「一句话概括」通常不需要全文;这是 Pi 故意用截断换上下文窗口的设计取舍。
If README is huge, truncateHead appends continuation hints — turn-2 model may only see the head. Through-line «one sentence summary» rarely needs full file; truncation trades context window for completeness.
用户输入「读取 README.md,用一句话告诉我这个项目做什么」后,packages/coding-agent/src/core/tools/read.ts 定义了此阶段的类型契约与副作用边界。
After «read README.md and tell me what this project does in one sentence», packages/coding-agent/src/core/tools/read.ts defines type contracts and side-effect boundaries here.
在 pi-mono 仓库打开该文件,搜索 export 与 AgentEvent——这是比读 README 更快的定位法。
In pi-mono, search export and AgentEvent — faster than README alone.
设计原则:核心循环(agent-loop)不 import TUI;本模块通过事件与回调与上游通信。
Principle: agent-loop never imports TUI; this module talks upstream via events/callbacks.
pi-textbook 对应 checkpoint 提供剥离 OAuth/TUI 后的最小可运行切片,建议先跑测试再读生产文件。
Matching textbook checkpoint offers a minimal slice without OAuth/TUI — run its test before production file.
read.ts 内的 async 函数通常是 IO 边界:磁盘(read/write JSONL)、网络(streamSimple)、或子进程(bash)。
async functions in read.ts are usually IO boundaries: disk, network, or subprocess.
成功路径与错误路径都必须 emit 事件,否则 TUI 会卡在 streaming 状态、JSONL 会缺 entry。
Success and error paths must emit events or TUI stalls streaming and JSONL misses entries.
length 截断时 agent-loop.ts 的 failToolCallsFromTruncatedMessage 批量失败 tool calls,防止执行损坏参数。
On length truncation, failToolCallsFromTruncatedMessage fails all tool calls to avoid corrupted args.
对照 packages/agent/test/agent-loop.test.ts 中的同名场景测试用例。
Compare with matching scenarios in packages/agent/test/agent-loop.test.ts.
AgentLoopConfig(packages/agent/src/types.ts)列出所有注入点:convertToLlm、getSteeringMessages、prepareNextTurn 等。
AgentLoopConfig in types.ts lists injection points: convertToLlm, getSteeringMessages, prepareNextTurn, etc.
AgentSession 在构造 config 时绑定本模块实现;换模式(print/rpc/interactive)不换 agent-loop。
AgentSession binds this module when building config; modes change, agent-loop does not.
fork 项目时优先替换本模块而非复制 agent-loop——这是 Pi 社区的实际 fork 模式。
When forking, replace this module first rather than copying agent-loop — the common community pattern.
--verbose 模式下 stderr 事件类型可回溯到 packages/coding-agent/src/core/tools/read.ts 的具体行。
Under --verbose, stderr event types trace to lines in packages/coding-agent/src/core/tools/read.ts.
显式事件总线 vs 隐式回调:Pi 选择前者,事件类型在 AgentEvent union 中版本化。
Explicit event bus vs implicit callbacks: Pi chooses the former; events versioned in AgentEvent union.
扩展应 subscribe 事件而非 monkey-patch 私有方法——ExtensionRunner 提供正规 hook 面。
Extensions should subscribe to events, not monkey-patch privates — ExtensionRunner provides hooks.
JSONL CURRENT_SESSION_VERSION = 3:改 entry schema 必须 bump version 并提供迁移。
JSONL CURRENT_SESSION_VERSION = 3: schema changes need version bump and migration.
对比 Claude Code:闭源栈无法确认 compaction 触发点;Pi 的 shouldCompact 在源码中可读。
vs Claude Code: closed stack hides compaction triggers; Pi's shouldCompact is readable.
① 主线 prompt 进入本章模块时,第一个被调用的 export 函数是什么?② 它 emit 的第一个 AgentEvent 类型是什么?③ 若在此层抛错,TUI 会卡在什么状态?④ 对应的 pi-textbook checkpoint 测试命令是什么?⑤ packages/coding-agent/src/core/tools/read.ts 中哪一行最值得打断点?
① First export called when through-line enters this module? ② First AgentEvent type emitted? ③ If this layer throws, what TUI state stalls? ④ Matching pi-textbook checkpoint test command? ⑤ Best breakpoint line in packages/coding-agent/src/core/tools/read.ts?
不要一次读完整个文件。用「从测试入手」法:先 npm test -- <matching-test>,看失败断言指向哪行,再读那一行上下游 30 行。生产 pi-mono 的测试覆盖率在 agent-loop 与 session-manager 最高——优先读测试。
Don't read whole files at once. «Test-first» method: run npm test -- <matching-test>, see which assertion fails, read ±30 lines around that line. Highest test coverage in agent-loop and session-manager — read tests first.
对照本文 TRACE 盒与 --verbose stderr:每个 event type 应在源码中有且仅有一处「决策点」——若找不到,说明事件在更上层(AgentSession)或更下层(pi-ai adapter)合并发出。
Cross-check TRACE box with --verbose stderr: each event type should have exactly one «decision point» in source — if missing, event is merged upstream (AgentSession) or downstream (pi-ai adapter).
进阶:用 git blame packages/coding-agent/src/core/tools/read.ts 看最近改动——Pi 的 harness 接口仍在活跃演进,注释可能落后于 AgentLoopConfig 类型定义。类型即文档。
Advanced: git blame packages/coding-agent/src/core/tools/read.ts for recent changes — Pi harness APIs still evolve; comments may lag AgentLoopConfig types. Types are the doc.
站 12:packages/coding-agent/src/core/tools/read.ts 处理主线。
Station 12: packages/coding-agent/src/core/tools/read.ts on through-line.
| CP | 主题 | 教学 artifact | 生产映射 |
|---|---|---|---|
| cp 06 | Tool Contract | tool-contract | packages/agent/src/agent-loop.ts · packages/coding-agent/src/core/tools/ |
| cp 08 | Coding Tools | coding-tools | packages/coding-agent/src/core/tools/index.ts |
packages/coding-agent/src/core/tools/read.ts。
packages/coding-agent/src/core/tools/read.ts.
mock StreamFn 或 ScriptedModel。
mock StreamFn or ScriptedModel.
生产含 OAuth/TUI/40+ providers。
Production adds OAuth/TUI/40+ providers.
本章主线 prompt 经过此模块时,请在 pi-mono 打开 packages/coding-agent/src/core/tools/read.ts,搜索 export 与测试文件中的同名场景。对照 pi-textbook 对应 checkpoint 的 workshop 测试——先让测试绿,再读生产实现。
When the through-line prompt crosses this module, open packages/coding-agent/src/core/tools/read.ts in pi-mono, search export and matching test scenarios. Cross-read the pi-textbook checkpoint workshop test — green test first, then production.
建议命令:cd packages/agent && npm test -- agent-loop(路径因章而异)。测试文件是行为契约的executable spec,比注释更可靠。
Suggested: cd packages/agent && npm test -- agent-loop (path varies). Tests are executable specs of behavior contracts — more reliable than comments.
用 --verbose 跑一轮主线 prompt,把 stderr event 类型与源码中的 emit 调用逐行对齐——这是本文 C26 推荐的 trace 方法。
Run the through-line with --verbose, align stderr event types with emit calls in source — the trace method recommended in C26.
message_update 如何驱动 TUI
how message_update drives the TUI
agent-core 通过 AgentEvent tagged union 广播状态变化。message_update 携带 text_delta 或 toolcall_delta——TUI 差分渲染的唯一输入。
agent-core broadcasts state via AgentEvent tagged union. message_update carries text_delta or toolcall_delta — sole input for TUI differential render.
| 事件 | 订阅者 | 副作用 |
|---|---|---|
| agent_start | AgentSession 订阅者 | TUI / JSONL / Extension |
| turn_start | AgentSession 订阅者 | TUI / JSONL / Extension |
| message_start | AgentSession 订阅者 | TUI / JSONL / Extension |
| message_update | AgentSession 订阅者 | TUI / JSONL / Extension |
| message_end | AgentSession 订阅者 | TUI / JSONL / Extension |
| tool_execution_start | AgentSession 订阅者 | TUI / JSONL / Extension |
| tool_execution_update | AgentSession 订阅者 | TUI / JSONL / Extension |
| tool_execution_end | AgentSession 订阅者 | TUI / JSONL / Extension |
| turn_end | AgentSession 订阅者 | TUI / JSONL / Extension |
| agent_end | AgentSession 订阅者 | TUI / JSONL / Extension |
简化版:agent_start → turn_start → message_start(user) → message_end(user) → message_update×N → message_end(assistant) → tool_execution_* → message_update×M → message_end(assistant) → turn_end → agent_end。
Simplified: agent_start → turn_start → message_start(user) → … → agent_end.
events.ts)· 生产映射:
📘 pi-textbook checkpoint 01: TypeScript 生存集 (events.ts) · production:
event-stream.ts)· 生产映射:packages/ai/src/utils/event-stream.ts
📘 pi-textbook checkpoint 02: EventStream (event-stream.ts) · production: packages/ai/src/utils/event-stream.ts
AgentEvent 是 agent-core 与 coding-agent/TUI 的唯一契约。agent-loop 只 call emit(event);AgentSession 订阅后 fan-out 到 JSONL、TUI、ExtensionRunner。主线 prompt 一轮大约产生 25–40 个事件(含 message_update delta)。
AgentEvent is the sole contract between agent-core and coding-agent/TUI. agent-loop only emit(event); AgentSession fans out to JSONL, TUI, ExtensionRunner. One through-line turn emits ~25–40 events (including message_update deltas).
| # | location | 主线时刻 / through-line moment |
|---|---|---|
| 1 | agent_start | runLoop 进入 |
| 2 | turn_start | turn-1 开始 |
| 3 | message_start | user prompt |
| 4 | message_end | user 定稿 → JSONL append |
| 5 | message_start | assistant partial |
| 6 | message_update×N | toolcall_delta 组装 read(args) |
| 7 | message_end | stopReason=toolUse |
| 8 | tool_execution_start | read · call_1 |
| 9 | tool_execution_end | README 内容 |
| 10 | message_start/end | toolResult 消息 |
| 11 | turn_end | turn-1 完成 |
| 12 | turn_start | turn-2 |
| 13 | message_update×M | text_delta 最终回答 |
| 14 | message_end | stopReason=stop |
| 15 | turn_end | turn-2 完成 |
| 16 | agent_end | newMessages 返回 |
「message_update 驱动 TUI,message_end 驱动 JSONL」——不是两条管道,而是同一 emit 的两个订阅者消费不同阶段。TUI 在 update 时重绘;SessionManager 只在 end 时落盘,避免每个 delta 写一行 JSONL。
«message_update drives TUI, message_end drives JSONL» — not two pipes but two subscribers to same emit at different stages. TUI redraws on update; SessionManager persists only on end, avoiding per-delta JSONL lines.
pi --verbose 2>events.log 跑主线 prompt,grep message_update events.log | wc -l 应与 TUI 刷新次数同量级。
Run pi --verbose 2>events.log on through-line; grep message_update events.log | wc -l should match TUI refresh magnitude.
用户输入「读取 README.md,用一句话告诉我这个项目做什么」后,packages/coding-agent/src/core/agent-session.ts 定义了此阶段的类型契约与副作用边界。
After «read README.md and tell me what this project does in one sentence», packages/coding-agent/src/core/agent-session.ts defines type contracts and side-effect boundaries here.
在 pi-mono 仓库打开该文件,搜索 export 与 AgentEvent——这是比读 README 更快的定位法。
In pi-mono, search export and AgentEvent — faster than README alone.
设计原则:核心循环(agent-loop)不 import TUI;本模块通过事件与回调与上游通信。
Principle: agent-loop never imports TUI; this module talks upstream via events/callbacks.
pi-textbook 对应 checkpoint 提供剥离 OAuth/TUI 后的最小可运行切片,建议先跑测试再读生产文件。
Matching textbook checkpoint offers a minimal slice without OAuth/TUI — run its test before production file.
agent-session.ts 内的 async 函数通常是 IO 边界:磁盘(read/write JSONL)、网络(streamSimple)、或子进程(bash)。
async functions in agent-session.ts are usually IO boundaries: disk, network, or subprocess.
成功路径与错误路径都必须 emit 事件,否则 TUI 会卡在 streaming 状态、JSONL 会缺 entry。
Success and error paths must emit events or TUI stalls streaming and JSONL misses entries.
length 截断时 agent-loop.ts 的 failToolCallsFromTruncatedMessage 批量失败 tool calls,防止执行损坏参数。
On length truncation, failToolCallsFromTruncatedMessage fails all tool calls to avoid corrupted args.
对照 packages/agent/test/agent-loop.test.ts 中的同名场景测试用例。
Compare with matching scenarios in packages/agent/test/agent-loop.test.ts.
AgentLoopConfig(packages/agent/src/types.ts)列出所有注入点:convertToLlm、getSteeringMessages、prepareNextTurn 等。
AgentLoopConfig in types.ts lists injection points: convertToLlm, getSteeringMessages, prepareNextTurn, etc.
AgentSession 在构造 config 时绑定本模块实现;换模式(print/rpc/interactive)不换 agent-loop。
AgentSession binds this module when building config; modes change, agent-loop does not.
fork 项目时优先替换本模块而非复制 agent-loop——这是 Pi 社区的实际 fork 模式。
When forking, replace this module first rather than copying agent-loop — the common community pattern.
--verbose 模式下 stderr 事件类型可回溯到 packages/coding-agent/src/core/agent-session.ts 的具体行。
Under --verbose, stderr event types trace to lines in packages/coding-agent/src/core/agent-session.ts.
显式事件总线 vs 隐式回调:Pi 选择前者,事件类型在 AgentEvent union 中版本化。
Explicit event bus vs implicit callbacks: Pi chooses the former; events versioned in AgentEvent union.
扩展应 subscribe 事件而非 monkey-patch 私有方法——ExtensionRunner 提供正规 hook 面。
Extensions should subscribe to events, not monkey-patch privates — ExtensionRunner provides hooks.
JSONL CURRENT_SESSION_VERSION = 3:改 entry schema 必须 bump version 并提供迁移。
JSONL CURRENT_SESSION_VERSION = 3: schema changes need version bump and migration.
对比 Claude Code:闭源栈无法确认 compaction 触发点;Pi 的 shouldCompact 在源码中可读。
vs Claude Code: closed stack hides compaction triggers; Pi's shouldCompact is readable.
① 主线 prompt 进入本章模块时,第一个被调用的 export 函数是什么?② 它 emit 的第一个 AgentEvent 类型是什么?③ 若在此层抛错,TUI 会卡在什么状态?④ 对应的 pi-textbook checkpoint 测试命令是什么?⑤ packages/agent/src/agent-loop.ts 中哪一行最值得打断点?
① First export called when through-line enters this module? ② First AgentEvent type emitted? ③ If this layer throws, what TUI state stalls? ④ Matching pi-textbook checkpoint test command? ⑤ Best breakpoint line in packages/agent/src/agent-loop.ts?
不要一次读完整个文件。用「从测试入手」法:先 npm test -- <matching-test>,看失败断言指向哪行,再读那一行上下游 30 行。生产 pi-mono 的测试覆盖率在 agent-loop 与 session-manager 最高——优先读测试。
Don't read whole files at once. «Test-first» method: run npm test -- <matching-test>, see which assertion fails, read ±30 lines around that line. Highest test coverage in agent-loop and session-manager — read tests first.
对照本文 TRACE 盒与 --verbose stderr:每个 event type 应在源码中有且仅有一处「决策点」——若找不到,说明事件在更上层(AgentSession)或更下层(pi-ai adapter)合并发出。
Cross-check TRACE box with --verbose stderr: each event type should have exactly one «decision point» in source — if missing, event is merged upstream (AgentSession) or downstream (pi-ai adapter).
进阶:用 git blame packages/agent/src/agent-loop.ts 看最近改动——Pi 的 harness 接口仍在活跃演进,注释可能落后于 AgentLoopConfig 类型定义。类型即文档。
Advanced: git blame packages/agent/src/agent-loop.ts for recent changes — Pi harness APIs still evolve; comments may lag AgentLoopConfig types. Types are the doc.
站 13:packages/coding-agent/src/core/agent-session.ts 处理主线。
Station 13: packages/coding-agent/src/core/agent-session.ts on through-line.
| CP | 主题 | 教学 artifact | 生产映射 |
|---|---|---|---|
| cp 09 | Stateful Agent | stateful-agent | packages/agent/src/agent.ts |
packages/coding-agent/src/core/agent-session.ts。
packages/coding-agent/src/core/agent-session.ts.
mock StreamFn 或 ScriptedModel。
mock StreamFn or ScriptedModel.
生产含 OAuth/TUI/40+ providers。
Production adds OAuth/TUI/40+ providers.
本章主线 prompt 经过此模块时,请在 pi-mono 打开 packages/agent/src/types.ts,搜索 export 与测试文件中的同名场景。对照 pi-textbook 对应 checkpoint 的 workshop 测试——先让测试绿,再读生产实现。
When the through-line prompt crosses this module, open packages/agent/src/types.ts in pi-mono, search export and matching test scenarios. Cross-read the pi-textbook checkpoint workshop test — green test first, then production.
建议命令:cd packages/agent && npm test -- agent-loop(路径因章而异)。测试文件是行为契约的executable spec,比注释更可靠。
Suggested: cd packages/agent && npm test -- agent-loop (path varies). Tests are executable specs of behavior contracts — more reliable than comments.
用 --verbose 跑一轮主线 prompt,把 stderr event 类型与源码中的 emit 调用逐行对齐——这是本文 C26 推荐的 trace 方法。
Run the through-line with --verbose, align stderr event types with emit calls in source — the trace method recommended in C26.
Models · createProvider · streamSimple
Models · createProvider · streamSimple
pi-ai 抽象 40+ provider:Models 目录 · createProvider 工厂 · streamSimple 统一流式入口。agent-loop 只依赖 StreamFn 签名。
pi-ai abstracts 40+ providers: Models catalog · createProvider factory · streamSimple unified streaming entry. agent-loop depends only on StreamFn signature.
| Provider 族 | 认证 | 流式协议 |
|---|---|---|
| OpenAI-compatible | API key | SSE |
| Anthropic | API key | SSE events |
| OAuth (Kimi etc.) | OAuth token | provider-specific |
provider-adapter.ts)· 生产映射:packages/ai/src/api/*.ts · packages/ai/src/models.ts
📘 pi-textbook checkpoint 05: Provider Adapter (provider-adapter.ts) · production: packages/ai/src/api/*.ts · packages/ai/src/models.ts
pi-ai(packages/ai)是 Pi 的 LLM 适配层。streamSimple 是 agent-core 与 40+ provider 之间的唯一窄接口:输入 Model + Context + options,输出 AssistantMessageEventStream。主线 prompt turn-1 的 toolcall_delta 组装 read({"path":"README.md"}) 就发生在此层。
pi-ai (packages/ai) is Pi's LLM adapter layer. streamSimple is the sole narrow interface between agent-core and 40+ providers: Model + Context + options in, AssistantMessageEventStream out. Through-line turn-1 toolcall_delta assembling read({"path":"README.md"}) happens here.
| # | location | 主线时刻 / through-line moment |
|---|---|---|
| in | Context.messages | user + system + tools schema |
| auth | applyAuth | ANTHROPIC_API_KEY / OAuth bearer |
| http | anthropic-messages | POST /v1/messages stream:true |
| out | EventStream | start → toolcall_delta×N → done(toolUse) |
| up | agent-loop:317 | message_update → TUI tool 卡片 |
createProvider 把 auth、models、api 三块粘成不可再分的 Provider 对象。Models 类持有 provider map,ModelRuntime(coding-agent)在其上叠加 credential store、extension provider、availability snapshot——但HTTP/SSE 协议细节永远不下沉到 agent-loop。
createProvider glues auth, models, api into one Provider. Models holds the provider map; ModelRuntime adds credential store, extension providers, availability — but HTTP/SSE protocol never sinks into agent-loop.
registerFauxProvider 替换 streamSimple,观察 agent-loop 是否仍收到相同形状的 toolcall_delta。
Exercise: in pi-textbook replace streamSimple with registerFauxProvider; observe agent-loop still gets same-shaped toolcall_delta.
用户输入「读取 README.md,用一句话告诉我这个项目做什么」后,packages/ai/src/models.ts 定义了此阶段的类型契约与副作用边界。
After «read README.md and tell me what this project does in one sentence», packages/ai/src/models.ts defines type contracts and side-effect boundaries here.
在 pi-mono 仓库打开该文件,搜索 export 与 AgentEvent——这是比读 README 更快的定位法。
In pi-mono, search export and AgentEvent — faster than README alone.
设计原则:核心循环(agent-loop)不 import TUI;本模块通过事件与回调与上游通信。
Principle: agent-loop never imports TUI; this module talks upstream via events/callbacks.
pi-textbook 对应 checkpoint 提供剥离 OAuth/TUI 后的最小可运行切片,建议先跑测试再读生产文件。
Matching textbook checkpoint offers a minimal slice without OAuth/TUI — run its test before production file.
models.ts 内的 async 函数通常是 IO 边界:磁盘(read/write JSONL)、网络(streamSimple)、或子进程(bash)。
async functions in models.ts are usually IO boundaries: disk, network, or subprocess.
成功路径与错误路径都必须 emit 事件,否则 TUI 会卡在 streaming 状态、JSONL 会缺 entry。
Success and error paths must emit events or TUI stalls streaming and JSONL misses entries.
length 截断时 agent-loop.ts 的 failToolCallsFromTruncatedMessage 批量失败 tool calls,防止执行损坏参数。
On length truncation, failToolCallsFromTruncatedMessage fails all tool calls to avoid corrupted args.
对照 packages/agent/test/agent-loop.test.ts 中的同名场景测试用例。
Compare with matching scenarios in packages/agent/test/agent-loop.test.ts.
AgentLoopConfig(packages/agent/src/types.ts)列出所有注入点:convertToLlm、getSteeringMessages、prepareNextTurn 等。
AgentLoopConfig in types.ts lists injection points: convertToLlm, getSteeringMessages, prepareNextTurn, etc.
AgentSession 在构造 config 时绑定本模块实现;换模式(print/rpc/interactive)不换 agent-loop。
AgentSession binds this module when building config; modes change, agent-loop does not.
fork 项目时优先替换本模块而非复制 agent-loop——这是 Pi 社区的实际 fork 模式。
When forking, replace this module first rather than copying agent-loop — the common community pattern.
--verbose 模式下 stderr 事件类型可回溯到 packages/ai/src/models.ts 的具体行。
Under --verbose, stderr event types trace to lines in packages/ai/src/models.ts.
显式事件总线 vs 隐式回调:Pi 选择前者,事件类型在 AgentEvent union 中版本化。
Explicit event bus vs implicit callbacks: Pi chooses the former; events versioned in AgentEvent union.
扩展应 subscribe 事件而非 monkey-patch 私有方法——ExtensionRunner 提供正规 hook 面。
Extensions should subscribe to events, not monkey-patch privates — ExtensionRunner provides hooks.
JSONL CURRENT_SESSION_VERSION = 3:改 entry schema 必须 bump version 并提供迁移。
JSONL CURRENT_SESSION_VERSION = 3: schema changes need version bump and migration.
对比 Claude Code:闭源栈无法确认 compaction 触发点;Pi 的 shouldCompact 在源码中可读。
vs Claude Code: closed stack hides compaction triggers; Pi's shouldCompact is readable.
① 主线 prompt 进入本章模块时,第一个被调用的 export 函数是什么?② 它 emit 的第一个 AgentEvent 类型是什么?③ 若在此层抛错,TUI 会卡在什么状态?④ 对应的 pi-textbook checkpoint 测试命令是什么?⑤ packages/ai/src/api/stream-simple.ts 中哪一行最值得打断点?
① First export called when through-line enters this module? ② First AgentEvent type emitted? ③ If this layer throws, what TUI state stalls? ④ Matching pi-textbook checkpoint test command? ⑤ Best breakpoint line in packages/ai/src/api/stream-simple.ts?
不要一次读完整个文件。用「从测试入手」法:先 npm test -- <matching-test>,看失败断言指向哪行,再读那一行上下游 30 行。生产 pi-mono 的测试覆盖率在 agent-loop 与 session-manager 最高——优先读测试。
Don't read whole files at once. «Test-first» method: run npm test -- <matching-test>, see which assertion fails, read ±30 lines around that line. Highest test coverage in agent-loop and session-manager — read tests first.
对照本文 TRACE 盒与 --verbose stderr:每个 event type 应在源码中有且仅有一处「决策点」——若找不到,说明事件在更上层(AgentSession)或更下层(pi-ai adapter)合并发出。
Cross-check TRACE box with --verbose stderr: each event type should have exactly one «decision point» in source — if missing, event is merged upstream (AgentSession) or downstream (pi-ai adapter).
进阶:用 git blame packages/ai/src/api/stream-simple.ts 看最近改动——Pi 的 harness 接口仍在活跃演进,注释可能落后于 AgentLoopConfig 类型定义。类型即文档。
Advanced: git blame packages/ai/src/api/stream-simple.ts for recent changes — Pi harness APIs still evolve; comments may lag AgentLoopConfig types. Types are the doc.
站 14:packages/ai/src/models.ts 处理主线。
Station 14: packages/ai/src/models.ts on through-line.
| CP | 主题 | 教学 artifact | 生产映射 |
|---|---|---|---|
| cp 05 | Provider Adapter | provider-adapter.ts | packages/ai/src/api/*.ts · packages/ai/src/models.ts |
packages/ai/src/models.ts。
packages/ai/src/models.ts.
mock StreamFn 或 ScriptedModel。
mock StreamFn or ScriptedModel.
生产含 OAuth/TUI/40+ providers。
Production adds OAuth/TUI/40+ providers.
convertToLlm(messages: AgentMessage[]) 产出 pi-ai 的 Message[],其中 tool result 已折叠为 provider 特定格式。Anthropic 与 OpenAI 的 tool 块形状不同——pi-ai 的 adapter 层负责这一转换,agent-core 不应 import provider 细节。
convertToLlm(messages: AgentMessage[]) yields pi-ai Message[] with tool results folded to provider format. Anthropic vs OpenAI tool blocks differ — pi-ai adapters handle this; agent-core must not import provider details.
streamSimple(model, messages, tools) 返回 EventStream:先过程项(text_delta、toolcall_delta),后终态(done + usage)。TUI 只订阅 message_update;SessionManager 在 message_end 落盘——两者消费同一 stream 的不同阶段。
streamSimple(model, messages, tools) returns EventStream: process items first (text_delta, toolcall_delta), terminal state last (done + usage). TUI subscribes to message_update; SessionManager persists at message_end.
text_delta · toolcall_delta · done
text_delta · toolcall_delta · done
EventStream<T> 同时交付过程项(delta)和终态(done)。TUI 订阅过程项;agent-loop 等待终态 AssistantMessage。
EventStream<T> delivers both progress items (deltas) and terminal state (done). TUI subscribes to progress; agent-loop awaits terminal AssistantMessage.
| 事件 | 消费者 | 主线时刻 |
|---|---|---|
| text_delta | TUI Markdown | 最终回答流式显示 |
| toolcall_delta | TUI tool 卡片 | read 参数组装 |
| done | agent-loop | stopReason 判定 |
event-stream.ts)· 生产映射:packages/ai/src/utils/event-stream.ts
📘 pi-textbook checkpoint 02: EventStream (event-stream.ts) · production: packages/ai/src/utils/event-stream.ts
pi-ai 与 agent-core 之间的流式契约是 AssistantMessageEvent 联合类型 + AssistantMessageEventStream 队列。agent-loop 的 for await (event of response) 消费的正是这个协议——不是 provider 原始 SSE。主线 turn-1 的 read toolUse 由一串 toolcall_start → toolcall_delta×N → toolcall_end 组装。
The streaming contract between pi-ai and agent-core is AssistantMessageEvent union + AssistantMessageEventStream queue. agent-loop's for await (event of response) consumes this protocol — not raw provider SSE. Through-line turn-1 read toolUse is assembled from toolcall_start → toolcall_delta×N → toolcall_end.
| # | location | 主线时刻 / through-line moment |
|---|---|---|
| 1 | toolcall_start | content block tool_use 开始 |
| 2 | toolcall_delta | {"path" |
| 3 | toolcall_delta | :"README.md"} |
| 4 | toolcall_delta | } 片段… |
| 5 | toolcall_end | arguments 解析完成 · id=toolu_… |
| 6 | done | stopReason=toolUse |
| 7 | agent-loop | message_update×N → executeToolCalls |
设计要点:partial 字段在每个 event 里携带当前累积状态的 AssistantMessage 快照——TUI 不需要自己拼 delta;agent-loop 把 event 原样 emit 给 AgentSession,由订阅者决定是差分渲染还是等 message_end 落盘。
Design note: partial in each event carries a cumulative AssistantMessage snapshot — TUI need not assemble deltas; agent-loop emits events as-is to AgentSession; subscribers diff-render or persist on message_end.
--verbose 跑主线 prompt,stderr 里 toolcall_delta 行数应与 Anthropic SSE input_json_delta 包数同量级。
Run through-line with --verbose; stderr toolcall_delta count should match Anthropic SSE input_json_delta packets.
packages/ai/src/utils/event-stream.ts:泛型 EventStream
packages/ai/src/utils/event-stream.ts: generic EventStream
push(event):若 isComplete(event) 为真,设 done=true 并 resolveFinalResult;否则入队或交给 waiting consumer。
push(event): if isComplete, set done and resolveFinalResult; else queue or deliver to waiting consumer.
AsyncIterable 协议:消费者 for-await 时,队列空且未 done 则挂起在 waiting 数组。
AsyncIterable: for-await suspends on waiting array when queue empty and not done.
AssistantMessageEventStream 子类:isComplete 当 type===done 或 error;extractResult 返回 message 或抛错。
AssistantMessageEventStream: isComplete on done/error; extractResult returns message or throws.
第一次 stream(toolUse):text_delta 可能为空或极短;toolcall_delta 累积 read 参数;done 时 stopReason=toolUse。
First stream (toolUse): short text_delta; toolcall_delta accumulates read args; done with stopReason=toolUse.
第二次 stream(stop):text_delta 逐 chunk 推送最终总结;done 时 stopReason=stop。
Second stream (stop): text_delta chunks for summary; done with stopReason=stop.
streamAssistantResponse 把 LLM EventStream 事件映射为 AgentEvent message_update。
streamAssistantResponse maps LLM EventStream events to AgentEvent message_update.
createAgentStream(agent-loop.ts 145 行):isComplete 当 type===agent_end;extractResult 返回 messages 数组。
createAgentStream (line 145): isComplete on agent_end; extractResult returns messages array.
agentLoop 返回 EventStream
agentLoop returns EventStream
end(result) 手动 resolve——用于 abort 或错误路径提前结束。
end(result) manually resolves — for abort or error early termination.
EventStream 无界队列:高速 token 时 TUI diff 渲染可能落后;TuiMainScreen firstChanged/lastChanged 优化重绘区间。
Unbounded queue: TUI diff may lag on fast tokens; TuiMainScreen firstChanged/lastChanged optimizes redraw.
message_update 携带 partial content;message_end 携带完整 AssistantMessage。
message_update carries partial content; message_end carries full AssistantMessage.
textbook event-stream.ts 教学「过程项与终态同时交付」——生产代码在 streamSimple 与 agentLoop 两层复用同一模式。
Textbook checkpoint 02 teaches simultaneous partial and terminal delivery — production reuses pattern in streamSimple and agentLoop.
测试:用 ScriptedModel 注入固定 delta 序列,断言 message_update 次数与顺序。
Test: ScriptedModel injects fixed delta sequence; assert message_update count and order.
① 主线 prompt 进入本章模块时,第一个被调用的 export 函数是什么?② 它 emit 的第一个 AgentEvent 类型是什么?③ 若在此层抛错,TUI 会卡在什么状态?④ 对应的 pi-textbook checkpoint 测试命令是什么?⑤ packages/agent/src/agent-loop.ts 中哪一行最值得打断点?
① First export called when through-line enters this module? ② First AgentEvent type emitted? ③ If this layer throws, what TUI state stalls? ④ Matching pi-textbook checkpoint test command? ⑤ Best breakpoint line in packages/agent/src/agent-loop.ts?
不要一次读完整个文件。用「从测试入手」法:先 npm test -- <matching-test>,看失败断言指向哪行,再读那一行上下游 30 行。生产 pi-mono 的测试覆盖率在 agent-loop 与 session-manager 最高——优先读测试。
Don't read whole files at once. «Test-first» method: run npm test -- <matching-test>, see which assertion fails, read ±30 lines around that line. Highest test coverage in agent-loop and session-manager — read tests first.
对照本文 TRACE 盒与 --verbose stderr:每个 event type 应在源码中有且仅有一处「决策点」——若找不到,说明事件在更上层(AgentSession)或更下层(pi-ai adapter)合并发出。
Cross-check TRACE box with --verbose stderr: each event type should have exactly one «decision point» in source — if missing, event is merged upstream (AgentSession) or downstream (pi-ai adapter).
进阶:用 git blame packages/agent/src/agent-loop.ts 看最近改动——Pi 的 harness 接口仍在活跃演进,注释可能落后于 AgentLoopConfig 类型定义。类型即文档。
Advanced: git blame packages/agent/src/agent-loop.ts for recent changes — Pi harness APIs still evolve; comments may lag AgentLoopConfig types. Types are the doc.
站 15:EventStream push text_delta/toolcall_delta;done 触发 stopReason 分支。
Station 15: EventStream pushes deltas; done triggers stopReason branch.
| CP | 主题 | 教学 artifact | 生产映射 |
|---|---|---|---|
| cp 02 | EventStream | event-stream.ts | packages/ai/src/utils/event-stream.ts |
push 在 done 后忽略;正常路径不丢。
push ignores after done; normal path does not drop.
agent_end 是 AgentEvent;done 是 LLM AssistantMessageEvent。
agent_end is AgentEvent; done is LLM AssistantMessageEvent.
ScriptedModel 或自定义 StreamFn 返回预置 EventStream。
ScriptedModel or custom StreamFn returning preset EventStream.
本章主线 prompt 经过此模块时,请在 pi-mono 打开 packages/ai/src/utils/event-stream.ts,搜索 export 与测试文件中的同名场景。对照 pi-textbook 对应 checkpoint 的 workshop 测试——先让测试绿,再读生产实现。
When the through-line prompt crosses this module, open packages/ai/src/utils/event-stream.ts in pi-mono, search export and matching test scenarios. Cross-read the pi-textbook checkpoint workshop test — green test first, then production.
建议命令:cd packages/agent && npm test -- agent-loop(路径因章而异)。测试文件是行为契约的executable spec,比注释更可靠。
Suggested: cd packages/agent && npm test -- agent-loop (path varies). Tests are executable specs of behavior contracts — more reliable than comments.
用 --verbose 跑一轮主线 prompt,把 stderr event 类型与源码中的 emit 调用逐行对齐——这是本文 C26 推荐的 trace 方法。
Run the through-line with --verbose, align stderr event types with emit calls in source — the trace method recommended in C26.
40+ providers · OAuth · models.generated.ts
40+ providers · OAuth · models.generated.ts
models.generated.ts 由脚本从模型目录生成;OAuth 流程把 token 存 agent dir。无 key 时 formatNoApiKeyFoundMessage 给可操作的引导。
models.generated.ts is script-generated from model catalog; OAuth stores tokens in agent dir. Without keys, formatNoApiKeyFoundMessage gives actionable guidance.
Pi 启动时必须先回答两个问题:用哪个 model? 和 凭据从哪来? models.generated.ts 聚合 40+ provider 的静态目录;ModelRuntime 叠加 credential store 与 availability;findInitialModel 按 CLI → scoped → settings → first-available 优先级选型。没有 key 时 auth-guidance.ts 给出 /login 指引。
Pi boot must answer: which model? and where credentials come from? models.generated.ts aggregates 40+ provider static catalogs; ModelRuntime adds credential store and availability; findInitialModel picks CLI → scoped → settings → first-available. No key → auth-guidance.ts points to /login.
| # | location | 主线时刻 / through-line moment |
|---|---|---|
| 1 | CLI --provider/--model | 最高优先级 |
| 2 | scopedModels[0] | 非 resume 时 |
| 3 | settings default | hasConfiguredAuth |
| 4 | defaultModelPerProvider | anthropic/claude-sonnet-… |
| 5 | availableModels[0] | 任意有 key 的模型 |
| fail | formatNoModelsAvailableMessage | /login 指引 |
主线 prompt 能跑通的前提:findInitialModel 返回的 model 在 getAvailableSnapshot() 里,且 streamSimple 时 applyAuth 能解析出 apiKey。OAuth provider(如 Anthropic Claude Pro)走 credential store + refresh;纯 API key 走 env 或 /login 写入的 auth.json。
Through-line prompt requires: findInitialModel model is in getAvailableSnapshot(), and applyAuth resolves apiKey at streamSimple. OAuth providers use credential store + refresh; API key uses env or auth.json from /login.
~/.pi/agent/auth.json 中 anthropic 条目再启动 pi,应看到 formatNoModelsAvailableMessage 或 formatNoApiKeyFoundMessage。
Experiment: remove anthropic from ~/.pi/agent/auth.json and start pi; expect formatNoModelsAvailableMessage or formatNoApiKeyFoundMessage.
packages/ai/src/models.generated.ts 头部注释:auto-generated by scripts/generate-models.ts,勿手改。
models.generated.ts header: auto-generated by generate-models.ts, do not edit manually.
导入 AMAZON_BEDROCK_MODELS、ANTHROPIC_MODELS、OPENAI_MODELS 等 40+ provider 常量,汇总为 MODELS 对象。
Imports 40+ provider constants into MODELS object.
每个 Model 含 id、provider、contextWindow、maxTokens、cost、reasoning 支持等字段。
Each Model has id, provider, contextWindow, maxTokens, cost, reasoning support.
packages/ai/src/oauth.ts 与 bun-oauth.ts 处理交互式 OAuth(GitHub Copilot、OpenAI Codex 等)。
oauth.ts and bun-oauth.ts handle interactive OAuth for Copilot, Codex, etc.
env-api-keys.ts 从环境变量读取 ANTHROPIC_API_KEY 等;getApiKey 注入 agentLoop config。
env-api-keys.ts reads env vars; getApiKey injected into agentLoop config.
agent-session.ts formatNoApiKeyFoundMessage 在冷启动无 key 时给出 auth-guidance。
formatNoApiKeyFoundMessage gives auth-guidance on cold start without key.
CLI --model provider/id 或交互式 /model 选择;model_change entry 写入 JSONL。
CLI --model provider/id or /model picker; model_change entry in JSONL.
第一次 stream 用选定 Model 调 streamSimple;thinkingLevel 映射到 reasoning 参数。
First stream uses selected Model via streamSimple; thinkingLevel maps to reasoning.
换模型不丢会话:SessionManager 保留历史,新 model_change entry 标记切换点。
Model switch preserves session: model_change entry marks switch point.
coding-agent ModelRegistry 读取 generated catalog,合并用户 settings 与 extension 贡献的 provider config。
ModelRegistry reads generated catalog, merges user settings and extension provider configs.
modelsAreEqual 用于避免重复 emit model_select 事件。
modelsAreEqual avoids duplicate model_select events.
textbook Provider Adapter 教 SSE→统一事件;生产 pi-ai api/*.ts 实现各厂商 adapter。
Textbook checkpoint 05 teaches SSE→unified events; production api/*.ts implements per-vendor adapters.
① 主线 prompt 进入本章模块时,第一个被调用的 export 函数是什么?② 它 emit 的第一个 AgentEvent 类型是什么?③ 若在此层抛错,TUI 会卡在什么状态?④ 对应的 pi-textbook checkpoint 测试命令是什么?⑤ packages/agent/src/agent-loop.ts 中哪一行最值得打断点?
① First export called when through-line enters this module? ② First AgentEvent type emitted? ③ If this layer throws, what TUI state stalls? ④ Matching pi-textbook checkpoint test command? ⑤ Best breakpoint line in packages/agent/src/agent-loop.ts?
不要一次读完整个文件。用「从测试入手」法:先 npm test -- <matching-test>,看失败断言指向哪行,再读那一行上下游 30 行。生产 pi-mono 的测试覆盖率在 agent-loop 与 session-manager 最高——优先读测试。
Don't read whole files at once. «Test-first» method: run npm test -- <matching-test>, see which assertion fails, read ±30 lines around that line. Highest test coverage in agent-loop and session-manager — read tests first.
对照本文 TRACE 盒与 --verbose stderr:每个 event type 应在源码中有且仅有一处「决策点」——若找不到,说明事件在更上层(AgentSession)或更下层(pi-ai adapter)合并发出。
Cross-check TRACE box with --verbose stderr: each event type should have exactly one «decision point» in source — if missing, event is merged upstream (AgentSession) or downstream (pi-ai adapter).
进阶:用 git blame packages/agent/src/agent-loop.ts 看最近改动——Pi 的 harness 接口仍在活跃演进,注释可能落后于 AgentLoopConfig 类型定义。类型即文档。
Advanced: git blame packages/agent/src/agent-loop.ts for recent changes — Pi harness APIs still evolve; comments may lag AgentLoopConfig types. Types are the doc.
站 16:streamSimple 用 Model + Auth 调 provider adapter。
Station 16: streamSimple calls provider adapter with Model + Auth.
| CP | 主题 | 教学 artifact | 生产映射 |
|---|---|---|---|
| cp 05 | Provider Adapter | provider-adapter.ts | packages/ai/src/api/*.ts · packages/ai/src/models.ts |
在 pi-ai providers/ 加 models 文件并跑 generate-models。
Add models file under providers/ and run generate-models.
检查 bun-oauth 回调与 ~/.pi 凭据存储。
Check bun-oauth callback and ~/.pi credential storage.
settings.json 与 CLI --model。
settings.json and CLI --model.
本章主线 prompt 经过此模块时,请在 pi-mono 打开 packages/ai/src/models.generated.ts,搜索 export 与测试文件中的同名场景。对照 pi-textbook 对应 checkpoint 的 workshop 测试——先让测试绿,再读生产实现。
When the through-line prompt crosses this module, open packages/ai/src/models.generated.ts in pi-mono, search export and matching test scenarios. Cross-read the pi-textbook checkpoint workshop test — green test first, then production.
建议命令:cd packages/agent && npm test -- agent-loop(路径因章而异)。测试文件是行为契约的executable spec,比注释更可靠。
Suggested: cd packages/agent && npm test -- agent-loop (path varies). Tests are executable specs of behavior contracts — more reliable than comments.
用 --verbose 跑一轮主线 prompt,把 stderr event 类型与源码中的 emit 调用逐行对齐——这是本文 C26 推荐的 trace 方法。
Run the through-line with --verbose, align stderr event types with emit calls in source — the trace method recommended in C26.
CSI 2026 · firstChanged → lastChanged
CSI 2026 · firstChanged → lastChanged
Pi TUI 用 CSI 2026 差分更新:firstChanged 到 lastChanged 行重绘,而非全屏刷新。流式 token 因此不闪烁。
Pi TUI uses CSI 2026 differential updates: redraw firstChanged to lastChanged lines, not full screen. Streaming tokens don't flicker.
| 策略 | 全屏刷新 | 差分渲染 |
|---|---|---|
| 带宽 | O(screen) | O(changed lines) |
| 闪烁 | 明显 | minimal |
| 实现复杂度 | 低 | 需 damage tracking |
AgentSession 把 message_update 交给 interactive mode → TUI 组件树 → Markdown 增量解析 → 终端 write。
AgentSession hands message_update to interactive mode → TUI component tree → incremental Markdown → terminal write.
用户输入「读取 README.md,用一句话告诉我这个项目做什么」后,packages/tui/src/tui-main-screen.ts 定义了此阶段的类型契约与副作用边界。
After «read README.md and tell me what this project does in one sentence», packages/tui/src/tui-main-screen.ts defines type contracts and side-effect boundaries here.
在 pi-mono 仓库打开该文件,搜索 export 与 AgentEvent——这是比读 README 更快的定位法。
In pi-mono, search export and AgentEvent — faster than README alone.
设计原则:核心循环(agent-loop)不 import TUI;本模块通过事件与回调与上游通信。
Principle: agent-loop never imports TUI; this module talks upstream via events/callbacks.
pi-textbook 对应 checkpoint 提供剥离 OAuth/TUI 后的最小可运行切片,建议先跑测试再读生产文件。
Matching textbook checkpoint offers a minimal slice without OAuth/TUI — run its test before production file.
tui-main-screen.ts 内的 async 函数通常是 IO 边界:磁盘(read/write JSONL)、网络(streamSimple)、或子进程(bash)。
async functions in tui-main-screen.ts are usually IO boundaries: disk, network, or subprocess.
成功路径与错误路径都必须 emit 事件,否则 TUI 会卡在 streaming 状态、JSONL 会缺 entry。
Success and error paths must emit events or TUI stalls streaming and JSONL misses entries.
length 截断时 agent-loop.ts 的 failToolCallsFromTruncatedMessage 批量失败 tool calls,防止执行损坏参数。
On length truncation, failToolCallsFromTruncatedMessage fails all tool calls to avoid corrupted args.
对照 packages/agent/test/agent-loop.test.ts 中的同名场景测试用例。
Compare with matching scenarios in packages/agent/test/agent-loop.test.ts.
AgentLoopConfig(packages/agent/src/types.ts)列出所有注入点:convertToLlm、getSteeringMessages、prepareNextTurn 等。
AgentLoopConfig in types.ts lists injection points: convertToLlm, getSteeringMessages, prepareNextTurn, etc.
AgentSession 在构造 config 时绑定本模块实现;换模式(print/rpc/interactive)不换 agent-loop。
AgentSession binds this module when building config; modes change, agent-loop does not.
fork 项目时优先替换本模块而非复制 agent-loop——这是 Pi 社区的实际 fork 模式。
When forking, replace this module first rather than copying agent-loop — the common community pattern.
--verbose 模式下 stderr 事件类型可回溯到 packages/tui/src/tui-main-screen.ts 的具体行。
Under --verbose, stderr event types trace to lines in packages/tui/src/tui-main-screen.ts.
显式事件总线 vs 隐式回调:Pi 选择前者,事件类型在 AgentEvent union 中版本化。
Explicit event bus vs implicit callbacks: Pi chooses the former; events versioned in AgentEvent union.
扩展应 subscribe 事件而非 monkey-patch 私有方法——ExtensionRunner 提供正规 hook 面。
Extensions should subscribe to events, not monkey-patch privates — ExtensionRunner provides hooks.
JSONL CURRENT_SESSION_VERSION = 3:改 entry schema 必须 bump version 并提供迁移。
JSONL CURRENT_SESSION_VERSION = 3: schema changes need version bump and migration.
对比 Claude Code:闭源栈无法确认 compaction 触发点;Pi 的 shouldCompact 在源码中可读。
vs Claude Code: closed stack hides compaction triggers; Pi's shouldCompact is readable.
① 主线 prompt 进入本章模块时,第一个被调用的 export 函数是什么?② 它 emit 的第一个 AgentEvent 类型是什么?③ 若在此层抛错,TUI 会卡在什么状态?④ 对应的 pi-textbook checkpoint 测试命令是什么?⑤ packages/tui/src/terminal.ts 中哪一行最值得打断点?
① First export called when through-line enters this module? ② First AgentEvent type emitted? ③ If this layer throws, what TUI state stalls? ④ Matching pi-textbook checkpoint test command? ⑤ Best breakpoint line in packages/tui/src/terminal.ts?
不要一次读完整个文件。用「从测试入手」法:先 npm test -- <matching-test>,看失败断言指向哪行,再读那一行上下游 30 行。生产 pi-mono 的测试覆盖率在 agent-loop 与 session-manager 最高——优先读测试。
Don't read whole files at once. «Test-first» method: run npm test -- <matching-test>, see which assertion fails, read ±30 lines around that line. Highest test coverage in agent-loop and session-manager — read tests first.
对照本文 TRACE 盒与 --verbose stderr:每个 event type 应在源码中有且仅有一处「决策点」——若找不到,说明事件在更上层(AgentSession)或更下层(pi-ai adapter)合并发出。
Cross-check TRACE box with --verbose stderr: each event type should have exactly one «decision point» in source — if missing, event is merged upstream (AgentSession) or downstream (pi-ai adapter).
进阶:用 git blame packages/tui/src/terminal.ts 看最近改动——Pi 的 harness 接口仍在活跃演进,注释可能落后于 AgentLoopConfig 类型定义。类型即文档。
Advanced: git blame packages/tui/src/terminal.ts for recent changes — Pi harness APIs still evolve; comments may lag AgentLoopConfig types. Types are the doc.
站 17:packages/tui/src/tui-main-screen.ts 处理主线。
Station 17: packages/tui/src/tui-main-screen.ts on through-line.
packages/tui/src/tui-main-screen.ts。
packages/tui/src/tui-main-screen.ts.
mock StreamFn 或 ScriptedModel。
mock StreamFn or ScriptedModel.
生产含 OAuth/TUI/40+ providers。
Production adds OAuth/TUI/40+ providers.
本章主线 prompt 经过此模块时,请在 pi-mono 打开 packages/tui/src/terminal.ts,搜索 export 与测试文件中的同名场景。对照 pi-textbook 对应 checkpoint 的 workshop 测试——先让测试绿,再读生产实现。
When the through-line prompt crosses this module, open packages/tui/src/terminal.ts in pi-mono, search export and matching test scenarios. Cross-read the pi-textbook checkpoint workshop test — green test first, then production.
建议命令:cd packages/agent && npm test -- agent-loop(路径因章而异)。测试文件是行为契约的executable spec,比注释更可靠。
Suggested: cd packages/agent && npm test -- agent-loop (path varies). Tests are executable specs of behavior contracts — more reliable than comments.
用 --verbose 跑一轮主线 prompt,把 stderr event 类型与源码中的 emit 调用逐行对齐——这是本文 C26 推荐的 trace 方法。
Run the through-line with --verbose, align stderr event types with emit calls in source — the trace method recommended in C26.
Editor · Markdown · tool renderers
Editor · Markdown · tool renderers
Interactive mode 组合 Editor(输入)、Markdown(输出)、tool renderers(read 结果预览)。主线 prompt 在 Editor 提交,最终结果在 Markdown 区滚动。
Interactive mode composes Editor (input), Markdown (output), tool renderers (read preview). Through-line prompt submits from Editor; final answer scrolls in Markdown area.
用户输入「读取 README.md,用一句话告诉我这个项目做什么」后,packages/coding-agent/src/modes/interactive/interactive-mode.ts 定义了此阶段的类型契约与副作用边界。
After «read README.md and tell me what this project does in one sentence», packages/coding-agent/src/modes/interactive/interactive-mode.ts defines type contracts and side-effect boundaries here.
在 pi-mono 仓库打开该文件,搜索 export 与 AgentEvent——这是比读 README 更快的定位法。
In pi-mono, search export and AgentEvent — faster than README alone.
设计原则:核心循环(agent-loop)不 import TUI;本模块通过事件与回调与上游通信。
Principle: agent-loop never imports TUI; this module talks upstream via events/callbacks.
pi-textbook 对应 checkpoint 提供剥离 OAuth/TUI 后的最小可运行切片,建议先跑测试再读生产文件。
Matching textbook checkpoint offers a minimal slice without OAuth/TUI — run its test before production file.
interactive-mode.ts 内的 async 函数通常是 IO 边界:磁盘(read/write JSONL)、网络(streamSimple)、或子进程(bash)。
async functions in interactive-mode.ts are usually IO boundaries: disk, network, or subprocess.
成功路径与错误路径都必须 emit 事件,否则 TUI 会卡在 streaming 状态、JSONL 会缺 entry。
Success and error paths must emit events or TUI stalls streaming and JSONL misses entries.
length 截断时 agent-loop.ts 的 failToolCallsFromTruncatedMessage 批量失败 tool calls,防止执行损坏参数。
On length truncation, failToolCallsFromTruncatedMessage fails all tool calls to avoid corrupted args.
对照 packages/agent/test/agent-loop.test.ts 中的同名场景测试用例。
Compare with matching scenarios in packages/agent/test/agent-loop.test.ts.
AgentLoopConfig(packages/agent/src/types.ts)列出所有注入点:convertToLlm、getSteeringMessages、prepareNextTurn 等。
AgentLoopConfig in types.ts lists injection points: convertToLlm, getSteeringMessages, prepareNextTurn, etc.
AgentSession 在构造 config 时绑定本模块实现;换模式(print/rpc/interactive)不换 agent-loop。
AgentSession binds this module when building config; modes change, agent-loop does not.
fork 项目时优先替换本模块而非复制 agent-loop——这是 Pi 社区的实际 fork 模式。
When forking, replace this module first rather than copying agent-loop — the common community pattern.
--verbose 模式下 stderr 事件类型可回溯到 packages/coding-agent/src/modes/interactive/interactive-mode.ts 的具体行。
Under --verbose, stderr event types trace to lines in packages/coding-agent/src/modes/interactive/interactive-mode.ts.
显式事件总线 vs 隐式回调:Pi 选择前者,事件类型在 AgentEvent union 中版本化。
Explicit event bus vs implicit callbacks: Pi chooses the former; events versioned in AgentEvent union.
扩展应 subscribe 事件而非 monkey-patch 私有方法——ExtensionRunner 提供正规 hook 面。
Extensions should subscribe to events, not monkey-patch privates — ExtensionRunner provides hooks.
JSONL CURRENT_SESSION_VERSION = 3:改 entry schema 必须 bump version 并提供迁移。
JSONL CURRENT_SESSION_VERSION = 3: schema changes need version bump and migration.
对比 Claude Code:闭源栈无法确认 compaction 触发点;Pi 的 shouldCompact 在源码中可读。
vs Claude Code: closed stack hides compaction triggers; Pi's shouldCompact is readable.
① 打开 packages/coding-agent/src/modes/interactive/interactive-mode.ts;② 从 export 符号跟踪 README 路径调用链;③ 标注每个 await 的 IO 类型。
① Open packages/coding-agent/src/modes/interactive/interactive-mode.ts; ② trace README call chain from exports; ③ label each await's IO type.
④ 在 agent-loop.test.ts 或 coding-agent 测试中找覆盖此站的用例;⑤ 用 pi --verbose 验证事件顺序。
④ Find tests covering this station; ⑤ verify event order with pi --verbose.
记录三个不变量:若修改会破坏主线 prompt 的行为假设。
Record three invariants: assumptions whose change would break the through-line.
将观察结果与 pi-textbook workshop 的 checkpoint 测试输出 diff。
Diff observations against pi-textbook workshop checkpoint test output.
① 主线 prompt 进入本章模块时,第一个被调用的 export 函数是什么?② 它 emit 的第一个 AgentEvent 类型是什么?③ 若在此层抛错,TUI 会卡在什么状态?④ 对应的 pi-textbook checkpoint 测试命令是什么?⑤ packages/agent/src/agent-loop.ts 中哪一行最值得打断点?
① First export called when through-line enters this module? ② First AgentEvent type emitted? ③ If this layer throws, what TUI state stalls? ④ Matching pi-textbook checkpoint test command? ⑤ Best breakpoint line in packages/agent/src/agent-loop.ts?
不要一次读完整个文件。用「从测试入手」法:先 npm test -- <matching-test>,看失败断言指向哪行,再读那一行上下游 30 行。生产 pi-mono 的测试覆盖率在 agent-loop 与 session-manager 最高——优先读测试。
Don't read whole files at once. «Test-first» method: run npm test -- <matching-test>, see which assertion fails, read ±30 lines around that line. Highest test coverage in agent-loop and session-manager — read tests first.
对照本文 TRACE 盒与 --verbose stderr:每个 event type 应在源码中有且仅有一处「决策点」——若找不到,说明事件在更上层(AgentSession)或更下层(pi-ai adapter)合并发出。
Cross-check TRACE box with --verbose stderr: each event type should have exactly one «decision point» in source — if missing, event is merged upstream (AgentSession) or downstream (pi-ai adapter).
进阶:用 git blame packages/agent/src/agent-loop.ts 看最近改动——Pi 的 harness 接口仍在活跃演进,注释可能落后于 AgentLoopConfig 类型定义。类型即文档。
Advanced: git blame packages/agent/src/agent-loop.ts for recent changes — Pi harness APIs still evolve; comments may lag AgentLoopConfig types. Types are the doc.
站 18:packages/coding-agent/src/modes/interactive/interactive-mode.ts 处理主线。
Station 18: packages/coding-agent/src/modes/interactive/interactive-mode.ts on through-line.
packages/coding-agent/src/modes/interactive/interactive-mode.ts。
packages/coding-agent/src/modes/interactive/interactive-mode.ts.
mock StreamFn 或 ScriptedModel。
mock StreamFn or ScriptedModel.
生产含 OAuth/TUI/40+ providers。
Production adds OAuth/TUI/40+ providers.
本章主线 prompt 经过此模块时,请在 pi-mono 打开 packages/tui/src/components/editor.ts,搜索 export 与测试文件中的同名场景。对照 pi-textbook 对应 checkpoint 的 workshop 测试——先让测试绿,再读生产实现。
When the through-line prompt crosses this module, open packages/tui/src/components/editor.ts in pi-mono, search export and matching test scenarios. Cross-read the pi-textbook checkpoint workshop test — green test first, then production.
建议命令:cd packages/agent && npm test -- agent-loop(路径因章而异)。测试文件是行为契约的executable spec,比注释更可靠。
Suggested: cd packages/agent && npm test -- agent-loop (path varies). Tests are executable specs of behavior contracts — more reliable than comments.
用 --verbose 跑一轮主线 prompt,把 stderr event 类型与源码中的 emit 调用逐行对齐——这是本文 C26 推荐的 trace 方法。
Run the through-line with --verbose, align stderr event types with emit calls in source — the trace method recommended in C26.
registerTool · registerCommand · events
registerTool · registerCommand · events
扩展通过 registerTool · registerCommand · 事件 hook 注入能力——无需 MCP,同进程 TypeScript。
Extensions inject via registerTool · registerCommand · event hooks — no MCP, same-process TypeScript.
| Hook | 时机 | 用例 |
|---|---|---|
| session_start | 会话加载 | 恢复扩展状态 |
| before_compact | 压缩前 | 保留 artifact 索引 |
| tool_execution_end | 工具后 | 自动 lint |
resources-extensions)· 生产映射:packages/coding-agent/src/core/extensions/ · resource-loader.ts
📘 pi-textbook checkpoint 12: Resources + Extensions (resources-extensions) · production: packages/coding-agent/src/core/extensions/ · resource-loader.ts
用户输入「读取 README.md,用一句话告诉我这个项目做什么」后,packages/coding-agent/src/core/extensions/types.ts 定义了此阶段的类型契约与副作用边界。
After «read README.md and tell me what this project does in one sentence», packages/coding-agent/src/core/extensions/types.ts defines type contracts and side-effect boundaries here.
在 pi-mono 仓库打开该文件,搜索 export 与 AgentEvent——这是比读 README 更快的定位法。
In pi-mono, search export and AgentEvent — faster than README alone.
设计原则:核心循环(agent-loop)不 import TUI;本模块通过事件与回调与上游通信。
Principle: agent-loop never imports TUI; this module talks upstream via events/callbacks.
pi-textbook 对应 checkpoint 提供剥离 OAuth/TUI 后的最小可运行切片,建议先跑测试再读生产文件。
Matching textbook checkpoint offers a minimal slice without OAuth/TUI — run its test before production file.
types.ts 内的 async 函数通常是 IO 边界:磁盘(read/write JSONL)、网络(streamSimple)、或子进程(bash)。
async functions in types.ts are usually IO boundaries: disk, network, or subprocess.
成功路径与错误路径都必须 emit 事件,否则 TUI 会卡在 streaming 状态、JSONL 会缺 entry。
Success and error paths must emit events or TUI stalls streaming and JSONL misses entries.
length 截断时 agent-loop.ts 的 failToolCallsFromTruncatedMessage 批量失败 tool calls,防止执行损坏参数。
On length truncation, failToolCallsFromTruncatedMessage fails all tool calls to avoid corrupted args.
对照 packages/agent/test/agent-loop.test.ts 中的同名场景测试用例。
Compare with matching scenarios in packages/agent/test/agent-loop.test.ts.
AgentLoopConfig(packages/agent/src/types.ts)列出所有注入点:convertToLlm、getSteeringMessages、prepareNextTurn 等。
AgentLoopConfig in types.ts lists injection points: convertToLlm, getSteeringMessages, prepareNextTurn, etc.
AgentSession 在构造 config 时绑定本模块实现;换模式(print/rpc/interactive)不换 agent-loop。
AgentSession binds this module when building config; modes change, agent-loop does not.
fork 项目时优先替换本模块而非复制 agent-loop——这是 Pi 社区的实际 fork 模式。
When forking, replace this module first rather than copying agent-loop — the common community pattern.
--verbose 模式下 stderr 事件类型可回溯到 packages/coding-agent/src/core/extensions/types.ts 的具体行。
Under --verbose, stderr event types trace to lines in packages/coding-agent/src/core/extensions/types.ts.
显式事件总线 vs 隐式回调:Pi 选择前者,事件类型在 AgentEvent union 中版本化。
Explicit event bus vs implicit callbacks: Pi chooses the former; events versioned in AgentEvent union.
扩展应 subscribe 事件而非 monkey-patch 私有方法——ExtensionRunner 提供正规 hook 面。
Extensions should subscribe to events, not monkey-patch privates — ExtensionRunner provides hooks.
JSONL CURRENT_SESSION_VERSION = 3:改 entry schema 必须 bump version 并提供迁移。
JSONL CURRENT_SESSION_VERSION = 3: schema changes need version bump and migration.
对比 Claude Code:闭源栈无法确认 compaction 触发点;Pi 的 shouldCompact 在源码中可读。
vs Claude Code: closed stack hides compaction triggers; Pi's shouldCompact is readable.
① 主线 prompt 进入本章模块时,第一个被调用的 export 函数是什么?② 它 emit 的第一个 AgentEvent 类型是什么?③ 若在此层抛错,TUI 会卡在什么状态?④ 对应的 pi-textbook checkpoint 测试命令是什么?⑤ packages/agent/src/agent-loop.ts 中哪一行最值得打断点?
① First export called when through-line enters this module? ② First AgentEvent type emitted? ③ If this layer throws, what TUI state stalls? ④ Matching pi-textbook checkpoint test command? ⑤ Best breakpoint line in packages/agent/src/agent-loop.ts?
不要一次读完整个文件。用「从测试入手」法:先 npm test -- <matching-test>,看失败断言指向哪行,再读那一行上下游 30 行。生产 pi-mono 的测试覆盖率在 agent-loop 与 session-manager 最高——优先读测试。
Don't read whole files at once. «Test-first» method: run npm test -- <matching-test>, see which assertion fails, read ±30 lines around that line. Highest test coverage in agent-loop and session-manager — read tests first.
对照本文 TRACE 盒与 --verbose stderr:每个 event type 应在源码中有且仅有一处「决策点」——若找不到,说明事件在更上层(AgentSession)或更下层(pi-ai adapter)合并发出。
Cross-check TRACE box with --verbose stderr: each event type should have exactly one «decision point» in source — if missing, event is merged upstream (AgentSession) or downstream (pi-ai adapter).
进阶:用 git blame packages/agent/src/agent-loop.ts 看最近改动——Pi 的 harness 接口仍在活跃演进,注释可能落后于 AgentLoopConfig 类型定义。类型即文档。
Advanced: git blame packages/agent/src/agent-loop.ts for recent changes — Pi harness APIs still evolve; comments may lag AgentLoopConfig types. Types are the doc.
站 19:packages/coding-agent/src/core/extensions/types.ts 处理主线。
Station 19: packages/coding-agent/src/core/extensions/types.ts on through-line.
| CP | 主题 | 教学 artifact | 生产映射 |
|---|---|---|---|
| cp 12 | Resources + Extensions | resources-extensions | packages/coding-agent/src/core/extensions/ · resource-loader.ts |
packages/coding-agent/src/core/extensions/types.ts。
packages/coding-agent/src/core/extensions/types.ts.
mock StreamFn 或 ScriptedModel。
mock StreamFn or ScriptedModel.
生产含 OAuth/TUI/40+ providers。
Production adds OAuth/TUI/40+ providers.
本章主线 prompt 经过此模块时,请在 pi-mono 打开 packages/coding-agent/src/core/extensions/types.ts,搜索 export 与测试文件中的同名场景。对照 pi-textbook 对应 checkpoint 的 workshop 测试——先让测试绿,再读生产实现。
When the through-line prompt crosses this module, open packages/coding-agent/src/core/extensions/types.ts in pi-mono, search export and matching test scenarios. Cross-read the pi-textbook checkpoint workshop test — green test first, then production.
建议命令:cd packages/agent && npm test -- agent-loop(路径因章而异)。测试文件是行为契约的executable spec,比注释更可靠。
Suggested: cd packages/agent && npm test -- agent-loop (path varies). Tests are executable specs of behavior contracts — more reliable than comments.
用 --verbose 跑一轮主线 prompt,把 stderr event 类型与源码中的 emit 调用逐行对齐——这是本文 C26 推荐的 trace 方法。
Run the through-line with --verbose, align stderr event types with emit calls in source — the trace method recommended in C26.
jiti 加载 · 事件合并 · hook 注入
jiti load · event merge · hook injection
ExtensionRunner 用 jiti 动态加载扩展 TS,合并事件处理器,在 agent 生命周期关键点注入 hook。
ExtensionRunner uses jiti to load extension TS, merges event handlers, injects hooks at agent lifecycle points.
多个扩展可订阅同一事件;Runner 按加载顺序调用;before_compact 可返回修改后的 summary。
Multiple extensions can subscribe; Runner calls in load order; before_compact can return modified summary.
composition-root)· 生产映射:packages/coding-agent/src/core/agent-session.ts · sdk.ts
📘 pi-textbook checkpoint 13: Composition Root (composition-root) · production: packages/coding-agent/src/core/agent-session.ts · sdk.ts
用户输入「读取 README.md,用一句话告诉我这个项目做什么」后,packages/coding-agent/src/core/extensions/runner.ts 定义了此阶段的类型契约与副作用边界。
After «read README.md and tell me what this project does in one sentence», packages/coding-agent/src/core/extensions/runner.ts defines type contracts and side-effect boundaries here.
在 pi-mono 仓库打开该文件,搜索 export 与 AgentEvent——这是比读 README 更快的定位法。
In pi-mono, search export and AgentEvent — faster than README alone.
设计原则:核心循环(agent-loop)不 import TUI;本模块通过事件与回调与上游通信。
Principle: agent-loop never imports TUI; this module talks upstream via events/callbacks.
pi-textbook 对应 checkpoint 提供剥离 OAuth/TUI 后的最小可运行切片,建议先跑测试再读生产文件。
Matching textbook checkpoint offers a minimal slice without OAuth/TUI — run its test before production file.
runner.ts 内的 async 函数通常是 IO 边界:磁盘(read/write JSONL)、网络(streamSimple)、或子进程(bash)。
async functions in runner.ts are usually IO boundaries: disk, network, or subprocess.
成功路径与错误路径都必须 emit 事件,否则 TUI 会卡在 streaming 状态、JSONL 会缺 entry。
Success and error paths must emit events or TUI stalls streaming and JSONL misses entries.
length 截断时 agent-loop.ts 的 failToolCallsFromTruncatedMessage 批量失败 tool calls,防止执行损坏参数。
On length truncation, failToolCallsFromTruncatedMessage fails all tool calls to avoid corrupted args.
对照 packages/agent/test/agent-loop.test.ts 中的同名场景测试用例。
Compare with matching scenarios in packages/agent/test/agent-loop.test.ts.
AgentLoopConfig(packages/agent/src/types.ts)列出所有注入点:convertToLlm、getSteeringMessages、prepareNextTurn 等。
AgentLoopConfig in types.ts lists injection points: convertToLlm, getSteeringMessages, prepareNextTurn, etc.
AgentSession 在构造 config 时绑定本模块实现;换模式(print/rpc/interactive)不换 agent-loop。
AgentSession binds this module when building config; modes change, agent-loop does not.
fork 项目时优先替换本模块而非复制 agent-loop——这是 Pi 社区的实际 fork 模式。
When forking, replace this module first rather than copying agent-loop — the common community pattern.
--verbose 模式下 stderr 事件类型可回溯到 packages/coding-agent/src/core/extensions/runner.ts 的具体行。
Under --verbose, stderr event types trace to lines in packages/coding-agent/src/core/extensions/runner.ts.
显式事件总线 vs 隐式回调:Pi 选择前者,事件类型在 AgentEvent union 中版本化。
Explicit event bus vs implicit callbacks: Pi chooses the former; events versioned in AgentEvent union.
扩展应 subscribe 事件而非 monkey-patch 私有方法——ExtensionRunner 提供正规 hook 面。
Extensions should subscribe to events, not monkey-patch privates — ExtensionRunner provides hooks.
JSONL CURRENT_SESSION_VERSION = 3:改 entry schema 必须 bump version 并提供迁移。
JSONL CURRENT_SESSION_VERSION = 3: schema changes need version bump and migration.
对比 Claude Code:闭源栈无法确认 compaction 触发点;Pi 的 shouldCompact 在源码中可读。
vs Claude Code: closed stack hides compaction triggers; Pi's shouldCompact is readable.
① 打开 packages/coding-agent/src/core/extensions/runner.ts;② 从 export 符号跟踪 README 路径调用链;③ 标注每个 await 的 IO 类型。
① Open packages/coding-agent/src/core/extensions/runner.ts; ② trace README call chain from exports; ③ label each await's IO type.
④ 在 agent-loop.test.ts 或 coding-agent 测试中找覆盖此站的用例;⑤ 用 pi --verbose 验证事件顺序。
④ Find tests covering this station; ⑤ verify event order with pi --verbose.
记录三个不变量:若修改会破坏主线 prompt 的行为假设。
Record three invariants: assumptions whose change would break the through-line.
将观察结果与 pi-textbook workshop 的 checkpoint 测试输出 diff。
Diff observations against pi-textbook workshop checkpoint test output.
① 主线 prompt 进入本章模块时,第一个被调用的 export 函数是什么?② 它 emit 的第一个 AgentEvent 类型是什么?③ 若在此层抛错,TUI 会卡在什么状态?④ 对应的 pi-textbook checkpoint 测试命令是什么?⑤ packages/agent/src/agent-loop.ts 中哪一行最值得打断点?
① First export called when through-line enters this module? ② First AgentEvent type emitted? ③ If this layer throws, what TUI state stalls? ④ Matching pi-textbook checkpoint test command? ⑤ Best breakpoint line in packages/agent/src/agent-loop.ts?
不要一次读完整个文件。用「从测试入手」法:先 npm test -- <matching-test>,看失败断言指向哪行,再读那一行上下游 30 行。生产 pi-mono 的测试覆盖率在 agent-loop 与 session-manager 最高——优先读测试。
Don't read whole files at once. «Test-first» method: run npm test -- <matching-test>, see which assertion fails, read ±30 lines around that line. Highest test coverage in agent-loop and session-manager — read tests first.
对照本文 TRACE 盒与 --verbose stderr:每个 event type 应在源码中有且仅有一处「决策点」——若找不到,说明事件在更上层(AgentSession)或更下层(pi-ai adapter)合并发出。
Cross-check TRACE box with --verbose stderr: each event type should have exactly one «decision point» in source — if missing, event is merged upstream (AgentSession) or downstream (pi-ai adapter).
进阶:用 git blame packages/agent/src/agent-loop.ts 看最近改动——Pi 的 harness 接口仍在活跃演进,注释可能落后于 AgentLoopConfig 类型定义。类型即文档。
Advanced: git blame packages/agent/src/agent-loop.ts for recent changes — Pi harness APIs still evolve; comments may lag AgentLoopConfig types. Types are the doc.
站 20:packages/coding-agent/src/core/extensions/runner.ts 处理主线。
Station 20: packages/coding-agent/src/core/extensions/runner.ts on through-line.
| CP | 主题 | 教学 artifact | 生产映射 |
|---|---|---|---|
| cp 12 | Resources + Extensions | resources-extensions | packages/coding-agent/src/core/extensions/ · resource-loader.ts |
| cp 13 | Composition Root | composition-root | packages/coding-agent/src/core/agent-session.ts · sdk.ts |
packages/coding-agent/src/core/extensions/runner.ts。
packages/coding-agent/src/core/extensions/runner.ts.
mock StreamFn 或 ScriptedModel。
mock StreamFn or ScriptedModel.
生产含 OAuth/TUI/40+ providers。
Production adds OAuth/TUI/40+ providers.
本章主线 prompt 经过此模块时,请在 pi-mono 打开 packages/coding-agent/src/core/extensions/runner.ts,搜索 export 与测试文件中的同名场景。对照 pi-textbook 对应 checkpoint 的 workshop 测试——先让测试绿,再读生产实现。
When the through-line prompt crosses this module, open packages/coding-agent/src/core/extensions/runner.ts in pi-mono, search export and matching test scenarios. Cross-read the pi-textbook checkpoint workshop test — green test first, then production.
建议命令:cd packages/agent && npm test -- agent-loop(路径因章而异)。测试文件是行为契约的executable spec,比注释更可靠。
Suggested: cd packages/agent && npm test -- agent-loop (path varies). Tests are executable specs of behavior contracts — more reliable than comments.
用 --verbose 跑一轮主线 prompt,把 stderr event 类型与源码中的 emit 调用逐行对齐——这是本文 C26 推荐的 trace 方法。
Run the through-line with --verbose, align stderr event types with emit calls in source — the trace method recommended in C26.
npm · git · 可分享的 harness 能力
npm · git · shareable harness capabilities
pi package 命令管理可分享的 harness 能力包——npm 或 git 依赖,把工具+扩展+资源打包分发。
pi package manages shareable harness capability bundles — npm or git deps packaging tools+extensions+resources.
| 分发 | 格式 | 消费者 |
|---|---|---|
| npm | package.json + pi manifest | pi install |
| git | repo URL + ref | pi package add |
| local | path | 开发调试 |
用户输入「读取 README.md,用一句话告诉我这个项目做什么」后,packages/coding-agent/src/core/package-manager.ts 定义了此阶段的类型契约与副作用边界。
After «read README.md and tell me what this project does in one sentence», packages/coding-agent/src/core/package-manager.ts defines type contracts and side-effect boundaries here.
在 pi-mono 仓库打开该文件,搜索 export 与 AgentEvent——这是比读 README 更快的定位法。
In pi-mono, search export and AgentEvent — faster than README alone.
设计原则:核心循环(agent-loop)不 import TUI;本模块通过事件与回调与上游通信。
Principle: agent-loop never imports TUI; this module talks upstream via events/callbacks.
pi-textbook 对应 checkpoint 提供剥离 OAuth/TUI 后的最小可运行切片,建议先跑测试再读生产文件。
Matching textbook checkpoint offers a minimal slice without OAuth/TUI — run its test before production file.
package-manager.ts 内的 async 函数通常是 IO 边界:磁盘(read/write JSONL)、网络(streamSimple)、或子进程(bash)。
async functions in package-manager.ts are usually IO boundaries: disk, network, or subprocess.
成功路径与错误路径都必须 emit 事件,否则 TUI 会卡在 streaming 状态、JSONL 会缺 entry。
Success and error paths must emit events or TUI stalls streaming and JSONL misses entries.
length 截断时 agent-loop.ts 的 failToolCallsFromTruncatedMessage 批量失败 tool calls,防止执行损坏参数。
On length truncation, failToolCallsFromTruncatedMessage fails all tool calls to avoid corrupted args.
对照 packages/agent/test/agent-loop.test.ts 中的同名场景测试用例。
Compare with matching scenarios in packages/agent/test/agent-loop.test.ts.
AgentLoopConfig(packages/agent/src/types.ts)列出所有注入点:convertToLlm、getSteeringMessages、prepareNextTurn 等。
AgentLoopConfig in types.ts lists injection points: convertToLlm, getSteeringMessages, prepareNextTurn, etc.
AgentSession 在构造 config 时绑定本模块实现;换模式(print/rpc/interactive)不换 agent-loop。
AgentSession binds this module when building config; modes change, agent-loop does not.
fork 项目时优先替换本模块而非复制 agent-loop——这是 Pi 社区的实际 fork 模式。
When forking, replace this module first rather than copying agent-loop — the common community pattern.
--verbose 模式下 stderr 事件类型可回溯到 packages/coding-agent/src/core/package-manager.ts 的具体行。
Under --verbose, stderr event types trace to lines in packages/coding-agent/src/core/package-manager.ts.
显式事件总线 vs 隐式回调:Pi 选择前者,事件类型在 AgentEvent union 中版本化。
Explicit event bus vs implicit callbacks: Pi chooses the former; events versioned in AgentEvent union.
扩展应 subscribe 事件而非 monkey-patch 私有方法——ExtensionRunner 提供正规 hook 面。
Extensions should subscribe to events, not monkey-patch privates — ExtensionRunner provides hooks.
JSONL CURRENT_SESSION_VERSION = 3:改 entry schema 必须 bump version 并提供迁移。
JSONL CURRENT_SESSION_VERSION = 3: schema changes need version bump and migration.
对比 Claude Code:闭源栈无法确认 compaction 触发点;Pi 的 shouldCompact 在源码中可读。
vs Claude Code: closed stack hides compaction triggers; Pi's shouldCompact is readable.
① 打开 packages/coding-agent/src/core/package-manager.ts;② 从 export 符号跟踪 README 路径调用链;③ 标注每个 await 的 IO 类型。
① Open packages/coding-agent/src/core/package-manager.ts; ② trace README call chain from exports; ③ label each await's IO type.
④ 在 agent-loop.test.ts 或 coding-agent 测试中找覆盖此站的用例;⑤ 用 pi --verbose 验证事件顺序。
④ Find tests covering this station; ⑤ verify event order with pi --verbose.
记录三个不变量:若修改会破坏主线 prompt 的行为假设。
Record three invariants: assumptions whose change would break the through-line.
将观察结果与 pi-textbook workshop 的 checkpoint 测试输出 diff。
Diff observations against pi-textbook workshop checkpoint test output.
① 主线 prompt 进入本章模块时,第一个被调用的 export 函数是什么?② 它 emit 的第一个 AgentEvent 类型是什么?③ 若在此层抛错,TUI 会卡在什么状态?④ 对应的 pi-textbook checkpoint 测试命令是什么?⑤ packages/agent/src/agent-loop.ts 中哪一行最值得打断点?
① First export called when through-line enters this module? ② First AgentEvent type emitted? ③ If this layer throws, what TUI state stalls? ④ Matching pi-textbook checkpoint test command? ⑤ Best breakpoint line in packages/agent/src/agent-loop.ts?
不要一次读完整个文件。用「从测试入手」法:先 npm test -- <matching-test>,看失败断言指向哪行,再读那一行上下游 30 行。生产 pi-mono 的测试覆盖率在 agent-loop 与 session-manager 最高——优先读测试。
Don't read whole files at once. «Test-first» method: run npm test -- <matching-test>, see which assertion fails, read ±30 lines around that line. Highest test coverage in agent-loop and session-manager — read tests first.
对照本文 TRACE 盒与 --verbose stderr:每个 event type 应在源码中有且仅有一处「决策点」——若找不到,说明事件在更上层(AgentSession)或更下层(pi-ai adapter)合并发出。
Cross-check TRACE box with --verbose stderr: each event type should have exactly one «decision point» in source — if missing, event is merged upstream (AgentSession) or downstream (pi-ai adapter).
进阶:用 git blame packages/agent/src/agent-loop.ts 看最近改动——Pi 的 harness 接口仍在活跃演进,注释可能落后于 AgentLoopConfig 类型定义。类型即文档。
Advanced: git blame packages/agent/src/agent-loop.ts for recent changes — Pi harness APIs still evolve; comments may lag AgentLoopConfig types. Types are the doc.
站 21:packages/coding-agent/src/core/package-manager.ts 处理主线。
Station 21: packages/coding-agent/src/core/package-manager.ts on through-line.
| CP | 主题 | 教学 artifact | 生产映射 |
|---|---|---|---|
| cp 12 | Resources + Extensions | resources-extensions | packages/coding-agent/src/core/extensions/ · resource-loader.ts |
packages/coding-agent/src/core/package-manager.ts。
packages/coding-agent/src/core/package-manager.ts.
mock StreamFn 或 ScriptedModel。
mock StreamFn or ScriptedModel.
生产含 OAuth/TUI/40+ providers。
Production adds OAuth/TUI/40+ providers.
本章主线 prompt 经过此模块时,请在 pi-mono 打开 packages/coding-agent/src/core/extensions/loader.ts,搜索 export 与测试文件中的同名场景。对照 pi-textbook 对应 checkpoint 的 workshop 测试——先让测试绿,再读生产实现。
When the through-line prompt crosses this module, open packages/coding-agent/src/core/extensions/loader.ts in pi-mono, search export and matching test scenarios. Cross-read the pi-textbook checkpoint workshop test — green test first, then production.
建议命令:cd packages/agent && npm test -- agent-loop(路径因章而异)。测试文件是行为契约的executable spec,比注释更可靠。
Suggested: cd packages/agent && npm test -- agent-loop (path varies). Tests are executable specs of behavior contracts — more reliable than comments.
用 --verbose 跑一轮主线 prompt,把 stderr event 类型与源码中的 emit 调用逐行对齐——这是本文 C26 推荐的 trace 方法。
Run the through-line with --verbose, align stderr event types with emit calls in source — the trace method recommended in C26.
parentId · leafId · fork
parentId · leafId · fork
每个会话是 JSONL 文件:parentId 链构成树,leafId 指向当前叶节点,fork 创建 parentSession 子会话。
Each session is a JSONL file: parentId chain forms tree, leafId points to current leaf, fork creates child with parentSession.
| Entry type | 用途 | LLM 可见? |
|---|---|---|
| message | user/assistant/tool | ✓ |
| compaction | 压缩摘要 | ✓(替换早期) |
| branch_summary | fork 摘要 | △ |
| model_change | 审计 | ✗ |
session.ts)· 生产映射:packages/coding-agent/src/core/session-manager.ts
📘 pi-textbook checkpoint 10: Session Tree (session.ts) · production: packages/coding-agent/src/core/session-manager.ts
| CP | 主题 | 教学 artifact | 生产映射 |
|---|---|---|---|
| cp 10 | Session Tree | session.ts | packages/coding-agent/src/core/session-manager.ts |
用户输入「读取 README.md,用一句话告诉我这个项目做什么」后,packages/coding-agent/src/core/session-manager.ts 定义了此阶段的类型契约与副作用边界。
After «read README.md and tell me what this project does in one sentence», packages/coding-agent/src/core/session-manager.ts defines type contracts and side-effect boundaries here.
在 pi-mono 仓库打开该文件,搜索 export 与 AgentEvent——这是比读 README 更快的定位法。
In pi-mono, search export and AgentEvent — faster than README alone.
设计原则:核心循环(agent-loop)不 import TUI;本模块通过事件与回调与上游通信。
Principle: agent-loop never imports TUI; this module talks upstream via events/callbacks.
pi-textbook 对应 checkpoint 提供剥离 OAuth/TUI 后的最小可运行切片,建议先跑测试再读生产文件。
Matching textbook checkpoint offers a minimal slice without OAuth/TUI — run its test before production file.
session-manager.ts 内的 async 函数通常是 IO 边界:磁盘(read/write JSONL)、网络(streamSimple)、或子进程(bash)。
async functions in session-manager.ts are usually IO boundaries: disk, network, or subprocess.
成功路径与错误路径都必须 emit 事件,否则 TUI 会卡在 streaming 状态、JSONL 会缺 entry。
Success and error paths must emit events or TUI stalls streaming and JSONL misses entries.
length 截断时 agent-loop.ts 的 failToolCallsFromTruncatedMessage 批量失败 tool calls,防止执行损坏参数。
On length truncation, failToolCallsFromTruncatedMessage fails all tool calls to avoid corrupted args.
对照 packages/agent/test/agent-loop.test.ts 中的同名场景测试用例。
Compare with matching scenarios in packages/agent/test/agent-loop.test.ts.
AgentLoopConfig(packages/agent/src/types.ts)列出所有注入点:convertToLlm、getSteeringMessages、prepareNextTurn 等。
AgentLoopConfig in types.ts lists injection points: convertToLlm, getSteeringMessages, prepareNextTurn, etc.
AgentSession 在构造 config 时绑定本模块实现;换模式(print/rpc/interactive)不换 agent-loop。
AgentSession binds this module when building config; modes change, agent-loop does not.
fork 项目时优先替换本模块而非复制 agent-loop——这是 Pi 社区的实际 fork 模式。
When forking, replace this module first rather than copying agent-loop — the common community pattern.
--verbose 模式下 stderr 事件类型可回溯到 packages/coding-agent/src/core/session-manager.ts 的具体行。
Under --verbose, stderr event types trace to lines in packages/coding-agent/src/core/session-manager.ts.
显式事件总线 vs 隐式回调:Pi 选择前者,事件类型在 AgentEvent union 中版本化。
Explicit event bus vs implicit callbacks: Pi chooses the former; events versioned in AgentEvent union.
扩展应 subscribe 事件而非 monkey-patch 私有方法——ExtensionRunner 提供正规 hook 面。
Extensions should subscribe to events, not monkey-patch privates — ExtensionRunner provides hooks.
JSONL CURRENT_SESSION_VERSION = 3:改 entry schema 必须 bump version 并提供迁移。
JSONL CURRENT_SESSION_VERSION = 3: schema changes need version bump and migration.
对比 Claude Code:闭源栈无法确认 compaction 触发点;Pi 的 shouldCompact 在源码中可读。
vs Claude Code: closed stack hides compaction triggers; Pi's shouldCompact is readable.
① 主线 prompt 进入本章模块时,第一个被调用的 export 函数是什么?② 它 emit 的第一个 AgentEvent 类型是什么?③ 若在此层抛错,TUI 会卡在什么状态?④ 对应的 pi-textbook checkpoint 测试命令是什么?⑤ packages/coding-agent/src/core/session-manager.ts 中哪一行最值得打断点?
① First export called when through-line enters this module? ② First AgentEvent type emitted? ③ If this layer throws, what TUI state stalls? ④ Matching pi-textbook checkpoint test command? ⑤ Best breakpoint line in packages/coding-agent/src/core/session-manager.ts?
不要一次读完整个文件。用「从测试入手」法:先 npm test -- <matching-test>,看失败断言指向哪行,再读那一行上下游 30 行。生产 pi-mono 的测试覆盖率在 agent-loop 与 session-manager 最高——优先读测试。
Don't read whole files at once. «Test-first» method: run npm test -- <matching-test>, see which assertion fails, read ±30 lines around that line. Highest test coverage in agent-loop and session-manager — read tests first.
对照本文 TRACE 盒与 --verbose stderr:每个 event type 应在源码中有且仅有一处「决策点」——若找不到,说明事件在更上层(AgentSession)或更下层(pi-ai adapter)合并发出。
Cross-check TRACE box with --verbose stderr: each event type should have exactly one «decision point» in source — if missing, event is merged upstream (AgentSession) or downstream (pi-ai adapter).
进阶:用 git blame packages/coding-agent/src/core/session-manager.ts 看最近改动——Pi 的 harness 接口仍在活跃演进,注释可能落后于 AgentLoopConfig 类型定义。类型即文档。
Advanced: git blame packages/coding-agent/src/core/session-manager.ts for recent changes — Pi harness APIs still evolve; comments may lag AgentLoopConfig types. Types are the doc.
站 22:packages/coding-agent/src/core/session-manager.ts 处理主线。
Station 22: packages/coding-agent/src/core/session-manager.ts on through-line.
| CP | 主题 | 教学 artifact | 生产映射 |
|---|---|---|---|
| cp 10 | Session Tree | session.ts | packages/coding-agent/src/core/session-manager.ts |
packages/coding-agent/src/core/session-manager.ts。
packages/coding-agent/src/core/session-manager.ts.
mock StreamFn 或 ScriptedModel。
mock StreamFn or ScriptedModel.
生产含 OAuth/TUI/40+ providers。
Production adds OAuth/TUI/40+ providers.
每条 entry 含 id、parentId、type、message 或 event 载荷。主线 prompt 的第一行 user entry 的 parentId 指向 session 根或上一 leaf;assistant toolUse 行的 parentId 指向 user entry——形成树而非链表。
Each entry has id, parentId, type, and message or event payload. First user entry's parentId points to session root or prior leaf; assistant toolUse points to user entry — a tree, not a linked list.
fork(fromEntryId) 创建新 session 文件,复制祖先链到切点,后续 append 挂在新分支上。这是 Pi 替代 sub-agent 的方式:不是进程内 spawn,而是会话树分支。
fork(fromEntryId) creates a new session file, copies ancestor chain to cut point; subsequent appends hang on the new branch. Pi's sub-agent alternative: session tree branch, not in-process spawn.
上下文压缩 · SQLite lanes
context compression · SQLite lanes
Compaction:上下文超阈值时,历史消息不动(JSONL 完整保留),但重建发给模型的 context——用 LLM 生成摘要替换早期消息。
Compaction: when context exceeds threshold, history stays in JSONL but rebuilt context for model — LLM summary replaces early messages.
agent-loop 不管 token 数;AgentSession 在 turn_end 检查 shouldCompact;扩展可在 before_compact 注入结构化摘要。
agent-loop ignores token count; AgentSession checks shouldCompact at turn_end; extensions inject structured summary in before_compact.
context.ts)· 生产映射:packages/coding-agent/src/core/compaction/
📘 pi-textbook checkpoint 11: Context Compaction (context.ts) · production: packages/coding-agent/src/core/compaction/
| CP | 主题 | 教学 artifact | 生产映射 |
|---|---|---|---|
| cp 11 | Context Compaction | context.ts | packages/coding-agent/src/core/compaction/ |
用户输入「读取 README.md,用一句话告诉我这个项目做什么」后,packages/coding-agent/src/core/compaction/index.ts 定义了此阶段的类型契约与副作用边界。
After «read README.md and tell me what this project does in one sentence», packages/coding-agent/src/core/compaction/index.ts defines type contracts and side-effect boundaries here.
在 pi-mono 仓库打开该文件,搜索 export 与 AgentEvent——这是比读 README 更快的定位法。
In pi-mono, search export and AgentEvent — faster than README alone.
设计原则:核心循环(agent-loop)不 import TUI;本模块通过事件与回调与上游通信。
Principle: agent-loop never imports TUI; this module talks upstream via events/callbacks.
pi-textbook 对应 checkpoint 提供剥离 OAuth/TUI 后的最小可运行切片,建议先跑测试再读生产文件。
Matching textbook checkpoint offers a minimal slice without OAuth/TUI — run its test before production file.
index.ts 内的 async 函数通常是 IO 边界:磁盘(read/write JSONL)、网络(streamSimple)、或子进程(bash)。
async functions in index.ts are usually IO boundaries: disk, network, or subprocess.
成功路径与错误路径都必须 emit 事件,否则 TUI 会卡在 streaming 状态、JSONL 会缺 entry。
Success and error paths must emit events or TUI stalls streaming and JSONL misses entries.
length 截断时 agent-loop.ts 的 failToolCallsFromTruncatedMessage 批量失败 tool calls,防止执行损坏参数。
On length truncation, failToolCallsFromTruncatedMessage fails all tool calls to avoid corrupted args.
对照 packages/agent/test/agent-loop.test.ts 中的同名场景测试用例。
Compare with matching scenarios in packages/agent/test/agent-loop.test.ts.
AgentLoopConfig(packages/agent/src/types.ts)列出所有注入点:convertToLlm、getSteeringMessages、prepareNextTurn 等。
AgentLoopConfig in types.ts lists injection points: convertToLlm, getSteeringMessages, prepareNextTurn, etc.
AgentSession 在构造 config 时绑定本模块实现;换模式(print/rpc/interactive)不换 agent-loop。
AgentSession binds this module when building config; modes change, agent-loop does not.
fork 项目时优先替换本模块而非复制 agent-loop——这是 Pi 社区的实际 fork 模式。
When forking, replace this module first rather than copying agent-loop — the common community pattern.
--verbose 模式下 stderr 事件类型可回溯到 packages/coding-agent/src/core/compaction/index.ts 的具体行。
Under --verbose, stderr event types trace to lines in packages/coding-agent/src/core/compaction/index.ts.
显式事件总线 vs 隐式回调:Pi 选择前者,事件类型在 AgentEvent union 中版本化。
Explicit event bus vs implicit callbacks: Pi chooses the former; events versioned in AgentEvent union.
扩展应 subscribe 事件而非 monkey-patch 私有方法——ExtensionRunner 提供正规 hook 面。
Extensions should subscribe to events, not monkey-patch privates — ExtensionRunner provides hooks.
JSONL CURRENT_SESSION_VERSION = 3:改 entry schema 必须 bump version 并提供迁移。
JSONL CURRENT_SESSION_VERSION = 3: schema changes need version bump and migration.
对比 Claude Code:闭源栈无法确认 compaction 触发点;Pi 的 shouldCompact 在源码中可读。
vs Claude Code: closed stack hides compaction triggers; Pi's shouldCompact is readable.
① 主线 prompt 进入本章模块时,第一个被调用的 export 函数是什么?② 它 emit 的第一个 AgentEvent 类型是什么?③ 若在此层抛错,TUI 会卡在什么状态?④ 对应的 pi-textbook checkpoint 测试命令是什么?⑤ packages/agent/src/agent-loop.ts 中哪一行最值得打断点?
① First export called when through-line enters this module? ② First AgentEvent type emitted? ③ If this layer throws, what TUI state stalls? ④ Matching pi-textbook checkpoint test command? ⑤ Best breakpoint line in packages/agent/src/agent-loop.ts?
不要一次读完整个文件。用「从测试入手」法:先 npm test -- <matching-test>,看失败断言指向哪行,再读那一行上下游 30 行。生产 pi-mono 的测试覆盖率在 agent-loop 与 session-manager 最高——优先读测试。
Don't read whole files at once. «Test-first» method: run npm test -- <matching-test>, see which assertion fails, read ±30 lines around that line. Highest test coverage in agent-loop and session-manager — read tests first.
对照本文 TRACE 盒与 --verbose stderr:每个 event type 应在源码中有且仅有一处「决策点」——若找不到,说明事件在更上层(AgentSession)或更下层(pi-ai adapter)合并发出。
Cross-check TRACE box with --verbose stderr: each event type should have exactly one «decision point» in source — if missing, event is merged upstream (AgentSession) or downstream (pi-ai adapter).
进阶:用 git blame packages/agent/src/agent-loop.ts 看最近改动——Pi 的 harness 接口仍在活跃演进,注释可能落后于 AgentLoopConfig 类型定义。类型即文档。
Advanced: git blame packages/agent/src/agent-loop.ts for recent changes — Pi harness APIs still evolve; comments may lag AgentLoopConfig types. Types are the doc.
站 23:packages/coding-agent/src/core/compaction/index.ts 处理主线。
Station 23: packages/coding-agent/src/core/compaction/index.ts on through-line.
| CP | 主题 | 教学 artifact | 生产映射 |
|---|---|---|---|
| cp 11 | Context Compaction | context.ts | packages/coding-agent/src/core/compaction/ |
packages/coding-agent/src/core/compaction/index.ts。
packages/coding-agent/src/core/compaction/index.ts.
mock StreamFn 或 ScriptedModel。
mock StreamFn or ScriptedModel.
生产含 OAuth/TUI/40+ providers。
Production adds OAuth/TUI/40+ providers.
本章主线 prompt 经过此模块时,请在 pi-mono 打开 packages/coding-agent/src/core/compaction/,搜索 export 与测试文件中的同名场景。对照 pi-textbook 对应 checkpoint 的 workshop 测试——先让测试绿,再读生产实现。
When the through-line prompt crosses this module, open packages/coding-agent/src/core/compaction/ in pi-mono, search export and matching test scenarios. Cross-read the pi-textbook checkpoint workshop test — green test first, then production.
建议命令:cd packages/agent && npm test -- agent-loop(路径因章而异)。测试文件是行为契约的executable spec,比注释更可靠。
Suggested: cd packages/agent && npm test -- agent-loop (path varies). Tests are executable specs of behavior contracts — more reliable than comments.
用 --verbose 跑一轮主线 prompt,把 stderr event 类型与源码中的 emit 调用逐行对齐——这是本文 C26 推荐的 trace 方法。
Run the through-line with --verbose, align stderr event types with emit calls in source — the trace method recommended in C26.
minimal harness 的设计取舍
design trade-offs of a minimal harness
对比三条路线:Claude Code(密封 CLI + MCP)、Cursor(密封 IDE + 全生态)、Pi(开源 harness + 终端 + JSONL)。
Three routes: Claude Code (sealed CLI + MCP), Cursor (sealed IDE + ecosystem), Pi (open harness + terminal + JSONL).
| 维度 | Claude Code | Cursor | Pi |
|---|---|---|---|
| 目标用户 | 开箱即用 | IDE 用户 | 想读源码的人 |
| 会话 | 专有云 | 专有 | 本地 JSONL 树 |
| 扩展 | MCP | VS Code | TS Extension API |
| 可观测 | 低 | 低 | 高(每事件可 log) |
| 主线 prompt 可 trace | ✗ | ✗ | ✓ --verbose |
Pi 牺牲:一键 MCP 市场、GUI 权限、IDE 集成。Pi 获得:fork 会话、RPC 嵌入、完整事件日志、教学友好的代码体积。
Pi sacrifices: one-click MCP, GUI permissions, IDE integration. Pi gains: fork sessions, RPC embed, full event log, teachable code size.
| 特性 | Claude Code | Cursor | Pi |
|---|---|---|---|
| MCP 工具市场 | ✓ | ✓ | ✗ (Extension API) |
| 子 agent | ✓ | △ | ✗ (session fork) |
| Plan 模式 | ✓ | ✓ | ✗ |
| JSONL 会话树 | ✗ | ✗ | ✓ |
| 源码可读 | ✗ | ✗ | ✓ |
| --verbose 事件 | ✗ | ✗ | ✓ |
| IDE 集成 | △ | ✓ | ✗ |
| OAuth 40+ 模型 | △ | △ | ✓ |
用户输入「读取 README.md,用一句话告诉我这个项目做什么」后,— 定义了此阶段的类型契约与副作用边界。
After «read README.md and tell me what this project does in one sentence», — defines type contracts and side-effect boundaries here.
在 pi-mono 仓库打开该文件,搜索 export 与 AgentEvent——这是比读 README 更快的定位法。
In pi-mono, search export and AgentEvent — faster than README alone.
设计原则:核心循环(agent-loop)不 import TUI;本模块通过事件与回调与上游通信。
Principle: agent-loop never imports TUI; this module talks upstream via events/callbacks.
pi-textbook 对应 checkpoint 提供剥离 OAuth/TUI 后的最小可运行切片,建议先跑测试再读生产文件。
Matching textbook checkpoint offers a minimal slice without OAuth/TUI — run its test before production file.
— 内的 async 函数通常是 IO 边界:磁盘(read/write JSONL)、网络(streamSimple)、或子进程(bash)。
async functions in — are usually IO boundaries: disk, network, or subprocess.
成功路径与错误路径都必须 emit 事件,否则 TUI 会卡在 streaming 状态、JSONL 会缺 entry。
Success and error paths must emit events or TUI stalls streaming and JSONL misses entries.
length 截断时 agent-loop.ts 的 failToolCallsFromTruncatedMessage 批量失败 tool calls,防止执行损坏参数。
On length truncation, failToolCallsFromTruncatedMessage fails all tool calls to avoid corrupted args.
对照 packages/agent/test/agent-loop.test.ts 中的同名场景测试用例。
Compare with matching scenarios in packages/agent/test/agent-loop.test.ts.
AgentLoopConfig(packages/agent/src/types.ts)列出所有注入点:convertToLlm、getSteeringMessages、prepareNextTurn 等。
AgentLoopConfig in types.ts lists injection points: convertToLlm, getSteeringMessages, prepareNextTurn, etc.
AgentSession 在构造 config 时绑定本模块实现;换模式(print/rpc/interactive)不换 agent-loop。
AgentSession binds this module when building config; modes change, agent-loop does not.
fork 项目时优先替换本模块而非复制 agent-loop——这是 Pi 社区的实际 fork 模式。
When forking, replace this module first rather than copying agent-loop — the common community pattern.
--verbose 模式下 stderr 事件类型可回溯到 — 的具体行。
Under --verbose, stderr event types trace to lines in —.
显式事件总线 vs 隐式回调:Pi 选择前者,事件类型在 AgentEvent union 中版本化。
Explicit event bus vs implicit callbacks: Pi chooses the former; events versioned in AgentEvent union.
扩展应 subscribe 事件而非 monkey-patch 私有方法——ExtensionRunner 提供正规 hook 面。
Extensions should subscribe to events, not monkey-patch privates — ExtensionRunner provides hooks.
JSONL CURRENT_SESSION_VERSION = 3:改 entry schema 必须 bump version 并提供迁移。
JSONL CURRENT_SESSION_VERSION = 3: schema changes need version bump and migration.
对比 Claude Code:闭源栈无法确认 compaction 触发点;Pi 的 shouldCompact 在源码中可读。
vs Claude Code: closed stack hides compaction triggers; Pi's shouldCompact is readable.
① 主线 prompt 进入本章模块时,第一个被调用的 export 函数是什么?② 它 emit 的第一个 AgentEvent 类型是什么?③ 若在此层抛错,TUI 会卡在什么状态?④ 对应的 pi-textbook checkpoint 测试命令是什么?⑤ packages/agent/src/agent-loop.ts 中哪一行最值得打断点?
① First export called when through-line enters this module? ② First AgentEvent type emitted? ③ If this layer throws, what TUI state stalls? ④ Matching pi-textbook checkpoint test command? ⑤ Best breakpoint line in packages/agent/src/agent-loop.ts?
不要一次读完整个文件。用「从测试入手」法:先 npm test -- <matching-test>,看失败断言指向哪行,再读那一行上下游 30 行。生产 pi-mono 的测试覆盖率在 agent-loop 与 session-manager 最高——优先读测试。
Don't read whole files at once. «Test-first» method: run npm test -- <matching-test>, see which assertion fails, read ±30 lines around that line. Highest test coverage in agent-loop and session-manager — read tests first.
对照本文 TRACE 盒与 --verbose stderr:每个 event type 应在源码中有且仅有一处「决策点」——若找不到,说明事件在更上层(AgentSession)或更下层(pi-ai adapter)合并发出。
Cross-check TRACE box with --verbose stderr: each event type should have exactly one «decision point» in source — if missing, event is merged upstream (AgentSession) or downstream (pi-ai adapter).
进阶:用 git blame packages/agent/src/agent-loop.ts 看最近改动——Pi 的 harness 接口仍在活跃演进,注释可能落后于 AgentLoopConfig 类型定义。类型即文档。
Advanced: git blame packages/agent/src/agent-loop.ts for recent changes — Pi harness APIs still evolve; comments may lag AgentLoopConfig types. Types are the doc.
—。
—.
mock StreamFn 或 ScriptedModel。
mock StreamFn or ScriptedModel.
生产含 OAuth/TUI/40+ providers。
Production adds OAuth/TUI/40+ providers.
本章主线 prompt 经过此模块时,请在 pi-mono 打开 packages/coding-agent/src/core/sdk.ts,搜索 export 与测试文件中的同名场景。对照 pi-textbook 对应 checkpoint 的 workshop 测试——先让测试绿,再读生产实现。
When the through-line prompt crosses this module, open packages/coding-agent/src/core/sdk.ts in pi-mono, search export and matching test scenarios. Cross-read the pi-textbook checkpoint workshop test — green test first, then production.
建议命令:cd packages/agent && npm test -- agent-loop(路径因章而异)。测试文件是行为契约的executable spec,比注释更可靠。
Suggested: cd packages/agent && npm test -- agent-loop (path varies). Tests are executable specs of behavior contracts — more reliable than comments.
用 --verbose 跑一轮主线 prompt,把 stderr event 类型与源码中的 emit 调用逐行对齐——这是本文 C26 推荐的 trace 方法。
Run the through-line with --verbose, align stderr event types with emit calls in source — the trace method recommended in C26.
JSONL 进程协议 · CBOR 远程会话
JSONL process protocol · CBOR remote sessions
--mode rpc 把 AgentSession 变成 JSONL 帧协议:stdin 收命令,stdout 吐事件。远程场景用 pi-server + CBOR。
--mode rpc turns AgentSession into JSONL frame protocol: stdin commands, stdout events. Remote uses pi-server + CBOR.
| 模式 | 传输 | 用例 |
|---|---|---|
| rpc | JSONL stdin/out | CI · 脚本 |
| pi-server | HTTP + CBOR | 远程 harness |
| interactive | TUI | 日常开发 |
packages/protocol/src/schemas.ts:PROTOCOL_VERSION = 1;SessionPhaseSchema 对齐 AgentHarnessPhase(idle/turn/compaction/branch_summary/retry)。
schemas.ts: PROTOCOL_VERSION = 1; SessionPhaseSchema aligns with AgentHarnessPhase.
ModelRefSchema、ThinkingLevelSchema、JsonValueSchema 用 Type.Strict 禁止 additionalProperties。
ModelRefSchema, ThinkingLevelSchema, JsonValueSchema use strict objects.
帧格式在 framing.ts + codec.ts:长度前缀 JSONL 或 CBOR 二进制。
Frame format in framing.ts + codec.ts: length-prefixed JSONL or CBOR binary.
main.ts 解析 --mode rpc → rpc-entry.ts 读 stdin JSONL 命令 → 调用 AgentSession 方法 → 写 stdout 响应。
main.ts --mode rpc → rpc-entry.ts reads stdin JSONL commands → AgentSession methods → stdout responses.
主线 prompt 可作为 rpc prompt 命令注入,无需 TUI。
Through-line injectable as rpc prompt command without TUI.
适合 CI、外部 IDE 插件、多 pi 实例编排。
Suits CI, external IDE plugins, multi-pi orchestration.
packages/server CBOR over HTTP 暴露远程 AgentSession;client 包 acquireSession 获取 lease。
server package exposes remote AgentSession via CBOR HTTP; client acquireSession gets lease.
exclusive vs shared lease 防止并发写(client README 30 行)。
exclusive vs shared lease prevents concurrent writes (client README line 30).
RPC + fork:父进程 rpc fork 命令创建 branch session,子 pi 实例处理子任务——替代 sub-agent 的官方路径之一。
RPC + fork: parent rpc fork creates branch session, child pi handles subtask — official sub-agent alternative.
管道 stdin 发送 prompt 命令 + pi --mode rpc --verbose 可无 TUI 观察完整事件 JSONL。
Pipe stdin prompt command + pi --mode rpc --verbose observes full event JSONL without TUI.
对比 interactive:同一 AgentSession.prompt() 路径,仅 I/O 层从 TUI 换为 stdin/stdout 帧。
vs interactive: same AgentSession.prompt() path, only I/O layer swaps TUI for stdin/stdout frames.
packages/coding-agent/src/rpc-entry.ts 解析每行 JSON 命令:prompt、abort、set_model、fork 等,映射到 AgentSession 公开方法。
rpc-entry.ts parses each JSON line command: prompt, abort, set_model, fork, etc., mapping to AgentSession public methods.
响应帧同样 JSONL 序列化 AgentEvent,外部编排器可重建与 --verbose 等价的日志。
Response frames serialize AgentEvent as JSONL; external orchestrator can rebuild --verbose-equivalent logs.
① 主线 prompt 进入本章模块时,第一个被调用的 export 函数是什么?② 它 emit 的第一个 AgentEvent 类型是什么?③ 若在此层抛错,TUI 会卡在什么状态?④ 对应的 pi-textbook checkpoint 测试命令是什么?⑤ packages/agent/src/agent-loop.ts 中哪一行最值得打断点?
① First export called when through-line enters this module? ② First AgentEvent type emitted? ③ If this layer throws, what TUI state stalls? ④ Matching pi-textbook checkpoint test command? ⑤ Best breakpoint line in packages/agent/src/agent-loop.ts?
不要一次读完整个文件。用「从测试入手」法:先 npm test -- <matching-test>,看失败断言指向哪行,再读那一行上下游 30 行。生产 pi-mono 的测试覆盖率在 agent-loop 与 session-manager 最高——优先读测试。
Don't read whole files at once. «Test-first» method: run npm test -- <matching-test>, see which assertion fails, read ±30 lines around that line. Highest test coverage in agent-loop and session-manager — read tests first.
对照本文 TRACE 盒与 --verbose stderr:每个 event type 应在源码中有且仅有一处「决策点」——若找不到,说明事件在更上层(AgentSession)或更下层(pi-ai adapter)合并发出。
Cross-check TRACE box with --verbose stderr: each event type should have exactly one «decision point» in source — if missing, event is merged upstream (AgentSession) or downstream (pi-ai adapter).
进阶:用 git blame packages/agent/src/agent-loop.ts 看最近改动——Pi 的 harness 接口仍在活跃演进,注释可能落后于 AgentLoopConfig 类型定义。类型即文档。
Advanced: git blame packages/agent/src/agent-loop.ts for recent changes — Pi harness APIs still evolve; comments may lag AgentLoopConfig types. Types are the doc.
站 21:RPC JSONL 帧或 CBOR 远程驱动 AgentSession。
Station 21: RPC JSONL frames or CBOR remote drives AgentSession.
| CP | 主题 | 教学 artifact | 生产映射 |
|---|---|---|---|
| cp 13 | Composition Root | composition-root | packages/coding-agent/src/core/agent-session.ts · sdk.ts |
是,同一类不同 I/O 层。
Yes, same class, different I/O layer.
pi-server 远程;本地 RPC 用 JSONL。
pi-server remote; local RPC uses JSONL.
管道 stdin/stdout + 断言响应帧序列。
Pipe stdin/stdout + assert response frame sequence.
本章主线 prompt 经过此模块时,请在 pi-mono 打开 packages/protocol/src/,搜索 export 与测试文件中的同名场景。对照 pi-textbook 对应 checkpoint 的 workshop 测试——先让测试绿,再读生产实现。
When the through-line prompt crosses this module, open packages/protocol/src/ in pi-mono, search export and matching test scenarios. Cross-read the pi-textbook checkpoint workshop test — green test first, then production.
建议命令:cd packages/agent && npm test -- agent-loop(路径因章而异)。测试文件是行为契约的executable spec,比注释更可靠。
Suggested: cd packages/agent && npm test -- agent-loop (path varies). Tests are executable specs of behavior contracts — more reliable than comments.
用 --verbose 跑一轮主线 prompt,把 stderr event 类型与源码中的 emit 调用逐行对齐——这是本文 C26 推荐的 trace 方法。
Run the through-line with --verbose, align stderr event types with emit calls in source — the trace method recommended in C26.
--verbose · event log · session file
--verbose · event log · session file
自己 trace 主线 prompt 的三件套:pi --verbose 看 AgentEvent、打开 JSONL 会话文件、对照 pi-textbook workshop 测试。
Trace the through-line yourself: pi --verbose for AgentEvents, open JSONL session file, compare pi-textbook workshop tests.
1. pi-textbook checkpoint 00 七里程碑 → 2. pi-mono agent-loop.ts runLoop → 3. agent-session.ts prompt() → 4. 本文 C02 22 站地图。
1. pi-textbook cp 00 seven milestones → 2. pi-mono agent-loop.ts runLoop → 3. agent-session.ts prompt() → 4. this article C02 22-station map.
读完之后,用一句话回答:
「读取 README.md,用一句话告诉我这个项目做什么」——
在 Pi 里,这句话究竟触发了什么? After reading, answer in one sentence:
«read README.md and tell me what this project does in one sentence» —
what exactly does that line trigger in Pi? Field Note · 10
用户输入「读取 README.md,用一句话告诉我这个项目做什么」后,packages/coding-agent/src/main.ts 定义了此阶段的类型契约与副作用边界。
After «read README.md and tell me what this project does in one sentence», packages/coding-agent/src/main.ts defines type contracts and side-effect boundaries here.
在 pi-mono 仓库打开该文件,搜索 export 与 AgentEvent——这是比读 README 更快的定位法。
In pi-mono, search export and AgentEvent — faster than README alone.
设计原则:核心循环(agent-loop)不 import TUI;本模块通过事件与回调与上游通信。
Principle: agent-loop never imports TUI; this module talks upstream via events/callbacks.
pi-textbook 对应 checkpoint 提供剥离 OAuth/TUI 后的最小可运行切片,建议先跑测试再读生产文件。
Matching textbook checkpoint offers a minimal slice without OAuth/TUI — run its test before production file.
main.ts 内的 async 函数通常是 IO 边界:磁盘(read/write JSONL)、网络(streamSimple)、或子进程(bash)。
async functions in main.ts are usually IO boundaries: disk, network, or subprocess.
成功路径与错误路径都必须 emit 事件,否则 TUI 会卡在 streaming 状态、JSONL 会缺 entry。
Success and error paths must emit events or TUI stalls streaming and JSONL misses entries.
length 截断时 agent-loop.ts 的 failToolCallsFromTruncatedMessage 批量失败 tool calls,防止执行损坏参数。
On length truncation, failToolCallsFromTruncatedMessage fails all tool calls to avoid corrupted args.
对照 packages/agent/test/agent-loop.test.ts 中的同名场景测试用例。
Compare with matching scenarios in packages/agent/test/agent-loop.test.ts.
AgentLoopConfig(packages/agent/src/types.ts)列出所有注入点:convertToLlm、getSteeringMessages、prepareNextTurn 等。
AgentLoopConfig in types.ts lists injection points: convertToLlm, getSteeringMessages, prepareNextTurn, etc.
AgentSession 在构造 config 时绑定本模块实现;换模式(print/rpc/interactive)不换 agent-loop。
AgentSession binds this module when building config; modes change, agent-loop does not.
fork 项目时优先替换本模块而非复制 agent-loop——这是 Pi 社区的实际 fork 模式。
When forking, replace this module first rather than copying agent-loop — the common community pattern.
--verbose 模式下 stderr 事件类型可回溯到 packages/coding-agent/src/main.ts 的具体行。
Under --verbose, stderr event types trace to lines in packages/coding-agent/src/main.ts.
显式事件总线 vs 隐式回调:Pi 选择前者,事件类型在 AgentEvent union 中版本化。
Explicit event bus vs implicit callbacks: Pi chooses the former; events versioned in AgentEvent union.
扩展应 subscribe 事件而非 monkey-patch 私有方法——ExtensionRunner 提供正规 hook 面。
Extensions should subscribe to events, not monkey-patch privates — ExtensionRunner provides hooks.
JSONL CURRENT_SESSION_VERSION = 3:改 entry schema 必须 bump version 并提供迁移。
JSONL CURRENT_SESSION_VERSION = 3: schema changes need version bump and migration.
对比 Claude Code:闭源栈无法确认 compaction 触发点;Pi 的 shouldCompact 在源码中可读。
vs Claude Code: closed stack hides compaction triggers; Pi's shouldCompact is readable.
① 主线 prompt 进入本章模块时,第一个被调用的 export 函数是什么?② 它 emit 的第一个 AgentEvent 类型是什么?③ 若在此层抛错,TUI 会卡在什么状态?④ 对应的 pi-textbook checkpoint 测试命令是什么?⑤ packages/agent/src/agent-loop.ts 中哪一行最值得打断点?
① First export called when through-line enters this module? ② First AgentEvent type emitted? ③ If this layer throws, what TUI state stalls? ④ Matching pi-textbook checkpoint test command? ⑤ Best breakpoint line in packages/agent/src/agent-loop.ts?
不要一次读完整个文件。用「从测试入手」法:先 npm test -- <matching-test>,看失败断言指向哪行,再读那一行上下游 30 行。生产 pi-mono 的测试覆盖率在 agent-loop 与 session-manager 最高——优先读测试。
Don't read whole files at once. «Test-first» method: run npm test -- <matching-test>, see which assertion fails, read ±30 lines around that line. Highest test coverage in agent-loop and session-manager — read tests first.
对照本文 TRACE 盒与 --verbose stderr:每个 event type 应在源码中有且仅有一处「决策点」——若找不到,说明事件在更上层(AgentSession)或更下层(pi-ai adapter)合并发出。
Cross-check TRACE box with --verbose stderr: each event type should have exactly one «decision point» in source — if missing, event is merged upstream (AgentSession) or downstream (pi-ai adapter).
进阶:用 git blame packages/agent/src/agent-loop.ts 看最近改动——Pi 的 harness 接口仍在活跃演进,注释可能落后于 AgentLoopConfig 类型定义。类型即文档。
Advanced: git blame packages/agent/src/agent-loop.ts for recent changes — Pi harness APIs still evolve; comments may lag AgentLoopConfig types. Types are the doc.
站 26:packages/coding-agent/src/main.ts 处理主线。
Station 26: packages/coding-agent/src/main.ts on through-line.
| CP | 主题 | 教学 artifact | 生产映射 |
|---|---|---|---|
| cp 00 | 一次 README 读取的七个里程碑 | prologue.ts | packages/coding-agent/src/main.ts · packages/agent/src/agent-loop.ts |
| cp 14 | Eval Capstone | eval-capstone | packages/evals/ |
packages/coding-agent/src/main.ts。
packages/coding-agent/src/main.ts.
mock StreamFn 或 ScriptedModel。
mock StreamFn or ScriptedModel.
生产含 OAuth/TUI/40+ providers。
Production adds OAuth/TUI/40+ providers.
本章主线 prompt 经过此模块时,请在 pi-mono 打开 packages/coding-agent/src/cli.ts,搜索 export 与测试文件中的同名场景。对照 pi-textbook 对应 checkpoint 的 workshop 测试——先让测试绿,再读生产实现。
When the through-line prompt crosses this module, open packages/coding-agent/src/cli.ts in pi-mono, search export and matching test scenarios. Cross-read the pi-textbook checkpoint workshop test — green test first, then production.
建议命令:cd packages/agent && npm test -- agent-loop(路径因章而异)。测试文件是行为契约的executable spec,比注释更可靠。
Suggested: cd packages/agent && npm test -- agent-loop (path varies). Tests are executable specs of behavior contracts — more reliable than comments.
用 --verbose 跑一轮主线 prompt,把 stderr event 类型与源码中的 emit 调用逐行对齐——这是本文 C26 推荐的 trace 方法。
Run the through-line with --verbose, align stderr event types with emit calls in source — the trace method recommended in C26.