从阶段二 trace 实践进入 Pi 源码,沿一次真实 turn 理解 message state、tool call loop、retry、abort、wait idle 和 compaction 触发点。
Agent Loop 是 Pi runtime 的心脏:它把用户 prompt 扩展成模型响应、流式更新、工具执行、工具结果回填、下一轮模型调用、结束或继续的循环。

当 assistant message 里出现 tool call 时,Pi 为什么会继续请求模型,而不是直接结束?
- transcript state 保存已完成消息和当前 streaming message。
- turn lifecycle 把一次 assistant response 和它触发的 tool results 视作一轮。
- tool-call loop 执行工具并把结果回填上下文。
- session-level policy 在外层处理 retry、abort、compaction 和持久化。
源码推演(省略版)
Section titled “源码推演(省略版)”下面不是完整源码,而是把本课主线压缩成可以在文档里直接阅读的关键形状。读者即使不打开本地源码,也应该能看出运行时如何组织职责。
async function runLoop(context: Message[]) { while (true) { const assistant = await streamAssistantResponse(context); context.push(assistant.message);
if (assistant.toolCalls.length === 0) break;
const results = await executeToolCalls(assistant.toolCalls); context.push(...results.map(toToolResultMessage));
if (shouldStopAfterTools(results)) break; }}- 这段代码补足了原文只列路径时缺失的主循环。
- 关键点是
context.push(...toolResults):下一次模型请求能看到工具结果,所以它可以继续解释、修复或调用下一个工具。 - abort 和 retry 不应该随意塞进内层循环;它们更适合作为 session policy 包住这条主线。
根据省略版循环,标出 message_update、tool_execution_start、tool_execution_end、turn_end 应该在哪些位置发出。
- 能解释一次 turn 为什么包含 assistant message 和 tool results。
- 能说明工具结果如何进入下一次模型请求。
- 能区分低层 loop 和 session policy 的职责。
- 实践题:基于本课的省略版源码,补出一个最小实现草图,要求写清输入、输出、副作用和错误处理。
- 思考题:本课机制如果只靠 prompt 约束,而不放进 harness 或 runtime,会出现什么工程风险?