跳转到内容

用 Pi 的接口做一个自己的 Agent

前八章的教学内核把 Agent Loop 的状态逐个摊开了。做真实项目时,资源加载、终端界面和 Session 产品层可以直接使用 Pi 已有的实现。

下面做一个仓库维护 Agent。它需要按固定步骤审查项目,快速读取仓库概况,拒绝直接修改凭据文件,也能被脚本或桌面应用调用。

三个需求分别落在 Pi 的 Skill、Extension 和 SDK 上。

入口 适合放什么 运行形态
Skill 领域术语、步骤、模板和参考资料 Markdown,按需进入上下文
Extension 新工具、命令、事件策略、Provider 与 UI TypeScript,与 Pi 同进程运行
SDK 自定义 CLI、批处理、服务或图形界面 由你的程序创建并管理 AgentSession

Extension API 可以注册 Tool、Command、Shortcut、CLI flag,并监听 Session、Model、Message 与 Tool 事件。查看固定版本的 ExtensionAPI

Skill 不执行代码。Pi 启动时只读取名称和 description,模型需要时再打开完整的 SKILL.md 与引用文件。这样一大批操作手册不会挤进每一次请求。查看 Skill 加载约定

SDK 的主要入口是 createAgentSession()。它接收模型、工具 allowlist、custom tools、ResourceLoader、SettingsManager 与 SessionManager,再返回可订阅、可 prompt、可 dispose 的 AgentSession查看 SDK 参数与返回值

在项目中创建:

.pi/
└── skills/
└── repository-maintenance/
├── SKILL.md
└── references/
└── report-format.md

SKILL.md 可以这样写:

---
name: repository-maintenance
description: 审查仓库结构、依赖、未提交状态和验证入口。用于项目接手、升级前检查和技术债盘点。
---
# Repository Maintenance
1. 确认工作目录、分支与未提交状态。
2. 查找 package manifest、构建配置和项目说明。
3. 分开记录文件或命令证明的事实,以及仍需验证的推断。
4. 未得到授权时只读取和诊断。
5. 报告格式遵循 references/report-format.md。

references/report-format.md 只放这份 Skill 独有的输出合同:

# 审查报告格式
## 已确认
- 结论
- 证据:文件路径或实际命令
## 仍需确认
- 问题
- 缺少什么证据
## 建议的下一条只读命令

description 决定模型何时会想到这份 Skill。“帮助处理仓库”太泛,“用于项目接手、升级前检查和技术债盘点”给出了明确场景。Pi 会校验名称、description 和目录结构;重名 Skill 会按发现顺序处理并发出警告。查看 Skill 校验规则

需要确定性调用时,直接输入:

/skill:repository-maintenance 检查当前仓库,只报告,不修改

观察三件事:是否按 reference 中的结构输出,是否引用实际文件路径,会话里是否出现任何写入。自然语言匹配可以作为便利入口,不能代替显式实验。

Skill 会教模型怎样审查,却不能创建新的运行时工具。项目 Extension 可以增加一个只读的 repo_snapshot。把下面代码保存到 .pi/extensions/repository-maintenance.ts

import { readdir, readFile } from "node:fs/promises";
import { join, resolve } from "node:path";
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
import { Type } from "typebox";
export default function repositoryMaintenance(pi: ExtensionAPI) {
pi.registerTool({
name: "repo_snapshot",
label: "Repository Snapshot",
description: "读取仓库顶层文件与 package scripts,不修改文件。",
parameters: Type.Object({}),
async execute(_id, _params, _signal, _onUpdate, ctx) {
const entries = (await readdir(ctx.cwd)).sort();
let scripts: string[] = [];
try {
const manifest = JSON.parse(
await readFile(join(ctx.cwd, "package.json"), "utf8"),
);
scripts = Object.keys(manifest.scripts ?? {}).sort();
} catch {}
return {
content: [{
type: "text",
text: JSON.stringify({ cwd: ctx.cwd, entries, scripts }, null, 2),
}],
details: { cwd: ctx.cwd, entries, scripts },
};
},
});
pi.on("tool_call", async (event, ctx) => {
if (event.toolName !== "write" && event.toolName !== "edit") return;
const input = event.input as { path?: unknown };
if (typeof input.path !== "string") return;
const target = resolve(ctx.cwd, input.path);
const protectedFiles = [
resolve(ctx.cwd, ".env"),
resolve(ctx.cwd, ".env.local"),
];
if (protectedFiles.includes(target)) {
return { block: true, reason: "本 Agent 不直接修改本地凭据文件" };
}
});
}

