随笔

Claude Code 深度源码解读 02:Context 构建是一条输入装配线

拆解 submitMessage、system prompt、processUserInput、attachments、memory 与 transcript 写入顺序。

2026-04-06 Claude Code源码解读Agent Harness技术深读

简化版入口:Context 构建

机制图
机制图

我的设问与回应

设问 1:Context 是不是把资料尽量多地塞给模型?

回应答案: 不是。Context 构建更像输入装配线:按来源、作用域、生命周期把系统规则、用户输入、附件、memory、项目快照和 Hook 输出组装成模型可消费的消息。

证据与证明路径: 证据来自 QueryEngine、fetchSystemPromptParts、processUserInput、getAttachmentMessages、recordTranscript 的顺序。git status 被明确标注为会话开始时快照,说明源码并不把它当成运行时动态反馈。

还值得继续学习或反思: 要继续学习如何给每类上下文标 source、scope、freshness,避免上下文完整性变成噪声堆积。

设问 2:为什么模型请求前要先写 transcript?

回应答案: 因为 transcript 是恢复与审计边界,不只是日志。用户消息先落盘,后续 API 中断、fallback 或 resume 才有可恢复事实来源。

证据与证明路径: 证据是原报告把 recordTranscript 放在模型请求前,并在贡献部分强调支持恢复与审计。

还值得继续学习或反思: 如果产品只记录 UI progress,不记录进入模型的规范化消息,就无法可靠 resume。

源码调研主体

本组解读基于冻结源码快照进行教育、防御和架构研究。

本期主题:Harness 如何把用户输入转化为稳定、可执行、上下文充分的模型认知窗口。

1. 研究问题

本期围绕一个问题展开:

Claude Code Harness 如何在真正执行工具之前,让模型先“看对问题”?

这比工具执行更靠前。一个 coding agent 稳定解决问题,第一步不是执行命令,而是把用户意图、项目状态、系统约束、文件上下文、记忆、任务状态和权限上下文组织成模型可用的输入窗口。

2. 对应总览节点

对应第 1 期总览图中的节点:

  • User Input
  • Context Preparation
  • Memory
  • Lifecycle Hooks
  • Transcript / Session State
  • Query Loop

这部分机制参考前文「Context 构建 机制图」。

3. 核心流程图

这部分机制参考前文「Context 构建 机制图」。

4. 关键源码入口

职责文件
会话入口与上下文总装src/QueryEngine.ts
system/user/system context 获取src/utils/queryContext.ts
系统与用户上下文来源src/context.ts
用户输入处理主入口src/utils/processUserInput/processUserInput.ts
普通文本 prompt 转消息src/utils/processUserInput/processTextPrompt.ts
附件体系src/utils/attachments.ts
memory promptsrc/memdir/memdir.ts
message 创建与规范化src/utils/messages.ts
transcript 写入src/utils/sessionStorage.ts

4.1 主线与场景特殊处理分类

机制/模块分类为什么这样归类
QueryEngine.submitMessage()主线机制会话 turn 的入口,负责把输入、上下文、工具环境和模型请求串起来
fetchSystemPromptParts()主线机制构造 API 前缀上下文,是模型请求前的基础步骤
processUserInput()主线机制将用户输入归一化为 Message,是进入模型前的必经处理
processTextPrompt()主线机制普通 prompt 的最终消息构造路径
ToolUseContext / ProcessUserInputContext主线机制后续工具、权限、Hook、附件与状态访问共享的运行时上下文
getAttachmentMessages()主线机制 + 场景增强聚合点附件聚合是主线,但内部大量附件按场景条件启用
getUserContext() 中的 CLAUDE.md场景增强依赖项目中是否存在相关指令文件,也可被环境变量或 bare mode 影响
getSystemContext() 中的 git status场景增强只在 git 仓库、非 remote、git instructions 未关闭时注入;不是运行时依赖
IDE selection / opened file场景增强只在 IDE 连接并提供选区或打开文件时注入
LSP diagnostics / diagnostics场景增强依赖诊断系统或 LSP 状态,不是常规文本任务必经路径
UserPromptSubmit Hooks安全/治理横切可在模型请求前补充上下文、阻断继续或改变输入效果
transcript 写入可观测/恢复支撑在模型请求前持久化用户消息,支撑 resume 和审计
image resize / image metadata场景增强只在 content blocks 或 pasted images 存在时触发
queued commands / task notifications场景增强 + 可观测/恢复支撑只在队列存在时注入,支撑异步任务和通知进入当前 turn

