π Pi Agent Study
Chapter 04 · default tools

04 · Default tools

默认工具如何变成 agent 的手

这一章看 readwriteeditbash: 它们如何注册成 AgentTool,如何处理文件和命令边界,以及工具结果如何回到模型上下文。

这一章看什么

目标是能解释工具如何注册、如何执行、如何保护资源、如何把输出交还给模型。

read

  • 路径解析和 access。
  • 文本分页与截断。
  • 图片作为 image content。

write

  • 新建或整文件覆盖。
  • 自动创建父目录。
  • 同文件写入排队。

edit

  • 唯一 oldText 匹配。
  • 多 edits 基于原文件。
  • 返回 diff 和 patch。

bash

  • spawn shell 命令。
  • partial update。
  • 失败输出回传模型。

工具就是本地函数

模型不会自己读文件、改文件或跑命令。它只能生成 toolCall,由 Pi 在本地执行对应函数。

name

工具的稳定名字。模型在 toolCall 里用这个名字选择要调用的本地函数。

description

告诉模型这个工具能做什么、什么时候该用、有什么边界。

schema

参数结构和类型。agent loop 必须拿到完整参数后才能校验和执行。

execute

真正运行在本地的函数。它返回结果或错误,再被包装成 ToolResultMessage

最小闭环 工具 schema 先交给模型;模型生成 toolCall;Pi 执行本地 execute; 执行结果回填成 toolResult,下一轮模型才能看到。

从定义到执行工具

默认工具先是产品层定义,最后才变成 agent loop 可执行的工具。

01definitionscreateAllToolDefinitions()
02base registry进入 _baseToolDefinitions
03refresh合并内置、扩展、SDK 工具。
04wrapToolDefinition 适配成 AgentTool
05registry进入 _toolRegistry
06activesetActiveToolsByName()
07agent写入 agent.state.tools
包含什么
服务谁
ToolDefinition
labelpromptSnippetpromptGuidelinesrenderCallrenderResult、extension ctx。
coding-agent 产品层:prompt、TUI、扩展、来源管理。
AgentTool
namedescriptionparametersprepareArgumentsexecutionModeexecute
agent core:模型可见 schema 和本地执行。
一句话 ToolDefinition 给 pi 用,AgentTool 给 agent loop 用,wrapToolDefinition() 是中间适配器。

read:把文件变成上下文

read 的目标不是一次读最多,而是读到足够信息,并保留继续探索的路径。

{
  path: string,
  offset?: number,
  limit?: number
}

执行路径

  • 解析路径并检查可读。
  • 检测图片 MIME type。
  • 图片返回 text + image content。
  • 文本按 offset/limit 截取。
  • 使用 truncateHead() 做 2000 行 / 50KB 保护。

适合

  • 读取源码、Markdown、配置文件。
  • 用 offset/limit 分页探索大文件。
  • 读取图片并交给模型观察。

不适合

  • 一次把超大文件全部塞进上下文。
  • 替代搜索工具做全仓库定位。
  • 读取不需要进入模型上下文的临时输出。

为什么分页

  • 模型上下文有限。
  • 大文件不能一次全塞进去。
  • offset/limit 是主动分页。
  • truncateHead 是被动保护。

为什么保留 head

  • 源码开头有 imports 和类型。
  • Markdown 开头有标题和结构。
  • 配置文件开头常有整体结构。
  • 没读完会提示下一次 offset。

write:完整覆盖文件

write 风险高,因为它不读旧文件、不做 diff,只把文件变成模型给出的完整内容。

{
  path: string,
  content: string
}

执行路径

  • 解析目标路径。
  • 进入同文件 mutation queue。
  • 自动创建父目录。
  • 调用 writeFile() 覆盖内容。
  • 返回成功写入字节数。
边界 write 适合新文件或整文件重写。已有文件的小改更适合 edit,否则模型漏掉旧内容就等于删除。

适合

  • 创建新文件。
  • 生成完整配置或示例文件。
  • 明确要整文件重写的场景。

