Harness · Coding Agent Book10 / Pi
研究总览
M10 · SOURCE-GROUNDED TUTORIAL

Pi
从源码学会它怎么工作

优秀的可嵌入 Agent Harness 内核与 SDK;默认安全、MCP 和内建多 Agent 控制面刻意保持轻量。 我们不把 README 当结论,而是沿主循环、工具、上下文、权限、扩展、协作和状态一路读到实现。

TypeScript · Embeddable Agent KernelMITc820aa26fe0925 个结论 · 76 处引用
这门课怎么读

先建立直觉,再沿一条任务链钻进代码

参考教程的做法不是把 API 名称罗列出来,而是从一个小白能理解的问题开始,先解释“为什么需要这个机制”,再用概念对比、执行链路和固定提交的源码回答“它究竟怎么做”。本页把 Pi 的 25 个源码结论重新编排成十节课;每个结论都保留证据等级、文件路径、行号和可点击源码。

你会得到一张可复述的架构地图一次完整任务的链路追踪能迁移到自研 Harness 的设计判断
你不会得到把 README 功能当成已验证事实把 prompt 约束说成 OS 沙箱把一次双模型调用夸成多 Agent 平台
M00 · MAP

先看全景:这个 Agent 的控制面在哪里

下面的图不是产品宣传图,而是把固定提交里最关键的入口、循环、模型、工具、安全、状态和协作节点放在一张地图上。

架构图全屏打开 ↗
一轮执行链路全屏打开 ↗
核心机制

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

上下文

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

适用建设

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

M00.5 · TRACE

跟踪一个任务:从输入到交付

把下面九步当成你读源码时的“地图坐标”。每到一个节点,都能回到后面的章节查具体实现。

读图提醒

箭头只表示控制面之间的关系,不代表每个实现都同步、串行或拥有 OS 隔离;真正的边界要以对应章节的源码摘录和 caveat 为准。

M01 · ORIENTATION

先把 Agent 看成一台会交付的机器

如果只看 README,你知道它能做什么;钻进源码后,我们要知道它为什么能做、什么时候会停、失败后谁负责收拾。

先用一个生活比喻

把 Agent 想成一间带传送带的工作室:入口收任务,主循环决定下一步,模型负责提出动作,工具负责动手,状态账本负责让下一班人接着干。

本节阅读法先问问题读事实看代码做迁移判断
读源码时先问本课的判断方式
这一层有没有独立证据?没有就不把其他层的能力冒充成默认行为;回到固定提交继续追调用链。
谁拥有最终控制权?区分模型输出、框架规则、用户审批和 OS 隔离四种不同力量。
这一章先建立通用概念

固定提交账本没有把该维度单独拆出,但它会在其他章节的源码路径中体现。先沿执行链路阅读,再回到报告页核对证据。

小练习 1

打开本节任意一个源码摘录,先遮住白话解释,只根据函数名、状态字段和调用顺序猜它解决什么问题;再展开证据列表,检查你的猜测有没有越过源码边界。

M02 · LOOP

主循环:模型为什么会继续动

一次模型调用为什么会变成十几步?循环靠什么继续,靠什么停止?

先用一个生活比喻

像一个会看回执的快递员:模型先写行动单,工具返回回执,主循环把回执放回桌面,再让模型决定下一张行动单。

这套实现先回答了什么?

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

本节阅读法先问问题读事实看代码做迁移判断
源码问题固定提交给出的线索白话结论
仓库是“通用 Harness 内核 + 完整 Coding Agent 产品层”的双轨架构packages/agent/src/harness/agent-harness.ts:171一边是可嵌入任何产品的发动机,一边是已经带 CLI、会话、扩展和交互界面的整车;当前两套代码有重叠,不能把发动机的新接口直接当成整车每条路径都已采用。
低层循环把 steering、工具执行和 follow-up 分成内外两层packages/agent/src/agent-loop.ts:155用户中途插话会先纠偏当前工作,排队的新任务则等当前回合稳定后再接着做。
截断响应禁止执行工具;每回合可热刷新完整运行状态packages/agent/src/agent-loop.ts:208模型半句话里拼出的命令不会贸然执行;同时下一轮可以换模型、换工具或换说明书。
新 AgentHarness 把请求 hooks、消息持久化和运行时变更统一到 turn boundarypackages/agent/src/harness/agent-harness.ts:354每轮开始先拍一张配置快照,模型请求前后都能挂钩;消息落盘和配置变化在明确边界完成,减少并发写乱序。
01
L1 · fact · 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、会话、扩展和交互界面的整车;当前两套代码有重叠,不能把发动机的新接口直接当成整车每条路径都已采用。