5. 机制拆解

5.1 QueryEngine 是 Context 构建的总装层

QueryEngine.submitMessage() 做的第一件事不是调用模型,而是冻结本轮所需的运行参数:

  • 当前工作目录。
  • 命令集合。
  • 工具集合。
  • MCP 连接。
  • agent 定义。
  • 权限检查函数。
  • AppState 读写函数。
  • read file cache。
  • thinking config。
  • 模型选择。

随后它构造 ProcessUserInputContext。这个上下文继承自 ToolUseContext,但在输入处理阶段多了 slash command、本地 UI、附件处理所需的能力。

5.2 fetchSystemPromptParts 构造 API 前缀上下文

fetchSystemPromptParts() 返回三类上下文:

上下文来源作用
defaultSystemPromptgetSystemPrompt()规定模型行为、工具说明、运行策略
userContextgetUserContext()注入 CLAUDE.md、当前日期等用户/项目上下文
systemContextgetSystemContext()注入可选系统状态快照,例如 git status、cache breaker

这三类上下文构成模型请求的稳定前缀。它们被单独构造,是为了让 prompt cache 和上下文边界更可控。

5.3 context.ts 提供可选项目状态快照

getUserContext()getSystemContext() 是项目环境进入模型窗口的重要入口。

getUserContext() 主要负责:

  • 发现并读取 CLAUDE.md 类记忆/指令文件。
  • 写入当前日期。
  • 缓存 CLAUDE.md 内容,供 auto-mode classifier 等机制复用。

getSystemContext() 主要负责注入会话级系统状态快照。git 不是运行时依赖,也不是 Context 构建主线,只是 coding 场景下的可选项目状态信号:

  • 在非 remote、且 git instructions 未关闭时,读取一次 git status。
  • 读取当前分支、main branch、最近提交、git user,并明确标注这是 conversation start 的快照。
  • 注入 cache breaker 等系统级调试信息。

这些信息不是用户显式输入,但在存在 git 仓库时会影响模型对项目初始状态的判断。源码中的提示文本明确说明该 git status 是 “at the start of the conversation” 的快照,并且 “will not update during the conversation”。因此它不是运行中动态反馈机制,也不是非 git 项目必须具备的条件;运行中的 git 变化仍需要通过工具调用、附件、diff/status 命令或其他上下文刷新路径进入模型。

5.4 processUserInput 是“输入归一化器”

processUserInput() 把不同来源的输入统一成消息:

这部分机制参考前文「Context 构建 机制图」。

它处理的输入形态包括:

  • 普通字符串 prompt。
  • SDK / IDE 传入的 content blocks。
  • 粘贴图片。
  • slash command。
  • bash mode 输入。
  • remote bridge 输入。
  • agent mention。
  • ultraplan keyword。
  • IDE selection。

这说明 Context 构建不是简单地把用户文本塞进 API,而是一个多入口归一化过程。

5.5 getAttachmentMessages 是上下文扩展中枢

getAttachmentMessages() 是本期最关键的函数之一。它负责把“用户没有直接写在 prompt 里,但模型应该看到”的信息注入上下文。

附件分三类:

这部分机制参考前文「Context 构建 机制图」。

这种分层有明确目的:

  • 用户输入附件先执行,因为它可能触发 nested memory。
  • thread-safe 附件可以在主线程和 subagent 中使用。
  • main-thread 附件只给主会话使用,避免 subagent 拿到不该拿的 UI/IDE 状态。

5.6 UserPromptSubmit Hooks 在模型调用前介入

processUserInput() 在确认需要查询模型后,会执行 executeUserPromptSubmitHooks()

Hook 可以做三类关键事情:

  1. 返回 blocking error,直接阻止本轮模型调用。
  2. 返回 prevent continuation,让原始 prompt 留在上下文中但停止继续。
  3. 返回 additional context,作为 hook_additional_context 附件加入消息。

这意味着 Hooks 在工具执行前就能影响模型看到的问题定义。

5.7 processTextPrompt 形成最终用户消息

普通 prompt 最后进入 processTextPrompt()。它负责:

  • 生成 prompt id。
  • 记录 tracing / OTel 事件。
  • 识别 negative / keep-going 类用户意图。
  • 合并文本和图片 content blocks。
  • 创建 UserMessage
  • 将附件追加在 user message 之后。

