CODING AGENT HARNESS · SOURCE AUDITREPORT 10 / 18
10

Pi

优秀的可嵌入 Agent Harness 内核与 SDK;默认安全、MCP 和内建多 Agent 控制面刻意保持轻量。

TypeScript · Embeddable Agent KernelMITmain
SOURCE
VERIFIED
Repository
earendil-works/pi
Commit
c820aa26fe0907e053e881a957722693fc094c9c
Commit date
2026-07-28T00:01:51+02:00
Findings
25
Citations
76
Tracked files
1,148
EXECUTIVE READING

先给结论,再进入源码

核心机制

低层 steering/tool/follow-up;上层 AgentHarness turn boundary

上下文

输出预留;不切 tool result;overflow 只自动重试一次

安全边界

宿主 shell/filesystem;trust 保护项目可执行资源

适用建设

作为自研 Agent SDK 底座、需要极强可编程性

值得借鉴

  • 内核/产品双轨清楚
  • Provider 热注册
  • 扩展 API 覆盖核心边界

需要警惕

  • 默认无沙箱/逐命令审批
  • 无内建 MCP
  • 多 Agent 只到示例层

直接带走

  • turn-boundary runtime refresh
  • cut point 不切工具结果
  • 可替换 extension hooks
00 · METHOD

研究口径:先锁提交,再沿运行链读代码

README POLICY

README 只用于入口定位;核心结论来自 agent loop、AgentHarness、AgentSession、compaction、tool、trust、extension、provider、session 与测试源码。

FACT POLICY

严格区分 packages/agent 的可复用 Harness、packages/coding-agent 的产品运行时,以及 examples/extensions 中必须手动加载的参考实现。

INFERENCE POLICY

仓库不存在的能力只作限定性结论;特别不把示例 permission gate、sandbox、subagent 当作默认产品能力。

L1运行实现
L2接口契约
L3测试证明
L4文档佐证
L5明确推断

本页引用 45 个不同源码/测试文件;证据角色分布:实现 48 · 测试 9 · 契约 9 · Prompt 3 · 配置 1 · 证据 6。代码块是固定提交中的原文截取,长区间仅在中部折叠,首尾行号保持真实。

01 · TECHNICAL MAPS

架构总图与单轮执行链路

两张图均由本页证据账本生成,并通过 Archify showcase 9 项校验(0 error / 0 warning)。图可单独打开、搜索、缩放和追踪关系。

FIGURE 01Pi Harness 架构图全屏打开 ↗
FIGURE 02用户输入到工具回写的技术链路全屏打开 ↗
02 · COVERAGE MAP

审计维度与证据等级

架构与 Agent Loop verified L1 / L2 / L3

已审计低层 agent loop、通用 AgentHarness 和产品 AgentSession 的衔接与重叠。

Provider、流式与重试 verified L1 / L2 / L3

已审计 provider/protocol 矩阵、stream hooks、动态凭据和两层 retry。

上下文、压缩与恢复 verified L1 / L2 / L3

已审计 token 估算、cut point、split turn、结构化摘要、overflow recovery 和 retained tail。

工具、编辑与执行 verified L1 / L2 / L3

已审计 read/bash/edit/write、并行调度、截断、精确替换和同文件互斥。

执行环境、权限与沙箱 verified L1 / L2 / L3

默认 Node/CLI 运行在宿主;sandbox 与 command approval 仅有可选 extension 示例。

信任与凭据 verified L1 / L2 / L3

已审计 project resource trust、settings/package gate 与凭据锁/权限。

指令、Skills 与扩展 verified L1 / L2 / L3

已审计 system/context/skill/template 分层、资源优先级和 in-process extension hooks。

连接器 verified L1 / L2

固定提交的 runtime/tool registry 未实现内建 MCP;可由 extension 注册自定义工具/协议。

子 Agent 与协作 verified L1 / L2

子 Agent 是 subprocess extension 示例,不是默认调度器。

会话与观测 verified L1 / L2 / L3

已审计 JSONL tree、fork/navigation、事件、usage/cost、HTML/JSON/RPC 模式。

测试、评测与成熟度 partial L2 / L3

机制测试密集;仓库行为 eval 目前只有 smoke 与 extension 两个 suite,未见统一 coding benchmark。

01
DIMENSION · ARCHITECTURE-LOOP

架构与 Agent Loop

本章共 4 个可定位结论;结论按“实现事实 → 白话解释 → 工程影响 → 源码摘录”展开。

01
L1事实pi-architecture-001

仓库是“通用 Harness 内核 + 完整 Coding Agent 产品层”的双轨架构

源码事实

packages/agent 的 AgentHarness 直接拥有 session、model、tools、resources、queue 和 hooks;packages/coding-agent 的 AgentSession 仍有自己的 runtime build、resource loader、tool registry、retry 与 compaction 适配。

白话解释

一边是可嵌入任何产品的发动机,一边是已经带 CLI、会话、扩展和交互界面的整车;当前两套代码有重叠,不能把发动机的新接口直接当成整车每条路径都已采用。

对自研 Harness 的含义

架构抽象领先,但迁移期会产生两个 session/compaction/tool 生命周期,需要明确长期收敛边界。

关键源码 · 实现
packages/agent/src/harness/agent-harness.ts · L171–L223
  171  export class AgentHarness<
  172  	TContext extends object | undefined = undefined,
  173  	TSkill extends Skill = Skill,
  174  	TPromptTemplate extends PromptTemplate = PromptTemplate,
  175  	TTool extends AgentHarnessTool<TContext> = AgentHarnessTool<TContext>,
  176  > {
  177  	private session: Session;
  178  	readonly models: Models;
  179  	private phase: AgentHarnessPhase = "idle";
  180  	private runAbortController?: AbortController;
  181  	private runPromise?: Promise<void>;
  182  	private pendingSessionWrites: PendingSessionWrite[] = [];
  183  	private model: Model<any>;
  184  	private thinkingLevel: ThinkingLevel;
  185  	private systemPrompt: AgentHarnessSystemPrompt<TContext, TSkill, TPromptTemplate, TTool> | undefined;
  186  	private toolContext: AgentHarnessToolContextSource<TContext> | undefined;
  187  	private streamOptions: AgentHarnessStreamOptions;
  188  	private retry: RetryPolicy | undefined;
  189  	private resources: AgentHarnessResources<TSkill, TPromptTemplate>;
  190  	private tools = new Map<string, TTool>();
  191  	private activeToolNames: string[];
  192  	private steerQueue: UserMessage[] = [];
  193  	private steeringQueueMode: QueueMode;
  194  	private followUpQueue: UserMessage[] = [];
      … 19 lines omitted; exact range 171–223 …
  214  		this.model = options.model;
  215  		this.thinkingLevel = options.thinkingLevel ?? "off";
  216  		this.activeToolNames = options.activeToolNames
  217  			? [...options.activeToolNames]
  218  			: (options.tools ?? []).map((tool) => tool.name);
  219  		this.validateUniqueNames(this.activeToolNames, "Duplicate active tool name(s)");
  220  		this.validateToolNames(this.activeToolNames);
  221  		this.steeringQueueMode = options.steeringMode ?? "one-at-a-time";
  222  		this.followUpQueueMode = options.followUpMode ?? "one-at-a-time";
  223  	}
查看全部 3 处证据
  • 实现 packages/agent/src/harness/agent-harness.ts:171–223 AgentHarness 持有通用运行时状态和 hook。
  • 实现 packages/coding-agent/src/core/agent-session.ts:375–400 产品 AgentSession 仍自行订阅 agent、安装 hooks、构造 runtime。
  • 实现 packages/coding-agent/src/core/agent-session.ts:2560–2598 产品层独立构造工具和 Agent runtime。
02
L1事实pi-loop-001

低层循环把 steering、工具执行和 follow-up 分成内外两层

源码事实

agentLoop 外层消费 follow-up,内层在 assistant tool call 或 steering 消息存在时继续;每轮发出 agent/turn/message 事件并把工具结果加入上下文。

白话解释

用户中途插话会先纠偏当前工作,排队的新任务则等当前回合稳定后再接着做。

对自研 Harness 的含义

交互打断与自治续跑共享同一 loop,而不是 UI 另开旁路。

