随笔
Claude Code 深度源码解读 01:Harness 不是工具集合,而是受控运行时
从交叉对象和运行时不变量切入,重建 Claude Code Harness 的工程读法。
简化版入口:Harness 总览。
我的设问与回应
设问 1:Harness 的边界到底在哪里?
回应答案: 边界不在模型输出处,而在模型输出被转换为可验证协议事件之后。用户输入、Context、Query Loop、Tool Execution、Permissions、Hooks、Memory、Transcript 共同构成边界。
证据与证明路径: 证据来自总览报告对 Message、Tool、ToolUseContext、AppState、Transcript / Session State 的归类,以及 Tools、Permissions、Hooks 的交叉展开。它证明 Claude Code 不是工具列表,而是一组围绕运行时上下文协作的控制层。
还值得继续学习或反思: 进一步要追的是这些交叉对象是否有稳定测试边界,以及新增场景能力时是否会绕过这些对象。
设问 2:主线功能和场景插入应该怎么分?
回应答案: 主线是没有它系统就不能推进的链路;场景插入是 MCP、remote、team、worktree、auto memory 等条件能力。
证据与证明路径: 证据是第 1 期把 Context、Query Loop、Tools、Permissions、Hooks、Memory 分别展开,又在后续路线中要求标注主线与场景特殊处理。
还值得继续学习或反思: 读 agent 源码不能按目录读,要按运行时是否改变主线语义读。
源码调研主体
本组解读基于冻结源码快照进行教育、防御和架构研究。
范围:只分析 Harness 运行机制,不提供复用实现,不改变源码边界。
0. 调研目标
本期目标是建立 Harness 机制的第一版可导航地图,用“总-分”的方式把源码中的关键运行机制画清楚:
- Tools:模型的手脚,解决行动能力。
- Context:模型当前思考窗口,解决模型看到什么。
- Memory:长期存储与召回,解决持久化记忆。
- Hooks:事件驱动反射,解决自动化介入。
- Permissions:安全围栏,解决安全底线。
本报告不把所有细节堆在一张图里,而是先给出总览,再对关键节点分别展开。每张展开图都标注其对应的总览节点。
0.1 主线与场景特殊处理标注
后续报告统一使用四类标签,避免把场景增强误写成 Harness 必经路径:
| 标签 | 含义 |
|---|---|
主线机制 | 常规任务中直接支撑用户输入、模型调用、工具回灌或会话推进的路径 |
场景增强 | 只在特定环境、功能开关、输入形态或产品模式下生效的补充能力 |
安全/治理横切 | 对权限、安全边界、策略约束、风险控制有关键作用的横切机制 |
可观测/恢复支撑 | 支撑 transcript、审计、恢复、调试和进度展示的机制 |
本期作为总览报告,先给出一级机制地图;从第 2 期开始,每期都在关键源码入口之后补充分类表。
1. Harness 总览图
对应目标:明确整体协同关系,而不是展开每个节点内部细节。
这部分机制参考前文「Harness 总览 机制图」。
总览解读
Harness 的核心不是某一个目录,而是一个由 QueryEngine.submitMessage() 和 query() 驱动的闭环:
- 用户输入先经过 Context Preparation。
- Query Loop 将上下文、工具、权限上下文发送给模型。
- 模型流式返回文本或
tool_use。 tool_use进入 Tool Execution。- Tool Execution 必须经过 Permissions 与 Hooks。
- 工具结果以
tool_result形式回灌给 Query Loop。 - Memory、Hooks、Compaction、Session State 横切整个循环。
2. Context Preparation 展开图
对应总览节点:Context Preparation
目标:解释模型“看到什么”是如何准备出来的。
这部分机制参考前文「Harness 总览 机制图」。
关键源码入口
| 职责 | 文件 |
|---|---|
| 会话生命周期入口 | src/QueryEngine.ts |
| system/user/system context 构造 | src/utils/queryContext.ts |
| 默认 system prompt | src/constants/prompts.ts |
| 用户输入处理 | src/utils/processUserInput/processUserInput.ts |
| 附件注入 | src/utils/attachments.ts |
| 消息规范化 | src/utils/messages.ts |
机制说明
QueryEngine.submitMessage() 是 Context Preparation 的中心。它先调用 fetchSystemPromptParts() 获取三类 API 前缀上下文:
defaultSystemPromptuserContextsystemContext
随后根据运行条件决定是否调用 loadMemoryPrompt(),把“如何使用记忆系统”的机制说明加入 system prompt。之后 processUserInput() 把用户输入加工成模型可消费的消息,包括普通文本、slash command、附件、图片、IDE 上下文等。
最后构造 ToolUseContext。这个对象非常关键,它不是普通参数包,而是工具执行、权限判断、Hook 执行、状态更新、读文件缓存、记忆去重等机制共享的运行时上下文。
3. Query Loop 展开图
对应总览节点:Query Loop
目标:解释模型请求、工具调用、结果回灌、停止和恢复如何循环。
这部分机制参考前文「Harness 总览 机制图」。
关键源码入口
| 职责 | 文件 |
|---|---|
| 主查询循环 | src/query.ts |
| QueryEngine 调用 query | src/QueryEngine.ts |
| query 配置快照 | src/query/config.ts |
| token budget | src/query/tokenBudget.ts |
| stop hook 接入 | src/query/stopHooks.ts |
| compact 机制 | src/services/compact/ |
机制说明
query() 是 Harness 的主循环。它不是一次模型请求后结束,而是在以下条件下反复迭代:
- 模型产生
tool_use,需要执行工具并把结果回灌。 - Stop Hook 返回阻断信息,需要把阻断反馈加入上下文再问模型。
- token budget 或 compact 机制要求续跑。
- prompt too long、max output tokens、fallback model 等恢复路径触发。
在每轮请求前,query() 会处理消息窗口、工具结果预算、compact 状态和相关记忆预取。模型流式返回时,如果检测到 tool_use,则进入工具执行;如果没有工具调用,则进入 stop hook 和完成判断。
4. Tools 展开图
对应总览节点:Tool Execution
目标:解释模型如何从 tool_use 变成实际行动。
这部分机制参考前文「Harness 总览 机制图」。
关键源码入口
| 职责 | 文件 |
|---|---|
| 工具注册 | src/tools.ts |
| 工具接口 | src/Tool.ts |
| 批量/串并行编排 | src/services/tools/toolOrchestration.ts |
| 单个工具执行 | src/services/tools/toolExecution.ts |
| 流式工具执行器 | src/services/tools/StreamingToolExecutor.ts |
| 工具 Hook 封装 | src/services/tools/toolHooks.ts |
工具池来源
这部分机制参考前文「Harness 总览 机制图」。
机制说明
工具系统有两层:
- 工具注册层:
tools.ts决定哪些工具进入工具池。 - 工具执行层:
toolExecution.ts决定单个tool_use如何被执行。
StreamingToolExecutor 的作用是让并发安全的工具在模型流式输出时提前执行。它会维护工具队列,区分 concurrency-safe 工具和非 concurrency-safe 工具,并保证结果按工具调用顺序回灌。
runTools() 是非流式或剩余工具执行路径。它将工具调用分成批次:多个连续的并发安全工具可并行执行;非并发安全工具串行执行。
5. Permissions 展开图
对应总览节点:Permissions
目标:解释安全围栏如何拦截、放行或转交用户/宿主决策。
这部分机制参考前文「Harness 总览 机制图」。
关键源码入口
| 职责 | 文件 |
|---|---|
| 权限类型 | src/types/permissions.ts |
| 权限主决策 | src/utils/permissions/permissions.ts |
| React/交互承接 | src/hooks/useCanUseTool.tsx |
| 权限队列与上下文 | src/hooks/toolPermission/PermissionContext.ts |
| 交互处理 | src/hooks/toolPermission/handlers/interactiveHandler.ts |
| coordinator 处理 | src/hooks/toolPermission/handlers/coordinatorHandler.ts |
| swarm worker 处理 | src/hooks/toolPermission/handlers/swarmWorkerHandler.ts |
| Bash 权限细化 | src/tools/BashTool/bashPermissions.ts |
机制说明
权限机制分两层:
hasPermissionsToUseTool():生成 allow / ask / deny 决策。useCanUseTool():把 ask 决策接到 UI、自动分类器、coordinator、swarm worker 或宿主回调。
决策顺序的安全意义很强:
- deny 规则最先命中。
- 工具自身的
checkPermissions()可执行内容级安全判断。 - safety check 和内容级 ask 可以抵抗 bypass。
- bypass 和 always allow 在这些检查之后才生效。
- auto mode 不直接等于放行,而是走 classifier、allowlist、acceptEdits fast path 和 denial tracking。
这说明 Permissions 是 Harness 的安全边界,不只是 UI 弹窗。
6. Hooks 展开图
对应总览节点:Lifecycle Hooks 与 Tool Hooks
目标:解释事件驱动机制在哪里介入主循环。
这部分机制参考前文「Harness 总览 机制图」。
关键源码入口
| 职责 | 文件 |
|---|---|
| Hook 聚合执行 | src/utils/hooks.ts |
| Hook 事件广播 | src/utils/hooks/hookEvents.ts |
| 会话临时 Hook | src/utils/hooks/sessionHooks.ts |
| prompt Hook 执行 | src/utils/hooks/execPromptHook.ts |
| agent Hook 执行 | src/utils/hooks/execAgentHook.ts |
| HTTP Hook 执行 | src/utils/hooks/execHttpHook.ts |
| 工具 Hook 封装 | src/services/tools/toolHooks.ts |
| Stop Hook 接入 | src/query/stopHooks.ts |
机制说明
Hooks 是 Harness 的事件驱动反射层。它可以在多个关键节点介入:
- 用户提交 prompt 后、模型请求前。
- 工具执行前。
- 权限请求时。
- 工具执行后或失败后。
- 模型停止时。
- compact 前后。
- session start / session end 等生命周期点。
Hook 的输出不只是日志。它可以补充上下文、阻止继续、修改权限决策、产生阻断反馈,甚至通过异步 Hook 在后续回合唤醒模型。
7. Memory 展开图
对应总览节点:Memory
目标:解释长期记忆如何进入当前上下文窗口。
这部分机制参考前文「Harness 总览 机制图」。
关键源码入口
| 职责 | 文件 |
|---|---|
| memory prompt | src/memdir/memdir.ts |
| relevant memory 选择 | src/memdir/findRelevantMemories.ts |
| memory 文件扫描 | src/memdir/memoryScan.ts |
| memory 类型 | src/memdir/memoryTypes.ts |
| memory 新鲜度 | src/memdir/memoryAge.ts |
| memory 附件注入 | src/utils/attachments.ts |
| session memory | src/services/SessionMemory/sessionMemory.ts |
| session memory compact | src/services/compact/sessionMemoryCompact.ts |
| team memory sync | src/services/teamMemorySync/ |
| auto memory consolidation | src/services/autoDream/autoDream.ts |
机制说明
Memory 至少有三种进入模型上下文的路径:
- 机制提示路径:
loadMemoryPrompt()把“如何使用记忆系统”的说明加入 system prompt。 - 相关记忆召回路径:
startRelevantMemoryPrefetch()根据当前用户输入异步搜索相关 memory 文件,形成relevant_memories附件。 - 嵌套指令路径:文件路径触发 nested memory,沿路径查找
CLAUDE.md和条件规则,形成 nested memory 附件。
这个设计说明 Memory 不是单纯的数据库读写,而是和 Context Preparation、Query Loop、附件系统、agent 隔离、team memory 同时耦合。
8. Tools、Permissions、Hooks 的交叉展开
对应总览节点:Tool Execution + Permissions + Tool Hooks
目标:解释一次工具调用的完整安全链路。
这部分机制参考前文「Harness 总览 机制图」。
机制说明
模型不能直接执行工具。模型只产生 tool_use,真正执行前必须穿过:
- 工具查找与 schema 校验。
- 权限决策。
- PermissionRequest Hook。
- PreToolUse Hook。
- 工具自己的
call()。 - PostToolUse / failure Hook。
- 结果映射与回灌。
因此 Harness 的行动能力不是“模型调用函数”这么简单,而是一个受状态、权限、Hook、并发、安全检查共同约束的执行器。
9. 第一版源码归类索引
Tools
| 模块 | 作用 |
|---|---|
src/tools.ts | 内置工具集合、MCP 工具合并、deny rule 过滤 |
src/Tool.ts | Tool 接口、ToolUseContext、ToolPermissionContext |
src/services/tools/toolExecution.ts | 单个工具调用生命周期 |
src/services/tools/toolOrchestration.ts | 串并行工具编排 |
src/services/tools/StreamingToolExecutor.ts | 流式工具执行 |
src/tools/BashTool/ | Bash 执行、命令安全、沙箱、权限 |
src/tools/FileReadTool/ | 文件读取与附件上下文 |
src/tools/FileEditTool/ | 文件编辑与 diff |
src/tools/AgentTool/ | 子代理与 agent memory |
src/tools/MCPTool/ | MCP 工具适配 |
Context
| 模块 | 作用 |
|---|---|
src/QueryEngine.ts | 会话生命周期和上下文准备 |
src/query.ts | 主查询循环 |
src/context.ts | 用户/系统上下文收集 |
src/utils/queryContext.ts | system/user/system context 组装 |
src/utils/processUserInput/processUserInput.ts | 用户输入加工 |
src/utils/attachments.ts | 附件、文件、memory、任务上下文注入 |
src/utils/messages.ts | 消息规范化、tool_use/tool_result 配对 |
src/components/ContextVisualization.tsx | 上下文可视化入口 |
Memory
| 模块 | 作用 |
|---|---|
src/memdir/memdir.ts | memory prompt 与目录保证 |
src/memdir/findRelevantMemories.ts | 相关记忆选择 |
src/memdir/memoryScan.ts | memory 文件扫描 |
src/memdir/memoryTypes.ts | memory 类型说明 |
src/memdir/paths.ts | memory 路径 |
src/memdir/teamMemPrompts.ts | team memory prompt |
src/services/SessionMemory/ | session memory |
src/services/extractMemories/ | memory 提取 |
src/services/autoDream/ | memory consolidation |
src/services/teamMemorySync/ | team memory 同步 |
Hooks
| 模块 | 作用 |
|---|---|
src/utils/hooks.ts | Hook 主执行器 |
src/utils/hooks/hookEvents.ts | Hook 事件广播 |
src/utils/hooks/sessionHooks.ts | 会话内临时 Hook |
src/utils/hooks/execPromptHook.ts | prompt Hook |
src/utils/hooks/execAgentHook.ts | agent Hook |
src/utils/hooks/execHttpHook.ts | HTTP Hook |
src/utils/hooks/hooksConfigManager.ts | Hook 配置说明 |
src/components/hooks/ | Hook 配置 UI |
src/commands/hooks/ | /hooks 命令 |
Permissions
| 模块 | 作用 |
|---|---|
src/types/permissions.ts | 权限模式、规则、更新、决策类型 |
src/utils/permissions/permissions.ts | 权限主决策链 |
src/hooks/useCanUseTool.tsx | 交互环境下的权限承接 |
src/hooks/toolPermission/PermissionContext.ts | 权限请求上下文与队列 |
src/hooks/toolPermission/handlers/interactiveHandler.ts | 用户交互审批 |
src/hooks/toolPermission/handlers/coordinatorHandler.ts | coordinator 审批路径 |
src/hooks/toolPermission/handlers/swarmWorkerHandler.ts | swarm worker 审批路径 |
src/utils/permissions/permissionRuleParser.ts | 权限规则解析 |
src/utils/permissions/permissionsLoader.ts | 权限配置加载 |
src/utils/permissions/yoloClassifier.ts | auto mode 分类器 |
10. 初步结论
10.1 Harness 的核心抽象
本项目的 Harness 可以概括为:
一个以 Query Loop 为中心,把模型流式输出、工具执行、权限决策、Hook 事件、记忆召回、上下文压缩和会话持久化绑定在一起的运行时外壳。
10.2 五个一级维度不是并列目录,而是运行时角色
- Tools 是行动执行层。
- Context 是模型输入层。
- Memory 是跨回合信息层。
- Hooks 是事件干预层。
- Permissions 是安全约束层。
它们都通过 ToolUseContext、AppState、Message、query() 和 Tool 接口发生交汇。
10.3 当前最关键的交汇对象
| 对象 | 重要性 |
|---|---|
ToolUseContext | 几乎所有工具、权限、Hook、状态更新都依赖它 |
Message | 模型上下文、工具结果、附件、系统提醒的统一载体 |
Tool | 行动能力的统一接口 |
ToolPermissionContext | 权限模式、规则、工作目录边界的统一载体 |
AppState | UI、MCP、权限、任务、Hook、agent 状态的运行时存储 |
机制主线收束
站在架构师视角,我会把这组源码机制收束为三个问题:为什么需要它,什么时候必须引入它,以及真正要设计的是什么。
- why:Harness 要解决的是模型推理和真实副作用之间缺少工程缓冲层的问题。
- when:当 agent 需要读写文件、调用 shell、连接外部服务或长期执行任务时,必须把模型输出纳入受控运行时。
- what:先定义 Message、Tool、ToolUseContext、State、Transcript 这类交叉对象,再让工具、权限、Hook、Memory 围绕它们扩展。
Harness 总览 这组源码最值得带走的,不是某个函数或某个配置项,而是它如何把模型能力放进一组可验证、可拒绝、可恢复、可审计的工程边界里。读源码时,如果只记住名词,会很快散;如果抓住 why、when、what,就能把这组机制迁移到自己的 agent 架构判断中。