不适合

  • 已有文件的一两处小改。
  • 没有读完整文件就覆盖旧文件。
  • 需要保留用户局部编辑的文件。

mutation queue:文件资源锁

它不是替代 agent loop 的 sequential,而是在文件路径级别保护同一份文件。

agent loop sequential

  • 管理一批 tool calls 的调度顺序。
  • 作用范围是当前 assistant message。
  • 主要解决工具间顺序依赖。

withFileMutationQueue

  • 管理同一个文件的 mutation 顺序。
  • 同文件串行,不同文件仍可并行。
  • 保护真实 filesystem 操作 settle 后再释放队列。

edit:精确局部替换

edit 的策略是宁愿失败,也不要猜模型想改哪一处。

{
  path: string,
  edits: [
    {
      oldText: string,
      newText: string
    }
  ]
}

执行路径

  • 检查文件可读可写。
  • 读取文件并去除 BOM。
  • 统一换行为 LF 做匹配。
  • 所有 oldText 在原文件中定位。
  • 确认唯一且不重叠后从后往前应用。
  • 恢复原换行风格,返回 diff/patch。

找不到

oldText 不存在时失败,避免模型以为已经修改成功。

不唯一

匹配到多处时失败,逼模型提供更多上下文。

重叠

同一批 edits 不能重叠,相近修改要合并成一个 edit。

适合

  • 已有文件的精确局部修改。
  • 保留文件其他内容不变。
  • 用 diff/patch 核对修改结果。

不适合

  • oldText 太短或不唯一。
  • 同一批修改互相重叠。
  • 想重写整个文件时硬拆很多小 edit。

bash:命令执行与流式观察

bash 的 partial update 给 UI 和事件流看,最终 tool result 才给模型看。

{
  command: string,
  timeout?: number
}

执行路径

  • 拼接 command prefix。
  • 通过 spawnHook 调整 command/cwd/env。
  • spawn shell 子进程。
  • stdout/stderr 进入 OutputAccumulator
  • 节流发 tool_execution_update
  • 命令结束后返回最终结果。

为什么保留 tail

  • 测试失败通常在最后。
  • 编译错误汇总常在最后。
  • 日志最新内容在最后。
  • 命令最终状态在最后。

失败回传

  • exit code 非 0 会转成 error tool result。
  • timeout 会附带已有输出。
  • abort 会附带已有输出。
  • 模型看到的是失败原因和输出尾部。

适合

  • 运行检查命令和定向测试。
  • 观察命令输出、exit code、timeout。
  • 用短命令收集仓库状态。

不适合

  • 无边界长期运行的命令。
  • 高风险删除、重置、清理命令。
  • 需要人工确认的交互式命令。

工具输出如何回传模型

工具返回值不是直接塞给模型,而是先变成 ToolResultMessage

01toolCall模型生成 assistant tool call。
02executeagent loop 调用工具。
03result工具返回 AgentToolResult
04message包装成 ToolResultMessage
05context追加到 Context.messages
06provider转成厂商 payload。
07next模型继续生成下一轮。
两条流 事件流给人和客户端看;上下文流给模型看。bash 的 partial update 只进事件流,最终结果才进上下文。

04 完成度

默认工具已经读完,下一节进入 session、历史、恢复和压缩。

已经完成

  • 理解 ToolDefinition -> AgentTool
  • 理解工具 registry 和 active tools。
  • 理解 read 的分页和截断。
  • 理解 write/edit 的文件安全边界。
  • 理解 bash 的流式 update 和失败回传。

下一章继续

  • AgentSession 的长期状态。
  • 看 session file 如何保存历史。
  • 看 resume/fork/switch。
  • 看 compaction 为什么需要。
自测题 为什么 ToolDefinitionAgentTool 要分层?为什么 edit 要求 oldText 唯一?为什么小改旧文件不该优先用 write?为什么 bash 失败也要把输出回传给模型?