关键源码 · 实现
packages/agent/src/agent-loop.ts · L155–L275
  155  async function runLoop(
  156  	initialContext: AgentContext,
  157  	newMessages: AgentMessage[],
  158  	initialConfig: AgentLoopConfig,
  159  	signal: AbortSignal | undefined,
  160  	emit: AgentEventSink,
  161  	streamFunction: StreamFn,
  162  ): Promise<void> {
  163  	let currentContext = initialContext;
  164  	let config = initialConfig;
  165  	let firstTurn = true;
  166  	// Check for steering messages at start (user may have typed while waiting)
  167  	let pendingMessages: AgentMessage[] = (await config.getSteeringMessages?.()) || [];
  168  
  169  	// Outer loop: continues when queued follow-up messages arrive after agent would stop
  170  	while (true) {
  171  		let hasMoreToolCalls = true;
  172  
  173  		// Inner loop: process tool calls and steering messages
  174  		while (hasMoreToolCalls || pendingMessages.length > 0) {
  175  			if (!firstTurn) {
  176  				await emit({ type: "turn_start" });
  177  			} else {
  178  				firstTurn = false;
      … 87 lines omitted; exact range 155–275 …
  266  			pendingMessages = followUpMessages;
  267  			continue;
  268  		}
  269  
  270  		// No more messages, exit
  271  		break;
  272  	}
  273  
  274  	await emit({ type: "agent_end", messages: newMessages });
  275  }
查看全部 2 处证据
  • 实现 packages/agent/src/agent-loop.ts:155–275 内外循环、队列和生命周期事件。
  • 测试 packages/agent/test/agent-loop.test.ts:1–80 低层循环和事件顺序的测试入口。
03
L1事实pi-loop-002

截断响应禁止执行工具;每回合可热刷新完整运行状态

源码事实

assistant stopReason 为 length 时,即使带 tool call 也不执行,因为参数可能被截断;prepareNextTurn 可替换 context、model、thinking、system prompt 和 tools。

白话解释

模型半句话里拼出的命令不会贸然执行;同时下一轮可以换模型、换工具或换说明书。

对自研 Harness 的含义

工具安全包含协议完整性检查,动态配置不必重启会话。

关键源码 · 实现
packages/agent/src/agent-loop.ts · L208–L245
  208  				// A "length" stop means the output was cut off by the token limit, so
  209  				// every tool call in the message may carry truncated arguments. Fail
  210  				// them all instead of executing potentially borked calls.
  211  				const executedToolBatch =
  212  					message.stopReason === "length"
  213  						? await failToolCallsFromTruncatedMessage(toolCalls, emit)
  214  						: await executeToolCalls(currentContext, message, config, signal, emit);
  215  				toolResults.push(...executedToolBatch.messages);
  216  				hasMoreToolCalls = !executedToolBatch.terminate;
  217  
  218  				for (const result of toolResults) {
  219  					currentContext.messages.push(result);
  220  					newMessages.push(result);
  221  				}
  222  			}
  223  
  224  			await emit({ type: "turn_end", message, toolResults });
  225  
  226  			const nextTurnContext = {
  227  				message,
  228  				toolResults,
  229  				context: currentContext,
  230  				newMessages,
  231  			};
      … 4 lines omitted; exact range 208–245 …
  236  					...config,
  237  					model: nextTurnSnapshot.model ?? config.model,
  238  					reasoning:
  239  						nextTurnSnapshot.thinkingLevel === undefined
  240  							? config.reasoning
  241  							: nextTurnSnapshot.thinkingLevel === "off"
  242  								? undefined
  243  								: nextTurnSnapshot.thinkingLevel,
  244  				};
  245  			}
查看全部 2 处证据
  • 实现 packages/agent/src/agent-loop.ts:208–245 length 防执行和 next-turn refresh。
  • 实现 packages/coding-agent/src/core/agent-session.ts:520–540 产品层每回合重建 system/tools/model/thinking。
04
L1事实pi-harness-001

新 AgentHarness 把请求 hooks、消息持久化和运行时变更统一到 turn boundary

源码事实

Harness 在每轮创建 state snapshot,通过 beforeProviderRequest/beforeProviderPayload/afterProviderResponse 包装模型请求;message_end 串行写 session,turn 结束后刷新状态并消费 steer/follow-up;model/tool/resource 变更也持久化并发事件。

白话解释

每轮开始先拍一张配置快照,模型请求前后都能挂钩;消息落盘和配置变化在明确边界完成,减少并发写乱序。

对自研 Harness 的含义

适合作为自研 Harness 的可复用内核参考,尤其是 transport、storage 和 execution capability 的解耦。

关键源码 · 实现
packages/agent/src/harness/agent-harness.ts · L354–L497
  354  	private async createTurnState(): Promise<AgentHarnessTurnState<TContext, TSkill, TPromptTemplate, TTool>> {
  355  		const context = await this.session.buildContext();
  356  		const resources = this.getResources();
  357  		const sessionMetadata = await this.session.getMetadata();
  358  		const toolContext = await this.resolveToolContext();
  359  		const tools = [...this.tools.values()];
  360  		const activeTools = this.activeToolNames
  361  			.map((name) => this.tools.get(name))
  362  			.filter((tool): tool is TTool => tool !== undefined);
  363  		let systemPrompt = "You are a helpful assistant.";
  364  		if (typeof this.systemPrompt === "string") {
  365  			systemPrompt = this.systemPrompt;
  366  		} else if (this.systemPrompt) {
  367  			systemPrompt = await this.systemPrompt({
  368  				session: this.session,
  369  				model: this.model,
  370  				thinkingLevel: this.thinkingLevel,
  371  				activeTools,
  372  				resources,
  373  			});
  374  		}
  375  		return {
  376  			messages: context.messages,
  377  			resources,
      … 110 lines omitted; exact range 354–497 …
  488  				setTurnState(nextTurnState);
  489  				return {
  490  					context: this.createContext(nextTurnState),
  491  					model: nextTurnState.model,
  492  					thinkingLevel: nextTurnState.thinkingLevel,
  493  				};
  494  			},
  495  			getSteeringMessages: async () => this.drainQueuedMessages(this.steerQueue, this.steeringQueueMode),
  496  			getFollowUpMessages: async () => this.drainQueuedMessages(this.followUpQueue, this.followUpQueueMode),
  497  		};
查看全部 3 处证据
  • 实现 packages/agent/src/harness/agent-harness.ts:354–497 turn snapshot、请求 hooks 和 loop 装配。
  • 实现 packages/agent/src/harness/agent-harness.ts:512–656 串行持久化、turn 执行和 settled。
  • 实现 packages/agent/src/harness/agent-harness.ts:884–1023 运行中模型、工具和资源变更。
02
DIMENSION · TOOLS-EDITING

工具、编辑与执行

本章共 3 个可定位结论;结论按“实现事实 → 白话解释 → 工程影响 → 源码摘录”展开。

05
L1事实pi-tool-dispatch-001

工具默认可并行,声明 sequential 或全局策略才串行

源码事实

同一 assistant message 的 tool calls 默认 Promise 并发;任一工具 executionMode=sequential 或配置顺序执行时改为串行。结果按原 tool call 顺序返回,before hook 可阻断,after hook 可改写结果、usage 和 terminate。

白话解释

互不冲突的工具一起跑更快;有副作用的工具可以声明排队。即使并发完成顺序不同,交给模型的账本仍按原顺序。

对自研 Harness 的含义

吞吐高,但自定义工具作者必须正确声明副作用和并发语义。

