---
title: "Pi Harness 技术笔记：双层 Agent Loop、扩展与 Durable Session"
description: "从 Pi 官方源码理解 Agent 的双层循环、消息转换、工具执行、会话树和扩展机制，并提炼 English Studio 可借鉴的设计。"
---

> Documentation Index
> Fetch the complete documentation index at: https://docs.chengchencode.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Pi Harness 技术笔记：双层 Agent Loop、扩展与 Durable Session

> **修改前的版本**
>
> 这是替换前保留的原文。现行版本在本栏「概览」。

> **学习目标**
>
> 读完本文，你应能回答：Pi 为什么保持最小核心？内外两层循环分别做什么？会话历史如何分叉与压缩？哪些设计适合 English Studio？

官方资料：[Pi 项目](https://github.com/earendil-works/pi)（原 `badlogic/pi-mono`） · [Agent Core](https://github.com/earendil-works/pi/blob/main/packages/agent/README.md) · [核心循环源码](https://github.com/earendil-works/pi/blob/main/packages/agent/src/agent-loop.ts) · [会话文档](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/sessions.md) · [扩展文档](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/extensions.md)

本文按 2026-10-15 官方 `main` 的文档与源码整理；English Studio 部分是工程建议，不代表已经落地。

## 先建立心智模型

Pi 是一个<strong>最小、可扩展的 Agent Harness</strong>，而不是把所有功能放进一个超级 Agent。它用少量通用机制提供模型调用、工具循环和事件；上层应用再用扩展、技能、提示词和会话管理组成具体产品。

| 层次 | 负责什么 | 相关包 |
| --- | --- | --- |
| 模型协议 | 多模型消息和流式适配 | `pi-ai` |
| 执行引擎 | Agent 状态、Loop、工具、消息队列、事件流 | `pi-agent-core` |
| 产品能力 | 会话树、资源装配、技能、扩展和终端交互 | `pi-coding-agent` |
| 外部入口 | CLI、print / JSON、RPC 和 SDK | 使用方 |

Pi Coding Agent 默认采用 `read`、`write`、`edit`、`bash` 四类基本工具；子 Agent、计划模式等不被强制写进每个实例，可按需通过扩展和包增加。官方 [README](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/README.md) 明确提出这种取舍。

> **核心设计原则**
>
> Harness 负责“如何可靠地执行”，不负责规定每个产品必须具备哪种编排。复用一套执行循环，再交给应用决定工具、权限与业务流程。

## 一、真正理解 Pi 的双层 Agent Loop

Agent 并不是“一次提问、一次回答”：模型可以返回工具调用，工具结果再回填上下文，模型继续决策。Pi 的 [`runLoop()`](https://github.com/earendil-works/pi/blob/main/packages/agent/src/agent-loop.ts) 采用两层循环：

- <strong>内层循环：</strong>围绕模型回复、工具结果和 steering（运行中途的补充/改向指令）继续推进。
- <strong>外层循环：</strong>内层已经可以停止时，检查 follow-up 队列，有后续任务则再运行，否则结束。

### 为什么需要 steering 和 follow-up 两种队列？

| 机制 | 什么时候生效 | 学习场景 |
| --- | --- | --- |
| `steering` | 当前运行还未完成，在下一个合适的轮次介入 | “先别解释，改成出一道练习题” |
| `follow-up` | 当前任务自然完成后再处理 | “等语法分析完，再整理成生词卡” |

它们是输入调度机制，不等于同时运行的两个 Agent，更不是自动化业务状态机。

一个 Turn 的关键环节：拼装待处理消息 → `prepareRequest` → 模型流式输出 → 校验和执行工具 → 结果写回 → `finishTurn` 与 `turn_end`。出现错误或取消时可以提前结束，而不是保证所有工具都执行完。

下面是为理解源码而做的<strong>逻辑伪代码</strong>，不是 Pi 原封不动的实现：

~~~typescript
while (true) { // 外层：在当前工作结束时检查 follow-up
  let toolsOwed = true;
  while (toolsOwed || pending.length > 0) {
    appendMessages(pending);
    const answer = await callModel();
    const results = await executeTools(answer.toolCalls);
    appendToolResults(results);
    toolsOwed = shouldContinueFromTools(results);
    pending = await getSteeringMessages();
  }
  pending = await getFollowUpMessages();
  if (pending.length === 0) break;
}
~~~

真实源码还有请求准备、异步事件、并发工具、取消处理和终止控制。它表达的重点是：<strong>执行调度属于宿主代码；大模型只提出下一步操作。</strong>

## 二、消息管线：存储的消息不一定都发送给模型

Pi 区分两种消息类型：

- `AgentMessage` 是应用中的消息，可以包含 UI 数据和自定义事件。
- `Message` 是模型能够理解的角色消息。

每次模型请求前，`transformContext()` 可整理历史或注入内容，然后 `convertToLlm()` 将 AgentMessage 转成模型可接受的格式。

| 阶段 | 输入到输出 | 意义 |
| --- | --- | --- |
| 原始消息 | `AgentMessage[]` | 会话及 UI 业务信息 |
| `transformContext` | AgentMessage → AgentMessage | 裁剪、筛选、注入 |
| `convertToLlm` | AgentMessage → Message | 转成模型协议 |
| Provider | Message → stream | 生成模型回复 |

例如 English Studio 的“已保存一张词汇卡”可以在应用会话里保留完整结构，模型下一轮只需要读取“本次保存了一个复习词汇”即可。<strong>会话持久化内容与本轮模型上下文必须分开设计。</strong>

### 事件流与工具执行

Pi 发布 `agent_start`、`turn_start`、`message_start/update/end`、`tool_execution_start/update/end`、`turn_end`、`agent_end` 等事件，上层 UI 可以分别展示“模型回答中”“工具执行中”“任务已结束”。

目前 Agent Core 支持并发工具模式；并发完成顺序可能与原始工具列表不同，但最终 toolResult 消息仍按模型来源顺序组织。`beforeToolCall` / `afterToolCall` 分别负责准入、阻断与结果后处理。具体约定参见 [Agent Core](https://github.com/earendil-works/pi/blob/main/packages/agent/README.md)。

## 三、Session：为什么它不等于模型上下文？

Pi Coding Agent 默认把会话持久化为 JSONL，并将历史组织成<strong>可分叉的树</strong>。用户当前选择的活动分支决定下一次模型请求使用哪段历史。

| 操作 | 行为 | 历史是否保留 |
| --- | --- | --- |
| `/tree` | 在当前会话树中切换分支 | 保留其它分支 |
| `/fork` | 从较早的用户消息创建新的会话 | 保留原会话 |
| `/clone` | 把活动分支复制为新会话 | 保留原会话 |
| `/compact` | 生成历史摘要，控制上下文长度 | 原始日志仍保留 |

自动 Compaction 控制模型上下文规模，而不是清空全部学习历史。真正用于下一轮的，只是活动分支、保留的最近消息和相关摘要。

> **Durable Session 的边界**
>
> JSONL 会话树提供历史记录与恢复入口，但不能替代应用业务事务。工具写入数据库后如果进程崩溃，是否重新执行还需要幂等键、工具执行状态与恢复策略。

## 四、Extension：如何不修改 Loop 就增加能力？

Pi 扩展是 TypeScript 模块，能通过 `ExtensionAPI` 注册模型可调用工具、命令、事件处理器、模型提供方和 UI 能力。

| 扩展点 | 用途 |
| --- | --- |
| `pi.registerTool()` | 加模型工具 |
| `pi.registerCommand()` | 加用户命令 |
| `pi.on("tool_call")` | 允许、修改或拒绝一次工具调用 |
| `pi.on("tool_result")` | 处理工具执行结果 |
| `before_agent_start` | 在新运行前补充上下文 |
| `pi.appendEntry()` | 保存扩展自己的 Session 记录 |

例如限制敏感工具（示例围绕官方 API 的最小用法）：

~~~typescript
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";

export default function (pi: ExtensionAPI) {
  pi.on("tool_call", async (event) => {
    if (event.toolName === "delete_learning_records") {
      return { block: true, reason: "删除学习记录前需要用户授权" };
    }
  });
}
~~~

> **安全提醒**
>
> Pi Extension 在同一个进程中运行，具备相同的操作系统权限，可能读取消息、文件、工具参数和凭据。只能加载可信扩展；它不是强隔离沙箱。

## 五、English Studio 可以直接借鉴哪些思想？

| Pi 的原理 | English Studio 的建议 |
| --- | --- |
| Provider 与 Loop 分离 | Rust Harness 统一接多种 LLM |
| 状态消息与模型消息分离 | 学习记录存结构化数据，按需投影成 Prompt |
| Steering / Follow-up | 分清“打断当前任务”与“安排下一任务” |
| Session Tree | 支持会话分支、恢复和历史摘要 |
| Extension | 安全审计、工具准入、临时上下文检索 |
| 业务逻辑保持在应用层 | 语法专项 Workflow 由确定性状态机控制 |

例如 Listening Agent 发现语法薄弱点，可以先保存结构化学习事件；用户决定进入专项训练后，应用层再启动 Grammar Agent。<strong>不能因为 Pi 具有 follow-up，就把它当成跨页面、跨天的 Workflow 引擎。</strong>

## 总结：记住三个结论

1. <strong>运行：</strong>内层 Loop 处理工具与 steering，外层 Loop 在自然结束时查看 follow-up。
2. <strong>上下文：</strong>Session 是可追溯的历史树，而模型上下文是它的选择性投影。
3. <strong>扩展：</strong>把功能加入工具和 Extension，不要为不同 Agent 复制一套循环。

进一步阅读：[Pi Agent Core](https://github.com/earendil-works/pi/blob/main/packages/agent/README.md) · [Session 文档](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/sessions.md) · [Extension 文档](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/extensions.md) · [yoagent 技术笔记](/notes/yoagent/) · [DeepSeek Harness 技术笔记](/notes/deepseek-harness/)

Source: https://docs.chengchencode.com/notes/pi-harness/previous/index.mdx
