Pi Agent 三层 Hook 系统架构

ReAct Loop 时间线映射与事件驱动设计

技术架构白皮书 · 2026年6月

执行摘要
Pi Agent 采用独特的三层 Hook 系统架构,将事件驱动编程模型深度集成到 ReAct Loop 中。本文档系统性地解析了三层 Hook——Agent Loop 层、Agent 事件层和 Harness/Extension 层——在整个 Agent 执行生命周期中的触发时机、设计原理与协作机制。通过类型安全的 HookEvent 系统和 Phantom Type 设计,Pi 实现了在 ReAct Loop 关键节点上的精细化控制能力。

一、三层 Hook 定义

层级所在文件核心特征作用
Agent Loop 层 agent-loop.ts 函数回调,直接参与执行逻辑 控制工具调用、转换消息格式、决定循环终止
Agent 事件层 agent.ts 只读事件流 AgentEvent 状态同步、UI 更新、日志记录
Harness/Extension 层 harness/ 目录 类型安全的拦截系统 HookEvent 支持 block/patch/cancel,支持扩展

二、Agent Loop 层回调函数

回调函数触发时机返回值语义
transformContext消息进入 LLM 前修改后的 AgentMessage[]
convertToLlm序列化为 LLM 请求前Message[] 格式转换
beforeToolCall工具调用执行前{ block?: boolean } 可阻断
afterToolCall工具执行完成后{ patch?: object } 可修改结果
prepareNextTurn每轮结束后AgentLoopTurnUpdate 修改上下文
shouldStopAfterTurn停止决策点boolean 是否终止循环
getApiKeyProvider 请求前认证密钥

三、Agent 事件层事件类型

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; ... }
  | { type: "message_end"; message: AgentMessage }  // → 触发 Session 写入
  | { type: "tool_execution_start/update/end"; ... }

四、Harness 层 Phantom Type 设计

declare const HookResult: unique symbol;

interface HookEvent<TType extends string, TResult = void> {
  type: TType;
  readonly [HookResult]?: TResult;  // 只在类型层面存在
}

type ResultOf<E> = E extends { readonly [HookResult]?: infer R } ? R : void;

五、完整 ReAct Loop 时间线

1. 整轮循环开始
agent_start before_agent_start
2. 单轮开始 + Prompt 注入
turn_start message_start/end transformContext context
3. LLM 请求准备
convertToLlm before_provider_request before_provider_payload getApiKey
4. 流式响应 (Reason Phase)
message_start message_update × N after_provider_response message_end session.appendMessage()
5. 工具调用 (Act Phase)
beforeToolCall tool_call tool_execution_start/update/end afterToolCall tool_result message_start/end
6. 回合结束 + 状态固化
turn_end prepareNextTurn shouldStopAfterTurn save_point
7. 整轮结束
agent_end settled

六、AppendOnlyLog 写入机制

6.1 写入时机:双重策略

时机触发事件写入方式说明
消息完成时 message_end 立即写入 每完成一条消息(user/assistant/toolResult),立即 session.appendMessage()
回合结束时 turn_end 批量刷新 flushPendingSessionWrites() — 写入队列中的 model/thinking/tools 变更
空闲期操作 phase = "idle" 立即写入 compaction、branch、model切换等结构性操作立即持久化

6.2 三种消息的写入时机与数据结构

消息类型触发时机修改窗口写入内容数据结构
Prompt 消息
role: "user"
agent_start
turn_start
无修改窗口
message_startmessage_end 连续触发
原始用户输入
UserMessage {
  role: "user";
  content: string | (TextContent|ImageContent)[];
  timestamp: number;
}
Assistant 消息
role: "assistant"
LLM 流式响应
完成后
流式传输期间持续更新
message_update × N
流式结束后 message_end
最终完整响应
(含 thinking/toolCalls)
AssistantMessage {
  role: "assistant";
  content: (TextContent|ThinkingContent|ToolCall)[];
  api: Api;
  provider: string;
  model: string;
  usage: Usage;
  stopReason: StopReason;
  timestamp: number;
}
ToolResult 消息
role: "toolResult"
工具执行
完成后
无修改窗口
工具返回后立即生成
工具执行结果
(可被 tool_result hook patch)
ToolResultMessage {
  role: "toolResult";
  toolCallId: string;
  toolName: string;
  content: (TextContent|ImageContent)[];
  details?: any;
  isError: boolean;
  timestamp: number;
}

