
引言假设你是一个刚开始学习 Agent 的开发者。你已经跟着教程写过几次 LLM 调用给模型一段 prompt、定义一个天气或搜索工具、把工具结果再发回模型。示例能够跑通但当你想继续追问时问题很快出现多轮任务怎样结束工具报错后模型如何调整上下文越来越长怎么办任务中断后怎样恢复这时转去阅读成熟的 Coding Agent体验通常又是另一种挫败。最典型的代表便是Claude Code和Codex它们作为生产级产品还要考虑处理界面、权限、跨端会话、复杂错误处理、平台适配和生态集成等问题。对初学者来说Agent 的核心循环往往被这些产品层代码包围难以从中切入去学习。简单教程不够深入成熟项目又无从下手学习路径恰好断在了这里。PI 恰好位于两者之间它不是只演示一次工具调用的玩具项目也没有把 Agent 的关键控制流淹没在大量产品实现中覆盖了理解Coding Agent核心运行机制所需的关键部分上下文处理与压缩工具调用Skill注入模型适配会话持久化和Agent Loop等等。PI的价值不在于功能更少而在于把Agent的核心控制流程都保留在了清晰的代码中同时Pi具有极大的拓展性你可以在学习更高级的Agent知识的同时在Pi上实现相应的功能:更成熟的上下文压缩策略多Agent调用与子Agent派发接入MCP服务PI的设计也在鼓励使用者在其上持续扩展设计出结合具体场景的生产级Agent1. PI 总体介绍1.1 PI 是什么PI 是一个用 TypeScript 编写、运行在 Node.js 上的开源 Coding Agent。它提供终端 CLI也把模型调用、Agent Loop、会话和终端交互拆成独立模块。模块功能packages/ai统一调用不同模型与 Providerpackages/agent实现 Agent Loop、消息状态和工具调用packages/coding-agent提供 CLI、内置工具、会话与上下文管理packages/tui负责终端交互界面1.2 核心循环与拓展接口PI 的核心循环只负责推进任务向模型发送当前上下文执行模型返回的工具调用再将工具结果写回上下文。packages/agent/src/agent-loop.ts 直接实现了这条流程循环从 assistant message 中取出toolCall执行工具后追加toolResultReAc执行流程下如果还有工具调用就继续请求模型。2. 完整Agent Loop的流程Agent Loop 可以完成短任务。要让 Agent 在真实项目中连续工作还要解决模型上下文、工具执行和任务恢复的问题。PI 将这些问题拆为四条链路机制要解决的问题PI 中的主要实现路径会话存储对话如何持久化以及回退后如何形成分支pi/packages/coding-agent/src/core/session-manager.ts、pi/packages/coding-agent/src/core/agent-session.ts上下文本次请求需要给模型哪些规则、历史和外部信息pi/packages/coding-agent/src/core/resource-loader.ts、pi/packages/coding-agent/src/core/system-prompt.ts、pi/packages/coding-agent/src/core/agent-session.ts压缩策略历史超过模型窗口后怎样保留可继续工作的信息pi/packages/coding-agent/src/core/compaction/compaction.ts工具调用如何根据当前任务选择、执行并返回工具结果pi/packages/agent/src/agent-loop.ts、pi/packages/coding-agent/src/core/tools/下面先看 PI 提供的工具再按这四条链路分析 Agent Loop。2.1 ToolsPI 默认启用四个工具足以完成基本的代码阅读、修改和验证工具作用read读取文件内容bash执行命令例如搜索代码或运行测试edit修改已有文件中的指定内容write写入文件仓库还内置grep搜索文件内容find查找文件ls列出目录powershell执行 PowerShell 命令。这些工具有现成实现但不属于默认启用的四个工具需要时可以在工具配置中选用。源码入口全部内置工具、默认工具组合。2.2 上下文每次API调用都是无状态的单次API调用的不会自动知道当前Agent对话的上下文所以Agent Harness需要在每次请求前组装上下文。而且相应的上下文组装策略也需要斟酌不能一股脑地将不同来源的上下文杂糅到一起这样会稀释上下文需要将不同来源的信息放在不同层次。信息来源PI 的处理方式项目规则加载AGENTS.md、CLAUDE.md项目约束提示词Skill先提供名称和描述任务匹配时再读取 Skill 正文会话历史只投影当前会话分支而不是整棵会话树工具结果作为toolResult留在消息历史中供下一次模型请求使用请求发送前PI 会先按需调用transformContext。它接收当前的应用层消息列表可用于筛选历史消息、调整顺序或注入额外信息未配置时消息保持原样。随后convertToLlm 将其中的自定义消息转换为通用的 LLM 消息并过滤不应进入模型上下文的消息。具体 Provider 的请求格式由后续模型调用层处理。源码入口资源加载、系统提示词、消息转换。2.3 会话存储PI 默认用一个 JSONL 文件保存一次会话。首行是会话信息后续每行是一条 entry节点共有的id、parentId和timestamp字段定义在 SessionEntryBase。model_change记录模型切换{ type: model_change, id: b2c3d4e5, parentId: a1b2c3d4, timestamp: 2024-12-03T14:05:00.000Z, provider: anthropic, modelId: claude-sonnet-4-5 }compaction记录压缩摘要及从哪个节点开始保留原始内容{ type: compaction, id: c3d4e5f6, parentId: b2c3d4e5, timestamp: 2024-12-03T14:10:00.000Z, summary: 已完成项目结构分析下一步检查工具调用。, firstKeptEntryId: a1b2c3d4, tokensBefore: 50000 }branch_summary把离开旧分支时生成的摘要接到新分支上{ type: branch_summary, id: d4e5f6a7, parentId: a1b2c3d4, timestamp: 2024-12-03T14:15:00.000Z, fromId: c3d4e5f6, summary: 原分支尝试了方案 A但尚未完成验证。 }此外还有记录推理级别的 thinking_level_change、记录用量的 usage以及标记节点的 label 等节点分类PI 按节点增量记录会话而不是等任务结束后一次性保存。AgentSession 的 message_end 事件处理会把完成的用户、助手和工具结果消息交给 SessionManager.appendMessage。新建会话时PI 先把会话信息和用户消息暂存在内存里此时还没有创建 JSONL 文件。模型的第一条回复生成完毕后PI 才创建文件把内存中已有的记录一次写入。此后每产生一条新记录就直接追加到文件末尾。PSPI 进程在第一条助手消息写入会话前就退出这次会话可能无法恢复。回退回退时PI 不删除旧节点而是通过 navigateTree 移动当前节点。如果选中一条旧的用户消息PI 会把当前节点移到它的父节点并把原消息放回输入框供修改再次发送后新消息以该父节点为parentId形成另一条分支。回退后getBranch 只沿当前节点的parentId找到这条分支。模型收到的消息再由 buildSessionProjection 生成。会话树中选中一条旧消息只是把 PI 当前的位置移过去。这一步不会修改会话文件。你从这里发送新消息后PI 才会把新消息接在所选消息后面形成一条新分支并写入同一个 JSONL 文件。原来的分支仍然保留。如果选择“总结旧分支”PI 也会写入一条摘要消息。PS切换会话分支只影响聊天记录不会把此前工具修改过的项目文件恢复原状。2.4 压缩长任务会不断积累用户消息、模型回复和工具结果。PI 不直接删除旧消息而是在当前分支的上下文接近模型窗口上限时将较早的内容概括成摘要。启用自动压缩时判断条件由 shouldCompact 实现有两个关键的变量reserveTokens当上下文窗口的剩余token达到reserveTokens就会触发压缩keepRecentTokens是压缩时尽量保留的近期消息量的Token数。前者决定何时压缩后者决定保留的消息token确定边界。prepareCompaction 根据当前分支的路径从最新消息向前累计 token找到尽量保留约keepRecentTokens的位置。firstKeptEntryId指向第一个原样保留的节点。切点不能直接落在toolResult上否则可能只保留工具结果却丢掉对应的工具调用如果单次用户任务本身太长也可以在助手消息处切开并单独概括此前的部分。切点选择生成摘要。compact 将边界之前的消息交给模型总结而不是机械截断文本。摘要重点保留任务目标、约束、进度、关键决策和下一步如果之前压缩过还会结合旧摘要更新。摘要请求保存并重建上下文。PI 追加一个包含summary、firstKeptEntryId等字段的 compaction 节点。下一次请求时buildContextEntries 选出该压缩节点和从firstKeptEntryId起保留的节点消息投影 再将压缩节点展开为系统提示词状态和摘要。旧节点仍在 JSONL 会话文件中压缩改变的是模型看到的历史不是原始记录。触发方式自动触发会在工具结果写入后、下一次助手回复前等时机执行/compact手动触发若模型请求发生上下文溢出时PI 压缩完上下文后会重试发送对话3. 从 PI 开始扩展自己的 Agent学习 PI 的下一步是在不断的Agent学习中以PI为基础扩展出更多Agent能力扩展方向可以做什么压缩策略根据任务定制摘要保留关键约束、进度和待办事项MCPMCP Server 提供的工具注册成 PI 工具多 Agent将派发子Agent注册为tool要处理独立子任务时调用结束后向主 Agent 返回结果Hook在特定的时间节点注入信息、拦截工具调用或处理结果扩展时可以参考PI官方源码自带的示例参考其实现慢慢推进。