Learn Pi Agent
s12

完整 Harness

分发与综合

多种机制,一个会话

65 LOC1 个工具机制集成
所有扩展点最终都汇入同一条循环。
机制定位
用户输入组装上下文模型推理tool_call 闸门执行工具tool_result 回传会话 / 持久化

六个机制同时挂在同一条循环上,互不冲突。

s11 → s12

"多种机制,一个会话" — 所有扩展点最终都汇入同一条循环。

Harness 层:集成 — 把零件装成一台机器。


问题

前面十一章各自演示了一个机制,但真实产品不会只用其中一个。一个可用的 coding agent harness 通常同时需要:

  • 自己的工具(业务动作)
  • 权限闸门(安全边界)
  • 结果截断(上下文预算)
  • 按需技能(领域知识)
  • 提示词模板(用户流程)
  • 子 agent(任务隔离)

它们会不会互相冲突?加载顺序有没有讲究?这一章把它们装进同一个 AgentSession,证明 pi 的扩展点是正交的。

解决方案

一个会话,六个机制:

const loader = new DefaultResourceLoader({
  cwd: process.cwd(),
  agentDir: getAgentDir(),
  extensionFactories: [permissionGate, truncateToolResults],        // s03 + s04
  additionalSkillPaths: ["../s05_skills/skills"],                    // s05
  additionalPromptTemplatePaths: ["../s06_prompt_templates/prompts"],// s06
});
await loader.reload();

const { session } = await createAgentSession({
  resourceLoader: loader,
  customTools: [...createTodoTools(TODOS_FILE), delegate],           // s02 + s08
  sessionManager: SessionManager.inMemory(),
});

没有一行改动 pi 的核心,没有一处配置互相打架。

工作原理

装配图

                        ┌─────────────────────────────────────────┐
   prompt ──► /review ──►  prompt 模板展开(s06)                  │
             │           └─────────────────────────────────────────┘
             ▼
        AgentSession ──► system prompt 里只有 skill 的 name+desc(s05)
             │
             ▼
        ┌─ while 循环 ─────────────────────────────────────────┐
        │  LLM 决定动作                                         │
        │     │                                                 │
        │     ├─ tool_call  ──► 权限闸门:allow/ask/block(s03) │
        │     │                                                 │
        │     ├─ execute ────► 自定义工具 todo_*(s02)          │
        │     │                delegate ──► 子 AgentSession(s08)│
        │     │                                                 │
        │     └─ tool_result ─► 截断超长输出(s04)              │
        │                                                       │
        │  结果追加,回到 LLM ◄──────────────────────────────────┘
        └───────────────────────────────────────────────────────┘

各机制的介入点

机制章节介入点是否改变循环
自定义工具s02工具池否
权限闸门s03tool_call否
结果截断s04tool_result否
技能s05system prompt + read否
提示词模板s06prompt 发送前否
子 agents08工具执行内部新开会话否

这张表的每一行都是「否」—— 这就是 pi 扩展设计的全部意义。

装配顺序的讲究

  • 扩展加载顺序 = 处理顺序。 多个 tool_call / tool_result 处理器按加载顺序链式执行。权限闸门应该尽量靠前(早拦截省算力),截断可以靠后(拿到最终结果再裁)。
  • 技能与模板只是资源,谁先加载不影响行为,只影响 system prompt 里的列表顺序。
  • 自定义工具和扩展工具共用工具池,名字不能重复。

什么时候该抽成 Pi Package

当这套组合要给别人用、要跨项目复用、要版本化时,按 s11 打包成一个 pi package。本章的装配就是那个包的「开发态」。

变更内容

组件s11s12
机制数量每章一个六个同会话
加载分散在各脚本DefaultResourceLoader 一次装配
工具池内置 + 单个内置 + todo_* + delegate
循环不变不变(正交,无冲突)

试一下

npm run s12
npm run s12 -- "把'完成 s12'加入待办,再让子 agent 统计 s01..s11 的 README 总字数"
npm run s12 -- "依次执行 echo hi 和 sudo rm -rf /var"   # 观察权限闸门

你会看到:todo_add 和 delegate 被调用(s02+s08)、turn 级追踪(s04)、危险命令被拦截(s03)、子 agent 在隔离上下文里干活(s08)。一个会话,全部生效。

再试几个:

  • npm run s12 -- "/review s02_custom_tools/store.ts" —— prompt 模板(s06)展开,同时结果截断(s04)在生效
  • npm run s12 -- "让子 agent 读 s01..s12 的 README,每个机制总结一句话" —— 委派与技能同时在场,观察它们互不干扰

本章要点

  • pi 的扩展点是正交的,可以自由组合,互不冲突。
  • 所有机制都「挂在循环上」,没有一个是「写进循环里」。
  • 扩展处理器按加载顺序链式执行,顺序即策略。
  • 组合成熟后,用 pi package(s11)固化、分发。

结课

你已经走完一条完整的 harness 工程路径:

s01 循环  →  s02 工具  →  s03 权限  →  s04 事件  →  s05 知识
   →  s06 流程  →  s07 上下文  →  s08 委派  →  s09 进程边界
      →  s10 模型接入  →  s11 分发  →  s12 集成

回到最初那句话:

Agency 来自模型。Harness 来自你。

pi 把循环搭好了。剩下的世界,由你扩展。

Bash is all you need。扩展点才是 harness 工程师的全部。