为什么这对自研重要

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

固定提交源码摘录
  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[] = [];
  195  	private followUpQueueMode: QueueMode;
      … 17 lines omitted; exact range 171–223 …
  213  		}
  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 处证据
02
L1 · fact · pi-loop-001

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

先看源码事实

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

翻译成白话

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

为什么这对自研重要

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

固定提交源码摘录
  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;
  179  			}
      … 85 lines omitted; exact range 155–275 …
  265  			// Set as pending so inner loop processes them
  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 处证据
03
L1 · fact · pi-loop-002

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

先看源码事实

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

翻译成白话

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

为什么这对自研重要

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

固定提交源码摘录
  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  			};
  232  			const nextTurnSnapshot = await config.prepareNextTurn?.(nextTurnContext);
      … 2 lines omitted; exact range 208–245 …
  235  				config = {
  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 处证据
04
L1 · fact · pi-harness-001

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

先看源码事实

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

翻译成白话

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

为什么这对自研重要

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

固定提交源码摘录
  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,
  378  			toolContext,
      … 108 lines omitted; exact range 354–497 …
  487  				const nextTurnState = await this.createTurnState();
  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 处证据
小练习 2

打开本节任意一个源码摘录,先遮住白话解释,只根据函数名、状态字段和调用顺序猜它解决什么问题;再展开证据列表,检查你的猜测有没有越过源码边界。

M03 · MODEL

模型调用:流式输出如何变成可执行步骤

模型输出的文字、思考、工具调用和错误,经过哪些转换才进入 Agent 状态?

先用一个生活比喻

模型像电话另一端的同事:你听到的不是一整段录音,而是一串实时片段;Harness 要边听边拼装,还要能在电话断线时留下可恢复的记录。

这套实现先回答了什么?

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

本节阅读法先问问题读事实看代码做迁移判断
源码问题固定提交给出的线索白话结论
Provider 不是单一 OpenAI 兼容层,而是多协议适配矩阵packages/ai/src/providers/all.ts:5每家模型的方言由独立翻译器处理,而不是假设所有服务都说 OpenAI 方言。
Provider 可热注册和覆盖,失败时退回内建组合packages/coding-agent/src/core/model-runtime.ts:193扩展可以接入私有模型甚至替换流协议;某个扩展写坏时,内置模型仍尽量可用。
传输重试与会话重试分层,上下文溢出单独处理packages/ai/src/utils/provider-retry.ts:22HTTP 临时故障在网络层重试;整轮失败在会话层重试;没钱和记忆塞满都不会被误当成网络抖动。
05
L1 · fact · 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 方言。

为什么这对自研重要

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

固定提交源码摘录
    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";
   29  import { opencodeProvider } from "./opencode.ts";
      … 4 lines omitted; exact range 5–44 …
   34  import { qwenTokenPlanCnProvider } from "./qwen-token-plan-cn.ts";
   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 处证据
06
L1 · fact · pi-provider-002

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

先看源码事实

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

翻译成白话

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

为什么这对自研重要

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

固定提交源码摘录
  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 {
  217  			this.models.setProvider(composeModelProvider(providerId, base, this.config, extension));
      … 2 lines omitted; exact range 193–230 …
  220  			this.compositionErrors.set(providerId, error instanceof Error ? error.message : String(error));
  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 处证据
07
L1 · fact · pi-retry-001

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

先看源码事实

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

翻译成白话

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

为什么这对自研重要

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

固定提交源码摘录
   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}`,
   46  		);
      … 9 lines omitted; exact range 22–66 …
   56  	}
   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 处证据
小练习 3

打开本节任意一个源码摘录,先遮住白话解释,只根据函数名、状态字段和调用顺序猜它解决什么问题;再展开证据列表,检查你的猜测有没有越过源码边界。

M04 · TOOLS

工具系统:Agent 的手脚怎样被注册和调度

模型看见的工具说明,和真正执行工具的代码,是不是同一个东西?并发、编辑和失败结果怎么处理?

先用一个生活比喻

工具系统像机场:模型提交登机牌,注册表确认航班,权限闸机检查证件,调度器决定跑道,最后才允许真正起飞。

这套实现先回答了什么?

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

本节阅读法先问问题读事实看代码做迁移判断
源码问题固定提交给出的线索白话结论
工具默认可并行,声明 sequential 或全局策略才串行packages/agent/src/agent-loop.ts:411互不冲突的工具一起跑更快;有副作用的工具可以声明排队。即使并发完成顺序不同,交给模型的账本仍按原顺序。
编辑采用唯一精确替换,并对同一文件串行化packages/coding-agent/src/core/tools/edit.ts:33它不凭模糊相似度猜改哪里;目标文字必须只出现一次。同一文件上的两把手术刀要排队,避免互相覆盖。
read 与 bash 使用不同方向的双阈值截断packages/coding-agent/src/core/tools/truncate.ts:1读文件通常从开头看,命令日志通常看结尾报错;超出的内容没有直接消失,而是留全文路径。
08
L1 · fact · pi-tool-dispatch-001

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

先看源码事实

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

翻译成白话

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

为什么这对自研重要

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

固定提交源码摘录
  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,
  435  	assistantMessage: AssistantMessage,
      … 107 lines omitted; exact range 411–553 …
  543  	const messages: ToolResultMessage[] = [];
  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 处证据
09
L1 · fact · pi-edit-001

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

先看源码事实

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

翻译成白话

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

为什么这对自研重要

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

固定提交源码摘录
   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 处证据
10
L1 · fact · pi-tool-output-001

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

先看源码事实

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

翻译成白话

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

为什么这对自研重要

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

固定提交源码摘录
    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 处证据
小练习 4

打开本节任意一个源码摘录,先遮住白话解释,只根据函数名、状态字段和调用顺序猜它解决什么问题;再展开证据列表,检查你的猜测有没有越过源码边界。

M05 · CONTEXT

上下文:有限窗口怎样装下长任务

当对话、工具输出、计划和记忆越来越多,系统怎样决定留下什么、折叠什么、放到哪里?

先用一个生活比喻

上下文不是聊天记录,而是一张会整理的工作台:常用零件放桌面,旧材料装进档案盒,必要时只留下索引卡。

这套实现先回答了什么?

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

本节阅读法先问问题读事实看代码做迁移判断
源码问题固定提交给出的线索白话结论
压缩阈值给输出预留固定预算,并尽量使用真实 usagepackages/coding-agent/src/core/compaction/compaction.ts:190不会等输入把窗口完全塞满;先给回答留座位。能拿到模型账单就用真实 token,拿不到才用字符粗算。
cut point 不切 tool result,并支持拆分超长 turnpackages/coding-agent/src/core/compaction/compaction.ts:345工具调用和结果不会被拦腰拆散;最近一个超长回合也不必全丢或全留,前半段概括、后半段保真。
overflow 最多压缩后自动重试一次,扩展可取消或替换摘要packages/coding-agent/src/core/agent-session.ts:1783记忆爆仓会整理再试一次,不会反复总结到死;插件也能接管公司自己的摘要策略。
11
L1 · fact · pi-context-001

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

先看源码事实

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

翻译成白话

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

为什么这对自研重要

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

固定提交源码摘录
  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,
  214  			lastUsageIndex: null,
      … 12 lines omitted; exact range 190–237 …
  227  		trailingTokens,
  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 处证据
12
L1 · fact · pi-context-002

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

先看源码事实

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

翻译成白话

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

为什么这对自研重要

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

固定提交源码摘录
  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   */
  369  export function findTurnStartIndex(entries: SessionEntry[], entryIndex: number, startIndex: number): number {
      … 80 lines omitted; exact range 345–460 …
  450  
  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 处证据
13
L1 · fact · pi-context-003

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

先看源码事实

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

翻译成白话

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

为什么这对自研重要

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

固定提交源码摘录
 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)");
 1807  			}
      … 106 lines omitted; exact range 1783–1924 …
 1914  				reason: "manual",
 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 处证据
小练习 5

打开本节任意一个源码摘录,先遮住白话解释,只根据函数名、状态字段和调用顺序猜它解决什么问题;再展开证据列表,检查你的猜测有没有越过源码边界。

M06 · SECURITY

权限与沙箱:能做什么,在哪里做

审批按钮、规则引擎、容器和操作系统沙箱分别解决什么问题?为什么“问过用户”不等于“隔离了风险”?

先用一个生活比喻

审批像门卫问你有没有预约,沙箱像把访客关在指定房间;前者决定是否放行,后者限制放行后能摸到什么。

这套实现先回答了什么?

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

本节阅读法先问问题读事实看代码做迁移判断
源码问题固定提交给出的线索白话结论
默认 CLI 在宿主 shell/filesystem 执行,不是沙箱packages/coding-agent/src/core/tools/bash.ts:82接口上可以换成远端 VM 或容器,但开箱即用的那套仍是在你的真实电脑里读写和跑命令。
Project Trust 保护仓库可执行资源,但不审批每条命令packages/coding-agent/src/core/project-trust.ts:24它问的是“要不要加载这个仓库自带的插件和说明书”,不是“接下来这条 rm 命令能不能执行”。
本地凭据文件使用权限收紧和跨进程锁packages/coding-agent/src/core/auth-storage.ts:21密钥本不仅不让其他本机用户随便看,两个 Pi 进程同时刷新 token 时也不会互相踩掉。
Sandbox 与命令审批是可选示例,不是默认安全基线packages/coding-agent/examples/extensions/sandbox/index.ts:1仓库教你如何装保险箱和门卫,但新车出厂默认没有把它们启用。
14
L1 · limitation · pi-execution-001

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

先看源码事实

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

翻译成白话

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

为什么这对自研重要

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

固定提交源码摘录
   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", () => {});
  106  				child.stdin?.end(command);
      … 31 lines omitted; exact range 82–148 …
  138  					throw new Error(`timeout:${timeout}`);
  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 处证据
15
L1 · fact · pi-trust-001

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

先看源码事实

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

翻译成白话

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

为什么这对自研重要

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

固定提交源码摘录
   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) {
   48  		return options.trustOverride;
      … 36 lines omitted; exact range 24–95 …
   85  
   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 处证据
16
L1 · fact · pi-auth-001

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

先看源码事实

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

翻译成白话

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

为什么这对自研重要

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

固定提交源码摘录
   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);
   45  			chmodSync(this.authPath, 0o600);
      … 89 lines omitted; exact range 21–145 …
  135  			return result;
  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 处证据
17
L1 · limitation · 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,必须安装/加载。

翻译成白话

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

为什么这对自研重要

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

固定提交源码摘录
    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": {
   25   *     "denyRead": ["~/.ssh", "~/.aws"],
      … 11 lines omitted; exact range 1–47 …
   37   * Setup:
   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 处证据
小练习 6

打开本节任意一个源码摘录,先遮住白话解释,只根据函数名、状态字段和调用顺序猜它解决什么问题;再展开证据列表,检查你的猜测有没有越过源码边界。

M07 · ECOSYSTEM

指令、MCP、Skills 与插件:能力如何接进来

一条系统指令、一个 Skill、一个 MCP server 和一个插件,分别在什么时候进入上下文和执行路径?

先用一个生活比喻

这像给工作室接设备:说明书不是设备,设备也不等于电源;成熟 Harness 会分别治理发现、信任、加载、调用和卸载。

这套实现先回答了什么?

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

本节阅读法先问问题读事实看代码做迁移判断
源码问题固定提交给出的线索白话结论
系统指令由 tool、context files、skills、custom/append prompt 多层拼装packages/coding-agent/src/core/system-prompt.ts:28模型看到的不是一段写死提示词,而是当前工具说明、用户规则、项目规则、技能目录和工作目录拼成的最终说明书。
Skill 采用懒加载目录,项目范围受 trust 控制packages/coding-agent/src/core/skills.ts:118先给模型一本技能目录,不把每本说明书都塞进上下文;它决定要用时再打开。仓库自带技能则要先信任仓库。
扩展在同一进程执行,覆盖面接近完整产品内核packages/coding-agent/src/core/extensions/loader.ts:66插件不是只能加一个小工具,它几乎能摸到 Agent 每个关节;代价是插件代码和主进程同等权限。
固定提交未实现内建 MCP client/serverpackages/coding-agent/src/core/agent-session.ts:2454Pi 可以通过扩展接任意协议,但开箱没有像部分竞品那样配置 MCP server 后自动发现 tools/resources/prompts。
18
L1 · fact · 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 再叠加。

翻译成白话

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

为什么这对自研重要

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

固定提交源码摘录
   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  		}
   52  
      … 8 lines omitted; exact range 28–71 …
   61  		}
   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 处证据
19
L1 · fact · 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 仅在信任后加载。

翻译成白话

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

为什么这对自研重要

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

固定提交源码摘录
  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",
  142  				baseDir,
      … 35 lines omitted; exact range 118–188 …
  178  	rootDir?: string,
  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 处证据
20
L1 · fact · 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 每个关节;代价是插件代码和主进程同等权限。

为什么这对自研重要

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

固定提交源码摘录
   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");
   90  	const typeboxValueEntry = require.resolve("typebox/value");
      … 23 lines omitted; exact range 66–124 …
  114  	_aliases = {
  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 处证据
21
L2 · limitation · 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。

为什么这对自研重要

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

固定提交源码摘录
 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" }),
 2478  					},
      … 55 lines omitted; exact range 2454–2544 …
 2534  				nextActiveToolNames.push(tool.name);
 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 处证据
小练习 7

打开本节任意一个源码摘录,先遮住白话解释,只根据函数名、状态字段和调用顺序猜它解决什么问题;再展开证据列表,检查你的猜测有没有越过源码边界。

M08 · COLLABORATION

子 Agent:把一个大任务拆成可治理的协作

什么时候是普通工具调用,什么时候才算子 Agent?子 Agent 的上下文、预算、取消和结果怎样回到父 Agent?

先用一个生活比喻

不是把同事叫来聊天就叫协作;真正的协作要有工单、权限、截止时间、交付物和回收机制。

这套实现先回答了什么?

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

本节阅读法先问问题读事实看代码做迁移判断
源码问题固定提交给出的线索白话结论
子 Agent 是独立 pi 进程示例,不是内建调度控制平面packages/coding-agent/examples/extensions/subagent/index.ts:1它展示了“主 Agent 叫几个临时 Pi 工人”的方法,但没有内核级 durable queue、共享记忆、远程 worker 或统一权限继承。
22
L1 · limitation · 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 或统一权限继承。

为什么这对自研重要

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

固定提交源码摘录
    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,
   25  	getAgentDir,
   26  	getMarkdownTheme,
   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 处证据
小练习 8

打开本节任意一个源码摘录,先遮住白话解释,只根据函数名、状态字段和调用顺序猜它解决什么问题;再展开证据列表,检查你的猜测有没有越过源码边界。

M09 · STATE

会话、持久化与观测:让一次运行变成可追溯事实

如果进程崩了、用户刷新了、任务跑了一夜,系统凭什么恢复并解释“刚才究竟发生了什么”?

先用一个生活比喻

内存像白板,数据库像目录,append-only journal 像监控录像;可靠 Harness 不只保存最后答案,还保存每次转弯。

这套实现先回答了什么?

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

本节阅读法先问问题读事实看代码做迁移判断
源码问题固定提交给出的线索白话结论
会话是 append-only JSONL 树,可移动叶子、fork 和保存扩展状态packages/coding-agent/src/core/session-manager.ts:30聊天不是一条会被覆盖的直线,而是一棵只追加的版本树;回到旧节点不会删除未来分支。
事件和会话账本细,输出模式丰富,但内核不是完整 OTEL 平台packages/agent/src/types.ts:138本地回放和多种客户端接入很强,能知道花了多少 token、每个工具发生了什么;但不像企业观测平台那样开箱把每步发到 OTEL 后端。
23
L1 · fact · pi-session-001

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

先看源码事实

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

翻译成白话

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

为什么这对自研重要

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

固定提交源码摘录
   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 {
   54  	type: "message";
      … 88 lines omitted; exact range 30–153 …
  143  /** Session entry - has id/parentId for tree structure (returned by "read" methods in SessionManager) */
  144  export type SessionEntry =
  145  	| SessionMessageEntry
  146  	| ThinkingLevelChangeEntry
  147  	| ModelChangeEntry
  148  	| CompactionEntry
  149  	| BranchSummaryEntry
  150  	| CustomEntry
  151  	| CustomMessageEntry
  152  	| LabelEntry
  153  	| SessionInfoEntry;
为什么相信这条结论?查看 3 处证据
24
L1 · fact · 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 后端。

为什么这对自研重要

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

固定提交源码摘录
  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
  162  	 *     return [{ role: "user", content: m.content, timestamp: m.timestamp }];
      … 47 lines omitted; exact range 138–220 …
  210  	 * If it returns true, the loop emits `agent_end` and exits before polling steering or follow-up queues,
  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 处证据
小练习 9

打开本节任意一个源码摘录,先遮住白话解释,只根据函数名、状态字段和调用顺序猜它解决什么问题;再展开证据列表,检查你的猜测有没有越过源码边界。

M10 · ENGINEERING

测试、恢复与工程取舍:把漂亮机制变成可靠产品

哪些行为有测试证明?哪些只是配置或 prompt 约定?当安全、性能、可恢复性冲突时,源码选择了什么?

先用一个生活比喻

这像验收一座桥:图纸说明结构,测试证明承重,故障演练证明断电后还能不能让人安全回来。

这套实现先回答了什么?

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

本节阅读法先问问题读事实看代码做迁移判断
源码问题固定提交给出的线索白话结论
机制测试密集,但仓库行为 eval 目前很窄packages/evals/src/pi-harness.ts:40发动机零件测试很多,真正让整车跑复杂编程赛道的公开考试却还很少。
25
L3 · fact · pi-evals-001

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

先看源码事实

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

翻译成白话

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

为什么这对自研重要

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

固定提交源码摘录
   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({
   64  				type: "tool_result",
      … 95 lines omitted; exact range 40–170 …
  160  						outputTokens: stats.tokens.output,
  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 处证据
小练习 10

打开本节任意一个源码摘录,先遮住白话解释,只根据函数名、状态字段和调用顺序猜它解决什么问题;再展开证据列表,检查你的猜测有没有越过源码边界。

M11 · PRACTICE

把读懂变成会判断

下面的练习不要求你先写一个完整 Agent,而是训练你检查设计边界:事实是什么、推断是什么、如果换成自研产品要补哪一层。

Q1

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

问题:如果把这段机制移植到你的 Agent,最先要确认哪个输入、状态或安全边界?

参考答案

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

证据:packages/agent/src/harness/agent-harness.ts:171
Q2

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

问题:如果把这段机制移植到你的 Agent,最先要确认哪个输入、状态或安全边界?

参考答案

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

证据:packages/agent/src/agent-loop.ts:155
Q3

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

问题:如果把这段机制移植到你的 Agent,最先要确认哪个输入、状态或安全边界?

参考答案

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

证据:packages/agent/src/agent-loop.ts:208
Q4

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

问题:如果把这段机制移植到你的 Agent,最先要确认哪个输入、状态或安全边界?

参考答案

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

证据:packages/agent/src/harness/agent-harness.ts:354
Q5

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

问题:如果把这段机制移植到你的 Agent,最先要确认哪个输入、状态或安全边界?

参考答案

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

证据:packages/agent/src/agent-loop.ts:411
APPENDIX · SOURCE INDEX

本课读过的实现文件

文件索引帮助你在课程外继续追踪调用链;每一条路径来自固定提交的证据账本。

  1. 01packages/agent/src/harness/agent-harness.tsL171–223, L354–497, L512–656, L884–1023
  2. 02packages/coding-agent/src/core/agent-session.tsL375–400, L2560–2598, L520–540, L2630–2705, L1943–2041, L1783–1924, L2047–2195, L2454–2544
  3. 03packages/agent/src/agent-loop.tsL155–275, L208–245, L411–553, L600–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, L86–127
  7. 07packages/coding-agent/src/core/model-runtime.tsL95–173, L193–230
  8. 08packages/coding-agent/src/core/extensions/types.tsL1340–1414, L1179–1340, L1280–1340
  9. 09packages/ai/src/utils/provider-retry.tsL22–66, L105–124
  10. 10packages/coding-agent/src/core/compaction/compaction.tsL190–237, L345–460, L467–537, L710–788
  11. 11packages/coding-agent/test/agent-session-auto-compaction-queue.test.tsL1–100
  12. 12packages/coding-agent/src/core/tools/edit.tsL33–53, L287–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, L217–239
  27. 27packages/coding-agent/src/core/resolve-config-value.tsL1–80
  28. 28packages/coding-agent/src/core/system-prompt.tsL28–71, L79–159
  29. 29packages/coding-agent/src/core/resource-loader.tsL67–123, L38–47
  30. 30packages/coding-agent/src/core/skills.tsL118–188, L327–360
  31. 31packages/coding-agent/src/core/package-manager.tsL2303–2466
  32. 32packages/coding-agent/src/core/extensions/loader.tsL66–124, L230–300
  33. 33packages/coding-agent/test/extensions-runner.test.tsL1–100
  34. 34packages/coding-agent/examples/extensions/sandbox/index.tsL1–47, L201–295
  35. 35packages/coding-agent/examples/extensions/permission-gate.tsL1–35
  36. 36packages/coding-agent/examples/extensions/subagent/index.tsL1–36, L267–428
  37. 37packages/coding-agent/examples/extensions/subagent/agents.tsL26–115
  38. 38packages/coding-agent/src/core/session-manager.tsL30–153, L1015–1188, L1354–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
下一步

从课程回到报告,做一次反向核验

教程负责让你读懂,报告负责让你查证。打开报告页,任选一个章节,尝试只靠源码摘录复述它的边界。

查看 Pi 报告 ↗