关键源码 · 实现
packages/agent/src/agent-loop.ts · L411–L553
  411  async function executeToolCalls(
  412  	currentContext: AgentContext,
  413  	assistantMessage: AssistantMessage,
  414  	config: AgentLoopConfig,
  415  	signal: AbortSignal | undefined,
  416  	emit: AgentEventSink,
  417  ): Promise<ExecutedToolCallBatch> {
  418  	const toolCalls = assistantMessage.content.filter((c) => c.type === "toolCall");
  419  	const hasSequentialToolCall = toolCalls.some(
  420  		(tc) => currentContext.tools?.find((t) => t.name === tc.name)?.executionMode === "sequential",
  421  	);
  422  	if (config.toolExecution === "sequential" || hasSequentialToolCall) {
  423  		return executeToolCallsSequential(currentContext, assistantMessage, toolCalls, config, signal, emit);
  424  	}
  425  	return executeToolCallsParallel(currentContext, assistantMessage, toolCalls, config, signal, emit);
  426  }
  427  
  428  type ExecutedToolCallBatch = {
  429  	messages: ToolResultMessage[];
  430  	terminate: boolean;
  431  };
  432  
  433  async function executeToolCallsSequential(
  434  	currentContext: AgentContext,
      … 109 lines omitted; exact range 411–553 …
  544  	for (const finalized of orderedFinalizedCalls) {
  545  		const toolResultMessage = createToolResultMessage(finalized);
  546  		await emitToolResultMessage(toolResultMessage, emit);
  547  		messages.push(toolResultMessage);
  548  	}
  549  
  550  	return {
  551  		messages,
  552  		terminate: shouldTerminateToolBatch(orderedFinalizedCalls),
  553  	};
查看全部 3 处证据
  • 实现 packages/agent/src/agent-loop.ts:411–553 顺序/并行调度和结果顺序。
  • 实现 packages/agent/src/agent-loop.ts:600–650 参数校验和 before/after hooks。
  • 测试 packages/coding-agent/test/agent-session-concurrent.test.ts:1–90 产品 session 的并发行为测试入口。
06
L1事实pi-edit-001

编辑采用唯一精确替换,并对同一文件串行化

源码事实

edit 要求 oldText 在文件中唯一且 replacement 不重叠,保留 BOM/行尾,写回后返回 diff/patch;同 canonical path 的 edit/write 进入 mutation queue,不同文件仍可并行。

白话解释

它不凭模糊相似度猜改哪里;目标文字必须只出现一次。同一文件上的两把手术刀要排队,避免互相覆盖。

对自研 Harness 的含义

机制简单可审计,面对漂移代码时会失败并要求模型重新读取。

关键源码 · 契约
packages/coding-agent/src/core/tools/edit.ts · L33–L53
   33  const replaceEditSchema = Type.Object(
   34  	{
   35  		oldText: Type.String({
   36  			description:
   37  				"Exact text for one targeted replacement. It must be unique in the original file and must not overlap with any other edits[].oldText in the same call.",
   38  		}),
   39  		newText: Type.String({ description: "Replacement text for this targeted edit." }),
   40  	},
   41  	{},
   42  );
   43  
   44  const editSchema = Type.Object(
   45  	{
   46  		path: Type.String({ description: "Path to the file to edit (relative or absolute)" }),
   47  		edits: Type.Array(replaceEditSchema, {
   48  			description:
   49  				"One or more targeted replacements. Each edit is matched against the original file, not incrementally. Do not include overlapping or nested edits. If two changes touch the same block or nearby lines, merge them into one edit instead.",
   50  		}),
   51  	},
   52  	{},
   53  );
查看全部 4 处证据
  • 契约 packages/coding-agent/src/core/tools/edit.ts:33–53 精确唯一替换 schema。
  • 实现 packages/coding-agent/src/core/tools/edit.ts:287–361 读取、规范化、写入和 diff。
  • 实现 packages/coding-agent/src/core/tools/file-mutation-queue.ts:28–60 per-file serialization。
  • 测试 packages/coding-agent/test/file-mutation-queue.test.ts:1–100 并发文件修改测试入口。
07
L1事实pi-tool-output-001

read 与 bash 使用不同方向的双阈值截断

源码事实

默认输出上限 2000 行或 50KB;read 保留头部并给 offset 续读提示,bash 保留尾部并把完整超长输出写入 /tmp/pi-bash-*.log。

白话解释

读文件通常从开头看,命令日志通常看结尾报错;超出的内容没有直接消失,而是留全文路径。

对自研 Harness 的含义

减少工具输出占满上下文,同时维持可追溯性。

关键源码 · 配置
packages/coding-agent/src/core/tools/truncate.ts · L1–L12
    1  /**
    2   * Shared truncation utilities for tool outputs.
    3   *
    4   * Truncation is based on two independent limits - whichever is hit first wins:
    5   * - Line limit (default: 2000 lines)
    6   * - Byte limit (default: 50KB)
    7   *
    8   * Never returns partial lines (except bash tail truncation edge case).
    9   */
   10  
   11  export const DEFAULT_MAX_LINES = 2000;
   12  export const DEFAULT_MAX_BYTES = 50 * 1024; // 50KB
查看全部 3 处证据
  • 配置 packages/coding-agent/src/core/tools/truncate.ts:1–12 默认双阈值和方向。
  • 实现 packages/coding-agent/src/core/tools/read.ts:203–300 文本/图片读取与 continuation。
  • 实现 packages/coding-agent/src/core/bash-executor.ts:46–129 bash 尾部截断与全文临时文件。
03
DIMENSION · PROVIDER-STREAMING-RETRY

Provider、流式与重试

本章共 3 个可定位结论;结论按“实现事实 → 白话解释 → 工程影响 → 源码摘录”展开。

08
L1事实pi-provider-001

Provider 不是单一 OpenAI 兼容层,而是多协议适配矩阵

源码事实

内建 provider registry 装配 Anthropic、OpenAI/Codex/Azure、Google/Vertex、Bedrock、Mistral、Kimi/Moonshot、OpenRouter、GitHub Copilot、xAI 等数十个 provider;API 目录分别实现 messages、responses、chat completions、Gemini、Bedrock 和 pi-messages 协议。

白话解释

每家模型的方言由独立翻译器处理,而不是假设所有服务都说 OpenAI 方言。

对自研 Harness 的含义

覆盖面很广,也意味着协议行为和 usage/tool schema 回归成本高。

关键源码 · 实现
packages/ai/src/providers/all.ts · L5–L44
    5  import { amazonBedrockProvider } from "./amazon-bedrock.ts";
    6  import { antLingProvider } from "./ant-ling.ts";
    7  import { anthropicProvider } from "./anthropic.ts";
    8  import { azureOpenAIResponsesProvider } from "./azure-openai-responses.ts";
    9  import { cerebrasProvider } from "./cerebras.ts";
   10  import { cloudflareAIGatewayProvider } from "./cloudflare-ai-gateway.ts";
   11  import { cloudflareWorkersAIProvider } from "./cloudflare-workers-ai.ts";
   12  import modelDataManifest from "./data/.manifest.json" with { type: "json" };
   13  import { deepseekProvider } from "./deepseek.ts";
   14  import { fireworksProvider } from "./fireworks.ts";
   15  import { githubCopilotProvider } from "./github-copilot.ts";
   16  import { googleProvider } from "./google.ts";
   17  import { googleVertexProvider } from "./google-vertex.ts";
   18  import { groqProvider } from "./groq.ts";
   19  import { huggingfaceProvider } from "./huggingface.ts";
   20  import { kimiCodingProvider } from "./kimi-coding.ts";
   21  import { minimaxProvider } from "./minimax.ts";
   22  import { minimaxCnProvider } from "./minimax-cn.ts";
   23  import { mistralProvider } from "./mistral.ts";
   24  import { moonshotaiProvider } from "./moonshotai.ts";
   25  import { moonshotaiCnProvider } from "./moonshotai-cn.ts";
   26  import { nvidiaProvider } from "./nvidia.ts";
   27  import { openaiProvider } from "./openai.ts";
   28  import { openaiCodexProvider } from "./openai-codex.ts";
      … 6 lines omitted; exact range 5–44 …
   35  import { radiusProvider } from "./radius.ts";
   36  import { togetherProvider } from "./together.ts";
   37  import { vercelAIGatewayProvider } from "./vercel-ai-gateway.ts";
   38  import { xaiProvider } from "./xai.ts";
   39  import { xiaomiProvider } from "./xiaomi.ts";
   40  import { xiaomiTokenPlanAmsProvider } from "./xiaomi-token-plan-ams.ts";
   41  import { xiaomiTokenPlanCnProvider } from "./xiaomi-token-plan-cn.ts";
   42  import { xiaomiTokenPlanSgpProvider } from "./xiaomi-token-plan-sgp.ts";
   43  import { zaiProvider } from "./zai.ts";
   44  import { zaiCodingCnProvider } from "./zai-coding-cn.ts";
查看全部 3 处证据
  • 实现 packages/ai/src/providers/all.ts:5–44 内建 provider imports。
  • 实现 packages/ai/src/providers/all.ts:86–127 provider registry 实例化。
  • 实现 packages/coding-agent/src/core/model-runtime.ts:95–173 builtin/config/credential/remote catalog 合成。
09
L1事实pi-provider-002

Provider 可热注册和覆盖,失败时退回内建组合

源码事实

ModelRuntime 合并内建、文件配置、原生扩展配置和 extension provider;扩展可注册模型、OAuth 与自定义 stream。组合错误会保留诊断并回退 builtin,而不是让整个模型目录不可用。

白话解释

扩展可以接入私有模型甚至替换流协议;某个扩展写坏时,内置模型仍尽量可用。

对自研 Harness 的含义

插件化 provider 适合企业网关,但必须把最终解析来源和错误显式展示。

关键源码 · 实现
packages/coding-agent/src/core/model-runtime.ts · L193–L230
  193  	private providerIds(): Set<string> {
  194  		return new Set([
  195  			...this.builtins.keys(),
  196  			...this.nativeExtensionProviders.keys(),
  197  			...this.config.getProviderIds(),
  198  			...this.extensionProviders.keys(),
  199  		]);
  200  	}
  201  
  202  	private recomposeProvider(providerId: string): void {
  203  		const base = this.nativeExtensionProviders.get(providerId) ?? this.builtins.get(providerId);
  204  		const extension = this.extensionProviders.get(providerId);
  205  		if (!base && !this.config.getProvider(providerId) && !extension) {
  206  			this.models.deleteProvider(providerId);
  207  			this.compositionErrors.delete(providerId);
  208  			return;
  209  		}
  210  		if (base && !this.config.getProvider(providerId) && !extension) {
  211  			// No overlays: use the builtin untouched so its auth/login/stream behavior is exact.
  212  			this.models.setProvider(base);
  213  			this.compositionErrors.delete(providerId);
  214  			return;
  215  		}
  216  		try {
      … 4 lines omitted; exact range 193–230 …
  221  			if (base) this.models.setProvider(base);
  222  			else this.models.deleteProvider(providerId);
  223  		}
  224  	}
  225  
  226  	private rebuildProviders(): void {
  227  		this.models.clearProviders();
  228  		this.compositionErrors.clear();
  229  		for (const providerId of this.providerIds()) this.recomposeProvider(providerId);
  230  		this.updateModelSnapshot();
查看全部 2 处证据
  • 实现 packages/coding-agent/src/core/model-runtime.ts:193–230 多来源 overlay 与 fallback。
  • 契约 packages/coding-agent/src/core/extensions/types.ts:1340–1414 扩展 provider/模型注册 API。
10
L1事实pi-retry-001

传输重试与会话重试分层,上下文溢出单独处理

源码事实

AI provider retry 解析 status、retry-after 和 headers,使用带 jitter 的可中止等待且单次上限 60 秒;AgentSession 把 transient error 与 quota/billing/overflow 分开,overflow 不进入普通指数退避。

白话解释

HTTP 临时故障在网络层重试;整轮失败在会话层重试;没钱和记忆塞满都不会被误当成网络抖动。

对自研 Harness 的含义

避免重复重试不可恢复错误,也让 compaction 接管真正的上下文问题。

关键源码 · 实现
packages/ai/src/utils/provider-retry.ts · L22–L66
   22  /** Mirrors the pinned OpenAI/Anthropic SDK retry policy; review when either SDK is upgraded. */
   23  function isRetryableProviderError(error: ProviderError): boolean {
   24  	const shouldRetry = error.headers?.get("x-should-retry");
   25  	if (shouldRetry === "true") return true;
   26  	if (shouldRetry === "false") return false;
   27  
   28  	if (error.status === undefined) return true;
   29  	return (
   30  		error.status === 408 ||
   31  		error.status === 409 ||
   32  		error.status === 429 ||
   33  		(typeof error.status === "number" && error.status >= 500)
   34  	);
   35  }
   36  
   37  function validateServerRetryDelayMs(
   38  	delayMs: number,
   39  	maxRetryDelayMs: number | undefined,
   40  	providerErrorMessage: string,
   41  ): number {
   42  	const maxDelayMs = maxRetryDelayMs ?? DEFAULT_MAX_RETRY_DELAY_MS;
   43  	if (maxDelayMs > 0 && delayMs > maxDelayMs) {
   44  		throw new Error(
   45  			`Server requested ${Math.ceil(delayMs / 1000)}s retry delay (max: ${Math.ceil(maxDelayMs / 1000)}s). ${providerErrorMessage}`,
      … 11 lines omitted; exact range 22–66 …
   57  
   58  	const retryAfter = error.headers?.get("retry-after");
   59  	if (retryAfter) {
   60  		const seconds = Number.parseFloat(retryAfter);
   61  		const delayMs = Number.isNaN(seconds) ? Date.parse(retryAfter) - Date.now() : seconds * 1000;
   62  		return validateServerRetryDelayMs(delayMs, maxRetryDelayMs, error.message);
   63  	}
   64  
   65  	const exponentialDelay = Math.min(0.5 * 2 ** retryIndex, 8) * 1000;
   66  	return exponentialDelay * (1 - Math.random() * 0.25);
查看全部 3 处证据
  • 实现 packages/ai/src/utils/provider-retry.ts:22–66 header/status/retry-after 与 jitter。
  • 实现 packages/ai/src/utils/provider-retry.ts:105–124 可中止等待。
  • 实现 packages/coding-agent/src/core/agent-session.ts:2630–2705 session transient classifier 和 bounded backoff。
04
DIMENSION · CONTEXT-COMPACTION

上下文、压缩与恢复

本章共 3 个可定位结论;结论按“实现事实 → 白话解释 → 工程影响 → 源码摘录”展开。

11
L1事实pi-context-001

压缩阈值给输出预留固定预算,并尽量使用真实 usage

源码事实

默认 reserveTokens 为 16384,contextTokens 超过 contextWindow-reserve 才压缩。估算优先取最后 assistant usage 加尾随消息,缺失时用 chars/4,图片按近似字符成本处理。

白话解释

不会等输入把窗口完全塞满;先给回答留座位。能拿到模型账单就用真实 token,拿不到才用字符粗算。

对自研 Harness 的含义

比单纯按消息数量可靠,但固定 16k 对不同模型/任务可调性很重要。

关键源码 · 实现
packages/coding-agent/src/core/compaction/compaction.ts · L190–L237
  190  function getLastAssistantUsageInfo(messages: AgentMessage[]): { usage: Usage; index: number } | undefined {
  191  	for (let i = messages.length - 1; i >= 0; i--) {
  192  		const usage = getAssistantUsage(messages[i]);
  193  		if (usage) return { usage, index: i };
  194  	}
  195  	return undefined;
  196  }
  197  
  198  /**
  199   * Estimate context tokens from messages, using the last assistant usage when available.
  200   * If there are messages after the last usage, estimate their tokens with estimateTokens.
  201   */
  202  export function estimateContextTokens(messages: AgentMessage[]): ContextUsageEstimate {
  203  	const usageInfo = getLastAssistantUsageInfo(messages);
  204  
  205  	if (!usageInfo) {
  206  		let estimated = 0;
  207  		for (const message of messages) {
  208  			estimated += estimateTokens(message);
  209  		}
  210  		return {
  211  			tokens: estimated,
  212  			usageTokens: 0,
  213  			trailingTokens: estimated,
      … 14 lines omitted; exact range 190–237 …
  228  		lastUsageIndex: usageInfo.index,
  229  	};
  230  }
  231  
  232  /**
  233   * Check if compaction should trigger based on context usage.
  234   */
  235  export function shouldCompact(contextTokens: number, contextWindow: number, settings: CompactionSettings): boolean {
  236  	if (!settings.enabled) return false;
  237  	return contextTokens > contextWindow - settings.reserveTokens;
查看全部 2 处证据
  • 实现 packages/coding-agent/src/core/compaction/compaction.ts:190–237 默认预算、token 估算与阈值。
  • 实现 packages/coding-agent/src/core/agent-session.ts:1943–2041 真实 usage、stale usage 与 overflow 判定。
12
L1事实pi-context-002

cut point 不切 tool result,并支持拆分超长 turn

源码事实

算法从后往前保留 recentTokens,切点不会落在 tool result;若单回合太长可把 turn prefix 单独摘要、保留后段。summary 使用 Goal/Constraints/Progress/Decisions/Next Steps/Critical Context 结构并可迭代更新旧摘要。

白话解释

工具调用和结果不会被拦腰拆散;最近一个超长回合也不必全丢或全留,前半段概括、后半段保真。

对自研 Harness 的含义

上下文压缩保留执行连续性,结构化摘要便于后续 Agent 接棒。

关键源码 · 实现
packages/coding-agent/src/core/compaction/compaction.ts · L345–L460
  345  /**
  346   * Find valid cut points: indices of context-visible user-like or assistant messages.
  347   * Never cut at tool results (they must follow their tool call).
  348   * When we cut at an assistant message with tool calls, its tool results follow it
  349   * and will be kept.
  350   */
  351  function findValidCutPoints(entries: SessionEntry[], startIndex: number, endIndex: number): number[] {
  352  	const cutPoints: number[] = [];
  353  	for (let i = startIndex; i < endIndex; i++) {
  354  		const entry = entries[i];
  355  		if (entry.type === "compaction") {
  356  			continue;
  357  		}
  358  		if (sessionEntryToContextMessages(entry).some(isCutPointMessage)) {
  359  			cutPoints.push(i);
  360  		}
  361  	}
  362  	return cutPoints;
  363  }
  364  
  365  /**
  366   * Find the context-visible user-role message that starts the turn containing the given entry index.
  367   * Returns -1 if no turn start found before the index.
  368   */
      … 82 lines omitted; exact range 345–460 …
  451  	// Determine if this is a split turn
  452  	const cutEntry = entries[cutIndex];
  453  	const startsTurn = isTurnStartEntry(cutEntry);
  454  	const turnStartIndex = startsTurn ? -1 : findTurnStartIndex(entries, cutIndex, startIndex);
  455  
  456  	return {
  457  		firstKeptEntryIndex: cutIndex,
  458  		turnStartIndex,
  459  		isSplitTurn: !startsTurn && turnStartIndex !== -1,
  460  	};
查看全部 3 处证据
  • 实现 packages/coding-agent/src/core/compaction/compaction.ts:345–460 cut point、tool result 安全和 split turn。
  • Prompt packages/coding-agent/src/core/compaction/compaction.ts:467–537 结构化增量摘要指令。
  • 实现 packages/coding-agent/src/core/compaction/compaction.ts:710–788 摘要区间、retained messages 和文件操作提取。
13
L1事实pi-context-003

overflow 最多压缩后自动重试一次,扩展可取消或替换摘要

源码事实

manual/auto compaction 都先触发 extension hook;扩展可取消或提供完整 compaction。overflow 路径移除错误消息后只重试一次,阈值型压缩不会无条件续跑,只有队列还有消息才继续。

白话解释

记忆爆仓会整理再试一次,不会反复总结到死;插件也能接管公司自己的摘要策略。

对自研 Harness 的含义

有明确的恢复上限和可插拔压缩策略。

关键源码 · 实现
packages/coding-agent/src/core/agent-session.ts · L1783–L1924
 1783  	async compact(customInstructions?: string): Promise<CompactionResult> {
 1784  		this._disconnectFromAgent();
 1785  		await this.abort();
 1786  		this._compactionAbortController = new AbortController();
 1787  		this._emit({ type: "compaction_start", reason: "manual" });
 1788  
 1789  		try {
 1790  			if (!this.model) {
 1791  				throw new Error(formatNoModelSelectedMessage());
 1792  			}
 1793  
 1794  			const { apiKey, headers, env } = await this._getSummarizationRequestAuth(this.model);
 1795  
 1796  			const pathEntries = this.sessionManager.getBranch();
 1797  			const settings = this.settingsManager.getCompactionSettings();
 1798  
 1799  			const preparation = prepareCompaction(pathEntries, settings);
 1800  			if (!preparation) {
 1801  				// Check why we can't compact
 1802  				const lastEntry = pathEntries[pathEntries.length - 1];
 1803  				if (lastEntry?.type === "compaction") {
 1804  					throw new Error("Already compacted");
 1805  				}
 1806  				throw new Error("Nothing to compact (session too small)");
      … 108 lines omitted; exact range 1783–1924 …
 1915  				result: undefined,
 1916  				aborted,
 1917  				willRetry: false,
 1918  				errorMessage: aborted ? undefined : `Compaction failed: ${message}`,
 1919  			});
 1920  			throw error;
 1921  		} finally {
 1922  			this._compactionAbortController = undefined;
 1923  			this._reconnectToAgent();
 1924  		}
查看全部 3 处证据
  • 实现 packages/coding-agent/src/core/agent-session.ts:1783–1924 manual compact 与 extension override。
  • 实现 packages/coding-agent/src/core/agent-session.ts:2047–2195 auto compact、一次重试和队列续跑。
  • 测试 packages/coding-agent/test/agent-session-auto-compaction-queue.test.ts:1–100 压缩与 queued message 行为测试入口。
05
DIMENSION · EXECUTION-SANDBOX-PERMISSION

执行环境、权限与沙箱

本章共 2 个可定位结论;结论按“实现事实 → 白话解释 → 工程影响 → 源码摘录”展开。

14
L1限制pi-execution-001

默认 CLI 在宿主 shell/filesystem 执行,不是沙箱

源码事实

bash 默认用 cwd、继承宿主 env、spawn 用户 shell、无默认 timeout;read/edit/write 支持绝对路径和 ~,没有 cwd containment。通用 Harness 虽抽象 FileSystem/Shell/ExecutionEnv,默认 NodeExecutionEnv 仍直连宿主。

白话解释

接口上可以换成远端 VM 或容器,但开箱即用的那套仍是在你的真实电脑里读写和跑命令。

对自研 Harness 的含义

企业无人值守场景必须提供另一套 ExecutionEnv,不能把接口抽象等同于安全隔离。

关键源码 · 实现
packages/coding-agent/src/core/tools/bash.ts · L82–L148
   82  export function createLocalBashOperations(options?: { shellPath?: string }): BashOperations {
   83  	return {
   84  		exec: async (command, cwd, { onData, signal, timeout, env }) => {
   85  			const timeoutMs = resolveTimeoutMs(timeout);
   86  			if (signal?.aborted) {
   87  				throw new Error("aborted");
   88  			}
   89  			const shellConfig = getShellConfig(options?.shellPath);
   90  			try {
   91  				await fsAccess(cwd, constants.F_OK);
   92  			} catch {
   93  				throw new Error(`Working directory does not exist: ${cwd}\nCannot execute bash commands.`);
   94  			}
   95  
   96  			const commandFromStdin = shellConfig.commandTransport === "stdin";
   97  			const child = spawn(shellConfig.shell, commandFromStdin ? shellConfig.args : [...shellConfig.args, command], {
   98  				cwd,
   99  				detached: process.platform !== "win32",
  100  				env: env ?? getShellEnv(),
  101  				stdio: [commandFromStdin ? "pipe" : "ignore", "pipe", "pipe"],
  102  				windowsHide: true,
  103  			});
  104  			if (commandFromStdin) {
  105  				child.stdin?.on("error", () => {});
      … 33 lines omitted; exact range 82–148 …
  139  				}
  140  				return { exitCode };
  141  			} finally {
  142  				if (child.pid) untrackDetachedChildPid(child.pid);
  143  				if (timeoutHandle) clearTimeout(timeoutHandle);
  144  				if (signal) signal.removeEventListener("abort", onAbort);
  145  			}
  146  		},
  147  	};
  148  }
查看全部 4 处证据
  • 实现 packages/coding-agent/src/core/tools/bash.ts:82–148 本地 shell executor、cwd/env 和 process tree。
  • 实现 packages/coding-agent/src/core/tools/path-utils.ts:44–50 绝对路径和 tilde 解析。
  • 契约 packages/agent/src/harness/types.ts:146–373 可替换 filesystem/shell/env capability。
  • 实现 packages/agent/src/harness/env/nodejs.ts:344–497 默认 Node host shell。
15
L1限制pi-sandbox-example-001

Sandbox 与命令审批是可选示例,不是默认安全基线

源码事实

sandbox example 依赖 Anthropic sandbox-runtime,在 macOS sandbox-exec/Linux bubblewrap 中覆盖 bash/user_bash;permission-gate example 只用正则拦 rm -rf、sudo、chmod/chown 777 并弹 UI。两者位于 examples/extensions,必须安装/加载。

白话解释

仓库教你如何装保险箱和门卫,但新车出厂默认没有把它们启用。

对自研 Harness 的含义

能力表必须写“可扩展实现”,不能打成“默认支持沙箱/审批”。

关键源码 · 证据
packages/coding-agent/examples/extensions/sandbox/index.ts · L1–L47
    1  /**
    2   * Sandbox Extension - OS-level sandboxing for bash commands
    3   *
    4   * Uses @anthropic-ai/sandbox-runtime to enforce filesystem and network
    5   * restrictions on bash commands at the OS level (sandbox-exec on macOS,
    6   * bubblewrap on Linux).
    7   *
    8   * Note: this example intentionally overrides the built-in `bash` tool to show
    9   * how built-in tools can be replaced. Alternatively, you could sandbox `bash`
   10   * via `tool_call` input mutation without replacing the tool.
   11   *
   12   * Config files (merged, project takes precedence):
   13   * - ~/.pi/agent/extensions/sandbox.json (global)
   14   * - <cwd>/.pi/sandbox.json (project-local)
   15   *
   16   * Example .pi/sandbox.json:
   17   * ```json
   18   * {
   19   *   "enabled": true,
   20   *   "network": {
   21   *     "allowedDomains": ["github.com", "*.github.com"],
   22   *     "deniedDomains": []
   23   *   },
   24   *   "filesystem": {
      … 13 lines omitted; exact range 1–47 …
   38   * 1. Copy sandbox/ directory to ~/.pi/agent/extensions/
   39   * 2. Run `npm install` in ~/.pi/agent/extensions/sandbox/
   40   *
   41   * Linux also requires: bubblewrap, socat, ripgrep
   42   */
   43  
   44  import { spawn } from "node:child_process";
   45  import { existsSync, readFileSync } from "node:fs";
   46  import { join } from "node:path";
   47  import { SandboxManager, type SandboxRuntimeConfig } from "@anthropic-ai/sandbox-runtime";
查看全部 3 处证据
  • 证据 packages/coding-agent/examples/extensions/sandbox/index.ts:1–47 示例依赖、平台与默认 sandbox 配置。
  • 证据 packages/coding-agent/examples/extensions/sandbox/index.ts:201–295 覆盖 bash/user_bash 的可选实现。
  • 证据 packages/coding-agent/examples/extensions/permission-gate.ts:1–35 基于有限正则的可选审批示例。
06
DIMENSION · TRUST-SECRETS

信任与凭据

本章共 2 个可定位结论;结论按“实现事实 → 白话解释 → 工程影响 → 源码摘录”展开。

16
L1事实pi-trust-001

Project Trust 保护仓库可执行资源,但不审批每条命令

源码事实

检测到项目 settings、extensions、skills、prompts、themes、SYSTEM/APPEND 等资源才询问 trust;headless 默认不信任。未信任时清空项目 settings 并禁止项目资源装载/写入,但 built-in bash/edit 仍没有逐命令 approval。

白话解释

它问的是“要不要加载这个仓库自带的插件和说明书”,不是“接下来这条 rm 命令能不能执行”。

对自研 Harness 的含义

resource trust 和 action authorization 是两道不同的门,报告与产品 UI 都必须分开。

关键源码 · 实现
packages/coding-agent/src/core/project-trust.ts · L24–L95
   24  function formatProjectTrustPrompt(cwd: string): string {
   25  	return `Trust project folder?\n${cwd}\n\nThis allows pi to load ${CONFIG_DIR_NAME} settings and resources, install missing project packages, and execute project extensions.`;
   26  }
   27  
   28  async function selectProjectTrustOption(
   29  	cwd: string,
   30  	ctx: ProjectTrustContext,
   31  ): Promise<ProjectTrustOption | undefined> {
   32  	const options = getProjectTrustOptions(cwd, { includeSessionOnly: true });
   33  	const selected = await ctx.ui.select(
   34  		formatProjectTrustPrompt(cwd),
   35  		options.map((option) => option.label),
   36  	);
   37  	return options.find((option) => option.label === selected);
   38  }
   39  
   40  function saveProjectTrustPromptResult(trustStore: ProjectTrustStore, result: ProjectTrustOption): void {
   41  	if (result.updates.length > 0) {
   42  		trustStore.setMany(result.updates);
   43  	}
   44  }
   45  
   46  export async function resolveProjectTrusted(options: ResolveProjectTrustedOptions): Promise<boolean> {
   47  	if (options.trustOverride !== undefined) {
      … 38 lines omitted; exact range 24–95 …
   86  	if (!options.projectTrustContext.hasUI) {
   87  		return false;
   88  	}
   89  
   90  	const selected = await selectProjectTrustOption(options.cwd, options.projectTrustContext);
   91  	if (selected !== undefined) {
   92  		saveProjectTrustPromptResult(options.trustStore, selected);
   93  		return selected.trusted;
   94  	}
   95  	return false;
查看全部 4 处证据
  • 实现 packages/coding-agent/src/core/project-trust.ts:24–95 按模式解析信任,headless 默认拒绝。
  • 契约 packages/coding-agent/src/core/trust-manager.ts:29–37 需要信任的项目资源集合。
  • 实现 packages/coding-agent/src/core/settings-manager.ts:450–476 未信任时清空项目 settings。
  • 测试 packages/coding-agent/test/trust-manager.test.ts:1–68 信任发现与存储测试入口。
17
L1事实pi-auth-001

本地凭据文件使用权限收紧和跨进程锁

源码事实

auth storage 创建父目录为 0700、文件为 0600,使用 proper-lockfile 串行更新并在锁内重新读取,避免多个进程覆盖外部修改;API key 也可从环境或命令动态解析。

白话解释

密钥本不仅不让其他本机用户随便看,两个 Pi 进程同时刷新 token 时也不会互相踩掉。

对自研 Harness 的含义

本地 secret hygiene 扎实,但动态命令取 key 本身仍属于受信任配置。

关键源码 · 实现
packages/coding-agent/src/core/auth-storage.ts · L21–L145
   21  const AUTH_FILE_WRITE_OPTIONS = { encoding: "utf-8", mode: 0o600 } as const;
   22  
   23  export interface AuthStorageBackend {
   24  	withLock<T>(fn: (current: string | undefined) => LockResult<T>): T;
   25  	withLockAsync<T>(fn: (current: string | undefined) => Promise<LockResult<T>>): Promise<T>;
   26  }
   27  
   28  export class FileAuthStorageBackend implements AuthStorageBackend {
   29  	private authPath: string;
   30  
   31  	constructor(authPath: string = join(getAgentDir(), "auth.json")) {
   32  		this.authPath = normalizePath(authPath);
   33  	}
   34  
   35  	private ensureParentDir(): void {
   36  		const dir = dirname(this.authPath);
   37  		if (!existsSync(dir)) {
   38  			mkdirSync(dir, { recursive: true, mode: 0o700 });
   39  		}
   40  	}
   41  
   42  	private ensureFileExists(): void {
   43  		if (!existsSync(this.authPath)) {
   44  			writeFileSync(this.authPath, "{}", AUTH_FILE_WRITE_OPTIONS);
      … 91 lines omitted; exact range 21–145 …
  136  		} finally {
  137  			if (release) {
  138  				try {
  139  					await release();
  140  				} catch {
  141  					// Ignore unlock errors when lock is compromised.
  142  				}
  143  			}
  144  		}
  145  	}
查看全部 3 处证据
  • 实现 packages/coding-agent/src/core/auth-storage.ts:21–145 目录/文件模式、锁与原子更新。
  • 实现 packages/coding-agent/src/core/auth-storage.ts:217–239 读取时解析配置值并在锁内更新。
  • 实现 packages/coding-agent/src/core/resolve-config-value.ts:1–80 环境变量和命令型配置值解析。
07
DIMENSION · INSTRUCTIONS-SKILLS-EXTENSIONS

指令、Skills 与扩展

本章共 3 个可定位结论;结论按“实现事实 → 白话解释 → 工程影响 → 源码摘录”展开。

18
L1事实pi-instructions-001

系统指令由 tool、context files、skills、custom/append prompt 多层拼装

源码事实

system prompt 动态包含活跃工具及其 guidelines、产品核心指令、全局和从 cwd 向上的 AGENTS.md/CLAUDE.md、可用 skills XML、cwd;custom system 可替换核心正文但仍追加项目 context/skills,append prompt 再叠加。

白话解释

模型看到的不是一段写死提示词,而是当前工具说明、用户规则、项目规则、技能目录和工作目录拼成的最终说明书。

对自研 Harness 的含义

必须提供最终展开 prompt 的诊断视图,否则层级覆盖很难排查。

关键源码 · 实现
packages/coding-agent/src/core/system-prompt.ts · L28–L71
   28  export function buildSystemPrompt(options: BuildSystemPromptOptions): string {
   29  	const {
   30  		customPrompt,
   31  		selectedTools,
   32  		toolSnippets,
   33  		promptGuidelines,
   34  		appendSystemPrompt,
   35  		cwd,
   36  		contextFiles: providedContextFiles,
   37  		skills: providedSkills,
   38  	} = options;
   39  	const promptCwd = cwd.replace(/\\/g, "/");
   40  
   41  	const appendSection = appendSystemPrompt ? `\n\n${appendSystemPrompt}` : "";
   42  
   43  	const contextFiles = providedContextFiles ?? [];
   44  	const skills = providedSkills ?? [];
   45  
   46  	if (customPrompt) {
   47  		let prompt = customPrompt;
   48  
   49  		if (appendSection) {
   50  			prompt += appendSection;
   51  		}
      … 10 lines omitted; exact range 28–71 …
   62  
   63  		// Append skills section (only if read tool is available)
   64  		const customPromptHasRead = !selectedTools || selectedTools.includes("read");
   65  		if (customPromptHasRead && skills.length > 0) {
   66  			prompt += formatSkillsForPrompt(skills);
   67  		}
   68  
   69  		prompt += `\nCurrent working directory: ${promptCwd}`;
   70  
   71  		return prompt;
查看全部 3 处证据
  • 实现 packages/coding-agent/src/core/system-prompt.ts:28–71 custom prompt 与仍保留的 context/skills。
  • Prompt packages/coding-agent/src/core/system-prompt.ts:79–159 动态 tool、核心 prompt、context、skills 与 cwd。
  • 实现 packages/coding-agent/src/core/resource-loader.ts:67–123 全局/祖先 AGENTS 与 CLAUDE context discovery。
19
L1事实pi-skills-001

Skill 采用懒加载目录,项目范围受 trust 控制

源码事实

Skill discovery 递归查找 SKILL.md,校验 lower-kebab name 和 description,处理 ignore/symlink/冲突;系统 prompt 只放 name/description/location XML,要求模型需要时再读全文。用户 skills 总是可用,项目 .pi/skills 与祖先 .agents/skills 仅在信任后加载。

白话解释

先给模型一本技能目录,不把每本说明书都塞进上下文;它决定要用时再打开。仓库自带技能则要先信任仓库。

对自研 Harness 的含义

节省 token,且把技能供应链纳入项目 trust。

关键源码 · 实现
packages/coding-agent/src/core/skills.ts · L118–L188
  118  	const errors: string[] = [];
  119  
  120  	if (!description || description.trim() === "") {
  121  		errors.push("description is required");
  122  	} else if (description.length > MAX_DESCRIPTION_LENGTH) {
  123  		errors.push(`description exceeds ${MAX_DESCRIPTION_LENGTH} characters (${description.length})`);
  124  	}
  125  
  126  	return errors;
  127  }
  128  
  129  export interface LoadSkillsFromDirOptions {
  130  	/** Directory to scan for skills */
  131  	dir: string;
  132  	/** Source identifier for these skills */
  133  	source: string;
  134  }
  135  
  136  function createSkillSourceInfo(filePath: string, baseDir: string, source: string): SourceInfo {
  137  	switch (source) {
  138  		case "user":
  139  			return createSyntheticSourceInfo(filePath, {
  140  				source: "local",
  141  				scope: "user",
      … 37 lines omitted; exact range 118–188 …
  179  ): LoadSkillsResult {
  180  	const skills: Skill[] = [];
  181  	const diagnostics: ResourceDiagnostic[] = [];
  182  
  183  	if (!existsSync(dir)) {
  184  		return { skills, diagnostics };
  185  	}
  186  
  187  	const root = rootDir ?? dir;
  188  	const ig = ignoreMatcher ?? ignore();
查看全部 3 处证据
  • 实现 packages/coding-agent/src/core/skills.ts:118–188 frontmatter 校验和 skill parsing。
  • Prompt packages/coding-agent/src/core/skills.ts:327–360 lazy skill XML 指令。
  • 实现 packages/coding-agent/src/core/package-manager.ts:2303–2466 user/project/ancestor resource scope 与 trust gate。
20
L1事实pi-extension-001

扩展在同一进程执行,覆盖面接近完整产品内核

源码事实

loader 用 jiti 直接加载 TS/JS;extension API 可监听 trust/resource/session/compaction/context/provider/agent/turn/message/tool/input/user_bash 事件,并注册工具、命令、快捷键、flags、renderer 和 provider。多数 handler 错误被捕获并报告,before tool 失败会阻断执行。

白话解释

插件不是只能加一个小工具,它几乎能摸到 Agent 每个关节;代价是插件代码和主进程同等权限。

对自研 Harness 的含义

扩展生态极强,但必须把安装源和信任当作代码执行供应链治理。

关键源码 · 实现
packages/coding-agent/src/core/extensions/loader.ts · L66–L124
   66  	"@mariozechner/pi-tui": _bundledPiTui,
   67  	"@mariozechner/pi-ai": _bundledPiAiCompat,
   68  	"@mariozechner/pi-ai/compat": _bundledPiAiCompat,
   69  	"@mariozechner/pi-ai/oauth": _bundledPiAiOauth,
   70  	"@mariozechner/pi-ai/providers/all": _bundledPiAiProviders,
   71  	"@mariozechner/pi-coding-agent": _bundledPiCodingAgent,
   72  };
   73  
   74  const require = createRequire(import.meta.url);
   75  
   76  /**
   77   * Get aliases for jiti (used in Node.js/development mode).
   78   * In Bun binary mode, virtualModules is used instead.
   79   */
   80  let _aliases: Record<string, string> | null = null;
   81  
   82  function getAliases(): Record<string, string> {
   83  	if (_aliases) return _aliases;
   84  
   85  	const __dirname = path.dirname(fileURLToPath(import.meta.url));
   86  	const packageIndex = path.resolve(__dirname, "../..", "index.js");
   87  
   88  	const typeboxEntry = require.resolve("typebox");
   89  	const typeboxCompileEntry = require.resolve("typebox/compile");
      … 25 lines omitted; exact range 66–124 …
  115  		"@earendil-works/pi-coding-agent": piCodingAgentEntry,
  116  		"@earendil-works/pi-agent-core": piAgentCoreEntry,
  117  		"@earendil-works/pi-tui": piTuiEntry,
  118  		"@earendil-works/pi-ai/providers/all": piAiProvidersEntry,
  119  		"@earendil-works/pi-ai/compat": piAiCompatEntry,
  120  		"@earendil-works/pi-ai/oauth": piAiOauthEntry,
  121  		"@earendil-works/pi-ai": piAiCompatEntry,
  122  		"@mariozechner/pi-coding-agent": piCodingAgentEntry,
  123  		"@mariozechner/pi-agent-core": piAgentCoreEntry,
  124  		"@mariozechner/pi-tui": piTuiEntry,
查看全部 4 处证据
  • 实现 packages/coding-agent/src/core/extensions/loader.ts:66–124 jiti 同进程加载与 bundled module 映射。
  • 实现 packages/coding-agent/src/core/extensions/loader.ts:230–300 注册工具、命令、快捷键、flag、renderer。
  • 契约 packages/coding-agent/src/core/extensions/types.ts:1179–1340 完整 lifecycle event 和 extension API。
  • 测试 packages/coding-agent/test/extensions-runner.test.ts:1–100 扩展 runner 行为测试入口。
08
DIMENSION · CONNECTORS

连接器

本章共 1 个可定位结论;结论按“实现事实 → 白话解释 → 工程影响 → 源码摘录”展开。

21
L2限制pi-connectors-001

固定提交未实现内建 MCP client/server

源码事实

产品 runtime 枚举的 built-in tools 为 read/bash/edit/write,随后合并 SDK 和 extension tools;resource 与 extension contracts 中没有 MCP transport/session/tool discovery 实现。仓库 packages 源码检索未发现 MCP runtime contract。

白话解释

Pi 可以通过扩展接任意协议,但开箱没有像部分竞品那样配置 MCP server 后自动发现 tools/resources/prompts。

对自研 Harness 的含义

自研平台若依赖 MCP,需要额外的官方扩展或内核 connector 层。

关键源码 · 实现
packages/coding-agent/src/core/agent-session.ts · L2454–L2544
 2454  	private _refreshToolRegistry(options?: { activeToolNames?: string[]; includeAllExtensionTools?: boolean }): void {
 2455  		const previousRegistryNames = new Set(this._toolRegistry.keys());
 2456  		const previousActiveToolNames = this.getActiveToolNames();
 2457  		const allowedToolNames = this._allowedToolNames;
 2458  		const excludedToolNames = this._excludedToolNames;
 2459  		const isAllowedTool = (name: string): boolean =>
 2460  			(!allowedToolNames || allowedToolNames.has(name)) && !excludedToolNames?.has(name);
 2461  
 2462  		const registeredTools = this._extensionRunner.getAllRegisteredTools();
 2463  		const allCustomTools = [
 2464  			...registeredTools,
 2465  			...this._customTools.map((definition) => ({
 2466  				definition,
 2467  				sourceInfo: createSyntheticSourceInfo(`<sdk:${definition.name}>`, { source: "sdk" }),
 2468  			})),
 2469  		].filter((tool) => isAllowedTool(tool.definition.name));
 2470  		const definitionRegistry = new Map<string, ToolDefinitionEntry>(
 2471  			Array.from(this._baseToolDefinitions.entries())
 2472  				.filter(([name]) => isAllowedTool(name))
 2473  				.map(([name, definition]) => [
 2474  					name,
 2475  					{
 2476  						definition,
 2477  						sourceInfo: createSyntheticSourceInfo(`<builtin:${name}>`, { source: "builtin" }),
      … 57 lines omitted; exact range 2454–2544 …
 2535  			}
 2536  		} else if (!options?.activeToolNames) {
 2537  			for (const toolName of this._toolRegistry.keys()) {
 2538  				if (!previousRegistryNames.has(toolName)) {
 2539  					nextActiveToolNames.push(toolName);
 2540  				}
 2541  			}
 2542  		}
 2543  
 2544  		this.setActiveToolsByName([...new Set(nextActiveToolNames)]);
查看全部 3 处证据
  • 实现 packages/coding-agent/src/core/agent-session.ts:2454–2544 工具来源只包含 builtins、SDK 和 extension。
  • 契约 packages/coding-agent/src/core/resource-loader.ts:38–47 resource contract 未含 MCP session。
  • 契约 packages/coding-agent/src/core/extensions/types.ts:1280–1340 可由扩展自行注册工具/事件作为扩展点。
09
DIMENSION · SUBAGENTS-COLLABORATION

子 Agent 与协作

本章共 1 个可定位结论;结论按“实现事实 → 白话解释 → 工程影响 → 源码摘录”展开。

22
L1限制pi-subagent-001

子 Agent 是独立 pi 进程示例,不是内建调度控制平面

源码事实

subagent extension 支持 single/parallel/chain,最多 8 tasks、并发 4、每任务输出上限 50KB;每个任务 spawn `pi --mode json -p --no-session`,用 0600 临时 prompt,聚合 usage,取消时 TERM 后 KILL。Agent 定义来自 user/project Markdown。

白话解释

它展示了“主 Agent 叫几个临时 Pi 工人”的方法,但没有内核级 durable queue、共享记忆、远程 worker 或统一权限继承。

对自研 Harness 的含义

适合参考 subprocess orchestration,不应与成熟多 Agent 控制平面等同。

关键源码 · 证据
packages/coding-agent/examples/extensions/subagent/index.ts · L1–L36
    1  /**
    2   * Subagent Tool - Delegate tasks to specialized agents
    3   *
    4   * Spawns a separate `pi` process for each subagent invocation,
    5   * giving it an isolated context window.
    6   *
    7   * Supports three modes:
    8   *   - Single: { agent: "name", task: "..." }
    9   *   - Parallel: { tasks: [{ agent: "name", task: "..." }, ...] }
   10   *   - Chain: { chain: [{ agent: "name", task: "... {previous} ..." }, ...] }
   11   *
   12   * Uses JSON mode to capture structured output from subagents.
   13   */
   14  
   15  import { spawn } from "node:child_process";
   16  import * as fs from "node:fs";
   17  import * as os from "node:os";
   18  import * as path from "node:path";
   19  import type { AgentToolResult } from "@earendil-works/pi-agent-core";
   20  import type { Message } from "@earendil-works/pi-ai";
   21  import { StringEnum } from "@earendil-works/pi-ai";
   22  import {
   23  	CONFIG_DIR_NAME,
   24  	type ExtensionAPI,
      … 2 lines omitted; exact range 1–36 …
   27  	withFileMutationQueue,
   28  } from "@earendil-works/pi-coding-agent";
   29  import { Container, Markdown, Spacer, Text } from "@earendil-works/pi-tui";
   30  import { Type } from "typebox";
   31  import { type AgentConfig, type AgentScope, discoverAgents } from "./agents.ts";
   32  
   33  const MAX_PARALLEL_TASKS = 8;
   34  const MAX_CONCURRENCY = 4;
   35  const COLLAPSED_ITEM_COUNT = 10;
   36  const PER_TASK_OUTPUT_CAP = 50 * 1024;
查看全部 3 处证据
  • 证据 packages/coding-agent/examples/extensions/subagent/index.ts:1–36 示例定位、模式和硬上限。
  • 证据 packages/coding-agent/examples/extensions/subagent/index.ts:267–428 独立进程、临时 prompt、usage 与取消。
  • 证据 packages/coding-agent/examples/extensions/subagent/agents.ts:26–115 user/project agent Markdown discovery。
10
DIMENSION · SESSION-OBSERVABILITY

会话与观测

本章共 2 个可定位结论;结论按“实现事实 → 白话解释 → 工程影响 → 源码摘录”展开。

23
L1事实pi-session-001

会话是 append-only JSONL 树,可移动叶子、fork 和保存扩展状态

源码事实

每个 entry 有 id/parentId,类型覆盖 message、compaction、branch summary、model、thinking、custom;append 不改旧记录,buildContext 投影当前分支。navigation 只移动 leaf,可总结被放弃分支;fork 把路径复制到新 session 并记录 parentSession。

白话解释

聊天不是一条会被覆盖的直线,而是一棵只追加的版本树;回到旧节点不会删除未来分支。

对自研 Harness 的含义

天然支持回溯、分叉和扩展持久状态,适合审计和复杂交互。

关键源码 · 契约
packages/coding-agent/src/core/session-manager.ts · L30–L153
   30  export const CURRENT_SESSION_VERSION = 3;
   31  
   32  export interface SessionHeader {
   33  	type: "session";
   34  	version?: number; // v1 sessions don't have this
   35  	id: string;
   36  	timestamp: string;
   37  	cwd: string;
   38  	parentSession?: string;
   39  }
   40  
   41  export interface NewSessionOptions {
   42  	id?: string;
   43  	parentSession?: string;
   44  }
   45  
   46  export interface SessionEntryBase {
   47  	type: string;
   48  	id: string;
   49  	parentId: string | null;
   50  	timestamp: string;
   51  }
   52  
   53  export interface SessionMessageEntry extends SessionEntryBase {
      … 90 lines omitted; exact range 30–153 …
  144  export type SessionEntry =
  145  	| SessionMessageEntry
  146  	| ThinkingLevelChangeEntry
  147  	| ModelChangeEntry
  148  	| CompactionEntry
  149  	| BranchSummaryEntry
  150  	| CustomEntry
  151  	| CustomMessageEntry
  152  	| LabelEntry
  153  	| SessionInfoEntry;
查看全部 3 处证据
  • 契约 packages/coding-agent/src/core/session-manager.ts:30–153 entry 类型与 parent tree 合同。
  • 实现 packages/coding-agent/src/core/session-manager.ts:1015–1188 append-only 持久化与 entry append。
  • 实现 packages/coding-agent/src/core/session-manager.ts:1354–1490 leaf navigation、branch summary 与 fork。
24
L1事实pi-observability-001

事件和会话账本细,输出模式丰富,但内核不是完整 OTEL 平台

源码事实

Agent 发出 agent/turn/message/tool start/update/end 事件,assistant message 记录 provider/model、token、cost;CLI 支持 interactive、text、json、rpc 和 HTML export。telemetry core 主要是安装 telemetry 开关,固定提交未见完整 trace/span exporter 管线。

白话解释

本地回放和多种客户端接入很强,能知道花了多少 token、每个工具发生了什么;但不像企业观测平台那样开箱把每步发到 OTEL 后端。

对自研 Harness 的含义

自研平台可复用事件合同,但需补 trace id、span、指标/日志导出和集中查询。

关键源码 · 契约
packages/agent/src/types.ts · L138–L220
  138  	/** Thinking level for the next provider request. */
  139  	thinkingLevel?: ThinkingLevel;
  140  }
  141  
  142  export interface PrepareNextTurnContext extends ShouldStopAfterTurnContext {}
  143  
  144  export interface AgentLoopConfig extends SimpleStreamOptions {
  145  	model: Model<any>;
  146  
  147  	/**
  148  	 * Converts AgentMessage[] to LLM-compatible Message[] before each LLM call.
  149  	 *
  150  	 * Each AgentMessage must be converted to a UserMessage, AssistantMessage, or ToolResultMessage
  151  	 * that the LLM can understand. AgentMessages that cannot be converted (e.g., UI-only notifications,
  152  	 * status messages) should be filtered out.
  153  	 *
  154  	 * Contract: must not throw or reject. Return a safe fallback value instead.
  155  	 * Throwing interrupts the low-level agent loop without producing a normal event sequence.
  156  	 *
  157  	 * @example
  158  	 * ```typescript
  159  	 * convertToLlm: (messages) => messages.flatMap(m => {
  160  	 *   if (m.role === "custom") {
  161  	 *     // Convert custom message to user message
      … 49 lines omitted; exact range 138–220 …
  211  	 * without starting another LLM call. The current assistant response and any tool executions finish normally.
  212  	 *
  213  	 * Use this to request a graceful stop after the current turn, e.g. before context gets too full.
  214  	 *
  215  	 * Contract: must not throw or reject. Throwing interrupts the low-level agent loop without producing a normal event sequence.
  216  	 */
  217  	shouldStopAfterTurn?: (context: ShouldStopAfterTurnContext) => boolean | Promise<boolean>;
  218  
  219  	/**
  220  	 * Called after `turn_end` and before the loop decides whether another provider request should start.
查看全部 4 处证据
  • 契约 packages/agent/src/types.ts:138–220 agent/turn/message/tool event union。
  • 实现 packages/coding-agent/src/cli/args.ts:63–170 json/rpc/print/export/session 等模式。
  • 实现 packages/coding-agent/src/main.ts:109–124 interactive/json/print/rpc mode selection。
  • 实现 packages/coding-agent/src/core/telemetry.ts:1–14 有限的 telemetry 开关实现。
11
DIMENSION · TESTS-EVALS-MATURITY

测试、评测与成熟度

本章共 1 个可定位结论;结论按“实现事实 → 白话解释 → 工程影响 → 源码摘录”展开。

25
L3事实pi-evals-001

机制测试密集,但仓库行为 eval 目前很窄

源码事实

仓库包含 317 个 *.test.ts,覆盖 loop、compaction、trust、extensions、provider 和文件并发;packages/evals 的真实 AgentSession harness 会隔离临时目录、捕获 transcript/usage/tool calls,但 suite 只有“巴黎” smoke 与创建/重载/调用 hello extension 两类。

白话解释

发动机零件测试很多,真正让整车跑复杂编程赛道的公开考试却还很少。

对自研 Harness 的含义

工程成熟度不能只看单测数量;自研应补 repo-level edit/test/recovery、安全红队和长期任务 benchmark。

关键源码 · 测试
packages/evals/src/pi-harness.ts · L40–L170
   40  	return { provider, model };
   41  }
   42  
   43  function toTranscriptEvents(messages: AgentSession["messages"]): TranscriptEvent[] {
   44  	const events: TranscriptEvent[] = [];
   45  	for (const message of messages) {
   46  		if (message.role === "user") {
   47  			events.push({ type: "message", role: "user", content: contentText(message.content) });
   48  		} else if (message.role === "assistant") {
   49  			const text = contentText(message.content);
   50  			if (text) events.push({ type: "message", role: "assistant", content: text });
   51  			for (const part of message.content) {
   52  				if (part.type === "toolCall") {
   53  					events.push({
   54  						type: "tool_call",
   55  						id: part.id,
   56  						name: part.name,
   57  						arguments: normalizeRecord(part.arguments),
   58  					});
   59  				}
   60  			}
   61  		} else if (message.role === "toolResult") {
   62  			const text = contentText(message.content);
   63  			events.push({
      … 97 lines omitted; exact range 40–170 …
  161  						totalTokens: stats.tokens.total,
  162  						toolCalls: stats.toolCalls,
  163  					},
  164  				},
  165  			};
  166  		} finally {
  167  			signal?.removeEventListener("abort", abort);
  168  		}
  169  	} catch (error) {
  170  		outcome = { success: false, error };
查看全部 3 处证据
  • 测试 packages/evals/src/pi-harness.ts:40–170 真实 AgentSession、隔离目录和结果采集。
  • 测试 packages/evals/src/smoke.eval.ts:5–16 基础无工具 smoke。
  • 测试 packages/evals/src/extensions.eval.ts:16–41 extension 创建、reload 与 tool call eval。
APPENDIX · SOURCE INDEX

本报告引用过的实现文件

这是一份代码阅读索引,不是仓库文件总表。机器候选扫描覆盖整个仓库;进入结论的文件必须经人工沿调用链复核。

  1. 01packages/agent/src/harness/agent-harness.tsL171–223, 354–497, 512–656, 884–1023
  2. 02packages/coding-agent/src/core/agent-session.tsL375–400, 2560–2598, 520–540, 2630–2705, 1943–2041, 1783–1924, 2047–2195, 2454–2544
  3. 03packages/agent/src/agent-loop.tsL155–275, 208–245, 411–553, 600–650
  4. 04packages/agent/test/agent-loop.test.tsL1–80
  5. 05packages/coding-agent/test/agent-session-concurrent.test.tsL1–90
  6. 06packages/ai/src/providers/all.tsL5–44, 86–127
  7. 07packages/coding-agent/src/core/model-runtime.tsL95–173, 193–230
  8. 08packages/coding-agent/src/core/extensions/types.tsL1340–1414, 1179–1340, 1280–1340
  9. 09packages/ai/src/utils/provider-retry.tsL22–66, 105–124
  10. 10packages/coding-agent/src/core/compaction/compaction.tsL190–237, 345–460, 467–537, 710–788
  11. 11packages/coding-agent/test/agent-session-auto-compaction-queue.test.tsL1–100
  12. 12packages/coding-agent/src/core/tools/edit.tsL33–53, 287–361
  13. 13packages/coding-agent/src/core/tools/file-mutation-queue.tsL28–60
  14. 14packages/coding-agent/test/file-mutation-queue.test.tsL1–100
  15. 15packages/coding-agent/src/core/tools/truncate.tsL1–12
  16. 16packages/coding-agent/src/core/tools/read.tsL203–300
  17. 17packages/coding-agent/src/core/bash-executor.tsL46–129
  18. 18packages/coding-agent/src/core/tools/bash.tsL82–148
  19. 19packages/coding-agent/src/core/tools/path-utils.tsL44–50
  20. 20packages/agent/src/harness/types.tsL146–373
  21. 21packages/agent/src/harness/env/nodejs.tsL344–497
  22. 22packages/coding-agent/src/core/project-trust.tsL24–95
  23. 23packages/coding-agent/src/core/trust-manager.tsL29–37
  24. 24packages/coding-agent/src/core/settings-manager.tsL450–476
  25. 25packages/coding-agent/test/trust-manager.test.tsL1–68
  26. 26packages/coding-agent/src/core/auth-storage.tsL21–145, 217–239
  27. 27packages/coding-agent/src/core/resolve-config-value.tsL1–80
  28. 28packages/coding-agent/src/core/system-prompt.tsL28–71, 79–159
  29. 29packages/coding-agent/src/core/resource-loader.tsL67–123, 38–47
  30. 30packages/coding-agent/src/core/skills.tsL118–188, 327–360
  31. 31packages/coding-agent/src/core/package-manager.tsL2303–2466
  32. 32packages/coding-agent/src/core/extensions/loader.tsL66–124, 230–300
  33. 33packages/coding-agent/test/extensions-runner.test.tsL1–100
  34. 34packages/coding-agent/examples/extensions/sandbox/index.tsL1–47, 201–295
  35. 35packages/coding-agent/examples/extensions/permission-gate.tsL1–35
  36. 36packages/coding-agent/examples/extensions/subagent/index.tsL1–36, 267–428
  37. 37packages/coding-agent/examples/extensions/subagent/agents.tsL26–115
  38. 38packages/coding-agent/src/core/session-manager.tsL30–153, 1015–1188, 1354–1490
  39. 39packages/agent/src/types.tsL138–220
  40. 40packages/coding-agent/src/cli/args.tsL63–170
  41. 41packages/coding-agent/src/main.tsL109–124
  42. 42packages/coding-agent/src/core/telemetry.tsL1–14
  43. 43packages/evals/src/pi-harness.tsL40–170
  44. 44packages/evals/src/smoke.eval.tsL5–16
  45. 45packages/evals/src/extensions.eval.tsL16–41