CODING AGENT HARNESS · SOURCE AUDITREPORT 17 / 18
17

Prime Agent

把 provider-neutral Agent Loop、可扩展 AgentSession、结构化 compaction、RLM 子 Agent 与 resident daemon 组合成一个可嵌入的 TypeScript coding harness。

TypeScript · Extension-first Pi Harness / RLM Daemon AgentMITmain
SOURCE
VERIFIED
Repository
PrimeIntellect-ai/prime-agent
Commit
a3b3e753490d0a6ed180e905200c1a6690d78608
Commit date
2026-08-11T23:12:36+02:00
Findings
29
Citations
84
Tracked files
1,142
EXECUTIVE READING

先给结论,再进入源码

核心机制

AgentMessage loop;steer → tool batch → follow-up → continuation

上下文

动态 transform;reserve 16K、keep 20K;结构化 summary + branch tree

安全边界

默认 host shell;BashOperations 可替换,timeout/abort 不是 OS 隔离

适用建设

可嵌入 SDK、长任务恢复、TUI/RPC/print/daemon 共享运行时

值得借鉴

  • 低层 loop 与宿主 session 边界干净
  • 压缩摘要和 JSONL 分支恢复细
  • 扩展、MCP、RLM、daemon 控制面覆盖广

需要警惕

  • 默认 bash 是宿主 shell
  • 扩展 API 权限面很宽
  • daemon/RLM 恢复状态机维护成本高

直接带走

  • request-time context transform + dynamic credential
  • summary-first branch-aware session tree
  • before/after tool policy hooks 与 child registry
00 · METHOD

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

README POLICY

README 只用来定位入口和产品边界;结论以 packages/agent、packages/coding-agent 的运行实现、类型契约、资源/扩展/MCP/RLM/daemon 模块和测试为主,固定 shallow HEAD 后记录行号。

FACT POLICY

优先引用可执行 TypeScript、公共类型、持久化协议和测试;默认值、可配置能力和实际强制边界分别描述。源码没有证明 OS 隔离的地方不会把安全口号当成沙箱事实。

INFERENCE POLICY

把低层 agent-loop 与 coding-agent host 的组合关系标为架构推断;把 host shell、扩展权限面和 daemon 恢复复杂度作为限制/风险,不推测模型服务端行为。

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

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

01 · TECHNICAL MAPS

架构总图与单轮执行链路

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

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

审计维度与证据等级

架构与 Agent Loop verified L1 / L2 / L3

纯 AgentMessage loop、AgentSession host、interactive/print/rpc/daemon 入口。

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

上下文转换紧贴 provider call,支持动态 key、流式事件与 abort。

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

reserve/keep 预算、chars/4 估算、合法 cut point、结构化摘要和 branch summary。

工具分发与结果治理 verified L1 / L2 / L3

参数验证、before/after hook、串行/并行、流式结果和终止提示。

执行环境与沙箱 partial L1 / L2 / L3

bash 默认 spawn 宿主 shell;BashOperations 可换远端执行器,但本包没有默认 OS sandbox。

权限与安全 verified L1 / L2 / L3

tool hook、参数验证、abort、资源来源 metadata 与 daemon socket/worker 身份校验。

MCP 与连接器 verified L1 / L2 / L3

McpManager 管 OAuth provider、refresh/config/login host request,并区分内置 catalog 与用户覆盖。

指令、Skills 与插件 verified L1 / L2 / L3 / L4

AGENTS/CLAUDE 上下文、skills/prompts/themes/extensions 动态资源和冲突诊断。

子 Agent 与协作 verified L1 / L2 / L3

RLM child registry、daemon resident workers、prompt admission、heartbeats 与 recovery journals。

持久化与观测 verified L1 / L2 / L3

版本化 JSONL append-only session tree、原子 rewrite、typed events、telemetry/usage。

测试、基准与成熟度 verified L1 / L3

agent loop、compaction、session tree、daemon、RLM、MCP、telemetry 等有独立测试。

01
DIMENSION · ARCHITECTURE-LOOP

架构与 Agent Loop

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

01
L1事实prime-arch-001

低层 Agent Loop 是可复用的 provider-neutral 状态机

源码事实

agentLoop 只接收 prompts、AgentContext、AgentLoopConfig 和可选 streamFn;runLoop 把 steering、tool calls、follow-up、continuation 和 abort 编排成外层/内层循环,并最终发出 agent_end。

白话解释

Prime Agent 把“模型怎么流式回答、什么时候执行工具、用户插话后是否继续”抽成一个不依赖 TUI 的小内核,上层入口只负责喂配置和消费事件。

对自研 Harness 的含义

自研时可把 loop 做成纯运行时,再让 CLI、RPC、桌面 UI 共用;不要让界面组件自己复制一套 tool loop。

关键源码 · 实现
packages/agent/src/agent-loop.ts · L178–L205
  178   * Start an agent loop with a new prompt message.
  179   * The prompt is added to the context and events are emitted for it.
  180   */
  181  export function agentLoop(
  182  	prompts: AgentMessage[],
  183  	context: AgentContext,
  184  	config: AgentLoopConfig,
  185  	signal?: AbortSignal,
  186  	streamFn?: StreamFn,
  187  ): EventStream<AgentEvent, AgentMessage[]> {
  188  	const stream = createAgentStream();
  189  
  190  	endAgentStreamOnError(
  191  		stream,
  192  		runAgentLoop(
  193  			prompts,
  194  			context,
  195  			config,
  196  			async (event) => {
  197  				stream.push(event);
  198  			},
  199  			signal,
  200  			streamFn,
  201  		),
  202  	);
  203  
  204  	return stream;
  205  }
查看全部 3 处证据
  • 实现 packages/agent/src/agent-loop.ts:178–205 agentLoop 创建流并启动 runAgentLoop。
  • 实现 packages/agent/src/agent-loop.ts:307–373 外层/内层 turn loop、stream response、tool batch。
  • 实现 packages/agent/src/agent-loop.ts:422–460 follow-up/continuation poll 后发出 agent_end。
02
L1事实prime-loop-003

Steering、follow-up、continuation 是三个不同的队列语义

源码事实

runLoop 在当前 assistant turn 完成工具后轮询 steering;没有工具和 steering 时才轮询 follow-up;最后通过 getContinuationMessages 处理宿主拥有的长任务续跑策略。

白话解释

用户正在打断时是一种消息,用户等 Agent 停下来再追加是另一种消息,系统为了长目标自动继续又是第三种消息,三者不会混成一个 pending 数组。

对自研 Harness 的含义

长任务产品应给消息定义明确的 admission boundary,才能保证“插话”不会跳过当前工具调用或错误地重放。