关键设计原则:消息不可变性

一旦 message_end 触发,消息内容即视为最终确定,立即写入 AppendOnlyLog,之后不再修改。三种消息的差异在于:

6.3 官方消息修改入口

Pi 提供以下官方修改入口,在消息写入前对其进行修改:

修改目标Hook / 回调修改能力代码示例
System Prompt
系统指令
before_agent_start 可修改 systemPrompt
可注入初始 messages
harness.on("before_agent_start", async (event) => {
  return {
    systemPrompt: event.systemPrompt + "\n请记住:简洁回答。",
    messages: [{ role: "system", content: "上下文..." }]
  };
});
进入 LLM 的消息列表
(裁剪/注入)
contexttransformContext 可替换整个 messages 数组
可添加/删除/修改任意消息
harness.on("context", async (event) => {
  // 1. 裁剪过长历史
  const trimmed = event.messages.slice(-20);
  // 2. 注入系统提示
  const modified = [
    ...trimmed,
    { role: "user", content: "请记住:简洁回答。" }
  ];
  return { messages: modified };
});
LLM 请求参数 before_provider_request
before_provider_payload
可修改 model、streamOptions
可修改序列化后的 payload
harness.on("before_provider_request", async (event) => {
  return {
    model: cheaperModel,  // 降级模型
    streamOptions: { ... }
  };
});
ToolResult 内容 tool_result 可 patch content
可修改 detailsisError
harness.on("tool_result", async (event) => {
  if (event.toolName === "read_file") {
    return {
      content: "[截断] " + event.content.slice(0, 1000),
      details: { truncated: true }
    };
  }
});
下一轮的上下文 prepareNextTurn 可替换整个 context.messages
可切换 model/thinkingLevel
agent.prepareNextTurn = async (ctx) => {
  // 修改最后一条消息
  const modified = [...ctx.newMessages];
  modified[modified.length - 1].content += "[补充]";
  return {
    context: { ...ctx.context, messages: modified },
    model: newModel
  };
};

关键限制

对 Append Only Log 的友好性分析

不同修改入口对 Session 持久化的影响:

友好度入口层级Session 一致性
⭐⭐⭐ 最友好 before_agent_start
tool_result
Harness 修改后的内容正常走完 message_endsession.appendMessage()Session 记录与实际执行完全一致
⭐⭐ 次友好 context
prepareNextTurn
Agent Loop / Harness Session 存原始消息,执行看修改后的消息 —— 恢复时会得到与执行时不同的上下文,存在一致性风险
⭐ 不友好 before_provider_payload Harness 完全绕过 Session 记录,Session 存的是原始 payload,无法恢复实际发送的内容
设计建议:如需保证 Session 可恢复性与执行一致性,优先使用 before_agent_start(注入初始消息)和 tool_result(修改工具返回),避免使用 contextbefore_provider_payload 进行结构性修改。

6.4 Phase 管理与会话写入

type AgentHarnessPhase = "idle" | "turn" | "compaction" | "branch_summary" | "retry";

// "turn" 阶段:活跃操作期
if (this.phase === "turn") {
  // 变更进入 pending 队列,不立即写入
  this.pendingSessionWrites.push({ type: "model_change", ... });
}

// "idle" 阶段:可立即写入
if (this.phase === "idle") {
  await this.session.appendModelChange(model.provider, model.id);
}

6.5 Session Entry 类型

所有状态变更建模为不可变的 Entry,追加到 Session Tree:

type SessionTreeEntry =
  | MessageEntry              // 普通消息
  | ModelChangeEntry          // 模型切换
  | ThinkingLevelChangeEntry  // thinking 级别变更
  | ActiveToolsChangeEntry    // 工具集变更
  | CompactionEntry           // 压缩操作
  | BranchSummaryEntry        // 分支摘要
  | LabelEntry                // 标签
  | CustomEntry;              // 自定义数据

七、状态恢复机制

7.1 恢复流程:半持久化架构

1. Host App 重建 Runtime Dependencies
注册 tools 注册 models 注册 extensions/hooks 配置 auth providers
2. Harness 打开 Session
session.getBranch() 获取 leaf → root 的 entries
3. Reduce Session Entries → 当前状态
buildSessionContext() 提取 thinkingLevel/model/tools 重建 messages(处理 compaction)
4. 验证 Runtime Dependencies
检查 active tool names 验证 model registry
5. 和解未完成操作
标记 interrupted turns 恢复队列状态 恢复 pending writes

7.2 核心归约函数

export function buildSessionContext(pathEntries: SessionTreeEntry[]): SessionContext {
  // 从 entries 提取配置状态
  for (const entry of pathEntries) {
    if (entry.type === "thinking_level_change") {
      thinkingLevel = entry.thinkingLevel;
    } else if (entry.type === "model_change") {
      model = { provider: entry.provider, modelId: entry.modelId };
    } else if (entry.type === "active_tools_change") {
      activeToolNames = [...entry.activeToolNames];
    } else if (entry.type === "compaction") {
      compaction = entry;  // 记录压缩点
    }
  }

  // 重建消息列表(处理 compaction 截断)
  if (compaction) {
    // 压缩点之前的消息被 summary 替代
    messages.push(createCompactionSummaryMessage(...));
    // 只保留 firstKeptEntryId 之后的 entries
    for (const entry of keptEntries) appendMessage(entry);
  } else {
    for (const entry of pathEntries) appendMessage(entry);
  }

  return { messages, thinkingLevel, model, activeToolNames };
}

7.3 恢复策略

