oh-my-pi 全景代码解析(三):pi-agent-core 运行时与状态机
系列导读:本文深入 Pi 架构的心脏——
pi-agent-core包。这个包实现了 Agent 的核心请求-响应循环(Request-Response Loop),负责管理 Agent 的状态、工具调用生命周期、权限决策、以及与上下层的交互。我们将从核心循环的伪代码出发,逐步剖析状态机设计、权限门机制、工具注册系统、事件总线、以及无子 Agent 的设计哲学。
一、核心循环:Agent 的"心跳"
Agent 的核心循环是其全部行为的源泉。这个循环不是简单的"发送请求-接收响应",而是一个复杂的状态机驱动的异步生成器,需要处理流式输出、工具调用、用户中断、权限确认、上下文压缩等多种并发事件。
1.1 循环的整体结构
graph TD
subgraph "Agent Loop Phases"
A[Start Turn] --> B[Build Context]
B --> C[Check Compaction]
C -->|If needed| D[Compact Context]
C -->|No need| E[Call LLM]
D --> E
E --> F[Process Stream]
F --> G{Tool Calls?}
G -->|Yes| H[Check Permission]
H -->|Denied| I[Inject Denial]
H -->|Ask| J[Wait User Approval]
J -->|Approved| K[Execute Tool]
J -->|Rejected| I
H -->|Allowed| K
K --> L[Inject Result]
L --> M{More Tools?}
M -->|Yes| H
M -->|No| N[Turn Complete]
G -->|No| N
N --> O{User Input?}
O -->|Message| P[Append to Context]
O -->|Steering| Q[Interrupt + Append]
O -->|Follow-up| R[Queue for Next Turn]
O -->|None| S[End Session]
P --> A
Q --> A
R --> A
end核心循环使用异步生成器实现双向通信:
async function* runAgentLoop(config, context) {
while (!state.isInterrupted) {
const turnContext = await buildTurnContext(context);
const stream = await streamSimple(model, turnContext);
for await (const event of stream) {
yield event; // To UI layer
// Receive user input via yield return
}
}
}1.2 五阶段结构
循环被明确分为五个阶段,每个阶段都有明确的 yield 事件:
sequenceDiagram
participant User
participant UI as pi-tui
participant Agent as pi-agent-core
participant AI as pi-ai
participant Ext as Extension
User->>UI: Send message
UI->>Agent: runAgentLoop()
Agent->>Ext: onBeforeTurn()
Ext-->>Agent: Injected memories
Agent->>AI: streamSimple()
AI-->>Agent: text_delta
Agent-->>UI: yield text_delta
UI-->>User: Display text
AI-->>Agent: tool_call_start
Agent->>Ext: onCheckPermission()
Ext-->>Agent: ask
Agent-->>UI: yield permission_request
UI-->>User: Show confirm dialog
User-->>UI: Approve
UI-->>Agent: return approval
Agent->>Ext: onBeforeToolUse()
Agent->>Tool: execute()
Tool-->>Agent: result
Agent->>Ext: onAfterToolUse()
Agent-->>UI: yield tool_result
UI-->>User: Display result
AI-->>Agent: done
Agent-->>UI: yield turn_complete二、状态机:Agent 的"记忆"
Agent 的状态不是简单的"当前消息列表",而是一个包含丰富元数据的状态机。
2.1 AgentState 数据结构
classDiagram
class AgentState {
+Context context
+number turnCount
+number totalCost
+number totalTokens
+Map pendingToolCalls
+boolean isInterrupted
+Model currentModel
+string sessionPath
+string currentNodeId
+string[] branchStack
+Map lastToolResults
+Map fileReadCache
+Map extensionStates
}
class Context {
+string systemPrompt
+Message[] messages
+number temperature
+number maxTokens
}
class SessionNode {
+string id
+string parentId
+NodeType type
+string role
+string content
+NodeMetadata metadata
+string[] children
+string[] bookmarks
}
AgentState --> Context
AgentState --> SessionNode2.2 状态转换图
stateDiagram-v2
[*] --> Idle
Idle --> BuildingContext: User message
BuildingContext --> CallingLLM: Context ready
CallingLLM --> ProcessingStream: Stream started
ProcessingStream --> CheckingPermission: Tool call
ProcessingStream --> WaitingForInput: No tools
CheckingPermission --> WaitingForApproval: Ask
CheckingPermission --> ExecutingTool: Allow
CheckingPermission --> ProcessingStream: Deny
WaitingForApproval --> ExecutingTool: Approve
WaitingForApproval --> ProcessingStream: Reject
ExecutingTool --> ProcessingStream: More tools
ExecutingTool --> TurnComplete: All done
TurnComplete --> Idle: Follow-up
TurnComplete --> Idle: Steering
TurnComplete --> [*]: End三、权限门:安全与自由的平衡
Pi 的权限系统是一个经典的默认开放、可选收紧设计。默认 YOLO 模式——所有工具调用自动执行。
3.1 权限决策流程
graph TD
A[Tool Call Request] --> B{YOLO Mode?}
B -->|Yes| C[Allow]
B -->|No| D{Whitelist?}
D -->|Not in list| E[Deny]
D -->|In list| F{Extension Rules?}
F -->|Deny| E
F -->|Ask| G[Ask User]
F -->|Allow| H{Tool Rules?}
H -->|Match| I[Apply Rule Action]
H -->|No match| J[Default Policy]
J -->|Allow| C
J -->|Ask| G
J -->|Deny| E
G -->|Approve| C
G -->|Reject| E核心代码:
async function checkToolPermission(
toolName: string,
args: Record<string, unknown>,
config: AgentConfig,
extensions: Extension[]
): Promise<"allow" | "deny" | "ask"> {
if (config.yoloMode) return "allow";
if (config.allowedTools && !config.allowedTools.includes(toolName)) {
return "deny";
}
for (const ext of extensions) {
const decision = await ext.onCheckPermission?.({ toolName, args, config });
if (decision === "deny") return "deny";
if (decision === "ask") return "ask";
}
return config.defaultPermission || "ask";
}3.2 为什么默认 YOLO?
Mario Zechner 的哲学:
"Pi 默认不询问权限,因为编码 Agent 的核心价值在于流畅性。如果每次文件读取、每次 grep 搜索都需要确认,Agent 的实用性会大幅下降。"
这与 Unix 的设计一致:默认给予用户完全的自由,但提供强大的机制让用户按需收紧。
四、工具注册系统:从声明到执行
Pi 的工具系统不是静态的函数列表,而是一个动态的、可扩展的注册表。
4.1 工具注册表架构
graph TB
subgraph "Tool Registry"
R[Map string, ToolDefinition]
end
subgraph "Built-in Tools"
T1[read]
T2[write]
T3[edit]
T4[bash]
end
subgraph "Optional Tools (Rust)"
T5[grep]
T6[find]
T7[ls]
end
subgraph "Extension Tools"
T8[deploy]
T9[test]
T10[lint]
end
T1 -->|register| R
T2 -->|register| R
T3 -->|register| R
T4 -->|register| R
T5 -->|register| R
T6 -->|register| R
T7 -->|register| R
T8 -->|register| R
T9 -->|register| R
T10 -->|register| R
R -->|toLLMTools| LLM[LLM Context]
R -->|execute| Exec[Tool Execution]4.2 工具定义接口
interface ToolDefinition {
name: string;
description: string;
parameters: JSONSchema;
execute: (args, ctx) => Promise<unknown>;
checkPermission?: (args, ctx) => Promise<"allow" | "deny" | "ask">;
postProcess?: (result, ctx) => Promise<unknown>;
}五、事件总线:扩展的神经系统
Agent 核心循环通过事件总线与扩展通信。这是一个有序、可拦截、可修改的事件处理系统。
5.1 事件类型全景
graph LR
subgraph "Agent Events"
E1[phase]
E2[text_delta]
E3[thinking_delta]
E4[tool_call_start]
E5[tool_call_delta]
E6[tool_call_complete]
E7[usage]
E8[error]
E9[tool_executing]
E10[tool_result]
E11[tool_error]
E12[tool_denied]
E13[permission_request]
E14[compaction]
E15[turn_complete]
E16[session_end]
E17[interrupted]
E18[retry]
E19[wait_for_input]
end5.2 扩展事件处理顺序
sequenceDiagram
participant Agent
participant Ext1 as Extension 1
participant Ext2 as Extension 2
participant Ext3 as Extension 3
Agent->>Ext1: onBeforeTurn()
Ext1-->>Agent: messages
Agent->>Ext2: onBeforeTurn()
Ext2-->>Agent: messages
Agent->>Ext3: onBeforeTurn()
Ext3-->>Agent: messages
Agent->>Ext1: onCheckPermission()
Ext1-->>Agent: allow
Agent->>Ext2: onCheckPermission()
Ext2-->>Agent: ask
Note over Agent: Stop at first ask/deny六、无子 Agent 的设计哲学
Pi 最引人注目的架构决策之一是不内置子 Agent 工具。
6.1 为什么不要内置子 Agent?
Mario Zechner 的解释:
"子 Agent 是黑盒。当 Claude Code 启动一个子 Agent 时,你对其内部行为零可见性。它读取了哪些文件?它做了什么决策?它犯了什么错误?如果子 Agent 失败,调试是痛苦的。"
6.2 替代方案:bash 启动新进程
graph TB
subgraph "Claude Code Approach"
A1[Task Tool] --> A2[Sub-Agent 1]
A1 --> A3[Sub-Agent 2]
A1 --> A4[Sub-Agent 3]
A2 --> A5[Black Box Execution]
A3 --> A5
A4 --> A5
end
subgraph "Pi Approach"
B1[bash Tool] --> B2[spawn pi -p "task"]
B2 --> B3[New Process in tmux]
B3 --> B4[Fully Observable]
B3 --> B5[Interactable]
B3 --> B6[Debuggable]
end6.3 上下文收集模式
graph LR
subgraph "Session 1: Context Gathering"
S1A[Analyze codebase] --> S1B[Save CONTEXT.md]
end
subgraph "Session 2: Implementation"
S2A[Read CONTEXT.md] --> S2B[Implement feature]
end
S1B -->|Artifact| S2A七、错误处理与恢复策略
Agent 的错误处理不是简单的 try-catch,而是一个分层的、可恢复的策略系统。
7.1 错误分类与恢复策略
graph TD
A[Error Occurred] --> B{Classify Error}
B -->|LLM Error| C{Retryable?}
C -->|Yes| D[Exponential Backoff Retry]
C -->|No| E[Fallback Model]
B -->|Context Error| F{Context Limit?}
F -->|Yes| G[Compact Context]
G --> H[Retry]
B -->|Tool Error| I{Tool-specific?}
I -->|Yes| J[Tool PostProcess]
I -->|No| K[Inject Error to Context]
B -->|Permission Error| L[Inject Denial]
B -->|Config Error| M[Ask User]
D -->|Max retries| E
E -->|No fallback| N[Abort Session]
H -->|Still fails| N
J -->|Fixed| H
K --> H
L --> H
M --> H八、性能优化:Agent 循环的微观优化
8.1 消息历史的增量更新
使用链表结构而非数组,追加操作是 O(1):
interface MessageNode {
message: Message;
next: MessageNode | null;
prev: MessageNode | null;
}
function appendMessage(list, message) {
const newNode = { message, next: null, prev: list.tail };
if (list.tail) list.tail.next = newNode;
else list.head = newNode;
list.tail = newNode;
list.length++;
}8.2 Token 计数缓存
function estimateTokens(context) {
let total = countTokens(context.systemPrompt);
for (const msg of context.messages) {
if (msg._tokenCount && msg._tokenCountModel === context.model.id) {
total += msg._tokenCount; // Cache hit
} else {
const count = countTokens(msg.content);
msg._tokenCount = count;
msg._tokenCountModel = context.model.id;
total += count;
}
}
return total;
}九、设计原则总结
mindmap
root((pi-agent-core 原则))
生成器优先
双向通信
yield 事件
yield return 输入
阶段透明
每阶段 yield
UI 可显示状态
扩展可拦截
默认开放
YOLO 模式
权限扩展 opt-in
无子 Agent
bash 启动新进程
tmux 可观测
上下文收集模式
持久化状态
显式可序列化
可恢复
错误可恢复
分层策略
自动重试
降级
压缩
用户介入pi-agent-core 是 Agent"思考"和"行动"的引擎室。它的设计体现了生成器优先、阶段透明、默认开放、无子 Agent、持久化状态、错误可恢复六大核心原则。
在下一篇中,我们将深入 pi-tui,剖析这个不依赖任何现有终端 UI 库的差分渲染系统。
本系列基于 oh-my-pi v2026.07 和 Pi Agent v0.66+ 的公开源码与文档编写。