输出结构通常是:

UserMessage
AttachmentMessage[]

这组消息随后被 QueryEngine 推入 mutableMessages,并在模型请求前写入 transcript。

5.8 模型请求前先写 transcript

QueryEngine.submitMessage() 在进入 query() 之前,会把用户消息写入 transcript。

这个设计对稳定性很关键:如果进程在模型响应前被杀掉,用户输入仍然已经落盘,后续 resume 不会丢失任务入口。

6. 对稳定解决问题能力的贡献

6.1 提升问题理解完整性

模型看到的不只是用户 prompt,还包括:

  • 项目指令。
  • 当前日期。
  • git 状态。
  • IDE 选区。
  • 打开的文件。
  • diagnostics。
  • changed files。
  • queued commands。
  • plan / todo / task 状态。
  • memory 与 nested memory。

这让模型在行动前拥有更完整的问题背景。

6.2 降低上下文遗漏风险

附件系统把隐含上下文显式化。例如 IDE 选区、LSP diagnostics、todo reminders 都不是用户自然语言的一部分,但对 coding task 很关键。

6.3 保持主线程与 subagent 上下文边界

附件分为 thread-safe 和 main-thread,避免 subagent 误拿主线程专属状态。这是多 agent 稳定性的基础。

6.4 支持恢复与审计

模型请求前先写 transcript,使任务入口可恢复。即使模型尚未返回,系统也知道用户提交了什么。

6.5 支持前置纠偏

UserPromptSubmit Hook 可以在模型调用前阻止、补充或改写上下文。这比工具执行后再纠偏更早。

7. 风险、边界与待验证问题

7.1 Context 构建复杂度高

getAttachmentMessages() 聚合了大量上下文来源。优点是完整,风险是隐藏耦合强:某个附件异常、过大或时序不当,可能影响模型输入质量。

7.2 上下文完整性与噪声之间存在张力

越多附件不一定越好。计划提醒、todo、diagnostics、memory、changed files 都可能提高完整性,也可能挤占注意力窗口。

7.3 prompt cache 与动态上下文之间存在张力

fetchSystemPromptParts() 把 system prompt、user context、system context 作为稳定前缀,但 git status、CLAUDE.md、动态附件等都会影响缓存命中和上下文一致性。

7.4 Hook 过早介入可能改变用户意图

UserPromptSubmit Hook 可以阻断或补充上下文。它是稳定性机制,也是潜在的行为变更入口。

8. 本期结论

本期结论是:

Claude Code Harness 的稳定性首先来自输入侧治理。它在模型调用前完成了用户输入归一化、项目上下文收集、附件扩展、记忆提示、Hook 前置纠偏、运行时上下文构造和 transcript 落盘。

这说明它不是把 prompt 直接交给模型,而是先构造一个可执行、可恢复、带边界的认知窗口。

9. 下一期衔接

第 3 期进入 Tools / Permissions 控制链路。

建议选择 BashTool 作为代表样本,但定位必须明确:

  • 它不是 Harness 全貌的代表。
  • 它是行动能力与安全控制链路的代表。
  • 研究重点是模型如何从“看对问题”进入“受控行动”。

第 3 期应追踪:

tool_use -> StreamingToolExecutor / runTools -> runToolUse -> canUseTool -> checkPermissions -> Hooks -> BashTool.call -> shell execution -> tool_result

10. 拓展阅读:Context 构建澄清问答

本节记录第 2 期报告形成后的澄清问题。重点是区分主线机制与场景增强,避免把可选能力误写成 Harness 必经路径。

10.1 最终给模型 API 的完整 Prompt 如何构成?

问题:最终拼装出来给到模型 API 的完整 Prompt 的构成方式是什么?它们的前后关系如何影响模型 attention 和 cache 命中率?

澄清:最终请求不是一个单字符串 prompt,而是 Anthropic messages API 请求中的三大部分:

API request
├─ system: system prompt blocks
├─ messages: user / assistant / tool_result message array
└─ tools: tool schemas

主线关系:

这部分机制参考前文「Context 构建 机制图」。