场景默认策略说明
未完成的 Agent Turn 标记 interrupted 保守策略,不自动重试,保留队列/pending writes
未完成的 Provider 请求 标记 interrupted Provider streams 不可恢复,不自动重试
未完成的 Tool 调用 根据 metadata 决定 非幂等工具不重试,幂等/可重试工具可重新执行
缺失的 Active Tools fail(可配置 drop/disable 工具 registry 必须提供兼容实现

7.4 设计哲学

半持久化架构:Session 存储可序列化的状态和配置(messages、model changes、tool changes),但工具实现、模型对象、钩子逻辑等运行时依赖必须由 Host App 重新注入。

// 未来的恢复 Builder(设计中)
const harness = await AgentHarness.builder()
  .env(env)
  .session(session)           // 提供持久化 session
  .model(defaultModel)        // 提供默认模型
  .tools(runtimeTools)        // 提供运行时工具(必须)
  .restore({ missingActiveTools: "fail" });  // 恢复策略

八、Pi Extension 扩展系统

8.1 Extension 与三层 Hook 的关系

Extension 是 Pi 的用户级扩展机制,与三层 Hook 的关系如下:

层级Extension 可用性使用方式
Agent Loop 层 ❌ 不可用 函数回调(transformContext, prepareNextTurn 等)仅供 Host App 内部使用
Agent 事件层 ✅ 可用(只读观察) 通过 observe() 订阅 AgentEvent,用于 UI 更新、日志、监控
Harness/Extension 层 主要使用 通过 on() 订阅 HookEvent,支持 block/patch/cancel 语义

8.2 Extension 能操作的能力矩阵

能力类别API / 事件操作范围
消息修改
(Session 写入前)
before_agent_start 修改 systemPrompt,注入初始 message
context 替换整个 messages 数组(裁剪/注入)
tool_result patch content / details / isError
input transform text/images,或标记 handled
注册能力 pi.registerTool() 注册 LLM 可调用的工具(含自定义渲染)
pi.registerCommand() 注册用户命令(如 /my-cmd
pi.registerShortcut() 注册键盘快捷键(如 ctrl+k
pi.registerFlag() 注册 CLI 参数(如 --my-flag
UI 交互
ctx.ui
select/confirm/input 对话框交互(选择器、确认框、文本输入)
setStatus/setWidget/setTheme 状态栏、组件、主题控制
notify/editor/pasteToEditor 通知、多行编辑器、粘贴文本
Session 操作
ctx
getContextUsage/getSystemPrompt 读取上下文用量、system prompt
newSession/fork/navigateTree 新建 session、分叉、树导航
sendMessage/sendUserMessage 发送自定义消息、用户消息

8.3 Extension 的完整事件订阅

type ExtensionEvent =
  // Session 生命周期
  | ResourcesDiscoverEvent
  | SessionStartEvent | SessionBeforeSwitchEvent | SessionBeforeForkEvent
  | SessionBeforeCompactEvent | SessionCompactEvent | SessionShutdownEvent
  | SessionBeforeTreeEvent | SessionTreeEvent

  // Agent 执行(可修改的 HookEvent)
  | ContextEvent                    // 修改消息列表
  | BeforeProviderRequestEvent      // 修改请求 payload
  | BeforeAgentStartEvent           // 修改 systemPrompt
  | ToolCallEvent                   // 可 block,可 mutate input
  | ToolResultEvent                 // 可 patch result

  // Agent 执行(只读观察)
  | AgentStartEvent | AgentEndEvent
  | TurnStartEvent | TurnEndEvent
  | MessageStartEvent | MessageUpdateEvent | MessageEndEvent
  | ToolExecutionStartEvent | ToolExecutionUpdateEvent | ToolExecutionEndEvent
  | AfterProviderResponseEvent

  // 配置变更与用户输入
  | ModelSelectEvent | ThinkingLevelSelectEvent
  | UserBashEvent | InputEvent;

8.4 Extension 的限制

不能做的事原因
修改已写入 Session 的 Prompt/Assistant 消息 设计上不可变,AppendOnlyLog 一旦写入不可更改
直接使用 transformContext, prepareNextTurn 属于 Agent Loop 层,仅供 Host App 内部使用
访问原始 Agent 类实例 通过 ExtensionContext 封装的安全 API 访问
修改 message_update 期间的流式内容 AssistantMessage 流式期间不可修改,只有 message_end 后可观察

8.5 Extension 设计哲学

沙盒化扩展模型:Extension 只能通过类型安全的 Harness HookEvent 参与 Agent 生命周期,无法直接操作 Agent Loop 层的执行控制点。这种设计保证了:

一句话总结Extension = Harness 层事件订阅者 + 工具/命令/快捷键注册器 + UI 交互能力。Extension 无法使用 Agent Loop 层的函数回调,只能通过 Harness 层的类型安全事件系统参与 Agent 生命周期,保证沙盒性和系统稳定性。

九、关键设计原则总结

Agent Loop 层 = "执行控制点" —— 同步调用,直接返回值影响执行,如 block 工具、修改消息格式、决定停止。

Agent 事件层 = "状态通知流" —— 异步事件,只读观察,如 UI 更新、日志、监控。message_end 触发 Session 写入,turn_end 触发 pending writes 批量刷新。

Harness/Extension 层 = "拦截与扩展系统" —— 类型安全的 HookEvent,支持 block/patch/cancel,与 Agent 事件并行但独立。

状态持久化 = "Append-Only Log" —— 每个消息和配置变更作为不可变 Entry 追加写入,通过归约(reduce)重建当前状态,实现崩溃恢复和会话重建。

一句话总结:Agent Loop 层控制"怎么执行",Agent 事件层通知"发生了什么"并驱动"何时写入",Harness 层决定"是否允许/如何修改",Session 层保证"状态可恢复" —— 四层交织形成完整的可信 Agent 架构。