关键源码 · 实现
packages/agent/src/agent-loop.ts · L317–L345
  317  	// Check for steering messages at start (user may have typed while waiting)
  318  	let pendingMessages: AgentMessage[] = await pollMessagesUnlessAborted(config.getSteeringMessages, signal);
  319  
  320  	const shouldStopBeforeTurn = (): boolean => !firstTurn && (config.shouldStopBeforeTurn?.() ?? false);
  321  
  322  	// Outer loop: continues when queued follow-up messages arrive after agent would stop
  323  	while (true) {
  324  		throwIfAborted(signal);
  325  		let hasMoreToolCalls = true;
  326  
  327  		// Inner loop: process tool calls and steering messages
  328  		while (hasMoreToolCalls || pendingMessages.length > 0) {
  329  			throwIfAborted(signal);
  330  			if (!firstTurn) {
  331  				await emit({ type: "turn_start" });
  332  			} else {
  333  				firstTurn = false;
  334  			}
  335  
  336  			// Process pending messages (inject before next assistant response)
  337  			if (pendingMessages.length > 0) {
  338  				for (const message of pendingMessages) {
  339  					await emit({ type: "message_start", message });
  340  					await emit({ type: "message_end", message });
  341  					currentContext.messages.push(message);
  342  					newMessages.push(message);
  343  				}
  344  				pendingMessages = [];
  345  			}
查看全部 3 处证据
  • 实现 packages/agent/src/agent-loop.ts:317–345 turn 开始注入 steering pending messages。
  • 实现 packages/agent/src/agent-loop.ts:385–419 shouldStop、steering poll 和 turn boundary。
  • 契约 packages/agent/src/types.ts:206–244 steering/follow-up/continuation 的顺序和契约。
03
L2推断prime-recommend-001

最值得借鉴的是“纯 loop + coding host + extension bus”三层分离

源码事实

低层 agent-loop 只处理消息/工具/流;AgentSession 组合 compaction、resource loader、MCP、RLM、session manager;ExtensionRunner 再把 context/provider/tool/session 变换做成可插拔 bus。

白话解释

这套分层让我们既能复用基础 loop,又能按产品需要装配 TUI、RPC、daemon 或插件,而不是把所有能力塞进一个巨型 Agent 类。

对自研 Harness 的含义

自研架构建议沿此边界拆包:Core Loop、Harness Session、Policy/Extension Bus、Execution Adapters、Persistence/Control Plane。

边界
  • 这是跨模块架构归纳,不是仓库作者的单一官方标签。
关键源码 · 契约
packages/coding-agent/src/core/agent-session.ts · L1–L13
    1  /**
    2   * AgentSession - Core abstraction for agent lifecycle and session management.
    3   *
    4   * This class is shared between all run modes (interactive, print, rpc).
    5   * It encapsulates:
    6   * - Agent state access
    7   * - Event subscription with automatic session persistence
    8   * - Model and thinking level management
    9   * - Compaction (manual and auto)
   10   * - Bash execution
   11   * - Session switching and branching
   12   *
   13   * Modes use this class and add their own I/O layer on top.
查看全部 3 处证据
  • 契约 packages/coding-agent/src/core/agent-session.ts:1–13 AgentSession 作为 interactive/print/rpc 共用 host。
  • 契约 packages/coding-agent/src/core/agent-session.ts:412–500 session host 的 manager/resource/MCP/RLM 配置。
  • 契约 packages/coding-agent/src/core/extensions/types.ts:1028–1058 ExtensionAPI 覆盖 session/context/provider。
02
DIMENSION · PROVIDER-STREAMING

Provider、流式与重试

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

04
L1事实prime-arch-002

Provider 边界前才做上下文变换和密钥解析

源码事实

streamAssistantResponse 在每次 provider 请求前依次执行 transformContext、convertToLlm、动态 getApiKey、动态 system prompt,然后把 Context 交给 streamFunction;流事件逐步回写 context.messages,并对 abort 生成最终 aborted message。

白话解释

上下文不会在会话开始时被一次性拍扁,长任务中每次请求都可以重新裁剪、换系统提示和刷新短期 token。

对自研 Harness 的含义

上下文压缩、OAuth refresh 和 provider 适配应放在真正发请求的边界,避免 token 过期或 stale prompt。

关键源码 · 实现
packages/agent/src/agent-loop.ts · L467–L521
  467  async function streamAssistantResponse(
  468  	context: AgentContext,
  469  	config: AgentLoopConfig,
  470  	signal: AbortSignal | undefined,
  471  	emit: AgentEventSink,
  472  	streamFn?: StreamFn,
  473  ): Promise<AssistantMessage> {
  474  	let partialMessage: AssistantMessage | null = null;
  475  	let addedPartial = false;
  476  	const finishAbortedMessage = async () => {
  477  		const finalMessage = createAbortedAssistantMessage(config, partialMessage);
  478  		if (addedPartial) {
  479  			context.messages[context.messages.length - 1] = finalMessage;
  480  		} else {
  481  			context.messages.push(finalMessage);
  482  			await emit({ type: "message_start", message: { ...finalMessage } });
  483  		}
  484  		await emit({ type: "message_end", message: finalMessage });
  485  		return finalMessage;
  486  	};
  487  
  488  	try {
  489  		throwIfAborted(signal);
  490  		// Apply context transform if configured (AgentMessage[] → AgentMessage[])
      … 21 lines omitted; exact range 467–521 …
  512  		};
  513  
  514  		const response = await maybePromiseWithAbort(
  515  			streamFunction(config.model, llmContext, {
  516  				...config,
  517  				apiKey: resolvedApiKey,
  518  				signal,
  519  			}),
  520  			signal,
  521  		);
查看全部 3 处证据
  • 实现 packages/agent/src/agent-loop.ts:467–521 transformContext、convertToLlm、getApiKey 和 Context 组装。
  • 实现 packages/agent/src/agent-loop.ts:526–603 增量事件、终态 message 和 abort 收尾。
  • 契约 packages/agent/src/types.ts:148–183 transformContext 与动态 API key 契约。
05
L1事实prime-provider-001

运行时支持 provider 动态系统提示和过期 token 刷新

源码事实

AgentLoopConfig 文档明确 getSystemPrompt 每次 LLM call 解析,getApiKey 可为每次调用获取短期 OAuth token;streamAssistantResponse 在请求前执行两者。

白话解释

长时间工具执行后,下一次模型调用仍能拿到最新授权和最新资源说明。

对自研 Harness 的含义

多 provider harness 应把 credential refresh 和 system prompt resolution 做成 request-time hook,而不是启动时静态读取。

关键源码 · 契约
packages/agent/src/types.ts · L170–L183
  170  	transformContext?: (messages: AgentMessage[], signal?: AbortSignal) => Promise<AgentMessage[]>;
  171  
  172  	/** Resolves the system prompt immediately before each LLM call. */
  173  	getSystemPrompt?: () => string;
  174  
  175  	/**
  176  	 * Resolves an API key dynamically for each LLM call.
  177  	 *
  178  	 * Useful for short-lived OAuth tokens (e.g., GitHub Copilot) that may expire
  179  	 * during long-running tool execution phases.
  180  	 *
  181  	 * Contract: must not throw or reject. Return undefined when no key is available.
  182  	 */
  183  	getApiKey?: (provider: string) => Promise<string | undefined> | string | undefined;
查看全部 2 处证据
  • 契约 packages/agent/src/types.ts:170–183 动态 system prompt/API key 设计目的。
  • 实现 packages/agent/src/agent-loop.ts:501–519 请求前 key 和 system prompt resolve。
03
DIMENSION · TOOL-DISPATCH

工具分发与结果治理

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

06
L1事实prime-tools-001

工具调用先预检再执行,支持串行和并行两条路径

源码事实

executeToolCalls 根据全局 toolExecution 或单个工具 executionMode 决定串行/并行;两条路径都先 prepareToolCall,验证参数后才执行,结果 message 保持工具调用源顺序。

白话解释

多个独立查询可以并发跑,但带副作用的工具能强制串行;模型给错参数时不会直接进 shell,而是先变成错误 tool result。

对自研 Harness 的含义

工具调度应把“可并行性”和“参数校验”写进 ToolDefinition,而不是让模型提示词决定。

关键源码 · 实现
packages/agent/src/agent-loop.ts · L608–L623
  608  async function executeToolCalls(
  609  	currentContext: AgentContext,
  610  	assistantMessage: AssistantMessage,
  611  	config: AgentLoopConfig,
  612  	signal: AbortSignal | undefined,
  613  	emit: AgentEventSink,
  614  ): Promise<ExecutedToolCallBatch> {
  615  	const toolCalls = assistantMessage.content.filter((c) => c.type === "toolCall");
  616  	const hasSequentialToolCall = toolCalls.some(
  617  		(tc) => currentContext.tools?.find((t) => t.name === tc.name)?.executionMode === "sequential",
  618  	);
  619  	if (config.toolExecution === "sequential" || hasSequentialToolCall) {
  620  		return executeToolCallsSequential(currentContext, assistantMessage, toolCalls, config, signal, emit);
  621  	}
  622  	return executeToolCallsParallel(currentContext, assistantMessage, toolCalls, config, signal, emit);
  623  }
查看全部 3 处证据
  • 实现 packages/agent/src/agent-loop.ts:608–623 按全局/工具 executionMode 选择串行或并行。
  • 实现 packages/agent/src/agent-loop.ts:630–688 串行工具调用的 prepare、execute、result。
  • 实现 packages/agent/src/agent-loop.ts:690–735 并行准备、Promise.all 与结果顺序。
04
DIMENSION · PERMISSIONS-SECURITY

权限与安全

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

07
L1事实prime-tools-002

before/after tool hook 是可编程的策略门

源码事实

prepareToolCall 在参数 schema 验证后调用 beforeToolCall,返回 block 就生成错误结果而不执行;执行结束后 afterToolCall 可覆盖 content、details、isError 和 terminate。

白话解释

审批、审计、脱敏、工具白名单和“这个结果是否要终止 Agent”都可以在一个统一钩子里实现,而且 block 发生在真正执行前。

对自研 Harness 的含义

策略要留在 dispatch pipeline,而不是散落在每个工具内部;同时要审计扩展是否能覆盖结果或终止状态。

关键源码 · 实现
packages/agent/src/agent-loop.ts · L795–L848
  795  async function prepareToolCall(
  796  	currentContext: AgentContext,
  797  	assistantMessage: AssistantMessage,
  798  	toolCall: AgentToolCall,
  799  	config: AgentLoopConfig,
  800  	signal: AbortSignal | undefined,
  801  ): Promise<PreparedToolCall | ImmediateToolCallOutcome> {
  802  	const tool = currentContext.tools?.find((t) => t.name === toolCall.name);
  803  	if (!tool) {
  804  		return {
  805  			kind: "immediate",
  806  			result: createErrorToolResult(`Tool ${toolCall.name} not found`),
  807  			isError: true,
  808  		};
  809  	}
  810  
  811  	try {
  812  		const preparedToolCall = prepareToolCallArguments(tool, toolCall);
  813  		const validatedArgs = validateToolArguments(tool, preparedToolCall);
  814  		if (config.beforeToolCall) {
  815  			const beforeResult = await maybePromiseWithAbort(
  816  				config.beforeToolCall(
  817  					{
  818  						assistantMessage,
      … 20 lines omitted; exact range 795–848 …
  839  			args: validatedArgs,
  840  		};
  841  	} catch (error) {
  842  		return {
  843  			kind: "immediate",
  844  			result: createErrorToolResult(error instanceof Error ? error.message : String(error)),
  845  			isError: true,
  846  		};
  847  	}
  848  }
查看全部 4 处证据
  • 实现 packages/agent/src/agent-loop.ts:795–848 tool lookup、参数验证与 beforeToolCall block。
  • 实现 packages/agent/src/agent-loop.ts:850–904 abort-safe execute 与 streamed update。
  • 实现 packages/agent/src/agent-loop.ts:906–945 afterToolCall 对结果和 terminate 的覆盖。
  • 契约 packages/agent/src/types.ts:246–277 hook 的阻断与字段覆盖契约。
08
L1事实prime-ext-002

Extension runner 按注册顺序串行执行,session-before 可以取消

源码事实

runner.emit 遍历 extension 和 handler,捕获异常写入 ExtensionError;session_before_* 事件收到 cancel 会立即返回。emitToolCall 也按顺序处理 block,emitContext 与 before_provider_request 允许前一个结果成为下一个输入。

白话解释

扩展链像一条可观察的中间件管线:早期扩展能阻止 session switch 或 tool call,后面的扩展看到前面已经变换过的 context。

对自研 Harness 的含义

要把 extension handler 的顺序、异常隔离、阻断和结果变换写进契约测试,避免插件之间互相覆盖。

关键源码 · 实现
packages/coding-agent/src/core/extensions/runner.ts · L670–L711
  670  	private isSessionBeforeEvent(event: RunnerEmitEvent): event is SessionBeforeEvent {
  671  		return (
  672  			event.type === "session_before_switch" ||
  673  			event.type === "session_before_fork" ||
  674  			event.type === "session_before_compact" ||
  675  			event.type === "session_before_tree"
  676  		);
  677  	}
  678  
  679  	async emit<TEvent extends RunnerEmitEvent>(event: TEvent): Promise<RunnerEmitResult<TEvent>> {
  680  		const ctx = this.createContext();
  681  		let result: SessionBeforeEventResult | undefined;
  682  
  683  		for (const ext of this.extensions) {
  684  			const handlers = ext.handlers.get(event.type);
  685  			if (!handlers || handlers.length === 0) continue;
  686  
  687  			for (const handler of handlers) {
  688  				try {
  689  					const handlerResult = await handler(event, ctx);
  690  
  691  					if (this.isSessionBeforeEvent(event) && handlerResult) {
  692  						result = handlerResult as SessionBeforeEventResult;
  693  						if (result.cancel) {
      … 8 lines omitted; exact range 670–711 …
  702  						event: event.type,
  703  						error: message,
  704  						stack,
  705  					});
  706  				}
  707  			}
  708  		}
  709  
  710  		return result as RunnerEmitResult<TEvent>;
  711  	}
查看全部 4 处证据
  • 实现 packages/coding-agent/src/core/extensions/runner.ts:670–711 顺序 dispatch、session cancel、error capture。
  • 实现 packages/coding-agent/src/core/extensions/runner.ts:805–826 tool_call block chain。
  • 实现 packages/coding-agent/src/core/extensions/runner.ts:857–887 context handler 变换链。
  • 实现 packages/coding-agent/src/core/extensions/runner.ts:889–920 provider request payload 变换链。
05
DIMENSION · CONTEXT-COMPACTION

上下文、压缩与恢复

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

09
L1事实prime-context-001

默认压缩预留 16384 token,尾部保留 20000 token

源码事实

DEFAULT_COMPACTION_SETTINGS 将 enabled 设为 true、reserveTokens 设为 16384、keepRecentTokens 设为 20000;shouldCompact 在 contextTokens > contextWindow - reserveTokens 时触发。

白话解释

它不会等到 provider 报 context overflow 才处理,而是提前留出一块回答空间,再保留最近工作集。

对自研 Harness 的含义

自研应把 reserve/keep 做成可见配置并在模型切换时重新计算,不要硬编码单一窗口。

关键源码 · 配置
packages/coding-agent/src/core/compaction/compaction.ts · L122–L132
  122  export interface CompactionSettings {
  123  	enabled: boolean;
  124  	reserveTokens: number;
  125  	keepRecentTokens: number;
  126  }
  127  
  128  export const DEFAULT_COMPACTION_SETTINGS: CompactionSettings = {
  129  	enabled: true,
  130  	reserveTokens: 16384,
  131  	keepRecentTokens: 20000,
  132  };
查看全部 2 处证据
  • 配置 packages/coding-agent/src/core/compaction/compaction.ts:122–132 压缩默认值。
  • 实现 packages/coding-agent/src/core/compaction/compaction.ts:226–233 enabled 和 contextWindow-reserve 触发条件。
10
L1事实prime-context-002

Token 估算融合 provider usage 与 trailing message 估算

源码事实

estimateContextTokens 使用最后一个有效 assistant usage 作为锚点,对其后的消息用 estimateTokens 累加;没有 usage 时按角色估算,文本采用 chars/4,图片按约 4800 字符计。

白话解释

刚从模型拿到真实 token 账单就用真实数,刚塞进来的工具结果还没账单就先用保守估算,不会因为只看旧 usage 而漏算最新输入。

对自研 Harness 的含义

上下文预算应允许“真实 usage + 未结算尾巴”混合,且图片/工具输出需要有明确估算策略。

关键源码 · 实现
packages/coding-agent/src/core/compaction/compaction.ts · L138–L147
  138  /**
  139   * Calculate total context tokens from usage.
  140   * Uses the native totalTokens field when available, falls back to computing from components.
  141   *
  142   * Includes output: the assistant's response becomes part of the prompt on the next
  143   * request, so it counts toward the context the next turn will send.
  144   */
  145  export function calculateContextTokens(usage: Usage): number {
  146  	return usage.totalTokens || usage.input + usage.output + usage.cacheRead + usage.cacheWrite;
  147  }
查看全部 3 处证据
  • 实现 packages/coding-agent/src/core/compaction/compaction.ts:138–147 usage totalTokens 或分项求和。
  • 实现 packages/coding-agent/src/core/compaction/compaction.ts:192–224 last usage + trailing estimate。
  • 实现 packages/coding-agent/src/core/compaction/compaction.ts:243–297 各角色 chars/4 和图片估算。
11
L1事实prime-context-003

Cut point 避开孤立 tool result,保留完整 tool turn

源码事实

findValidCutPoints 允许在 user/assistant/custom/bashExecution 等消息切分,明确跳过 toolResult;findCutPoint 还能检测切在 turn 中间的情况并保留 turn prefix。

白话解释

压缩不会只删掉工具调用上半段或回执下半段,避免下一轮看到一张没有出处的工具结果。

对自研 Harness 的含义

对话裁剪需要 message role invariant;“按 N 条消息截断”对 coding agent 不够安全。

关键源码 · 实现
packages/coding-agent/src/core/compaction/compaction.ts · L303–L339
  303  /**
  304   * Find valid cut points: indices of user, assistant, custom, or bashExecution messages.
  305   * Never cut at tool results (they must follow their tool call).
  306   * When we cut at an assistant message with tool calls, its tool results follow it
  307   * and will be kept.
  308   * BashExecutionMessage is treated like a user message (user-initiated context).
  309   */
  310  function findValidCutPoints(entries: SessionEntry[], startIndex: number, endIndex: number): number[] {
  311  	const cutPoints: number[] = [];
  312  	for (let i = startIndex; i < endIndex; i++) {
  313  		const entry = entries[i];
  314  		switch (entry.type) {
  315  			case "message": {
  316  				const role = entry.message.role;
  317  				switch (role) {
  318  					case "bashExecution":
  319  					case "custom":
  320  					case "branchSummary":
  321  					case "compactionSummary":
  322  					case "user":
  323  					case "assistant":
  324  						cutPoints.push(i);
  325  						break;
  326  					case "toolResult":
      … 3 lines omitted; exact range 303–339 …
  330  			}
  331  			case "thinking_level_change":
  332  			case "model_change":
  333  			case "compaction":
  334  			case "branch_summary":
  335  			case "custom":
  336  			case "custom_message":
  337  			case "label":
  338  			case "session_info":
  339  				break;
查看全部 3 处证据
  • 实现 packages/coding-agent/src/core/compaction/compaction.ts:303–339 合法 cut point 与 toolResult 排除。
  • 实现 packages/coding-agent/src/core/compaction/compaction.ts:434–458 split turn 与 turnStartIndex。
  • 契约 packages/coding-agent/src/core/compaction/compaction.ts:618–634 messagesToSummarize、turnPrefix、fileOps 结构。
12
L1事实prime-context-004

摘要提示词固定为可恢复的结构化 checkpoint

源码事实

SUMMARIZATION_PROMPT 强制 Goal、Constraints & Preferences、Progress/Done/In Progress/Blocked、Key Decisions、Next Steps、Critical Context 七个区块,并要求保留精确路径、函数名和错误信息;更新摘要会合并 previous-summary。

白话解释

摘要不是一句“我们做了很多事”,而是下一任模型可以按清单接手的交接卡。

对自研 Harness 的含义

长任务的摘要模板应和恢复协议一起版本化,并对路径、错误和下一步做保真约束。

关键源码 · Prompt
packages/coding-agent/src/core/compaction/compaction.ts · L465–L496
  465  const SUMMARIZATION_PROMPT = `The messages above are a conversation to summarize. Create a structured context checkpoint summary that another LLM will use to continue the work.
  466  
  467  Use this EXACT format:
  468  
  469  ## Goal
  470  [What is the user trying to accomplish? Can be multiple items if the session covers different tasks.]
  471  
  472  ## Constraints & Preferences
  473  - [Any constraints, preferences, or requirements mentioned by user]
  474  - [Or "(none)" if none were mentioned]
  475  
  476  ## Progress
  477  ### Done
  478  - [x] [Completed tasks/changes]
  479  
  480  ### In Progress
  481  - [ ] [Current work]
  482  
  483  ### Blocked
  484  - [Issues preventing progress, if any]
  485  
  486  ## Key Decisions
  487  - **[Decision]**: [Brief rationale]
  488  
  489  ## Next Steps
  490  1. [Ordered list of what should happen next]
  491  
  492  ## Critical Context
  493  - [Any data, examples, or references needed to continue]
  494  - [Or "(none)" if not applicable]
  495  
  496  Keep each section concise. Preserve exact file paths, function names, and error messages.`;
查看全部 3 处证据
  • Prompt packages/coding-agent/src/core/compaction/compaction.ts:465–496 固定摘要格式和精确上下文要求。
  • Prompt packages/coding-agent/src/core/compaction/compaction.ts:501–538 previous-summary 更新规则。
  • 实现 packages/coding-agent/src/core/compaction/compaction.ts:552–611 0.8 reserve maxTokens 与 LLM summary。
13
L2推断prime-recommend-002

适合长程 coding,但要为宿主 shell 补强 sandbox policy

源码事实

项目同时提供 request-time context transform、结构化 compaction、branch-aware JSONL、RLM/daemon continuation 和截断 artifact;但默认 bash 是 host spawn,隔离依赖 BashOperations 或更高层。

白话解释

它很适合做“能停下来、能恢复、能继续”的工程 Agent;真正接入不可信代码库时,安全边界要由部署方补齐。

对自研 Harness 的含义

自研落地可以直接借鉴它的 loop/session/compaction 机制,同时把执行适配器设计成强制 sandbox backend,而不是默认本机 shell。

边界
  • “适合长程”是基于实现组合的工程判断;不能替代对具体部署的安全评审。
关键源码 · 实现
packages/coding-agent/src/core/compaction/compaction.ts · L636–L712
  636  export function prepareCompaction(
  637  	pathEntries: SessionEntry[],
  638  	settings: CompactionSettings,
  639  ): CompactionPreparation | undefined {
  640  	if (pathEntries.length > 0 && pathEntries[pathEntries.length - 1].type === "compaction") {
  641  		return undefined;
  642  	}
  643  
  644  	let prevCompactionIndex = -1;
  645  	for (let i = pathEntries.length - 1; i >= 0; i--) {
  646  		if (pathEntries[i].type === "compaction") {
  647  			prevCompactionIndex = i;
  648  			break;
  649  		}
  650  	}
  651  
  652  	let previousSummary: string | undefined;
  653  	let boundaryStart = 0;
  654  	if (prevCompactionIndex >= 0) {
  655  		const prevCompaction = pathEntries[prevCompactionIndex] as CompactionEntry;
  656  		previousSummary = prevCompaction.summary;
  657  		const firstKeptEntryIndex = pathEntries.findIndex((entry) => entry.id === prevCompaction.firstKeptEntryId);
  658  		boundaryStart = firstKeptEntryIndex >= 0 ? firstKeptEntryIndex : prevCompactionIndex + 1;
  659  	}
      … 43 lines omitted; exact range 636–712 …
  703  			extractFileOpsFromMessage(msg, fileOps);
  704  		}
  705  	}
  706  
  707  	return {
  708  		firstKeptEntryId,
  709  		messagesToSummarize,
  710  		turnPrefixMessages,
  711  		isSplitTurn: cutPoint.isSplitTurn,
  712  		tokensBefore,
查看全部 3 处证据
  • 实现 packages/coding-agent/src/core/compaction/compaction.ts:636–712 跨 compaction boundary 的 preparation 与 fileOps。
  • 实现 packages/coding-agent/src/core/session-manager.ts:1345–1475 原子 session persistence。
  • 实现 packages/coding-agent/src/core/tools/bash.ts:66–117 host shell spawn。
06
DIMENSION · PERSISTENCE-OBSERVABILITY

持久化与观测

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

14
L1事实prime-persistence-001

SessionManager 用带 parentId 的 JSONL 树表达分支、压缩和扩展状态

源码事实

SessionEntry 包含 message、compaction、branch_summary、custom、session_state、agent_status 等类型;SessionHeader 保存 version、cwd、parentSession、rlmDepth 和 git context。

白话解释

会话文件不是一条只能向后追加的聊天记录,而是一棵可导航的树;扩展可以持久化自己的 entry,又不会把内部状态强塞给模型。

对自研 Harness 的含义

要支持 fork、resume、compaction 和子 Agent,持久化格式需要 parent link、版本号和“不进入 LLM context”的 entry 类型。

关键源码 · 配置
packages/coding-agent/src/core/session-manager.ts · L33–L54
   33  export const CURRENT_SESSION_VERSION = 3;
   34  const SESSION_LIST_SEARCH_TEXT_MAX_CHARS = 64 * 1024;
   35  const SESSION_LIST_PARSE_MAX_LINE_CHARS = 1024 * 1024;
   36  const SESSION_LIST_LARGE_MESSAGE_PREVIEW_MAX_CHARS = 256;
   37  const SESSION_STREAMING_LOAD_THRESHOLD_BYTES = 128 * 1024 * 1024;
   38  const SESSION_ASYNC_PARSE_YIELD_BYTES = 4 * 1024 * 1024;
   39  
   40  // Entry types that can represent user intent (vs. daemon bookkeeping like
   41  // session_state/agent_status/git_state/child_usage_attributed). Used by
   42  // hasUserContent to decide whether a message-less draft is safe to discard.
   43  const CONTENT_ENTRY_TYPES = new Set([
   44  	"message",
   45  	"custom_message",
   46  	"custom",
   47  	"model_change",
   48  	"thinking_level_change",
   49  	"service_tier_change",
   50  	"session_info",
   51  	"label",
   52  	"compaction",
   53  	"branch_summary",
   54  ]);
查看全部 3 处证据
  • 配置 packages/coding-agent/src/core/session-manager.ts:33–54 当前 session version 和 content entry 类型。
  • 契约 packages/coding-agent/src/core/session-manager.ts:75–85 SessionHeader 字段。
  • 契约 packages/coding-agent/src/core/session-manager.ts:126–163 compaction/branch/custom entry 契约。
15
L1事实prime-persistence-002

Context 重建沿 parent tree,并把 compaction summary 放在 retained messages 前

源码事实

buildSessionContext 从 leaf 沿 parentId 回溯再 reverse,找到 compaction 后把 summary message 与 retainedMessages 放在前面,再追加 compaction 之后的消息;同时恢复 model、thinking level 和 service tier。

白话解释

从任意分支恢复时,模型看到的是“摘要→保留的旧消息→新分支”,UI 还可以知道真实的树边界。

对自研 Harness 的含义

恢复逻辑应是持久化格式的一部分,而不是 UI 临时拼接;压缩和分支要共享同一 ancestry traversal。

关键源码 · 实现
packages/coding-agent/src/core/session-manager.ts · L472–L535
  472  /**
  473   * Build the session context from entries using tree traversal.
  474   * If leafId is provided, walks from that entry to root.
  475   * Handles compaction and branch summaries along the path.
  476   */
  477  export function buildSessionContext(
  478  	entries: SessionEntry[],
  479  	leafId?: string | null,
  480  	byId?: Map<string, SessionEntry>,
  481  ): SessionContext {
  482  	// Build uuid index if not available
  483  	if (!byId) {
  484  		byId = new Map<string, SessionEntry>();
  485  		for (const entry of entries) {
  486  			byId.set(entry.id, entry);
  487  		}
  488  	}
  489  
  490  	// Find leaf
  491  	let leaf: SessionEntry | undefined;
  492  	if (leafId === null) {
  493  		// Explicitly null - return no messages (navigated to before first entry)
  494  		return { messages: [], thinkingLevel: "off", serviceTier: "default", model: null };
  495  	}
      … 30 lines omitted; exact range 472–535 …
  526  		} else if (entry.type === "service_tier_change") {
  527  			serviceTier = entry.serviceTier;
  528  		} else if (entry.type === "model_change") {
  529  			model = { provider: entry.provider, modelId: entry.modelId };
  530  		} else if (entry.type === "message" && entry.message.role === "assistant") {
  531  			model = { provider: entry.message.provider, modelId: entry.message.model };
  532  		} else if (entry.type === "compaction") {
  533  			compaction = entry;
  534  		}
  535  	}
查看全部 2 处证据
  • 实现 packages/coding-agent/src/core/session-manager.ts:472–535 leaf 到 root 的 tree traversal 与 setting 恢复。
  • 实现 packages/coding-agent/src/core/session-manager.ts:537–596 summary-first context 与 retained messages。
16
L1事实prime-persistence-003

写盘采用临时文件 rename,普通 entry 采用 append-only

源码事实

_rewriteFile 将完整 JSONL 写入带随机名的临时文件后 rename;_persist 在已有 assistant 后追加一行,尚未有 assistant 时暂缓非状态 entry,避免半成品会话。

白话解释

完整重写时不会留下半个 JSONL;常规消息又不必每次重写整个文件,兼顾可靠性与成本。

对自研 Harness 的含义

会话持久化要显式处理“第一条 assistant 尚未出现”的预模型状态,并用原子替换防崩溃损坏。

关键源码 · 实现
packages/coding-agent/src/core/session-manager.ts · L1345–L1364
 1345  	private _rewriteFile(): void {
 1346  		if (!this.persist || !this.sessionFile) return;
 1347  		const content = `${this.fileEntries.map((e) => JSON.stringify(e)).join("\n")}\n`;
 1348  		const targetPath = realpathIfPresent(this.sessionFile);
 1349  		const directory = dirname(targetPath);
 1350  		mkdirSync(directory, { recursive: true });
 1351  		const tempPath = join(directory, `.${basename(targetPath)}.${process.pid}.${randomUUID()}.tmp`);
 1352  		try {
 1353  			const metadata = statMetadataIfPresent(targetPath);
 1354  			writeFileSync(tempPath, content, metadata === undefined ? undefined : { mode: metadata.mode });
 1355  			if (metadata !== undefined) {
 1356  				chownSync(tempPath, metadata.uid, metadata.gid);
 1357  				chmodSync(tempPath, metadata.mode);
 1358  			}
 1359  			renameSync(tempPath, targetPath);
 1360  		} finally {
 1361  			rmSync(tempPath, { force: true });
 1362  		}
 1363  		this._notifyPersistListeners();
 1364  	}
查看全部 3 处证据
  • 实现 packages/coding-agent/src/core/session-manager.ts:1345–1364 临时文件、metadata 和原子 rename。
  • 实现 packages/coding-agent/src/core/session-manager.ts:1442–1475 flushNow 与 append persistence guard。
  • 实现 packages/coding-agent/src/core/session-manager.ts:1477–1499 append entry 更新内存 index 和 leaf。
17
L1事实prime-observe-001

Agent events 覆盖 agent、turn、message 和 tool execution 四层

源码事实

AgentEvent 类型至少包括 agent_start/end、turn_start/end、message_start/update/end、tool_execution_start/update/end;AgentSession 还扩展 compaction、RLM child update 等宿主事件。

白话解释

UI 可以画出模型流式文本、工具进度和每个 turn 的边界,而不是只能等最终字符串。

对自研 Harness 的含义

观测模型应把 lifecycle、partial output、tool artifact 和终态分层,便于 replay 与故障定位。

关键源码 · 契约
packages/agent/src/types.ts · L399–L421
  399  /**
  400   * Events emitted by the Agent for UI updates.
  401   *
  402   * `agent_end` is the last event emitted for a run, but awaited `Agent.subscribe()`
  403   * listeners for that event are still part of run settlement. The agent becomes
  404   * idle only after those listeners finish.
  405   */
  406  export type AgentEvent =
  407  	// Agent lifecycle
  408  	| { type: "agent_start" }
  409  	| { type: "agent_end"; messages: AgentMessage[] }
  410  	// Turn lifecycle - a turn is one assistant response + any tool calls/results
  411  	| { type: "turn_start" }
  412  	| { type: "turn_end"; message: AgentMessage; toolResults: ToolResultMessage[] }
  413  	// Message lifecycle - emitted for user, assistant, and toolResult messages
  414  	| { type: "message_start"; message: AgentMessage }
  415  	// Only emitted for assistant messages during streaming
  416  	| { type: "message_update"; message: AgentMessage; assistantMessageEvent: AssistantMessageEvent }
  417  	| { type: "message_end"; message: AgentMessage }
  418  	// Tool execution lifecycle
  419  	| { type: "tool_execution_start"; toolCallId: string; toolName: string; args: any }
  420  	| { type: "tool_execution_update"; toolCallId: string; toolName: string; args: any; partialResult: any }
  421  	| { type: "tool_execution_end"; toolCallId: string; toolName: string; result: any; isError: boolean };
查看全部 2 处证据
  • 契约 packages/agent/src/types.ts:399–421 agent/turn/message/tool event union。
  • 契约 packages/coding-agent/src/core/agent-session.ts:318–390 session compaction、RLM 与 host event 类型。
07
DIMENSION · EXECUTION-SANDBOX

执行环境与沙箱

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

18
L1限制prime-exec-001

默认 bash 是宿主 shell,不等于 OS 沙箱

源码事实

createLocalBashOperations 使用 getShellConfig 得到的 shell,spawn 时传入当前 cwd 与 shell env,detached 子进程由 killProcessTree 管理;BashOperations 只是可插拔执行接口。

白话解释

它能可靠地超时和杀掉进程树,但默认命令就是在当前机器的 shell 里跑;源码没有在这里自动启动容器、Seatbelt 或 bubblewrap。

对自研 Harness 的含义

如果产品面向不可信仓库,必须显式注入远端/容器 BashOperations 或在更上层做 sandbox policy,不能把 timeout 当隔离。

边界
  • 上层配置或 extension 可以替换 operations;本结论只针对这个默认实现。
关键源码 · 契约
packages/coding-agent/src/core/tools/bash.ts · L36–L58
   36  /**
   37   * Pluggable operations for the bash tool.
   38   * Override these to delegate command execution to remote systems (for example SSH).
   39   */
   40  export interface BashOperations {
   41  	/**
   42  	 * Execute a command and stream output.
   43  	 * @param command The command to execute
   44  	 * @param cwd Working directory
   45  	 * @param options Execution options
   46  	 * @returns Promise resolving to exit code (null if killed)
   47  	 */
   48  	exec: (
   49  		command: string,
   50  		cwd: string,
   51  		options: {
   52  			onData: (data: Buffer) => void;
   53  			signal?: AbortSignal;
   54  			timeout?: number;
   55  			env?: NodeJS.ProcessEnv;
   56  		},
   57  	) => Promise<{ exitCode: number | null }>;
   58  }
查看全部 3 处证据
  • 契约 packages/coding-agent/src/core/tools/bash.ts:36–58 BashOperations 可换远端执行器。
  • 实现 packages/coding-agent/src/core/tools/bash.ts:60–117 spawn host shell、env、timeout、abort 和 process tree kill。
  • 实现 packages/coding-agent/src/core/tools/bash.ts:276–298 默认 ops 与 bash schema/command。
19
L1事实prime-exec-002

Bash 输出有行/字节上限并把完整内容留在临时文件

源码事实

Bash tool description 宣布输出按 DEFAULT_MAX_LINES 或 DEFAULT_MAX_BYTES 截断;OutputAccumulator 在流式 update 和结束时生成 truncation/fullOutputPath details。

白话解释

大日志不会把上下文撑爆,模型还能拿到一个完整输出文件路径继续查。

对自研 Harness 的含义

执行工具应把展示输出和可恢复 artifact 分离,并在 tool result 中明确截断范围。

关键源码 · 实现
packages/coding-agent/src/core/tools/bash.ts · L250–L266
  250  	const truncation = result.details?.truncation;
  251  	const fullOutputPath = result.details?.fullOutputPath;
  252  	if (truncation?.truncated || fullOutputPath) {
  253  		const warnings: string[] = [];
  254  		if (fullOutputPath) {
  255  			warnings.push(`Full output: ${fullOutputPath}`);
  256  		}
  257  		if (truncation?.truncated) {
  258  			if (truncation.truncatedBy === "lines") {
  259  				warnings.push(`Truncated: showing ${truncation.outputLines} of ${truncation.totalLines} lines`);
  260  			} else {
  261  				warnings.push(
  262  					`Truncated: ${truncation.outputLines} lines shown (${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit)`,
  263  				);
  264  			}
  265  		}
  266  		component.addChild(new Text(`\n${theme.fg("warning", `[${warnings.join(". ")}]`)}`, 0, 0));
查看全部 3 处证据
  • 实现 packages/coding-agent/src/core/tools/bash.ts:250–266 truncation/fullOutputPath 警告。
  • 契约 packages/coding-agent/src/core/tools/bash.ts:276–287 bash 工具 schema 和截断承诺。
  • 实现 packages/coding-agent/src/core/tools/bash.ts:303–314 流式 snapshot 与 full output details。
20
L2风险prime-risk-001

扩展可以获得 shell/exec 和 active tool 控制权,权限面很宽

源码事实

ExtensionAPI 同时暴露 exec、registerTool、setActiveTools、provider registration、appendEntry 和 session actions;资源 loader 还会加载用户/项目/CLI extension。

白话解释

插件一旦被加载,不只是加一段说明,它可以执行命令、改工具面、持久化状态和注册模型。

对自研 Harness 的含义

生产部署应对 extension 来源做 allowlist/签名/隔离,至少在报告和审批面把 extension 能力显式列出来。

边界
  • 本风险是插件权限面的架构推断,不等于每个 extension 都恶意。
关键源码 · 契约
packages/coding-agent/src/core/extensions/types.ts · L1080–L1163
 1080  	/** Register a tool that the LLM can call. */
 1081  	registerTool<TParams extends TSchema = TSchema, TDetails = unknown, TState = any>(
 1082  		tool: ToolDefinition<TParams, TDetails, TState>,
 1083  	): void;
 1084  
 1085  	// =========================================================================
 1086  	// Command, Shortcut, Flag Registration
 1087  	// =========================================================================
 1088  
 1089  	/** Register a custom command. */
 1090  	registerCommand(name: string, options: Omit<RegisteredCommand, "name" | "sourceInfo">): void;
 1091  
 1092  	/** Register a keyboard shortcut. */
 1093  	registerShortcut(
 1094  		shortcut: KeyId,
 1095  		options: {
 1096  			description?: string;
 1097  			handler: (ctx: ExtensionContext) => Promise<void> | void;
 1098  		},
 1099  	): void;
 1100  
 1101  	/** Register a CLI flag. */
 1102  	registerFlag(
 1103  		name: string,
      … 50 lines omitted; exact range 1080–1163 …
 1154  	setLabel(entryId: string, label: string | undefined): void;
 1155  
 1156  	/** Execute a shell command. */
 1157  	exec(command: string, args: string[], options?: ExecOptions): Promise<ExecResult>;
 1158  
 1159  	/** Get the list of currently active tool names. */
 1160  	getActiveTools(): string[];
 1161  
 1162  	/** Get all configured tools with parameter schema and source metadata. */
 1163  	getAllTools(): ToolInfo[];
查看全部 2 处证据
  • 契约 packages/coding-agent/src/core/extensions/types.ts:1080–1163 插件工具、命令、exec、active tools 能力。
  • 实现 packages/coding-agent/src/core/resource-loader.ts:413–435 启用路径加载并保留冲突诊断。
08
DIMENSION · INSTRUCTIONS-SKILLS-PLUGINS

指令、Skills 与插件

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

21
L1事实prime-resources-001

ResourceLoader 统一管理 skills、prompts、themes、extensions 和 AGENTS 文件

源码事实

ResourceLoader 暴露 getSkills/getPrompts/getThemes/getAgentsFiles/getSystemPrompt、extendResources 和 reload;reload 解析启用路径、加载 extensions、skills、prompts、themes,再按 cwd/agentDir 加载项目上下文文件。

白话解释

指令、技能、主题和扩展不是四套互不相干的扫描器,而是在 reload 时形成一份带诊断的资源快照。

对自研 Harness 的含义

插件/技能热更新要有单一 loader 和明确的 reload boundary,才能通知 session 资源变了。

关键源码 · 契约
packages/coding-agent/src/core/resource-loader.ts · L23–L39
   23  export interface ResourceExtensionPaths {
   24  	skillPaths?: Array<{ path: string; metadata: PathMetadata }>;
   25  	promptPaths?: Array<{ path: string; metadata: PathMetadata }>;
   26  	themePaths?: Array<{ path: string; metadata: PathMetadata }>;
   27  }
   28  
   29  export interface ResourceLoader {
   30  	getExtensions(): LoadExtensionsResult;
   31  	getSkills(): { skills: Skill[]; diagnostics: ResourceDiagnostic[] };
   32  	getPrompts(): { prompts: PromptTemplate[]; diagnostics: ResourceDiagnostic[] };
   33  	getThemes(): { themes: Theme[]; diagnostics: ResourceDiagnostic[] };
   34  	getAgentsFiles(): { agentsFiles: Array<{ path: string; content: string }> };
   35  	getSystemPrompt(): string | undefined;
   36  	getAppendSystemPrompt(): string[];
   37  	extendResources(paths: ResourceExtensionPaths): void;
   38  	reload(): Promise<void>;
   39  }
查看全部 3 处证据
  • 契约 packages/coding-agent/src/core/resource-loader.ts:23–39 ResourceLoader 公共资源接口。
  • 实现 packages/coding-agent/src/core/resource-loader.ts:336–365 settings/package resolution 和 enabled resource paths。
  • 实现 packages/coding-agent/src/core/resource-loader.ts:409–479 extension/skill/prompt/theme/context file reload。
22
L1事实prime-resources-002

资源来源带 user/project/temporary metadata 并去重冲突

源码事实

resource-loader 根据 agentDir 和 cwd 下的 skills/prompts/themes/extensions 根目录给资源标注 scope;mergePaths 用 canonical path 去重,dedupePrompts 对同名 prompt 生成 collision diagnostics。

白话解释

系统知道一个 skill 是用户级、项目级还是临时 CLI 注入的;同名 prompt 不会静默覆盖,而是留下谁赢、谁输的诊断。

对自研 Harness 的含义

自研扩展系统需保留来源元数据和碰撞报告,否则排查“为什么这条指令生效”会非常困难。

关键源码 · 实现
packages/coding-agent/src/core/resource-loader.ts · L646–L678
  646  		const normalizedPath = resolve(filePath);
  647  		const agentRoots = [
  648  			join(this.agentDir, "skills"),
  649  			join(this.agentDir, "prompts"),
  650  			join(this.agentDir, "themes"),
  651  			join(this.agentDir, "extensions"),
  652  		];
  653  		const projectRoots = [
  654  			join(this.cwd, CONFIG_DIR_NAME, "skills"),
  655  			join(this.cwd, CONFIG_DIR_NAME, "prompts"),
  656  			join(this.cwd, CONFIG_DIR_NAME, "themes"),
  657  			join(this.cwd, CONFIG_DIR_NAME, "extensions"),
  658  		];
  659  
  660  		for (const root of agentRoots) {
  661  			if (this.isUnderPath(normalizedPath, root)) {
  662  				return { path: filePath, source: "local", scope: "user", origin: "top-level", baseDir: root };
  663  			}
  664  		}
  665  
  666  		for (const root of projectRoots) {
  667  			if (this.isUnderPath(normalizedPath, root)) {
  668  				return { path: filePath, source: "local", scope: "project", origin: "top-level", baseDir: root };
  669  			}
  670  		}
  671  
  672  		return {
  673  			path: filePath,
  674  			source: "local",
  675  			scope: "temporary",
  676  			origin: "top-level",
  677  			baseDir: statSync(normalizedPath).isDirectory() ? normalizedPath : resolve(normalizedPath, ".."),
  678  		};
查看全部 3 处证据
  • 实现 packages/coding-agent/src/core/resource-loader.ts:646–678 agent/project/temporary source metadata。
  • 实现 packages/coding-agent/src/core/resource-loader.ts:681–690 canonical path 去重。
  • 实现 packages/coding-agent/src/core/resource-loader.ts:811–830 prompt name collision diagnostics。
23
L1事实prime-ext-001

Extension API 覆盖生命周期、工具、命令、provider 和持久化

源码事实

ExtensionAPI 支持 session/context/provider/agent/turn/message/tool/input 等事件,registerTool、registerCommand、registerShortcut、registerFlag、appendEntry、exec、setActiveTools 和 provider registration。

白话解释

扩展不是只能加一个 prompt;它可以加入工具、CLI 命令、模型 provider、上下文变换和 session state。

对自研 Harness 的含义

这种能力适合构建插件生态,但必须把 extension 来源、冲突和权限当作一等公民。

关键源码 · 契约
packages/coding-agent/src/core/extensions/types.ts · L1024–L1074
 1024  /** Handler function type for events */
 1025  // biome-ignore lint/suspicious/noConfusingVoidType: void allows bare return statements
 1026  export type ExtensionHandler<E, R = undefined> = (event: E, ctx: ExtensionContext) => Promise<R | void> | R | void;
 1027  
 1028  /**
 1029   * ExtensionAPI passed to extension factory functions.
 1030   */
 1031  export interface ExtensionAPI {
 1032  	// =========================================================================
 1033  	// Event Subscription
 1034  	// =========================================================================
 1035  
 1036  	on(event: "resources_discover", handler: ExtensionHandler<ResourcesDiscoverEvent, ResourcesDiscoverResult>): void;
 1037  	on(event: "session_start", handler: ExtensionHandler<SessionStartEvent>): void;
 1038  	on(
 1039  		event: "session_before_switch",
 1040  		handler: ExtensionHandler<SessionBeforeSwitchEvent, SessionBeforeSwitchResult>,
 1041  	): void;
 1042  	on(event: "session_before_fork", handler: ExtensionHandler<SessionBeforeForkEvent, SessionBeforeForkResult>): void;
 1043  	on(
 1044  		event: "session_before_compact",
 1045  		handler: ExtensionHandler<SessionBeforeCompactEvent, SessionBeforeCompactResult>,
 1046  	): void;
 1047  	on(event: "session_compact", handler: ExtensionHandler<SessionCompactEvent>): void;
      … 17 lines omitted; exact range 1024–1074 …
 1065  	on(event: "tool_execution_start", handler: ExtensionHandler<ToolExecutionStartEvent>): void;
 1066  	on(event: "tool_execution_update", handler: ExtensionHandler<ToolExecutionUpdateEvent>): void;
 1067  	on(event: "tool_execution_end", handler: ExtensionHandler<ToolExecutionEndEvent>): void;
 1068  	on(event: "model_select", handler: ExtensionHandler<ModelSelectEvent>): void;
 1069  	on(event: "thinking_level_select", handler: ExtensionHandler<ThinkingLevelSelectEvent>): void;
 1070  	on(event: "tool_call", handler: ExtensionHandler<ToolCallEvent, ToolCallEventResult>): void;
 1071  	on(event: "tool_result", handler: ExtensionHandler<ToolResultEvent, ToolResultEventResult>): void;
 1072  	on(event: "user_bash", handler: ExtensionHandler<UserBashEvent, UserBashEventResult>): void;
 1073  	on(event: "input", handler: ExtensionHandler<InputEvent, InputEventResult>): void;
 1074  	on(event: "refine_complete", handler: ExtensionHandler<RefineCompleteEvent>): void;
查看全部 4 处证据
  • 契约 packages/coding-agent/src/core/extensions/types.ts:1024–1074 事件订阅面。
  • 契约 packages/coding-agent/src/core/extensions/types.ts:1080–1110 tool/command/shortcut/flag registration。
  • 契约 packages/coding-agent/src/core/extensions/types.ts:1140–1163 appendEntry、exec、active tool actions。
  • 契约 packages/coding-agent/src/core/extensions/types.ts:1188–1199 provider registration runtime timing。
09
DIMENSION · MCP-CONNECTORS

MCP 与连接器

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

24
L1事实prime-mcp-001

MCP host 侧把 OAuth provider 与 kernel 请求分开

源码事实

McpManager refresh/config/begin_login host request 会读取 auth storage、重新获取 API key,并把解析后的 server URL/config 交给 kernel;stdio server 由 Python 自己管理,HTTP 用户 server 才在 host 侧注册 OAuth provider。

白话解释

登录凭据、UI 交互和真正的 MCP kernel 连接没有混成一条黑盒;host 只做身份和配置桥接。

对自研 Harness 的含义

连接器设计应区分 credential plane、control plane 和 tool execution plane,避免把 token 直接塞进模型上下文。

关键源码 · 实现
packages/coding-agent/src/core/mcp/mcp-manager.ts · L36–L78
   36  export class McpManager {
   37  	private readonly authStorage: AuthStorage;
   38  	private readonly getUserServers: () => Record<string, McpServerConfig> | undefined;
   39  	private readonly beginLogin?: (server: string) => Promise<void>;
   40  	private integrations = new Map<string, ResolvedIntegration>();
   41  	/** Provider ids we registered for user servers, so refresh can drop removed ones. */
   42  	private registeredUserProviderIds = new Set<string>();
   43  
   44  	constructor(options: McpManagerOptions) {
   45  		this.authStorage = options.authStorage;
   46  		this.getUserServers = options.getUserServers ?? (() => undefined);
   47  		this.beginLogin = options.beginLogin;
   48  		this.resolveIntegrations();
   49  		this.registerProviders();
   50  	}
   51  
   52  	/** Re-read settings and re-register providers; call after a session reload. */
   53  	refresh(): void {
   54  		this.resolveIntegrations();
   55  		this.registerProviders();
   56  	}
   57  
   58  	private providerId(server: string): string {
   59  		return `mcp:${server}`;
      … 9 lines omitted; exact range 36–78 …
   69  				usesOAuth: entry.oauth?.kind === "oauth",
   70  			});
   71  		}
   72  		for (const [server, config] of Object.entries(this.getUserServers() ?? {})) {
   73  			if (config.type !== "http") continue; // stdio servers self-manage in Python
   74  			integrations.set(server, {
   75  				server,
   76  				label: server,
   77  				url: config.url,
   78  				usesOAuth: config.oauth === true,
查看全部 2 处证据
  • 实现 packages/coding-agent/src/core/mcp/mcp-manager.ts:36–78 McpManager 与 catalog/user server integration。
  • 实现 packages/coding-agent/src/core/mcp/mcp-manager.ts:147–198 mcp.refresh/config/begin_login host requests。
10
DIMENSION · SUBAGENTS-COLLABORATION

子 Agent 与协作

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

25
L1事实prime-collab-001

RLM child runtime 有显式 registry、深度和完成释放协议

源码事实

RlmSubagentRegistryEntry 记录 child id、active/session id、name、dir、status;CreateRlmSubagentRuntimeOptions 携带 parent、model、tools、rlmDepth、rlmMaxDepth 和 parent node;SubagentRuntimeHost 要求 create、complete、release、delete 等生命周期动作。

白话解释

子 Agent 不是简单 `Promise.all`:父子关系、状态、目录、模型、深度和完成后回收都有可查询的对象。

对自研 Harness 的含义

多 Agent 系统要把 child 生命周期持久化,才能支持观察、取消、恢复和成本归因。

关键源码 · 契约
packages/coding-agent/src/core/rlm-runtime.ts · L14–L39
   14  export interface RlmSpawnHandle {
   15  	rlm_child_id: string;
   16  	name: string;
   17  	session_dir: string;
   18  	model: string;
   19  }
   20  
   21  export type RlmSubagentRegistryStatus = "running" | "completed" | "error";
   22  
   23  export interface RlmSubagentRegistryEntry {
   24  	rlm_child_id: string;
   25  	active_session_id: string | null;
   26  	session_id: string | null;
   27  	session_name: string;
   28  	session_dir: string;
   29  	status: RlmSubagentRegistryStatus;
   30  }
   31  
   32  export interface RlmListSubagentsResult {
   33  	subagents: RlmSubagentRegistryEntry[];
   34  }
   35  
   36  export interface RlmDeleteSubagentResult {
   37  	subagent: RlmSubagentRegistryEntry;
   38  	outcome?: "deleted" | "skipped_running";
   39  }
查看全部 2 处证据
  • 契约 packages/coding-agent/src/core/rlm-runtime.ts:14–39 RLM handle 和 registry 状态。
  • 契约 packages/coding-agent/src/core/rlm-runtime.ts:201–241 child runtime 参数和 host 生命周期。
26
L1事实prime-collab-002

Daemon supervisor 是 resident worker 控制面而不是一次性 subprocess

源码事实

DaemonSupervisor 启动时取得 socket lease、创建 0700 descriptor dir、写 supervisor config/journal,加载 worker descriptors 并 adoptOrRecoverWorker;协议包含 prompt/prompt_and_wait、cancel、heartbeats、retry_worker、session tree/stats 等命令。

白话解释

后台 Agent 可以脱离前台 UI 常驻,客户端断线后仍能通过 socket 找回 worker,恢复 journal 和 heartbeat。

对自研 Harness 的含义

需要远程继续、移动端查看或长时间任务时,resident supervisor 比每次 CLI 启新进程更合适,但运维复杂度也会显著上升。

关键源码 · 契约
packages/coding-agent/src/modes/daemon/daemon-supervisor.ts · L158–L212
  158  	"list_saved_sessions",
  159  	"create",
  160  	"attach",
  161  	"reattach",
  162  	"detach",
  163  	"complete_owned_session",
  164  	"promote_owned_session",
  165  	"kill",
  166  	"rename",
  167  	"prompt",
  168  	"cancel_prompt_admission",
  169  	"prompt_and_wait",
  170  	"steer",
  171  	"follow_up",
  172  	"restore_next_turn",
  173  	"restore_actions",
  174  	"append_custom_message",
  175  	"resume_queue",
  176  	"send_message",
  177  	"agent_messages_status",
  178  	"agent_messages_pause",
  179  	"agent_messages_resume",
  180  	"agent_messages_clear",
  181  	"abort",
      … 21 lines omitted; exact range 158–212 …
  203  	"clear_queue",
  204  	"abort_and_clear_queue",
  205  	"cron_list",
  206  	"heartbeats_list",
  207  	"heartbeat_manage",
  208  	"cron_add",
  209  	"cron_cancel",
  210  	"heartbeat_get",
  211  	"heartbeat_set",
  212  	"heartbeat_update",
查看全部 3 处证据
  • 契约 packages/coding-agent/src/modes/daemon/daemon-supervisor.ts:158–212 daemon command surface。
  • 实现 packages/coding-agent/src/modes/daemon/daemon-supervisor.ts:641–705 socket lease、descriptor 权限、journals、adopt/recover。
  • 实现 packages/coding-agent/src/modes/daemon/daemon-supervisor.ts:920–944 扫描和标记 worker descriptor 为 recovering。
27
L1事实prime-collab-003

Prompt admission 与 heartbeat 让 daemon 交互可取消且可观测

源码事实

daemon supervisor 为每个 client/activeSession/admissionId 维护 waiting/owned/cancelled 状态,断开时转发 cancel_prompt_admission;heartbeats_list 会汇总 ready workers 的 heartbeat snapshot。

白话解释

用户点了等待式 prompt 后断网,不会留下一个永远占着队列的请求;心跳也能告诉控制面任务还活着还是已断开。

对自研 Harness 的含义

异步控制面需要 admission id、幂等 key 和 client disconnect cleanup,不应只靠 websocket 生命周期。

关键源码 · 实现
packages/coding-agent/src/modes/daemon/daemon-supervisor.ts · L1125–L1184
 1125  	private promptAdmissionKey(activeSessionId: string, publicAdmissionId: string): string {
 1126  		return `${activeSessionId}\0${publicAdmissionId}`;
 1127  	}
 1128  
 1129  	private promptAdmissionsFor(client: DaemonSocketClient): Map<string, SupervisorPromptAdmission> {
 1130  		let admissions = this.promptAdmissions.get(client);
 1131  		if (!admissions) {
 1132  			admissions = new Map();
 1133  			this.promptAdmissions.set(client, admissions);
 1134  		}
 1135  		return admissions;
 1136  	}
 1137  
 1138  	private getPromptAdmission(
 1139  		client: DaemonSocketClient,
 1140  		activeSessionId: string,
 1141  		publicAdmissionId: string,
 1142  	): SupervisorPromptAdmission | undefined {
 1143  		return this.promptAdmissions.get(client)?.get(this.promptAdmissionKey(activeSessionId, publicAdmissionId));
 1144  	}
 1145  
 1146  	private deletePromptAdmission(admission: SupervisorPromptAdmission): void {
 1147  		const admissions = this.promptAdmissions.get(admission.client);
 1148  		const key = this.promptAdmissionKey(admission.activeSessionId, admission.publicAdmissionId);
      … 26 lines omitted; exact range 1125–1184 …
 1175  					if (status === "owned") admission.status = "owned";
 1176  					else if (status === "cancelled") admission.status = "cancelled";
 1177  				})
 1178  				.catch((error: unknown) => {
 1179  					this.log(
 1180  						`Could not cancel prompt admission ${admission.workerAdmissionId} on disconnected client: ${String(error)}`,
 1181  					);
 1182  				});
 1183  		}
 1184  	}
查看全部 3 处证据
  • 实现 packages/coding-agent/src/modes/daemon/daemon-supervisor.ts:1125–1184 prompt admission map 与断线取消。
  • 实现 packages/coding-agent/src/modes/daemon/daemon-supervisor.ts:1186–1210 prompt admission 输入校验。
  • 实现 packages/coding-agent/src/modes/daemon/daemon-supervisor.ts:1635–1678 heartbeat snapshot 合并。
28
L2风险prime-risk-002

Daemon/RLM 的恢复正确性依赖多个 journal、lease 和 session 入口协同

源码事实

daemon supervisor 同时维护 supervisor config、worker descriptor、command journal、recovery journal、orphan journal、socket identity 与 session file;RLM 还把 child depth、parent node 和完成释放交给 host。

白话解释

它很强,但出错时要同时判断进程身份、socket、worker 状态、session 文件和 child registry,排障门槛高。

对自研 Harness 的含义

自研要先定义故障状态机和可重放事件,再扩展常驻 worker;否则“恢复”可能变成重复执行或孤儿进程。

边界
  • 这是基于多个实现模块的维护复杂度推断;并非源码声称的 bug。
关键源码 · 实现
packages/coding-agent/src/modes/daemon/daemon-supervisor.ts · L49–L53
   49  import type { PrivateFrame } from "../session-worker/private-framing.js";
   50  import { createActiveSessionId, type DaemonSocketClient } from "./active-session-state.js";
   51  import { CommandRecoveryJournal, createCommandIdempotencyKey } from "./command-recovery-journal.js";
   52  import { CompactAssistantStreamReconstructor, isCompactAssistantDelta } from "./compact-session-stream.js";
   53  import { DAEMON_CATALOG_ROLE_ENV, DaemonCatalogClient } from "./daemon-catalog-process.js";
查看全部 3 处证据
  • 实现 packages/coding-agent/src/modes/daemon/daemon-supervisor.ts:49–53 command/worker recovery journals。
  • 实现 packages/coding-agent/src/modes/daemon/daemon-supervisor.ts:633–660 descriptor/config/snapshot roots 与 startup fencing。
  • 契约 packages/coding-agent/src/core/rlm-runtime.ts:205–241 parent/child depth、publish/complete/release/delete。
11
DIMENSION · TESTS-BENCHMARKS-MATURITY

测试、基准与成熟度

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

29
L3事实prime-maturity-001

测试布局覆盖 agent、session、daemon、RLM、MCP 与 telemetry

源码事实

coding-agent test 目录包含 acp-rlm-subagents、daemon runtime lease、daemon supervisor eviction/side-question、agent-session tree/concurrent、MCP/auth、telemetry、exec、kernel startup 等契约测试。

白话解释

项目没有只测“模型能回答一句话”,而是把长会话、后台 worker、子 Agent 和工具执行拆成可回归的测试文件。

对自研 Harness 的含义

建设自研 harness 时应把恢复、并发、权限和观测作为一等测试维度,而不是上线后再补。

关键源码 · 测试
packages/coding-agent/test/acp-rlm-subagents.test.ts · L1–L12
    1  import { mkdirSync, rmSync } from "node:fs";
    2  import { tmpdir } from "node:os";
    3  import { join } from "node:path";
    4  import * as acp from "@agentclientprotocol/sdk";
    5  import { Agent } from "@earendil-works/pi-agent-core";
    6  import {
    7  	type AssistantMessage,
    8  	type Context,
    9  	createAssistantMessageEventStream,
   10  	getModel,
   11  	type TextContent,
   12  	type Usage,
查看全部 4 处证据
  • 测试 packages/coding-agent/test/acp-rlm-subagents.test.ts:1–12 ACP/RLM 子 Agent 测试入口。
  • 测试 packages/coding-agent/test/daemon-runtime-lease.test.ts:1–12 daemon runtime lease 测试入口。
  • 测试 packages/coding-agent/test/agent-session-tree-navigation.test.ts:1–12 session tree navigation 测试入口。
  • 测试 packages/coding-agent/test/telemetry.test.ts:1–12 telemetry 测试入口。
APPENDIX · SOURCE INDEX

本报告引用过的实现文件

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

  1. 01packages/agent/src/agent-loop.tsL178–205, 307–373, 422–460, 467–521, 526–603, 317–345, 385–419, 608–623, 630–688, 690–735, 795–848, 850–904, 906–945, 501–519
  2. 02packages/agent/src/types.tsL148–183, 206–244, 246–277, 399–421, 170–183
  3. 03packages/coding-agent/src/core/compaction/compaction.tsL122–132, 226–233, 138–147, 192–224, 243–297, 303–339, 434–458, 618–634, 465–496, 501–538, 552–611, 636–712
  4. 04packages/coding-agent/src/core/session-manager.tsL33–54, 75–85, 126–163, 472–535, 537–596, 1345–1364, 1442–1475, 1477–1499, 1345–1475
  5. 05packages/coding-agent/src/core/tools/bash.tsL36–58, 60–117, 276–298, 250–266, 276–287, 303–314, 66–117
  6. 06packages/coding-agent/src/core/resource-loader.tsL23–39, 336–365, 409–479, 646–678, 681–690, 811–830, 413–435
  7. 07packages/coding-agent/src/core/extensions/types.tsL1024–1074, 1080–1110, 1140–1163, 1188–1199, 1080–1163, 1028–1058
  8. 08packages/coding-agent/src/core/extensions/runner.tsL670–711, 805–826, 857–887, 889–920
  9. 09packages/coding-agent/src/core/mcp/mcp-manager.tsL36–78, 147–198
  10. 10packages/coding-agent/src/core/rlm-runtime.tsL14–39, 201–241, 205–241
  11. 11packages/coding-agent/src/modes/daemon/daemon-supervisor.tsL158–212, 641–705, 920–944, 1125–1184, 1186–1210, 1635–1678, 49–53, 633–660
  12. 12packages/coding-agent/src/core/agent-session.tsL318–390, 1–13, 412–500
  13. 13packages/coding-agent/test/acp-rlm-subagents.test.tsL1–12
  14. 14packages/coding-agent/test/daemon-runtime-lease.test.tsL1–12
  15. 15packages/coding-agent/test/agent-session-tree-navigation.test.tsL1–12
  16. 16packages/coding-agent/test/telemetry.test.tsL1–12