具体顺序:

  1. QueryEngine.submitMessage() 获取 defaultSystemPromptuserContextsystemContext
  2. 组装 systemPrompt = default/custom + memoryMechanicsPrompt + appendSystemPrompt
  3. query() 中通过 appendSystemContext(systemPrompt, systemContext) 得到 fullSystemPrompt
  4. query() 中通过 prependUserContext(messagesForQuery, userContext) 把 user context 包成一个 synthetic user message 前置到 messages。
  5. API 层调用 normalizeMessagesForAPI() 规范化 messages。
  6. API 层调用 buildSystemPromptBlocks() 把 system prompt 拆成可缓存的 system blocks。
  7. 最终请求同时包含 systemmessagestools

对 attention 的影响:

  • system 承载最高层行为约束和工具行为说明。
  • userContext 被包装成 <system-reminder> synthetic user message,并提示 “may or may not be relevant”。
  • attachments 跟随用户消息进入 messages,对当前任务局部上下文影响更直接。
  • tool results 在后续轮次回灌,影响模型下一步行动。

对 cache 命中率的影响:

  • system prompt 会按静态/动态边界拆 block。
  • 静态 system prompt 可使用 global/org cache scope。
  • 动态内容如 system context、append prompt、Chrome tool instructions、部分 tool schema 变化会影响缓存。
  • deferred tools / tool search 用来减少大工具池对上下文和缓存的冲击。

分类:

机制分类说明
system blocks主线机制每次模型请求的核心指令区
messages array主线机制会话、附件、工具结果的主要承载体
tools schemas主线机制agentic coding 能力的行动接口
prompt cache block 拆分可观测/恢复支撑 + 性能支撑不改变语义主线,但影响效率和稳定性
deferred tools / tool search场景增强 + 性能支撑工具池大或动态工具多时降低上下文和 cache 压力

10.2 Image 处理是否真实存在?具体方案是什么?

问题:模型是多模态模型,user input 中提前处理 image 等信息是否真实存在?处理方案有哪些?

澄清:真实存在。图片处理不是伪处理,最终会生成 Anthropic image content block 进入 UserMessage

两类输入路径:

  1. SDK/IDE 直接传入的 ContentBlockParam[] 中包含 image block。
  2. 用户粘贴图片,进入 pastedContents

处理流程:

这部分机制参考前文「Context 构建 机制图」。

具体处理:

  • 只对 source.type === 'base64' 的 image block 做 resize/downsample。
  • media_type 推断扩展名。
  • base64 decode 成 buffer 后压缩或缩放。
  • 返回新的 image content block:
{
  type: "image",
  source: {
    type: "base64",
    media_type: "image/...",
    data: "..."
  }
}
  • 粘贴图片还会被 storeImages() 保存到本地,以便模型后续通过工具引用图片路径。
  • 如果能获取尺寸,则生成 image metadata;如果只有 source path,也会生成 source path metadata。

最终进入模型的内容:

内容是否进入模型形式
image content blockUserMessage.content 中的多模态 block
image dimensions/source metadataisMeta user message
本地保存的图片文件不直接作为图片进入 API作为路径信息供后续工具引用

分类:

机制分类说明
普通文本 prompt主线机制常规输入路径
image content block场景增强只在多模态输入或粘贴图片时触发
image resize/downsample场景增强 + 安全/治理横切受 API 图片大小/尺寸限制驱动,防止请求失败
image metadata isMeta message场景增强辅助模型理解图片尺寸和来源

10.3 loadMemoryPrompt when enabled 的 when 由什么决定?

问题:流程中出现 loadMemoryPrompt when enabled,这里的 “when” 具体由哪些因素决定?

澄清:主要由 isAutoMemoryEnabled() 和若干产品/feature 场景决定。

isAutoMemoryEnabled() 的优先级:

1. CLAUDE_CODE_DISABLE_AUTO_MEMORY=true/1 -> 关闭
2. CLAUDE_CODE_DISABLE_AUTO_MEMORY=false/0 -> 开启
3. CLAUDE_CODE_SIMPLE / --bare -> 关闭
4. CLAUDE_CODE_REMOTE 且没有 CLAUDE_CODE_REMOTE_MEMORY_DIR -> 关闭
5. settings.autoMemoryEnabled 显式设置 -> 使用设置值
6. 默认开启

loadMemoryPrompt() 内部再分场景:

条件结果
feature('KAIROS') && autoEnabled && getKairosActive()使用 daily log prompt
feature('TEAMMEM') && team memory enabled使用 auto + team combined memory prompt
autoEnabled创建/确认 auto memory dir,返回普通 auto memory prompt
否则返回 null

