Skip to content
登录

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

Updated View as Markdown

官方资料:Pi 项目(原 badlogic/pi-mono) · Agent Core · 核心循环源码 · 会话文档 · 扩展文档

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

先建立心智模型

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

层次 负责什么 相关包
模型协议 多模型消息和流式适配 pi-ai
执行引擎 Agent 状态、Loop、工具、消息队列、事件流 pi-agent-core
产品能力 会话树、资源装配、技能、扩展和终端交互 pi-coding-agent
外部入口 CLI、print / JSON、RPC 和 SDK 使用方
PI / ARCHITECTURE分层封装,而不是把所有能力写入 Agent Loop
01
Pi CLI / SDK / RPC交互入口、外部进程或嵌入式应用
02
pi-coding-agent会话树、扩展、技能、资源装配与 UI
03
pi-agent-coreAgent 状态、工具循环、消息队列、事件
04
pi-ai不同模型提供商的消息和流式适配
05
模型服务LLM API(不等于 Harness)
箭头表示应用向下复用能力的依赖关系;不是运行时消息传输方向。

Pi Coding Agent 默认采用 read、write、edit、bash 四类基本工具;子 Agent、计划模式等不被强制写进每个实例,可按需通过扩展和包增加。官方 README 明确提出这种取舍。

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

Agent 并不是“一次提问、一次回答”:模型可以返回工具调用,工具结果再回填上下文,模型继续决策。Pi 的 runLoop() 采用两层循环:

  • 内层循环:围绕模型回复、工具结果和 steering(运行中途的补充/改向指令)继续推进。
  • 外层循环:内层已经可以停止时,检查 follow-up 队列,有后续任务则再运行,否则结束。
PI / TWO LOOPS内层跑工具,外层等待 follow-up
01
当前输入 / steering组装待处理消息,进入内层循环
02
prepareRequest → 模型流上下文转换、模型请求与消息事件
03
执行 tool calls校验参数 → 前后置 Hook → 工具结果回填
04
Turn 完成有工具调用或 steering → 再次执行内层循环
05
follow-up 队列仍有新任务 → 外层循环再次启动;否则 agent_end
两个嵌套 while 循环;异常/取消可提前结束
简化自源码 runLoop:steering 在继续迭代时被读取;follow-up 只在当前工作将要结束时进入。

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

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

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

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

下面是为理解源码而做的逻辑伪代码,不是 Pi 原封不动的实现:

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;
}

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

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

Pi 区分两种消息类型:

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

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

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

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

事件流与工具执行

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。

三、Session:为什么它不等于模型上下文?

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

PI / DURABLE SESSION持久化会话树与模型当前上下文分离
01
Session JSONL(树)记录每条消息、分支、压缩摘要等条目
02
选中活动分支/tree 回溯;/fork 独立新会话
03
按需 Compaction生成摘要并保留最近消息,不删除原始记录
04
构建模型请求历史只投影当前分支和必要上下文
JSONL 中的历史记录保留;模型读取活动分支和摘要,不是把所有分支塞进上下文。
操作 行为 历史是否保留
/tree 在当前会话树中切换分支 保留其它分支
/fork 从较早的用户消息创建新的会话 保留原会话
/clone 把活动分支复制为新会话 保留原会话
/compact 生成历史摘要,控制上下文长度 原始日志仍保留

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

四、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 的最小用法):

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: "删除学习记录前需要用户授权" };
    }
  });
}

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

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

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

总结:记住三个结论

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

进一步阅读:Pi Agent Core · Session 文档 · Extension 文档 · yoagent 技术笔记 · DeepSeek Harness 技术笔记

Navigation

Type to search…

↑↓ navigate↵ selectEsc close