PI
- earendil-works/pi
- Pi 是一个由 agent loop、模型适配层、工具系统、扩展系统和交互界面组成的 coding agent harness。
Runtime
interface AgentContext {
systemPrompt: string;
messages: Message[];
tools?: AgentTool[];
}
interface Agent {
prompt(message: string): Promise<void>;
continue(): Promise<void>;
abort(): void;
subscribe(listener: (event: AgentEvent) => void): () => void;
}
AgentContext保存一次模型请求可见的系统提示词、消息和工具集合。- agent loop 负责模型流式响应、工具调用、工具结果回填、steering/follow-up 消息和 turn 生命周期。
Agent对外提供 prompt、continue、abort 和事件订阅;调用方通过事件观察 assistant message、tool execution 和 agent 状态变化。
System Prompt
interface BuildSystemPromptOptions {
/** 自定义系统提示词;提供后替换默认提示词主体。 */
customPrompt?: string;
/** 当前模型可使用的内置工具名称。 */
selectedTools?: string[];
/** 按工具名称提供可选的简短说明。 */
toolSnippets?: Record<string, string>;
/** 附加到系统提示词中的指南。 */
promptGuidelines?: string[];
/** 追加到系统提示词末尾的内容。 */
appendSystemPrompt?: string;
/** 当前工作目录。 */
cwd: string;
/** 预加载的上下文内容。 */
contextFiles?: Array<{ path: string; content: string }>;
/** 预加载的技能内容。 */
skills?: Skill[];
}
function buildSystemPrompt(options: BuildSystemPromptOptions): string {
const {
customPrompt,
selectedTools,
toolSnippets,
promptGuidelines,
appendSystemPrompt,
cwd,
contextFiles: providedContextFiles,
skills: providedSkills,
} = options;
const promptCwd = cwd.replace(/\\/g, "/");
const appendSection = appendSystemPrompt ? `\n\n${appendSystemPrompt}` : "";
const contextFiles = providedContextFiles ?? [];
const skills = providedSkills ?? [];
if (customPrompt) {
let prompt = customPrompt;
if (appendSection) prompt += appendSection;
if (contextFiles.length > 0) {
prompt += "\n\n<project_context>\n\n";
prompt += "Project-specific instructions and guidelines:\n\n";
for (const { path, content } of contextFiles) {
prompt += `<project_instructions path="${path}">\n${content}\n</project_instructions>\n\n`;
}
prompt += "</project_context>\n";
}
const hasRead = !selectedTools || selectedTools.includes("read");
if (hasRead && skills.length > 0) prompt += formatSkillsForPrompt(skills);
return `${prompt}\nCurrent working directory: ${promptCwd}\n`;
}
const tools = selectedTools ?? ["read", "bash", "edit", "write"];
const visibleTools = tools.filter((name) => toolSnippets?.[name]);
const toolsList = visibleTools.length > 0
? visibleTools.map((name) => `- ${name}: ${toolSnippets![name]}`).join("\n")
: "(none)";
const guidelinesList: string[] = [];
const seenGuidelines = new Set<string>();
const addGuideline = (guideline: string): void => {
if (seenGuidelines.has(guideline)) return;
seenGuidelines.add(guideline);
guidelinesList.push(guideline);
};
const hasBash = tools.includes("bash");
const hasPowerShell = tools.includes("powershell");
const hasGrep = tools.includes("grep");
const hasFind = tools.includes("find");
const hasLs = tools.includes("ls");
const hasRead = tools.includes("read");
if ((hasBash || hasPowerShell) && !hasGrep && !hasFind && !hasLs) {
if (hasBash && hasPowerShell) {
addGuideline("Use bash or PowerShell for file operations like listing, searching, and finding files");
} else if (hasPowerShell) {
addGuideline("Use PowerShell for file operations like listing, searching, and finding files");
} else {
addGuideline("Use bash for file operations like ls, rg, find");
}
}
for (const guideline of promptGuidelines ?? []) {
const normalized = guideline.trim();
if (normalized) addGuideline(normalized);
}
addGuideline("Be concise in your responses");
addGuideline("Show file paths clearly when working with files");
let prompt = `You are an expert coding assistant operating inside pi, a coding agent harness. You help users by reading files, executing commands, editing code, and writing new files.
Available tools:
${toolsList}
In addition to the tools above, you may have access to other custom tools depending on the project.
Guidelines:
${guidelinesList.map((guideline) => `- ${guideline}`).join("\n")}
Pi documentation (read only when the user asks about pi itself, its SDK, extensions, themes, skills, or TUI):
- Main documentation: ${getReadmePath()}
- Additional docs: ${getDocsPath()}
- Examples: ${getExamplesPath()} (extensions, custom tools, SDK)
- When reading pi docs or examples, resolve docs/... under Additional docs and examples/... under Examples, not the current working directory
- When asked about: extensions (docs/extensions.md, examples/extensions/), themes (docs/themes.md), skills (docs/skills.md), prompt templates (docs/prompt-templates.md), TUI components (docs/tui.md), keybindings (docs/keybindings.md), SDK integrations (docs/sdk.md), custom providers (docs/custom-provider.md), adding models (docs/models.md), pi packages (docs/packages.md), environment variables (docs/environment-variables.md)
- When working on pi topics, read the docs and examples, and follow .md cross-references before implementing
- Always read pi .md files completely and follow links to related docs (e.g., tui.md for TUI API details)`;
if (appendSection) prompt += appendSection;
if (contextFiles.length > 0) {
prompt += "\n\n<project_context>\n\n";
prompt += "Project-specific instructions and guidelines:\n\n";
for (const { path, content } of contextFiles) {
prompt += `<project_instructions path="${path}">\n${content}\n</project_instructions>\n\n`;
}
prompt += "</project_context>\n";
}
if (hasRead && skills.length > 0) prompt += formatSkillsForPrompt(skills);
return `${prompt}\nCurrent working directory: ${promptCwd}`;
}
- 默认提示词由工具列表、工具使用指南、项目上下文和技能内容组成。
customPrompt替换默认提示词主体,但仍可追加上下文和技能。- 只有当前工具集合中包含
read时,技能内容才会自动注入默认提示词。 - 工具只在
selectedTools中启用;工具有定义不代表当前 agent 一定能调用它。
function buildCodingAgentHarnessSystemPrompt(input: {
cwd: string;
tools: readonly CodingAgentHarnessTool[];
activeToolNames: readonly string[];
systemPromptOptions?: Omit<BuildSystemPromptOptions, "cwd" | "selectedTools" | "toolSnippets" | "promptGuidelines">;
}): string {
const activeTools = input.activeToolNames.flatMap((name) =>
input.tools.find((tool) => tool.name === name) ?? [],
);
const toolSnippets = Object.fromEntries(activeTools.flatMap((tool) => {
const snippet = tool.promptSnippet?.replace(/[\r\n]+/g, " ").replace(/\s+/g, " ").trim();
return snippet ? [[tool.name, snippet]] : [];
}));
const promptGuidelines = activeTools.flatMap((tool) => tool.promptGuidelines ?? []);
return buildSystemPrompt({
...input.systemPromptOptions,
cwd: input.cwd,
selectedTools: activeTools.map((tool) => tool.name),
toolSnippets,
promptGuidelines,
});
}
- Harness 以
activeToolNames为准过滤工具;未启用的工具既不进入Available tools,也不贡献工具指南。 - 每个工具的
promptSnippet会压缩为一行;promptGuidelines按工具汇集,随后由buildSystemPrompt去重并追加全局基础指南。 - Agent Session 在工具集刷新、
reload()或扩展资源重新发现时重建 base system prompt;上下文文件和技能在这些重建路径中重新装载,临时的 system prompt override 不会改变该 base prompt 的装配规则。
Tools
interface AgentToolResult<TDetails> {
/** 回填给模型的文本或图片内容。 */
content: Array<TextContent | ImageContent>;
/** 提供给 UI、日志和调用方的结构化信息。 */
details: TDetails;
/** 工具自身的用量信息,不参与主模型上下文用量统计。 */
usage?: Usage;
/** 当前工具结果产生后可新增的工具名称。 */
addedToolNames?: string[];
/** 当前工具 batch 完成后是否请求停止 agent。 */
terminate?: boolean;
}
type ToolCallResult<TDetails = unknown> = Promise<AgentToolResult<TDetails>>;
type AgentToolUpdateCallback<TDetails = any> = (partialResult: AgentToolResult<TDetails>) => void;
/** Pi 内置 coding tools。 */
interface BuiltinTools {
read(input: {
path: string;
offset?: number;
limit?: number;
}): ToolCallResult;
bash(input: {
command: string;
timeout?: number;
}): ToolCallResult;
powershell(input: {
command: string;
timeout?: number;
}): ToolCallResult;
edit(input: {
path: string;
edits: Array<{
oldText: string;
newText: string;
}>;
}): ToolCallResult;
write(input: {
path: string;
content: string;
}): ToolCallResult;
grep(input: {
pattern: string;
path?: string;
glob?: string;
ignoreCase?: boolean;
literal?: boolean;
context?: number;
limit?: number;
}): ToolCallResult;
find(input: {
pattern: string;
path?: string;
limit?: number;
}): ToolCallResult;
ls(input: {
path?: string;
limit?: number;
}): ToolCallResult;
}
/** 默认 coding agent 使用的工具集合。 */
type CodingTools = Pick<BuiltinTools, "read" | "bash" | "edit" | "write">;
/** 只读 agent 使用的工具集合。 */
type ReadOnlyTools = Pick<BuiltinTools, "read" | "grep" | "find" | "ls">;
/** Extension 注册的自定义工具定义。 */
interface ToolDefinition<TParams extends TSchema = TSchema, TDetails = unknown, TState = any> {
name: string;
label: string;
description: string;
promptSnippet?: string;
promptGuidelines?: string[];
parameters: TParams;
constrainedSampling?: false | ConstrainedSamplingConfig;
renderShell?: "default" | "self";
prepareArguments?: (args: unknown) => Static<TParams>;
executionMode?: "sequential" | "parallel";
execute(
toolCallId: string,
params: Static<TParams>,
signal: AbortSignal | undefined,
onUpdate: AgentToolUpdateCallback<TDetails> | undefined,
ctx: ExtensionContext,
): Promise<AgentToolResult<TDetails>>;
renderCall?: (...args: unknown[]) => unknown;
renderResult?: (...args: unknown[]) => unknown;
}
interface ExtensionTools {
registerTool(tool: ToolDefinition): void;
}
/** pi-subagents 提供的顶层工具。 */
interface PiSubagentsTools {
Agent(input: {
prompt: string;
description: string;
subagent_type: string;
model?: string;
thinking?: "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
max_turns?: number;
run_in_background?: boolean;
resume?: string;
session_file?: string;
isolated?: boolean;
inherit_context?: boolean;
isolation?: "worktree";
/** 仅在 Scheduling 功能启用时加入;传入后改为延迟执行。 */
schedule?: string;
}): ToolCallResult;
get_subagent_result(input: {
agent_id: string;
wait?: boolean;
}): ToolCallResult;
steer_subagent(input: {
agent_id: string;
message: string;
}): ToolCallResult;
}
/** 允许嵌套委派时,注入到子 Agent 的同名受限工具。 */
interface NestedPiSubagentsTools {
Agent(input: {
prompt: string;
description: string;
subagent_type: string;
model?: string;
thinking?: string;
max_turns?: number;
run_in_background?: boolean;
resume?: string;
isolated?: boolean;
inherit_context?: boolean;
isolation?: "worktree";
}): ToolCallResult;
get_subagent_result(input: {
agent_id: string;
wait?: boolean;
}): ToolCallResult;
steer_subagent(input: {
agent_id: string;
message: string;
}): ToolCallResult;
}
read:读取文件内容,也可以处理模型支持的图片。bash:执行 Bash 命令。powershell:执行 PowerShell 命令;输入字段与bash相同。edit:使用精确的oldText/newText替换编辑文件;同一调用可以包含多个互不重叠的 edits。write:创建文件或完整重写文件。grep:按正则或字面量搜索文件内容,支持 glob、大小写、上下文行和结果数量限制。find:按 glob pattern 查找文件。ls:列出目录内容。- 内置工具可以使用自定义 operations 将实际文件或命令操作委托给其他执行环境;这不改变模型可见的工具输入合同。
- Extension 通过
registerTool()增加自定义工具。自定义工具可以流式报告部分结果,也可以提供自定义调用和结果渲染。
Tool Events
type ToolEvent =
| {
type: "tool_execution_start";
toolCallId: string;
toolName: string;
args: unknown;
}
| {
type: "tool_execution_update";
toolCallId: string;
toolName: string;
args: unknown;
partialResult: unknown;
}
| {
type: "tool_execution_end";
toolCallId: string;
toolName: string;
result: unknown;
isError: boolean;
};
- 工具调用会产生 start、可选的 update 和 end 事件。
- 工具可声明 sequential 或 parallel execution mode;未声明时使用 agent loop 的默认策略。
- 工具异常应转换为 error tool result,并通过正常事件和消息生命周期回填给模型。
Compact
interface ContextSummary {
goal: string[];
constraints: string[];
progress: {
done: string[];
inProgress: string[];
blocked: string[];
};
decisions: string[];
nextSteps: string[];
criticalContext: string[];
}
function compactContext(messages: Message[]): Promise<ContextSummary>;
- compact 将长对话压缩成结构化 checkpoint,供后续 agent 继续工作。
- 摘要保留目标、约束、进度、决策、下一步和关键上下文。
- compact 改变模型后续可见的消息投影,不等同于删除或压缩底层运行记录。
Extension Surface
interface ExtensionAPI {
registerTool(tool: ExtensionTool): void;
registerCommand(command: ExtensionCommand): void;
registerShortcut(shortcut: ExtensionShortcut): void;
registerEventListener(listener: ExtensionEventListener): void;
}
- Extension 可以注册工具、命令、快捷键和事件监听器。
- tool registry 会合并内置工具、SDK custom tools 和 extension tools,再按当前 active tool 设置筛选。
- 工具定义、当前 active tools 和模型实际可见的 tools 是三个不同层次。