还有一个 SDK/custom prompt 特殊路径:

customSystemPrompt 存在
AND CLAUDE_COWORK_MEMORY_PATH_OVERRIDE 有效

满足时,QueryEngine.submitMessage() 会额外注入 memory mechanics prompt。原因是 custom system prompt 会替换默认 system prompt,必须显式补回 memory 机制说明。

分类:

机制分类说明
isAutoMemoryEnabled()场景增强开关memory 不是所有运行模式必需
普通 auto memory prompt场景增强默认可能开启,但仍可被 env/settings/simple/remote 条件关闭
KAIROS daily log prompt场景增强产品/feature 模式
TEAMMEM combined prompt场景增强team memory 模式
Cowork override + custom prompt场景增强SDK/custom system prompt 场景补偿

10.4 特殊输入路径分别是什么?目的和特殊处理是什么?

问题:slash commandbash mode 输入remote bridge 输入agent mentionultraplan keywordIDE selection 分别对应哪些方面?目的是什么?处理过程中做了哪些特殊针对处理?

澄清:这些都不是同一层级的主线机制。它们是围绕主线输入处理的场景增强或安全治理路径。

分类目的特殊处理
slash command场景增强 / 命令入口让用户用 /compact/clear/hooks 等触发本地命令或 prompt commandinput 以 / 开头且未 skip 时走 processSlashCommand();slash command 的附件提取在 command 内部处理
bash mode 输入场景增强 / 直接 shell 输入用户手动执行 shell,不是模型发起工具调用processBashCommand();包装 <bash-input>;调用 BashTool.call()PowerShellTool.call()dangerouslyDisableSandbox: true;结果包装为 <bash-stdout> / <bash-stderr>shouldQuery: false
remote bridge 输入场景增强 / 远程控制 + 安全治理mobile/web/remote control 发来的输入,避免远端触发本地 UI 型命令默认 skipSlashCommands;若 bridgeOrigin 且命令通过 isBridgeSafeCommand() 才允许执行;local-jsx 禁止,prompt command 安全,少数 local command allowlist
agent mention场景增强 / 子代理路由提示用户用 @agent-xxx 表达要某个 agent 参与processAgentMentions() 提取 mention,匹配 active agent,生成 agent_mention attachment;找不到 agent 会记录失败
ultraplan keyword场景增强 / 计划模式快捷入口用户自然语言触发 /ultraplan只在 interactive prompt、非 slash、非 headless、未已有 ultraplan session 时触发;用 pre-expansion input 检测,避免 pasted content 误触发
IDE selection场景增强 / IDE 上下文注入把编辑器选区或当前打开文件给模型只在 IDE 连接、存在 filePath/text/lineStart 时注入;会检查 isFileReadDenied(),被权限 deny 的文件不会注入

主线与场景关系:

主线:
User input -> processUserInput -> UserMessage/AttachmentMessage -> query

场景增强:
slash command / bash mode / bridge / agent mention / ultraplan / IDE selection / images

安全横切:
bridge-safe command allowlist
file read deny check for IDE context
UserPromptSubmit hooks

可观测/恢复:
prompt id
OTel user_prompt
transcript before model call

重要边界:

  • slash command 是用户显式控制 CLI 功能,不等同于模型工具调用。
  • bash mode 是用户自己执行 shell,不是模型 tool_use
  • remote bridge 是跨设备输入路径,需要额外限制本地 UI 和命令执行。
  • agent mention 是上下文提示/路由信号,不等于一定 spawn agent。
  • ultraplan keyword 是自然语言快捷入口。
  • IDE selection 是上下文增强,不是必经路径。

机制主线收束

站在架构师视角,我会把这组源码机制收束为三个问题:为什么需要它,什么时候必须引入它,以及真正要设计的是什么。

  • why:Context 解决的是模型开始前是否看对问题,而不是让模型看更多材料。
  • when:当输入来源超过纯文本,例如图片、IDE、memory、项目规则、Hook,必须引入装配线。
  • what:按来源分层、按作用域过滤、按生命周期进入消息窗口,并把用户消息先落 transcript。

Context 构建 这组源码最值得带走的,不是某个函数或某个配置项,而是它如何把模型能力放进一组可验证、可拒绝、可恢复、可审计的工程边界里。读源码时,如果只记住名词,会很快散;如果抓住 why、when、what,就能把这组机制迁移到自己的 agent 架构判断中。