开发时显式加载:

Terminal window
pi -e .pi/extensions/repository-maintenance.ts

受信项目也会自动发现 .pi/extensions/ 下的代码。Pi 使用 TypeScript loader 直接加载文件,不要求预先打包。查看 Extension loader

接着做三次观察:

  1. 要求调用 repo_snapshot,结果应包含当前目录与 scripts;
  2. 要求修改普通测试文件,记录 tool_call 事件;
  3. 要求修改 .env,handler 应在内建 write/edit 之前被阻止。

第三项验证的是你写的 Extension policy,不代表 Pi 自带 OMP 那种 read、write、exec 审批体系。

Extension 与 Pi 在同一进程中运行,继承当前用户权限。Project Trust 只决定项目代码是否加载,加载后的代码仍不在沙箱里。查看 Extension 安全说明

这段示例只拦截两个规范化后的词法路径,没有覆盖 symlink、大小写差异或其他 .env.* 文件。真实凭据保护应使用更完整的路径策略,并配合进程级隔离。

CLI 适合人机对话;定时任务、桌面应用或批量仓库检查需要自己管理 Session。安装与教材一致的版本:

Terminal window
npm install @earendil-works/pi-coding-agent@0.82.1

先运行一次 pi 完成 Provider 与模型配置,再把以下入口保存为 repository-agent.mjs。这段代码使用相同的用户配置;它不是前面离线 Lab 的一部分,运行时会调用你选定的模型:

import {
createAgentSession,
DefaultResourceLoader,
getAgentDir,
SessionManager,
SettingsManager,
} from "@earendil-works/pi-coding-agent";
const cwd = process.cwd();
const agentDir = getAgentDir();
const settingsManager = SettingsManager.create(cwd, agentDir);
const resourceLoader = new DefaultResourceLoader({
cwd,
agentDir,
settingsManager,
});
await resourceLoader.reload();
const { session, extensionsResult } = await createAgentSession({
cwd,
settingsManager,
resourceLoader,
sessionManager: SessionManager.inMemory(cwd),
tools: ["read", "grep", "find", "ls", "repo_snapshot"],
});
if (extensionsResult.errors.length) {
throw new Error("仓库维护 Extension 加载失败");
}
try {
await session.prompt("按 repository-maintenance Skill 生成只读审查报告。");
} finally {
session.dispose();
}

运行入口:

Terminal window
node repository-agent.mjs

DefaultResourceLoader 统一装载 Extension、Skill、prompt、theme 与 context file,查看 reload 流程session.dispose() 也属于交付要求,否则可能留下订阅或后台句柄。

第九个 Lab 给 AgentCore 注入一个 count_lines Extension。它没有修改 core 文件,只增加 system prompt 片段与工具。

Terminal window
cd agent-harness-lab
npm run lab -- 09

预期观察:

{
"terminal": "completed",
"exposedTools": ["read", "count_lines"],
"systemPromptContainsExtension": true
}

Lab 09 只验证教学内核可以在不改 core 的情况下增加 prompt 片段和工具。Pi 的 Skill 发现、Extension 加载与 SDK 入口,需要用本章前面的三组真实 Pi 操作分别验证。

下一章进入 OMP。它把审批、Hashline、Memory、Browser 和 Task 放进一套独立运行时。我们会按改动大小选择 Config、Skill、Agent Definition、Extension 或 Fork,做出一个能维护的领域 Agent。