
XYDT
Co-Claw
随着 AI Agent 从对话迈向能够执行代码、操作文件、调用工具的自主智能体,Agent 框架正陷入过度复杂化的困境:动辄上万 token 的系统提示词、数十个内置工具、难以观测的子 Agent 黑盒,复杂度在堆叠,可观测性却在消失。如何构建一个既足够强大,又能被开发者完全理解和掌控的 Agent 运行时?
本文将从 Pi Agent 的极简主义设计哲学出发,深度拆解其不足千行代码的核心运行时、双层循环的精密控制流、以及三层嵌套的生命周期事件系统;揭示如何统一 LLM 消息与自定义事件,双队列机制如何优雅处理用户干预,以及不做什么为何比做什么更重要。通过对比 Claude Code、Codex 等主流框架的设计选择,带你全景俯瞰一个回归本质的 Agent 架构实践。

OpenClaw并没有自己构造一个agent loop,相反它把Agent相关的事情都交给了Pi Agent,或者说OpenClaw整体来看就是Pi Agent套壳,Pi Agent对外暴露的包是:
@mariozechner/pi-coding-agent(高级SDK)
@mariozechner/pi-agent-core (Agent Runtime)
@mariozechner/pi-ai (LLM API)
Pi Agent是个非常简洁的Code Agent,只有Read, Edit, Write, Bash四个基础工具,并且Bash工具还被OpenClaw给替换了,其他工具都是在OpenClaw中提供的。



https://github.com/badlogic/pi-mono/tree/main
一共包含7个package:


可以看到其Agent Loop 仍为传统的ReAct 模式

默认:read, bash, edit, write(4个)
可选:grep, find, ls(3个)
总计:7个内置工具
可扩展:通过扩展系统添加自定义工具

核心哲学:"An autonomous agent is just an LLM + tools + a loop." ——Mario Zechner
在当前 Agent 框架生态中,大多数项目在做加法:更多工具、更长提示词、更复杂的规划链、更多子 Agent。Pi 的创作者 Mario Zechner 认为这是一条弯路。他的核心论点是:
"前沿模型已经被 RL 训练得足够理解'编码 Agent'是什么。你不需要10,000 token的系统提示词。"

pi-agent-core 的全部源码只有 5 个文件、约 1,500 行代码:

这是整个设计最精妙的抽象:
// 空接口 —— 通过 Declaration Merging 扩展
export interface CustomAgentMessages {
// Empty by default - apps extend via declaration merging
}
// AgentMessage = LLM 消息 + 自定义消息
export type AgentMessage = Message | CustomAgentMessages[keyof CustomAgentMessages]
为什么不直接用 LLM 的 Message 类型? 因为真实应用中存在大量"非 LLM 消息":

应用层通过 Declaration Merging 扩展自定义消息类型,编译器自动推断联合类型。内部所有逻辑(压缩、分支、UI 渲染)都基于 AgentMessage 运转,仅在调用 LLM 的瞬间才通过 convertToLlm() 过滤为 Message[]。
这就是 Zechner 所称的"最晚转换"(late conversion)策略。
3.2 AgentEvent — 细粒度的生命周期事件export type AgentEvent =
| { type: "agent_start" }
| { type: "agent_end"; messages: AgentMessage[] }
| { type: "turn_start" }
| { type: "turn_end"; message: AgentMessage; toolResults: ToolResultMessage[] }
| { type: "message_start"; message: AgentMessage }
| { type: "message_update"; message: AgentMessage; assistantMessageEvent: AssistantMessageEvent }
| { type: "message_end"; message: AgentMessage }
| { type: "tool_execution_start"; toolCallId: string; toolName: string; args: any }
| { type: "tool_execution_update"; toolCallId: string; toolName: string; args: any; partialResult: any }
| { type: "tool_execution_end"; toolCallId: string; toolName: string; result: any; isError: boolean };
export type AgentEvent =
| { type: "agent_start" }
| { type: "agent_end"; messages: AgentMessage[] }
| { type: "turn_start" }
| { type: "turn_end"; message: AgentMessage; toolResults: ToolResultMessage[] }
| { type: "message_start"; message: AgentMessage }
| { type: "message_update"; message: AgentMessage; assistantMessageEvent: AssistantMessageEvent }
| { type: "message_end"; message: AgentMessage }
| { type: "tool_execution_start"; toolCallId: string; toolName: string; args: any }
| { type: "tool_execution_update"; toolCallId: string; toolName: string; args: any; partialResult: any }
| { type: "tool_execution_end"; toolCallId: string; toolName: string; result: any; isError: boolean };
三层嵌套的生命周期:Agent > Turn > Message/Tool。每一层都有 start/end 事件,构成完整的可观测性——这正是 Zechner 反复强调的:
"Claude Code 的 Plan Mode 会 spawn 一个子 Agent,你对子 Agent 内部的运作零可见性。这是黑盒中的黑盒。"
4.1 入口:agentLoop vs agentLoopContinue
两个入口函数体现了一个重要区分:

agentLoopContinue 的存在让 重试 变得优雅:出错后不需要重新构造 prompt,直接 continue() 即可从当前上下文继续。
4.2 双层循环结构
这是 runLoop() 的完整控制流——它是理解 Pi Agent 的关键:

// Steering mode: "all" = 一次性发送全部 | "one-at-a-time" = 每次只发一条
private steeringMode: "all" | "one-at-a-time";
private followUpMode: "all" | "one-at-a-time";
为什么需要 one-at-a-time? 考虑这个场景:用户在 Agent 工作时快速发送了 3 条修正。one-at-a-time 模式让 Agent 逐条处理,每条都能得到充分响应,而不是一股脑收到 3 条然后只回应最后一条。
5.2 prompt() vs continue() vs steer() vs followUp()



ProxyAssistantMessageEvent 比原始 AssistantMessageEvent 轻得多——没有 partial 字段,仅包含 contentIndex + delta 等最小信息。客户端通过 processProxyEvent() 逐步重建完整消息。
7.1 "不做什么"比"做什么"更重要

7.2 核心设计原则

7.3 写给开发者:何时借鉴 Pi 的设计
适合的场景:
你的 Agent 需要高可观测性 → 学习 Pi 的三层事件系统
你在构建编码/CLI Agent → 学习"4 工具 + bash"的极简策略
你需要跨 Provider 会话迁移 → 学习 convertToLlm的最晚转换模式
你需要用户中途干预 → 学习 Steering/FollowUp 双队列机制
你需要保持内置工具的简单,必要,→ 学习减少内置工具,外部工具可插拔,让Agent自己写工具
不适合的场景:
你需要复杂的多 Agent 编排 → Pi 的设计理念与此相悖
你需要严格的安全沙箱 → Pi 明确选择了"YOLO by default"
不迷信MCP→把 MCP 调用暴露为 CLI 接口的工具。够用就行,不需要内置到核心。



服务邮箱:XYDT@xydigit.com

XYDT
关注我们
微信号|XYDigit
夜雨聆风