EXECUTIVE READING
先给结论,再进入源码
核心机制 递归 sendMessageStream;100 turn 上限;loop 恢复一次
上下文 Legacy 摘要 + 新图/流水线/GC/蒸馏/真实 token 校准
安全边界 Policy 细;三平台真实隔离;默认不开启但净化 env
适用建设 Google 生态、大型扩展平台、上下文工程研究
值得借鉴 Scheduler 边界清晰 Policy 匹配维度丰富 新上下文系统前瞻
需要警惕 默认沙箱关闭 旧/新上下文并存增加复杂度 同 protocol 实例不并发 stream
直接带走 工具 scheduler 独立事件机 token API 反校准 extension capability hot reload
00 · METHOD
研究口径:先锁提交,再沿运行链读代码
README POLICY README/GEMINI.md 只作入口导航;结论来自 GeminiClient、GeminiChat、Turn、ContextManager、Scheduler、PolicyEngine、平台 sandbox、MCP、skills/extensions、recording 和测试。
FACT POLICY 区分稳定 legacy 路径、新 context-management gate、sandbox enabled 状态、交互/非交互默认与 Provider 认证类型。
INFERENCE POLICY Google 服务端路由、模型内部 next-speaker 与 safety checker 只描述本地调用契约,不推断服务端实现。
L1 运行实现
L2 接口契约
L3 测试证明
L4 文档佐证
L5 明确推断
本页引用 25 个不同源码/测试文件;证据角色分布:实现 58 · 契约 4 · 配置 3 · 测试 2。代码块是固定提交中的原文截取,长区间仅在中部折叠,首尾行号保持真实。
01 · TECHNICAL MAPS
架构总图与单轮执行链路
两张图均由本页证据账本生成,并通过 Archify showcase 9 项校验(0 error / 0 warning)。图可单独打开、搜索、缩放和追踪关系。
FIGURE 01 Gemini CLI Harness 架构图 全屏打开 ↗
FIGURE 02 用户输入到工具回写的技术链路 全屏打开 ↗
02 · COVERAGE MAP
审计维度与证据等级
架构与 Agent Loop
verified
L1 / L2 / L3
主递归 turn、模型路由、next-speaker、loop recovery、hook 与上限。
Provider、流式与重试
verified
L1 / L2 / L3
ContentGenerator、Google OAuth/API key/Vertex/Gateway、流式重试。
上下文、压缩与恢复
verified
L1 / L2 / L3
legacy compression 与 graph/pipeline context-management 两条路径。
工具调度器
verified
L1 / L2 / L3
Turn 工具提取、event-driven Scheduler、输出落盘/截断和取消。
策略、审批与沙箱
verified
L1 / L2 / L3
规则/安全检查/审批模式、环境净化和三平台真实沙箱。
MCP、扩展、Skills、记忆与 Hooks
verified
L1 / L2 / L3
MCP transports/OAuth/refresh/admin policy,扩展热装卸,GEMINI.md/JIT memory,skills 和 hooks。
子 Agent 与协作
verified
L1 / L2 / L3
local/remote protocol、AgentSession event replay、子 Agent 独立 registry/scheduler。
持久化、观测与成熟度
verified
L1 / L2 / L3
增量 JSONL、rewind/checkpoint、OTEL/GCP/file/console、规模与许可证。
01
DIMENSION · ARCHITECTURE-LOOP
架构与 Agent Loop
本章共 3 个可定位结论;结论按“实现事实 → 白话解释 → 工程影响 → 源码摘录”展开。
01
L1 事实 gemini-loop-001
主 Harness 用递归 sendMessageStream 驱动多 turn,硬上限为 100
源码事实 GeminiClient 将单次 turn 交给 processTurn;若 next-speaker 判定模型应继续,或 AfterAgent hook 返回继续原因,就递归调用 sendMessageStream 并递减 boundedTurns,入口始终 clamp 到 MAX_TURNS=100。
白话解释 一次用户请求可以连续让模型说、用工具、再说;但最多转 100 圈,避免无尽自言自语。
对自研 Harness 的含义 控制流直观,递归路径共享 prompt_id 和 hook state,需要严格做 activeCalls 记账。
关键源码 · 实现
复制
packages/core/src/core/client.ts · L79–L111
79 const MAX_TURNS = 100;
80
81 type BeforeAgentHookReturn =
82 | {
83 type: GeminiEventType.AgentExecutionStopped;
84 value: { reason: string; systemMessage?: string };
85 }
86 | {
87 type: GeminiEventType.AgentExecutionBlocked;
88 value: { reason: string; systemMessage?: string };
89 }
90 | { additionalContext: string | undefined }
91 | undefined;
92
93 export class GeminiClient {
94 private chat?: GeminiChat;
95 private sessionTurnCount = 0;
96
97 private readonly loopDetector: LoopDetectionService;
98 private readonly compressionService: ChatCompressionService;
99 private readonly agentHistoryProvider: AgentHistoryProvider;
100 private readonly toolOutputMaskingService: ToolOutputMaskingService;
101 private contextManager?: ContextManager;
102 private lastPromptId: string;
103 private currentSequenceModel: string | null = null;
104 private lastSentIdeContext: IdeContext | undefined;
105 private forceFullIdeContext = true;
106
107 /**
108 * At any point in this conversation, was compression triggered without
109 * being forced and did it fail?
110 */
111 private hasFailedCompressionAttempt = false;
查看全部 3 处证据
实现
packages/core/src/core/client.ts:79–111
MAX_TURNS 和 loop/compression/hook 状态。
实现
packages/core/src/core/client.ts:875–907
next-speaker 继续模型。
实现
packages/core/src/core/client.ts:910–1060
bounded recursion 与 hook continuation。
02
L1 事实 gemini-loop-002
每轮先做上下文、溢出、IDE 配对和 loop 检测,再锁定模型与工具
源码事实 processTurn 先运行 context management/压缩与工具输出 masking,估算请求 token;为保持 functionCall→functionResponse 紧邻,会延迟 IDE context;随后 loop detector、model router/availability 和 model-dependent tool declarations 才进入 Turn.run。
白话解释 开口前先整理历史、确认装得下、保证工具回执不被编辑器消息插队,然后才选本轮模型和工具箱。
对自研 Harness 的含义 上下文与模型选择顺序清楚,工具描述可随模型变化且同一 sequence 保持模型粘性。
关键源码 · 实现
复制
packages/core/src/core/client.ts · L614–L715
614 private async *processTurn(
615 request: PartListUnion,
616 signal: AbortSignal,
617 prompt_id: string,
618 boundedTurns: number,
619 displayContent?: PartListUnion,
620 ): AsyncGenerator<ServerGeminiStreamEvent, Turn> {
621 // Re-initialize turn (it was empty before if in loop, or new instance)
622 let turn = new Turn(this.getChat(), prompt_id);
623
624 this.sessionTurnCount++;
625 if (
626 this.config.getMaxSessionTurns() > 0 &&
627 this.sessionTurnCount > this.config.getMaxSessionTurns()
628 ) {
629 yield { type: GeminiEventType.MaxSessionTurns };
630 return turn;
631 }
632
633 if (!boundedTurns) {
634 return turn;
635 }
636
637 // Check for context window overflow
… 68 lines omitted; exact range 614–715 …
706 modelForLimitCheck,
707 );
708
709 if (estimatedRequestTokenCount > remainingTokenCount) {
710 yield {
711 type: GeminiEventType.ContextWindowWillOverflow,
712 value: { estimatedRequestTokenCount, remainingTokenCount },
713 };
714 return turn;
715 }
查看全部 2 处证据
实现
packages/core/src/core/client.ts:614–715
context、masking 和 token overflow。
实现
packages/core/src/core/client.ts:717–807
IDE 配对、loop、router 和 tools。
03
L1 事实 gemini-loop-003
循环检测能先恢复一次,再判定硬循环
源码事实 turnStarted 和每个流事件都送入 LoopDetectionService;count=1 进入 _recoverFromLoop,count>1 发 LoopDetected 并终止,剩余 turn 不足时转 MaxSessionTurns。
白话解释 第一次怀疑绕圈会给模型一次纠偏机会,第二次还绕就停。
对自研 Harness 的含义 比仅靠 turn 上限更早止损,并保留一次自恢复空间。
关键源码 · 实现
复制
packages/core/src/core/client.ts · L744–L763
744 // Re-initialize turn with fresh history
745 turn = new Turn(this.getChat(), prompt_id);
746
747 const loopResult = await this.loopDetector.turnStarted(signal);
748 if (loopResult.count > 1) {
749 yield { type: GeminiEventType.LoopDetected };
750 return turn;
751 } else if (loopResult.count === 1) {
752 if (boundedTurns <= 1) {
753 yield { type: GeminiEventType.MaxSessionTurns };
754 return turn;
755 }
756 return yield* this._recoverFromLoop(
757 loopResult,
758 signal,
759 prompt_id,
760 boundedTurns,
761 displayContent,
762 );
763 }
查看全部 2 处证据
实现
packages/core/src/core/client.ts:744–763
turn-start loop detection。
实现
packages/core/src/core/client.ts:810–855
stream-event loop detection 与恢复。
02
DIMENSION · PROVIDER-STREAMING
Provider、流式与重试
本章共 3 个可定位结论;结论按“实现事实 → 白话解释 → 工程影响 → 源码摘录”展开。
04
L2 事实 gemini-provider-001
统一 ContentGenerator 契约覆盖流式、非流式、计数与 embedding
源码事实 ContentGenerator 抽象 generateContent、generateContentStream、countTokens 和 embedContent;AuthType 覆盖 Google OAuth、Gemini API key、Vertex AI、ADC 与 Gateway。
白话解释 上层只认一套生成接口,底下可换个人 Google 登录、API key、企业 Vertex 或网关。
对自研 Harness 的含义 Provider 切换不改主循环,但 thought signature 兼容性仍需在切换认证后处理。
关键源码 · 契约
复制
packages/core/src/core/contentGenerator.ts · L35–L70
35
36 /**
37 * Interface abstracting the core functionalities for generating content and counting tokens.
38 */
39 export interface ContentGenerator {
40 generateContent(
41 request: GenerateContentParameters,
42 userPromptId: string,
43 role: LlmRole,
44 ): Promise<GenerateContentResponse>;
45
46 generateContentStream(
47 request: GenerateContentParameters,
48 userPromptId: string,
49 role: LlmRole,
50 ): Promise<AsyncGenerator<GenerateContentResponse>>;
51
52 countTokens(request: CountTokensParameters): Promise<CountTokensResponse>;
53
54 embedContent(request: EmbedContentParameters): Promise<EmbedContentResponse>;
55
56 userTier?: UserTierId;
57
58 userTierName?: string;
… 2 lines omitted; exact range 35–70 …
61 }
62
63 export enum AuthType {
64 LOGIN_WITH_GOOGLE = 'oauth-personal',
65 USE_GEMINI = 'gemini-api-key',
66 USE_VERTEX_AI = 'vertex-ai',
67 LEGACY_CLOUD_SHELL = 'cloud-shell',
68 COMPUTE_ADC = 'compute-default-credentials',
69 GATEWAY = 'gateway',
70 }
查看全部 2 处证据
契约
packages/core/src/core/contentGenerator.ts:35–70
ContentGenerator 与 AuthType。
实现
packages/core/src/core/contentGenerator.ts:72–110
环境自动检测和配置。
05
L1 事实 gemini-provider-002
个人/ADC 走 Code Assist,API key/Vertex/Gateway 走 Google GenAI SDK
源码事实 createContentGenerator 对 LOGIN_WITH_GOOGLE/COMPUTE_ADC 创建 CodeAssistServer 并加模型映射;Gemini/Vertex/Gateway 构造 GoogleGenAI,支持自定义 base URL、headers、proxy 和 Vertex routing headers。
白话解释 登录方式不仅换凭证,也可能换后端客户端;企业 Vertex 还能指定共享/专用路由。
对自研 Harness 的含义 同一主循环有多条传输实现,认证切换测试必须覆盖历史 thoughtSignature 清理。
关键源码 · 实现
复制
packages/core/src/core/contentGenerator.ts · L285–L310
285 if (
286 apiKeyAuthMechanism === 'bearer' &&
287 (config.authType === AuthType.USE_GEMINI ||
288 config.authType === AuthType.USE_VERTEX_AI) &&
289 config.apiKey
290 ) {
291 baseHeaders['Authorization'] = `Bearer ${config.apiKey}`;
292 }
293 if (
294 config.authType === AuthType.LOGIN_WITH_GOOGLE ||
295 config.authType === AuthType.COMPUTE_ADC
296 ) {
297 const httpOptions = { headers: baseHeaders };
298 return new LoggingContentGenerator(
299 new ModelMappingContentGenerator(
300 await createCodeAssistContentGenerator(
301 httpOptions,
302 config.authType,
303 gcConfig,
304 sessionId,
305 ),
306 CCPA_AI_MODEL_MAPPINGS,
307 ),
308 gcConfig,
309 );
310 }
查看全部 2 处证据
实现
packages/core/src/core/contentGenerator.ts:285–310
Code Assist 路径。
实现
packages/core/src/core/contentGenerator.ts:312–410
GenAI/Vertex/Gateway、headers、proxy 与 endpoint。
06
L1 事实 gemini-provider-003
连接阶段与中途流错误分开重试,中途流最多四次尝试
源码事实 GeminiChat 的 streamWithRetries 把连接阶段错误交给底层 retryWithBackoff;流迭代中的可重试网络/内容错误使用独立指数退避,并限制到 MID_STREAM_RETRY_OPTIONS 的四次总尝试。
白话解释 连不上和连上后半路断掉是两类事故,分别计数;不会因为全局重试设得很大就反复重播半截响应。
对自研 Harness 的含义 降低 mid-stream 重复输出/工具调用风险。
关键源码 · 实现
复制
packages/core/src/core/geminiChat.ts · L517–L578
517 const streamWithRetries = async function* (
518 this: GeminiChat,
519 ): AsyncGenerator<StreamEvent, void, void> {
520 try {
521 const maxAttempts = this.context.config.getMaxAttempts();
522
523 for (let attempt = 0; attempt < maxAttempts; attempt++) {
524 let isConnectionPhase = true;
525 try {
526 if (attempt > 0) {
527 yield { type: StreamEventType.RETRY };
528 }
529
530 // If this is a retry, update the key with the new context.
531 const currentConfigKey =
532 attempt > 0
533 ? { ...modelConfigKey, isRetry: true }
534 : modelConfigKey;
535
536 isConnectionPhase = true;
537 const stream = await this.makeApiCallAndProcessStream(
538 currentConfigKey,
539 requestHistory,
540 prompt_id,
… 28 lines omitted; exact range 517–578 …
569 };
570 }
571 return; // Stop the generator
572 }
573
574 if (isConnectionPhase) {
575 // Connection phase errors have already been retried by retryWithBackoff.
576 // If they bubble up here, they are exhausted or fatal.
577 throw error;
578 }
查看全部 2 处证据
实现
packages/core/src/core/geminiChat.ts:517–578
连接与流迭代错误边界。
实现
packages/core/src/core/geminiChat.ts:580–648
mid-stream 分类、退避与上限。
03
DIMENSION · CONTEXT-COMPACTION
上下文、压缩与恢复
本章共 5 个可定位结论;结论按“实现事实 → 白话解释 → 工程影响 → 源码摘录”展开。
07
L1 事实 gemini-context-001
Legacy 压缩默认在 50% 窗口触发,并保留最近约 30%
源码事实 ChatCompressionService 默认阈值为模型 token limit 的 0.5,压缩切点按序列化字符量寻找安全 user turn,目标保留最近 30%,且不会拆断 function call/response 边界。
白话解释 箱子装到一半就提前整理,最近三成原文留下,旧七成写成摘要;切口只选完整对话边界。
对自研 Harness 的含义 为长回复和工具调用预留较大余量,代价是较早产生摘要成本。
关键源码 · 配置
复制
packages/core/src/context/chatCompressionService.ts · L37–L52
37 /**
38 * Default threshold for compression token count as a fraction of the model's
39 * token limit. If the chat history exceeds this threshold, it will be compressed.
40 */
41 const DEFAULT_COMPRESSION_TOKEN_THRESHOLD = 0.5;
42
43 /**
44 * The fraction of the latest chat history to keep. A value of 0.3
45 * means that only the last 30% of the chat history will be kept after compression.
46 */
47 const COMPRESSION_PRESERVE_THRESHOLD = 0.3;
48
49 /**
50 * The budget for function response tokens in the preserved history.
51 */
52 const COMPRESSION_FUNCTION_RESPONSE_TOKEN_BUDGET = 50_000;
查看全部 2 处证据
配置
packages/core/src/context/chatCompressionService.ts:37–52
50% trigger、30% preserve 与工具预算。
实现
packages/core/src/context/chatCompressionService.ts:54–100
安全 split point。
08
L1 事实 gemini-context-002
旧工具输出采用反向预算,超额内容落临时文件并只留尾部
源码事实 压缩前从最新向最旧累计 function response token,优先保留近端;超过 50k 工具响应预算后,将较老大输出保存到文件并替换为最后 30 行和路径提示。
白话解释 最新日志全文留在桌面,旧日志搬进档案室,只在上下文里留末尾和取件地址。
对自研 Harness 的含义 既省 token 又保留可恢复原文,比不可逆截断更适合调试。
关键源码 · 实现
复制
packages/core/src/context/chatCompressionService.ts · L124–L142
124 /**
125 * Processes the chat history to ensure function responses don't exceed a specific token budget.
126 *
127 * This function implements a "Reverse Token Budget" strategy:
128 * 1. It iterates through the history from the most recent turn to the oldest.
129 * 2. It keeps a running tally of tokens used by function responses.
130 * 3. Recent tool outputs are preserved in full to maintain high-fidelity context for the current turn.
131 * 4. Once the budget (COMPRESSION_FUNCTION_RESPONSE_TOKEN_BUDGET) is exceeded, any older large
132 * tool responses are truncated to their last 30 lines and saved to a temporary file.
133 *
134 * This ensures that compression effectively reduces context size even when recent turns
135 * contain massive tool outputs (like large grep results or logs).
136 */
137 async function truncateHistoryToBudget(
138 history: readonly Content[],
139 config: Config,
140 ): Promise<Content[]> {
141 let functionResponseTokenCounter = 0;
142 const truncatedHistory: Content[] = [];
查看全部 2 处证据
实现
packages/core/src/context/chatCompressionService.ts:124–142
reverse token budget 设计。
实现
packages/core/src/context/chatCompressionService.ts:144–215
工具结果计数、保存与替换。
09
L1 事实 gemini-context-003
摘要膨胀会触发熔断,随后只做内容截断
源码事实 tryCompressChat 记录 COMPRESSION_FAILED_INFLATED_TOKEN_COUNT;非强制压缩一旦失败即保持 hasFailedCompressionAttempt,后续服务可返回 CONTENT_TRUNCATED 并直接更新 history,不重建 chat。
白话解释 如果摘要反而比原文胖,就不再每轮花钱重写摘要,改用更轻的裁剪。
对自研 Harness 的含义 避免压缩风暴,但本会话之后的语义保真会更多依赖工具输出 masking。
关键源码 · 实现
复制
packages/core/src/core/client.ts · L107–L120
107 /**
108 * At any point in this conversation, was compression triggered without
109 * being forced and did it fail?
110 */
111 private hasFailedCompressionAttempt = false;
112
113 constructor(private readonly context: AgentLoopContext) {
114 this.loopDetector = new LoopDetectionService(this.config);
115 this.compressionService = new ChatCompressionService();
116 this.agentHistoryProvider = new AgentHistoryProvider(
117 this.config.agentHistoryProviderConfig,
118 this.config,
119 );
120 this.toolOutputMaskingService = new ToolOutputMaskingService();
查看全部 2 处证据
实现
packages/core/src/core/client.ts:107–120
压缩失败状态。
实现
packages/core/src/core/client.ts:1196–1248
失败熔断、重建 chat 与轻量截断。
10
L1 事实 gemini-context-004
新 ContextManager 是图与流水线系统,带 preview late-bind、压力屏障、GC/蒸馏和结构校验
源码事实 ContextManager 从 durable AgentChatHistory 同步 pristine graph,对 pending request 建 ephemeral preview,等待 pipeline/hot-start barrier,执行 triggers,再带保护节点渲染、harden 与 invariant check;相同 node hash 可复用 render cache。
白话解释 新系统不再把历史当一长串消息,而是当可追溯的节点图;当前问题先在草稿区处理,确认后才影响长期账本。
对自研 Harness 的含义 能做精细蒸馏和增量管理,但复杂度显著高于 legacy summary,必须明确 feature gate 与回退路径。
关键源码 · 实现
复制
packages/core/src/context/contextManager.ts · L26–L88
26 export class ContextManager {
27 // Master state containing the pristine graph and current active graph.
28 private buffer: ContextWorkingBufferImpl =
29 ContextWorkingBufferImpl.initialize([]);
30
31 private readonly eventBus: ContextEventBus;
32 private readonly orchestrator: PipelineOrchestrator;
33
34 // Track what IDs have been evaluated for triggers to prevent redundant processing
35 private readonly evaluatedNodeIds = new Set<string>();
36
37 // Hysteresis tracking to prevent utility call churn
38 private lastTriggeredDeficit = 0;
39 private lastTriggeredNormalizeDeficit = 0;
40
41 // Cache for Anomaly 3 (Redundant Renders)
42 private lastRenderCache?: {
43 nodesHash: string;
44 result: {
45 history: HistoryTurn[];
46 apiHistory: Content[];
47 pendingApiHistory: Content[];
48 didApplyManagement: boolean;
49 baseUnits: number;
… 29 lines omitted; exact range 26–88 …
79 return;
80 }
81
82 this.buffer = this.buffer.applyProcessorResult(
83 event.processorId,
84 event.targets,
85 event.returnedNodes,
86 );
87 });
88 }
查看全部 3 处证据
实现
packages/core/src/context/contextManager.ts:26–88
master buffer、事件应用和 render cache。
实现
packages/core/src/context/contextManager.ts:90–195
sync、preview、barrier、triggers 与 render。
实现
packages/core/src/context/contextManager.ts:197–275
commit backstop、invariants、harden 和 late-bind 分割。
11
L1 事实 gemini-context-005
新上下文系统用真实 API token 反校准本地估算
源码事实 processTurn 把 ContextManager 渲染得到的 baseUnits 带入请求;Finished 事件若包含 promptTokenCount,则通过 eventBus 发 actualTokens 与 promptBaseUnits ground truth。
白话解释 先用本地尺子估,再用模型 API 的过磅结果校准尺子。
对自研 Harness 的含义 长期预算判断可适应多模态与不同模型 tokenizer 偏差。
关键源码 · 实现
复制
packages/core/src/core/client.ts · L640–L678
640 let currentBaseUnits = 0;
641 let apiHistoryOverride: Content[] | undefined = undefined;
642
643 if (this.config.getContextManagementConfig().enabled) {
644 if (this.contextManager) {
645 const rawPendingRequest = createUserContent(request);
646 const pendingRequest = {
647 id: randomUUID(),
648 content: rawPendingRequest,
649 };
650 const {
651 history: newHistory,
652 apiHistory,
653 pendingApiHistory,
654 baseUnits,
655 } = await this.contextManager.renderHistory(
656 pendingRequest,
657 undefined,
658 signal,
659 );
660
661 currentBaseUnits = baseUnits;
662
663 // Use the PROCESSED pending content if available (e.g. if cleaned or distilled)
… 5 lines omitted; exact range 640–678 …
669 // Late-bind the prompt: Append the active request to the managed history
670 // only for the purpose of the upcoming API call.
671 apiHistoryOverride = [...apiHistory, finalPendingContent];
672
673 this.getChat().setHistory(newHistory);
674
675 // Use the original request for display/recording,
676 // but the processed one for the API and durable history.
677 displayContent = rawPendingRequest.parts || [];
678 request = finalPendingContent.parts || [];
查看全部 2 处证据
实现
packages/core/src/core/client.ts:640–678
ContextManager render 与 baseUnits。
实现
packages/core/src/core/client.ts:829–838
token ground truth 回灌。
04
DIMENSION · TOOLS-SCHEDULER
工具调度器
本章共 2 个可定位结论;结论按“实现事实 → 白话解释 → 工程影响 → 源码摘录”展开。
12
L1 事实 gemini-tools-001
Turn 只解析模型流,工具执行交给独立 event-driven Scheduler
源码事实 Turn 从 generateContentStream 收集 text、thought、functionCall、usage 和 finish reason,将 functionCall 放进 pendingToolCalls;主/子 Agent 再以各自 registry、message bus、sandbox manager 创建 Scheduler 批量执行。
白话解释 模型流负责开任务单,调度器负责审批、排队、执行和回执;两者不是揉在一个 switch 里。
对自研 Harness 的含义 主 Agent 和子 Agent 能复用同一工具治理链,同时各自限定工具集。
关键源码 · 实现
复制
packages/core/src/core/turn.ts · L236–L320
236 | ServerGeminiModelInfoEvent
237 | ServerGeminiAgentExecutionStoppedEvent
238 | ServerGeminiAgentExecutionBlockedEvent;
239
240 // A turn manages the agentic loop turn within the server context.
241 export class Turn {
242 private callCounter = 0;
243
244 readonly pendingToolCalls: ToolCallRequestInfo[] = [];
245 private debugResponses: GenerateContentResponse[] = [];
246 private pendingCitations = new Set<string>();
247 private cachedResponseText: string | undefined = undefined;
248 finishReason: FinishReason | undefined = undefined;
249 private hasLoggedRagTrace = false;
250
251 constructor(
252 private readonly chat: GeminiChat,
253 private readonly prompt_id: string,
254 ) {}
255
256 // The run method yields simpler events suitable for server logic
257 async *run(
258 modelConfigKey: ModelConfigKey,
259 req: PartListUnion,
… 51 lines omitted; exact range 236–320 …
311 if (!resp) continue; // Skip if there's no response body
312
313 // Log RAG trace if enabled (only once per turn to avoid log bloat on streams)
314 if (
315 !this.hasLoggedRagTrace &&
316 this.chat.context.config.getLogRagSnippets?.()
317 ) {
318 let ragStatus: string | undefined;
319 let snippets: RagSnippet[] | undefined;
320
查看全部 2 处证据
实现
packages/core/src/core/turn.ts:236–320
Turn 流解析与 pending tool calls。
实现
packages/core/src/agents/agent-scheduler.ts:42–92
子 Agent Scheduler context 与生命周期。
13
L1 事实 gemini-tools-002
超大工具结果在调度阶段落盘,取消也返回合法 functionResponse
源码事实 ToolExecutor 对超过阈值的文本写入项目临时目录并替换为截断文本;取消时若已有部分输出仍先截断/保存,再构造带 error 的 functionResponse,保持 Gemini 协议配对。
白话解释 工具被叫停也必须交一张正式回执;已经产生的大输出不会硬塞回上下文。
对自研 Harness 的含义 失败/取消不会破坏下一轮历史,且原始输出可从文件追查。
关键源码 · 实现
复制
packages/core/src/scheduler/tool-executor.ts · L250–L297
250 content.length === 1 &&
251 'tool' in call &&
252 call.tool instanceof DiscoveredMCPTool
253 ) {
254 const firstPart = content[0];
255 if (typeof firstPart === 'object' && typeof firstPart.text === 'string') {
256 const textContent = firstPart.text;
257 const threshold = this.config.getTruncateToolOutputThreshold();
258
259 if (threshold > 0 && textContent.length > threshold) {
260 const originalContentLength = textContent.length;
261 const { outputFile: savedPath } = await saveTruncatedToolOutput(
262 textContent,
263 toolName,
264 callId,
265 this.config.storage.getProjectTempDir(),
266 this.context.promptId,
267 );
268 outputFile = savedPath;
269 const truncatedText = formatTruncatedToolOutput(
270 textContent,
271 outputFile,
272 threshold,
273 );
… 14 lines omitted; exact range 250–297 …
288 }),
289 );
290
291 return { truncatedContent, outputFile };
292 }
293 }
294 }
295
296 return { truncatedContent: content, outputFile };
297 }
查看全部 2 处证据
实现
packages/core/src/scheduler/tool-executor.ts:250–297
输出阈值、保存与截断。
实现
packages/core/src/scheduler/tool-executor.ts:299–368
取消结果与 functionResponse 配对。
05
DIMENSION · POLICY-SANDBOX
策略、审批与沙箱
本章共 5 个可定位结论;结论按“实现事实 → 白话解释 → 工程影响 → 源码摘录”展开。
14
L1 事实 gemini-policy-001
PolicyEngine 按优先级匹配工具、参数、MCP 身份、annotations、模式、交互状态和 subagent
源码事实 ruleMatches 对稳定序列化参数、MCP 全限定名、tool annotations、approval mode、interactive/non-interactive 与 subagent 做组合匹配;规则/checker/hook checker 都按 priority 降序。
白话解释 政策可以精确到“哪个子 Agent 在非交互模式调用哪个 MCP 的哪个参数”,不只是允许/禁止 Bash。
对自研 Harness 的含义 企业控制力强,但规则冲突需要良好解释器和测试。
关键源码 · 实现
复制
packages/core/src/policy/policy-engine.ts · L49–L195
49 function isWildcardPattern(name: string): boolean {
50 return name === '*' || name.includes('*');
51 }
52
53 /**
54 * Checks if a tool call matches a wildcard pattern.
55 * Supports global (*) and the explicit MCP (*mcp_serverName_**) format.
56 */
57 function matchesWildcard(
58 pattern: string,
59 toolName: string,
60 serverName: string | undefined,
61 ): boolean {
62 if (pattern === '*') {
63 return true;
64 }
65
66 if (pattern === `${MCP_TOOL_PREFIX}*`) {
67 return serverName !== undefined;
68 }
69
70 if (pattern.startsWith(MCP_TOOL_PREFIX) && pattern.endsWith('_*')) {
71 const expectedServerName = pattern.slice(MCP_TOOL_PREFIX.length, -2);
72 // 1. Must be an MCP tool call (has serverName)
… 113 lines omitted; exact range 49–195 …
186 if ('interactive' in rule && rule.interactive !== undefined) {
187 if (rule.interactive && nonInteractive) {
188 return false;
189 }
190 if (!rule.interactive && !nonInteractive) {
191 return false;
192 }
193 }
194
195 return true;
查看全部 2 处证据
实现
packages/core/src/policy/policy-engine.ts:49–195
完整规则匹配维度。
实现
packages/core/src/policy/policy-engine.ts:198–260
优先级、校验和默认决策。
15
L1 事实 gemini-policy-002
非交互默认拒绝,交互默认询问;危险命令强制 ASK,YOLO 例外
源码事实 PolicyEngine 未配置 defaultDecision 时,nonInteractive 为 DENY、交互为 ASK_USER;shell heuristic 将危险命令改为 ASK_USER,YOLO 保留原决定,已知安全命令可把 ASK 降为 ALLOW。
白话解释 没人看屏幕时不赌;有人在时先问。只有明确 YOLO 才允许危险命令绕过这层强制提问。
对自研 Harness 的含义 无头执行失败关闭,YOLO 是明确的高风险模式。
关键源码 · 配置
复制
packages/core/src/policy/policy-engine.ts · L253–L260
253 this.nonInteractive = config.nonInteractive ?? false;
254 this.defaultDecision =
255 config.defaultDecision ??
256 (this.nonInteractive ? PolicyDecision.DENY : PolicyDecision.ASK_USER);
257 this.disableAlwaysAllow = config.disableAlwaysAllow ?? false;
258 this.checkerRunner = checkerRunner;
259 this.approvalMode = config.approvalMode ?? ApprovalMode.DEFAULT;
260 this.sandboxManager = config.sandboxManager ?? new NoopSandboxManager();
查看全部 2 处证据
配置
packages/core/src/policy/policy-engine.ts:253–260
交互/非交互默认决策。
实现
packages/core/src/policy/policy-engine.ts:284–341
重定向与命令安全启发式。
16
L1 事实 gemini-sandbox-001
Sandbox 默认不开启,但即使关闭仍净化环境变量
源码事实 factory 只有 sandbox.enabled 才按平台选择 Linux/Mac/Windows manager,否则用 NoopSandboxManager;Noop 不隔离进程,但会按安全配置 sanitize environment 后原样运行。
白话解释 防护罩不是默认扣上的;没扣罩子时至少会先从进程环境里清理不该传给子进程的东西。
对自研 Harness 的含义 必须评价为“可选强隔离”,不能写成“默认在沙箱中执行”。
关键源码 · 实现
复制
packages/core/src/services/sandboxManagerFactory.ts · L19–L43
19 /**
20 * Creates a sandbox manager based on the provided settings.
21 */
22 export function createSandboxManager(
23 sandbox: SandboxConfig | undefined,
24 options: GlobalSandboxOptions,
25 approvalMode?: string,
26 ): SandboxManager {
27 if (!options.modeConfig && options.policyManager && approvalMode) {
28 options.modeConfig = options.policyManager.getModeConfig(approvalMode);
29 }
30
31 if (sandbox?.enabled) {
32 if (os.platform() === 'win32') {
33 return new WindowsSandboxManager(options);
34 } else if (os.platform() === 'linux') {
35 return new LinuxSandboxManager(options);
36 } else if (os.platform() === 'darwin') {
37 return new MacOsSandboxManager(options);
38 }
39 return new LocalSandboxManager(options);
40 }
41
42 return new NoopSandboxManager(options);
43 }
查看全部 2 处证据
实现
packages/core/src/services/sandboxManagerFactory.ts:19–43
enabled gate 与平台选择。
实现
packages/core/src/services/sandboxManager.ts:285–333
Noop 环境净化与无隔离透传。
17
L1 事实 gemini-sandbox-002
三平台使用真实 OS 隔离,并保护治理文件与 .env 类秘密
源码事实 Linux 使用 bubblewrap 并生成 seccomp BPF 禁 ptrace;macOS 用 sandbox-exec/Seatbelt profile;Windows 用 restricted token、Job Object、Low Integrity helper。共同模型保护 .git/.gitignore/.geminiignore,隐藏 .env/.env.*。
白话解释 开罩后不是靠模型自觉:Linux、macOS、Windows 各用系统级限制器,仓库规则和秘密文件另加保护。
对自研 Harness 的含义 跨平台安全面完整,但每个后端差异大,需各自做 denial 与路径逃逸回归。
关键源码 · 契约
复制
packages/core/src/services/sandboxManager.ts · L194–L223
194 /**
195 * Files that represent the governance or "constitution" of the repository
196 * and should be write-protected in any sandbox.
197 */
198 export const GOVERNANCE_FILES = [
199 { path: '.gitignore', isDirectory: false },
200 { path: '.geminiignore', isDirectory: false },
201 { path: '.git', isDirectory: true },
202 ] as const;
203
204 /**
205 * Files that contain sensitive secrets or credentials and should be
206 * completely hidden (deny read/write) in any sandbox.
207 */
208 export const SECRET_FILES = [
209 { pattern: '.env' },
210 { pattern: '.env.*' },
211 ] as const;
212
213 /**
214 * Checks if a given file name matches any of the secret file patterns.
215 */
216 export function isSecretFile(fileName: string): boolean {
217 return SECRET_FILES.some((s) => {
218 if (s.pattern.endsWith('*')) {
219 const prefix = s.pattern.slice(0, -1);
220 return fileName.startsWith(prefix);
221 }
222 return fileName === s.pattern;
223 });
查看全部 3 处证据
契约
packages/core/src/services/sandboxManager.ts:194–223
治理文件与秘密文件清单。
实现
packages/core/src/sandbox/linux/LinuxSandboxManager.ts:48–114
seccomp BPF 与 ptrace 拒绝。
实现
packages/core/src/sandbox/windows/WindowsSandboxManager.ts:56–72
Windows isolation 后端。
18
L1 风险 gemini-sandbox-003
默认 sandbox mode 的 default 是可写 workspace,plan 才只读
源码事实 SandboxPolicyManager 内建 fallback 中 plan 为 readonly/network off,default 和 accepting_edits 为 writable/network off;YOLO 明确打开网络、写权限和 yolo 标志。
白话解释 即使开启沙箱,普通默认模式也允许改工作区;“开沙箱”不等于“只读”。
对自研 Harness 的含义 产品 UI 必须同时展示 sandbox enabled 与当前 mode,避免用户产生错误安全感。
关键源码 · 配置
复制
packages/core/src/policy/sandboxPolicyManager.ts · L49–L94
49 private static get DEFAULT_CONFIG(): SandboxTomlSchemaType {
50 if (!SandboxPolicyManager._DEFAULT_CONFIG) {
51 const __filename = fileURLToPath(import.meta.url);
52 const __dirname = path.dirname(__filename);
53 const defaultPath = path.join(
54 __dirname,
55 'policies',
56 'sandbox-default.toml',
57 );
58 try {
59 const content = fs.readFileSync(defaultPath, 'utf8');
60 if (typeof content !== 'string') {
61 SandboxPolicyManager._DEFAULT_CONFIG = {
62 modes: {
63 plan: {
64 network: false,
65 readonly: true,
66 approvedTools: [],
67 allowOverrides: true,
68 },
69 default: {
70 network: false,
71 readonly: false,
72 approvedTools: [],
… 12 lines omitted; exact range 49–94 …
85 }
86 SandboxPolicyManager._DEFAULT_CONFIG = SandboxTomlSchema.parse(
87 toml.parse(content),
88 );
89 } catch (e) {
90 debugLogger.error(`Failed to parse default sandbox policy: ${e}`);
91 throw new Error(`Failed to parse default sandbox policy: ${e}`);
92 }
93 }
94 return SandboxPolicyManager._DEFAULT_CONFIG;
查看全部 2 处证据
配置
packages/core/src/policy/sandboxPolicyManager.ts:49–94
plan/default/accepting_edits 默认能力。
实现
packages/core/src/policy/sandboxPolicyManager.ts:140–158
YOLO 和 mode 映射。
06
DIMENSION · MCP-EXTENSIONS-SKILLS-MEMORY-HOOKS
MCP、扩展、Skills、记忆与 Hooks
本章共 5 个可定位结论;结论按“实现事实 → 白话解释 → 工程影响 → 源码摘录”展开。
19
L1 事实 gemini-mcp-001
MCP 支持 stdio、Streamable HTTP、SSE fallback、OAuth、动态目录刷新和 progress
源码事实 McpClient 连接后发现 tools/prompts/resources 并注册,监听 listChanged;网络 transport 首选 HTTP,失败可回退 SSE,401 仅在显式 oauth.enabled 时发现/认证并重试;调用有 timeout、abort 与 progress token 路由。
白话解释 本地子进程和远程服务都能接;服务器换工具会热刷新,远程登录不是自动偷偷弹出,必须配置允许 OAuth。
对自研 Harness 的含义 连接器成熟,且认证意图边界清楚;热刷新仍需依赖 registry sort 与 policy validation。
关键源码 · 实现
复制
packages/core/src/tools/mcp-client.ts · L188–L292
188 async connect(): Promise<void> {
189 if (this.status !== MCPServerStatus.DISCONNECTED) {
190 throw new Error(
191 `Can only connect when the client is disconnected, current state is ${this.status}`,
192 );
193 }
194 this.updateStatus(MCPServerStatus.CONNECTING);
195 try {
196 this.client = await connectToMcpServer(
197 this.clientVersion,
198 this.serverName,
199 this.serverConfig,
200 this.debugMode,
201 this.workspaceContext,
202 this.cliConfig,
203 );
204
205 this.registerNotificationHandlers();
206
207 const originalOnError = this.client.onerror;
208 this.client.onerror = (error) => {
209 if (this.status !== MCPServerStatus.CONNECTED) {
210 return;
211 }
… 71 lines omitted; exact range 188–292 …
283
284 /**
285 * Disconnects from the MCP server.
286 */
287 async disconnect(): Promise<void> {
288 if (this.status !== MCPServerStatus.CONNECTED) {
289 return;
290 }
291 for (const registries of this.registeredRegistries) {
292 registries.toolRegistry.removeMcpToolsByServer(this.serverName);
查看全部 4 处证据
实现
packages/core/src/tools/mcp-client.ts:188–292
连接、发现、注册与断开。
实现
packages/core/src/tools/mcp-client.ts:390–430
listChanged 动态刷新。
实现
packages/core/src/tools/mcp-client.ts:1829–1916
transport 选择与连接。
实现
packages/core/src/tools/mcp-client.ts:1927–2059
SSE fallback 和显式 OAuth gate。
20
L1 事实 gemini-extension-001
扩展是能力包:MCP、policy/checker、context、commands、hooks、agents 与 skills 可成组热装卸
源码事实 ExtensionLoader 启动扩展时连接 MCP、刷新工具、注册 rules/checkers;批次完成后统一 refresh memory/system prompt/hooks/agent registry/skills。停止时按 source 移除政策并做同样刷新。
白话解释 扩展不是单个脚本,而是一箱可协同变化的工具、规则、记忆、钩子和 Agent。
对自研 Harness 的含义 扩展卸载能回收治理状态;批量刷新减少 prompt cache 抖动。
关键源码 · 实现
复制
packages/core/src/utils/extensionLoader.ts · L31–L110
31 /**
32 * Fully initializes all active extensions.
33 *
34 * Called within `Config.initialize`, which must already have an
35 * McpClientManager, PromptRegistry, and GeminiChat set up.
36 */
37 async start(config: Config): Promise<void> {
38 this.isStarting = true;
39 try {
40 if (!this.config) {
41 this.config = config;
42 } else {
43 throw new Error('Already started, you may only call `start` once.');
44 }
45 await Promise.all(
46 this.getExtensions()
47 .filter((e) => e.isActive)
48 .map(this.startExtension.bind(this)),
49 );
50 } finally {
51 this.isStarting = false;
52 }
53 }
54
… 46 lines omitted; exact range 31–110 …
101 this.eventEmitter?.emit('extensionsStarting', {
102 total: this.startingCount,
103 completed: this.startCompletedCount,
104 });
105 if (this.startingCount === this.startCompletedCount) {
106 this.startingCount = 0;
107 this.startCompletedCount = 0;
108 }
109 await this.maybeRefreshMemories();
110 }
查看全部 3 处证据
实现
packages/core/src/utils/extensionLoader.ts:31–110
启动、MCP、policy 和批次刷新。
实现
packages/core/src/utils/extensionLoader.ts:113–132
统一刷新上下文、hooks、agents、skills。
实现
packages/core/src/utils/extensionLoader.ts:172–225
停止与按来源移除政策。
21
L1 事实 gemini-memory-001
GEMINI.md/Memory 分 global、user-project、extension、project,并支持受信目录 JIT 加载
源码事实 memory discovery 分类别收集并串联;环境 memory 从 trusted root 向上到 git root;工具触达子目录时,只有目标处于 trusted root 才 JIT 搜索并按 inode/device 去重。
白话解释 常驻规章分层保存,走进更深目录时才加载当地规则;不受信目录不会因为读一个文件就注入它的说明。
对自研 Harness 的含义 兼顾大仓库按需上下文与 prompt injection 边界。
关键源码 · 实现
复制
packages/core/src/utils/memoryDiscovery.ts · L383–L454
383 export function getExtensionMemoryPaths(
384 extensionLoader: ExtensionLoader,
385 ): string[] {
386 const extensionPaths = extensionLoader
387 .getExtensions()
388 .filter((ext) => ext.isActive)
389 .flatMap((ext) => ext.contextFiles)
390 .map((p) => toAbsolutePath(p));
391
392 // Deduplicate case-insensitively (so macOS/Windows don't keep two casings of
393 // the same file) while preserving the first encountered casing for display.
394 const seenKeys = new Set<string>();
395 const unique: string[] = [];
396 for (const p of extensionPaths) {
397 const key = normalizePath(p);
398 if (seenKeys.has(key)) continue;
399 seenKeys.add(key);
400 unique.push(p);
401 }
402 return unique.sort();
403 }
404
405 export async function getEnvironmentMemoryPaths(
406 trustedRoots: string[],
… 38 lines omitted; exact range 383–454 …
445 .map((p) => contentsMap.get(p))
446 .filter((c): c is GeminiFileContent => !!c),
447 );
448
449 return {
450 global: getConcatenated(paths.global),
451 extension: getConcatenated(paths.extension),
452 project: getConcatenated(paths.project),
453 userProjectMemory: getConcatenated(paths.userProjectMemory ?? []),
454 };
查看全部 3 处证据
实现
packages/core/src/utils/memoryDiscovery.ts:383–454
extension/environment 分层与分类。
实现
packages/core/src/utils/memoryDiscovery.ts:512–572
JIT trusted-root gate 与 traversal ceiling。
实现
packages/core/src/utils/memoryDiscovery.ts:578–639
文件身份去重与并发上限。
22
L1 事实 gemini-skills-001
Skills 有明确覆盖顺序,workspace skills 受 folder trust 保护
源码事实 SkillManager 依次加载 builtin、extension、user、.agents alias、workspace,后者同名覆盖前者;未信任 workspace 时直接跳过项目 skills,并可由管理员全局禁用或按名禁用。
白话解释 越靠近项目的技能优先级越高,但项目没被信任前不会让它改 Agent 的做事方式。
对自研 Harness 的含义 skills 被正确视为可执行影响,而非普通文档。
关键源码 · 实现
复制
packages/core/src/skills/skillManager.ts · L17–L99
17 export class SkillManager {
18 private skills: SkillDefinition[] = [];
19 private activeSkillNames: Set<string> = new Set();
20 private adminSkillsEnabled = true;
21
22 /**
23 * Clears all discovered skills.
24 */
25 clearSkills(): void {
26 this.skills = [];
27 }
28
29 /**
30 * Resets session-scoped state (active skill names).
31 */
32 reset(): void {
33 this.activeSkillNames.clear();
34 }
35
36 /**
37 * Sets administrative settings for skills.
38 */
39 setAdminSettings(enabled: boolean): void {
40 this.adminSkillsEnabled = enabled;
… 49 lines omitted; exact range 17–99 …
90 storage.getProjectSkillsDir(),
91 );
92 this.addSkillsWithPrecedence(projectSkills);
93
94 // 4.1 Workspace agent skills alias (.agents/skills)
95 const projectAgentSkills = await loadSkillsFromDir(
96 storage.getProjectAgentSkillsDir(),
97 );
98 this.addSkillsWithPrecedence(projectAgentSkills);
99 }
查看全部 2 处证据
实现
packages/core/src/skills/skillManager.ts:17–99
来源、优先级与 trust gate。
实现
packages/core/src/skills/skillManager.ts:124–188
冲突覆盖、禁用与展示。
23
L1 事实 gemini-hooks-001
Before/AfterAgent hooks 可停止、阻断、注入上下文或要求清空后继续
源码事实 BeforeAgent 对每个 prompt_id 去重并可 stop/block/additionalContext;AfterAgent 只在最外层且没有 pending tools 时运行,可 stop、block、clearContext,并将 continue reason 作为新请求递归运行。
白话解释 钩子既能在开工前加背景/拦截,也能在收工时验收,不合格可清空现场后让 Agent 按理由重做。
对自研 Harness 的含义 适合策略与质量门,但递归 continuation 要有 turn 上限和 hook state 防重。
关键源码 · 实现
复制
packages/core/src/core/client.ts · L153–L252
153 // Hook state to deduplicate BeforeAgent calls and track response for
154 // AfterAgent
155 private hookStateMap = new Map<
156 string,
157 {
158 hasFiredBeforeAgent: boolean;
159 cumulativeResponse: string;
160 activeCalls: number;
161 originalRequest: PartListUnion;
162 }
163 >();
164
165 private async fireBeforeAgentHookSafe(
166 request: PartListUnion,
167 prompt_id: string,
168 ): Promise<BeforeAgentHookReturn> {
169 let hookState = this.hookStateMap.get(prompt_id);
170 if (!hookState) {
171 hookState = {
172 hasFiredBeforeAgent: false,
173 cumulativeResponse: '',
174 activeCalls: 0,
175 originalRequest: request,
176 };
… 66 lines omitted; exact range 153–252 …
243
244 const hookOutput = await this.config
245 .getHookSystem()
246 ?.fireAfterAgentEvent(
247 partToString(finalRequest),
248 finalResponseText,
249 stopHookActive,
250 );
251
252 return hookOutput;
查看全部 2 处证据
实现
packages/core/src/core/client.ts:153–252
Before/AfterAgent 安全包装与去重。
实现
packages/core/src/core/client.ts:930–1034
stop/block/context/continuation 行为。
07
DIMENSION · SUBAGENTS-COLLABORATION
子 Agent 与协作
本章共 2 个可定位结论;结论按“实现事实 → 白话解释 → 工程影响 → 源码摘录”展开。
24
L1 事实 gemini-agent-001
子 Agent 统一为可订阅、可重放、可中止的 AgentProtocol
源码事实 AgentSession 包装 send/subscribe/abort/events,并把一次 stream 限定在 agent_start→agent_end;它先订阅再回放历史,处理 setup 期间 early events,支持按 eventId 或 streamId 重接。
白话解释 无论子 Agent 在本机还是远程,上层看到的都是一条带编号、能续看的事件流。
对自研 Harness 的含义 UI、SDK 与 A2A server 可共享协议,断线恢复不必理解每种 executor。
关键源码 · 契约
复制
packages/core/src/agent/agent-session.ts · L14–L69
14 /**
15 * AgentSession is a wrapper around AgentProtocol that provides a more
16 * convenient API for consuming agent activity as an AsyncIterable.
17 */
18 export class AgentSession implements AgentProtocol {
19 private _protocol: AgentProtocol;
20
21 constructor(protocol: AgentProtocol) {
22 this._protocol = protocol;
23 }
24
25 async send(payload: AgentSend): Promise<{ streamId: string | null }> {
26 return this._protocol.send(payload);
27 }
28
29 subscribe(callback: (event: AgentEvent) => void): Unsubscribe {
30 return this._protocol.subscribe(callback);
31 }
32
33 async abort(): Promise<void> {
34 return this._protocol.abort();
35 }
36
37 get events(): readonly AgentEvent[] {
… 22 lines omitted; exact range 14–69 …
60 * optionally replaying events from history or reattaching to an existing stream.
61 *
62 * @param options Options for replaying or reattaching to the event stream.
63 */
64 async *stream(
65 options: {
66 eventId?: string;
67 streamId?: string;
68 } = {},
69 ): AsyncIterable<AgentEvent> {
查看全部 3 处证据
契约
packages/core/src/agent/agent-session.ts:14–69
AgentProtocol wrapper 与 AsyncIterable。
实现
packages/core/src/agent/agent-session.ts:70–165
订阅优先、eventId resume 和生命周期过滤。
实现
packages/core/src/agent/agent-session.ts:166–223
streamId replay、early events 和清理。
25
L1 限制 gemini-agent-002
Local 子 Agent 支持后台执行和取消,但同一 protocol 实例不允许并发 stream
源码事实 LocalSubagentProtocol 在 send(message) 时若已有 activeStreamId 直接报错;它用 setTimeout 后台启动,独立 AbortController,取消时丢弃 partial output 并返回空 aborted result。
白话解释 一个子 Agent 会话一次只接一单;主线程能先拿到 streamId,不必等它做完,但不能同时塞第二单。
对自研 Harness 的含义 并发应通过多个 Agent 实例而非重入同一实例,取消的部分成果不会自动保留。
关键源码 · 实现
复制
packages/core/src/agents/local-subagent-protocol.ts · L69–L95
69 class LocalSubagentProtocol implements AgentProtocol {
70 private _events: AgentEvent[] = [];
71 private _subscribers = new Set<(event: AgentEvent) => void>();
72 private _streamId: string = randomUUID();
73 private _eventCounter = 0;
74 private _agentStartEmitted = false;
75 private _agentEndEmitted = false;
76 private _activeStreamId: string | undefined;
77 private _abortController = new AbortController();
78
79 // Result promise wiring — re-created per stream in _beginNewStream()
80 private _resultResolve!: (output: OutputObject) => void;
81 private _resultReject!: (err: unknown) => void;
82 private _resultPromise: Promise<OutputObject> | undefined;
83
84 // Buffered config from send({update})
85 private _bufferedConfig: Record<string, unknown> = {};
86
87 constructor(
88 private readonly definition: LocalAgentDefinition,
89 private readonly context: AgentLoopContext,
90 // Required for API parity across protocol constructors (local, remote, legacy)
91 _messageBus: MessageBus,
92 private readonly _rawActivityCallback?: (
93 activity: SubagentActivityEvent,
94 ) => void,
95 ) {}
查看全部 3 处证据
实现
packages/core/src/agents/local-subagent-protocol.ts:69–95
事件、stream 与 abort 状态。
实现
packages/core/src/agents/local-subagent-protocol.ts:112–162
单 active stream、后台启动与取消。
实现
packages/core/src/agents/local-subagent-protocol.ts:183–215
stream 重置和 abort 结果。
08
DIMENSION · PERSISTENCE-OBSERVABILITY-MATURITY
持久化、观测与成熟度
本章共 3 个可定位结论;结论按“实现事实 → 白话解释 → 工程影响 → 源码摘录”展开。
源码事实 ChatRecordingService 逐行解析;$rewindTo 删除目标及以后消息,$set 可增量更新 metadata 或用 messages 数组重建 checkpoint,坏行被隔离忽略,缺关键 metadata 时回退 legacy parser。
白话解释 对话文件像事件日志:可以写“回到某一步”、只改元数据,也能偶尔写一张完整快照;一行坏了不拖垮整份会话。
对自研 Harness 的含义 支持 resume/rewind 与格式迁移,需持续测试 checkpoint 和增量事件的一致性。
关键源码 · 实现
复制
packages/core/src/services/chatRecordingService.ts · L150–L203
150 try {
151 const fileStream = fs.createReadStream(filePath);
152 const rl = readline.createInterface({
153 input: fileStream,
154 crlfDelay: Infinity,
155 });
156
157 let metadata: Partial<ConversationRecord> = {};
158 const messagesMap = new Map<string, MessageRecord>();
159 const messageIds: string[] = [];
160 const messageKinds = new Map<
161 string,
162 { isUser: boolean; isResumable: boolean }
163 >();
164 let isTrackingMemoryScratchpadFreshness = false;
165 let memoryScratchpadIsStale = false;
166 let firstUserMessageStr: string | undefined;
167
168 for await (const line of rl) {
169 if (!line.trim()) continue;
170 try {
171 const record = JSON.parse(line) as unknown;
172 if (isRewindRecord(record)) {
173 if (isTrackingMemoryScratchpadFreshness) {
… 20 lines omitted; exact range 150–203 …
194 }
195 if (found) {
196 for (const id of idsToDelete) {
197 messagesMap.delete(id);
198 }
199 } else {
200 messagesMap.clear();
201 }
202 }
203 } else if (isMessageRecord(record)) {
查看全部 3 处证据
实现
packages/core/src/services/chatRecordingService.ts:150–203
JSONL、rewind 和错误隔离。
实现
packages/core/src/services/chatRecordingService.ts:203–300
message 与 metadata checkpoint。
实现
packages/core/src/services/chatRecordingService.ts:300–350
initial/legacy record 和 fallback。
27
L1 事实 gemini-observe-001
OpenTelemetry 可导出到 GCP、OTLP HTTP/gRPC、文件或控制台
源码事实 telemetry SDK 同时构造 trace/log/metric exporter,支持 GCP 直出、OTLP HTTP/grpc+gzip、单文件和 console;NodeSDK 加载 HTTP instrumentation,并可周期监控内存和 event loop。
白话解释 既能接企业观测平台,也能只落本地文件;模型调用之外还能看到进程内存和事件循环卡顿。
对自研 Harness 的含义 生产可观测性完整,但凭据、提示内容与遥测开关需纳入隐私治理。
关键源码 · 实现
复制
packages/core/src/telemetry/sdk.ts · L240–L318
240 const otlpEndpoint = config.getTelemetryOtlpEndpoint();
241 const otlpProtocol = config.getTelemetryOtlpProtocol();
242 const telemetryTarget = config.getTelemetryTarget();
243 const useCollector = config.getTelemetryUseCollector();
244
245 const parsedEndpoint = parseOtlpEndpoint(otlpEndpoint, otlpProtocol);
246 const telemetryOutfile = config.getTelemetryOutfile();
247 const useOtlp = !!parsedEndpoint && !telemetryOutfile;
248
249 const gcpProjectId =
250 process.env['OTLP_GOOGLE_CLOUD_PROJECT'] ||
251 process.env['GOOGLE_CLOUD_PROJECT'];
252 const useDirectGcpExport =
253 telemetryTarget === TelemetryTarget.GCP && !useCollector;
254
255 let spanExporter:
256 | OTLPTraceExporter
257 | OTLPTraceExporterHttp
258 | GcpTraceExporter
259 | FileSpanExporter
260 | ConsoleSpanExporter;
261 let logExporter:
262 | OTLPLogExporter
263 | OTLPLogExporterHttp
… 45 lines omitted; exact range 240–318 …
309 compression: CompressionAlgorithm.GZIP,
310 });
311 metricReader = new PeriodicExportingMetricReader({
312 exporter: new OTLPMetricExporter({
313 url: parsedEndpoint,
314 compression: CompressionAlgorithm.GZIP,
315 }),
316 exportIntervalMillis: 10000,
317 });
318 }
查看全部 2 处证据
实现
packages/core/src/telemetry/sdk.ts:240–318
GCP 与 OTLP exporters。
实现
packages/core/src/telemetry/sdk.ts:319–375
file/console、NodeSDK 和运行时监控。
28
L3 事实 gemini-maturity-001
大型 TypeScript monorepo,测试面广,Apache-2.0
源码事实 固定提交约 2,144 个 TypeScript 文件,机械统计 892 个 *.test.ts/tsx;根许可证是 Apache License 2.0。
白话解释 这是一套带 CLI、core、SDK、ACP/A2A、策略和平台沙箱的系统,不是单文件 demo。
对自研 Harness 的含义 架构可借鉴性高,但新旧上下文路径和多前端会扩大回归矩阵。
边界 文件数是固定 checkout 的机械统计,不等于测试覆盖率。
关键源码 · 契约
复制
LICENSE · L1–L28
1
2 Apache License
3 Version 2.0, January 2004
4 http://www.apache.org/licenses/
5
6 TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
8 1. Definitions.
9
10 "License" shall mean the terms and conditions for use, reproduction,
11 and distribution as defined by Sections 1 through 9 of this document.
12
13 "Licensor" shall mean the copyright owner or entity authorized by
14 the copyright owner that is granting the License.
15
16 "Legal Entity" shall mean the union of the acting entity and all
17 other entities that control, are controlled by, or are under common
18 control with that entity. For the purposes of this definition,
19 "control" means (i) the power, direct or indirect, to cause the
20 direction or management of such entity, whether by contract or
21 otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 outstanding shares, or (iii) beneficial ownership of such entity.
23
24 "You" (or "Your") shall mean an individual or Legal Entity
25 exercising permissions granted by this License.
26
27 "Source" form shall mean the preferred form for making modifications,
28 including but not limited to software source code, documentation
查看全部 3 处证据
契约
LICENSE:1–28
Apache License 2.0。
测试
packages/core/src/context/contextManager.test.ts:1–40
ContextManager 专项测试入口。
测试
packages/core/src/services/sandboxManager.integration.test.ts:1–40
sandbox 集成测试入口。
APPENDIX · SOURCE INDEX
本报告引用过的实现文件
这是一份代码阅读索引,不是仓库文件总表。机器候选扫描覆盖整个仓库;进入结论的文件必须经人工沿调用链复核。
01 packages/core/src/core/client.tsL79–111, 875–907, 910–1060, 614–715, 717–807, 744–763, 810–855, 107–120, 1196–1248, 640–678, 829–838, 153–252, 930–1034 02 packages/core/src/core/contentGenerator.tsL35–70, 72–110, 285–310, 312–410 03 packages/core/src/core/geminiChat.tsL517–578, 580–648 04 packages/core/src/context/chatCompressionService.tsL37–52, 54–100, 124–142, 144–215 05 packages/core/src/context/contextManager.tsL26–88, 90–195, 197–275 06 packages/core/src/core/turn.tsL236–320 07 packages/core/src/agents/agent-scheduler.tsL42–92 08 packages/core/src/scheduler/tool-executor.tsL250–297, 299–368 09 packages/core/src/policy/policy-engine.tsL49–195, 198–260, 253–260, 284–341 10 packages/core/src/services/sandboxManagerFactory.tsL19–43 11 packages/core/src/services/sandboxManager.tsL285–333, 194–223 12 packages/core/src/sandbox/linux/LinuxSandboxManager.tsL48–114 13 packages/core/src/sandbox/windows/WindowsSandboxManager.tsL56–72 14 packages/core/src/policy/sandboxPolicyManager.tsL49–94, 140–158 15 packages/core/src/tools/mcp-client.tsL188–292, 390–430, 1829–1916, 1927–2059 16 packages/core/src/utils/extensionLoader.tsL31–110, 113–132, 172–225 17 packages/core/src/utils/memoryDiscovery.tsL383–454, 512–572, 578–639 18 packages/core/src/skills/skillManager.tsL17–99, 124–188 19 packages/core/src/agent/agent-session.tsL14–69, 70–165, 166–223 20 packages/core/src/agents/local-subagent-protocol.tsL69–95, 112–162, 183–215 21 packages/core/src/services/chatRecordingService.tsL150–203, 203–300, 300–350 22 packages/core/src/telemetry/sdk.tsL240–318, 319–375 23 LICENSEL1–28 24 packages/core/src/context/contextManager.test.tsL1–40 25 packages/core/src/services/sandboxManager.integration.test.tsL1–40