本文基于 Claude Code 源码级逆向分析论文,结合真实的 harness 工作原理,用一个完整案例展示从用户需求到主 Agent CoT 推理、子任务拆解、子 Agent 独立调用、结果合并的全过程------包含每一步的日志和 CoT 推理。
目录
- [一、Claude Code Harness 架构总览](#一、Claude Code Harness 架构总览)
- [二、核心源码:queryLoop 主循环](#二、核心源码:queryLoop 主循环)
- [三、核心源码:5 层上下文压缩管线](#三、核心源码:5 层上下文压缩管线)
- 四、核心源码:工具调度与并行策略
- [五、真实案例:分析 GitHub 仓库代码质量](#五、真实案例:分析 GitHub 仓库代码质量)
- [六、主 Agent CoT → 子任务拆解全过程](#六、主 Agent CoT → 子任务拆解全过程)
- [七、子 Agent 的 system prompt 从哪来?](#七、子 Agent 的 system prompt 从哪来?)
- [八、子 Agent 独立执行日志(完整 CoT + 工具调用)](#八、子 Agent 独立执行日志(完整 CoT + 工具调用))
- 九、结果合并与最终交付
- 十、核心洞察总结
一、Claude Code Harness 架构总览

Claude Code 的 harness 是一个工程化的 Agent 运行时。论文的关键发现是:98.4% 的代码库是基础设施 (权限检查、上下文管理、工具路由),只有 1.6% 是 AI 决策逻辑。
这意味着------真正的 agent 能力不在于"模型有多聪明",而在于 harness 如何在模型周围构建安全、高效的执行框架。
源码文件映射
| 源文件 | 职责 | 核心函数 |
|---|---|---|
src/query.ts |
主循环 + 上下文压缩 | queryLoop() (L365-453) |
src/permissions.ts |
权限网关 | evaluatePermission() |
src/tools.ts |
工具池组装 | assembleToolPool() |
src/compact.ts |
上下文压缩策略 | compactConversation() |
StreamingToolExecutor |
流式工具执行 | --- |
toolOrchestration.ts |
工具编排 | runTools() |
yoloClassifier.ts |
ML 安全分类器 | --- |
二、核心源码:queryLoop 主循环
这是整个 harness 的心脏------一个异步生成器,分离了推理 (模型)和执行(harness):
typescript
// src/query.ts --- queryLoop() 概念伪码
async function* queryLoop(
systemPrompt: string,
tools: ToolSchema[],
history: Message[],
permissionCallback: PermissionCallback
): AsyncGenerator<LoopEvent> {
let state: State = initMutableState(systemPrompt, history);
let stopped = false;
while (!stopped) {
// ── (a) assemble: 组装模型可见的上下文 ──
const messagesForQuery = await getMessagesAfterCompactBoundary();
const context = assemble(
systemPrompt, // 指令头
tools, // 可调用工具签名 (最多 54 个)
messagesForQuery, // 经 5 层压缩后的历史
hookAdditions // hooks 推入的额外内容
);
// ── (b) model: 模型选择下一步动作 ──
const response = await deps.callModel(context, tools);
// response 包含: text content + tool_use blocks
if (response.isTextOnly()) {
// 纯文本回复 → 尝试停止
stopped = runStopHooks(response);
continue;
}
// ── (c) execute: 权限网关 + 执行工具 ──
for (const toolUse of response.toolUseBlocks) {
// 权限检查 (deny-first)
if (!permitted(toolUse, permissionCallback)) {
continue; // 被拒绝, 跳过
}
// pre-tool hooks: 拦截或重写
const finalAction = runPreToolHooks(toolUse);
// 实际执行工具
const result = await execute(finalAction);
// post-tool hooks: 变异或标注结果
const annotated = runPostToolHooks(result);
// 结果加入历史
state.history.append(finalAction, annotated);
}
}
}
关键设计:
State对象在 7 个"continue sites"做整体赋值------每次迭代状态一致- AsyncGenerator 设计:流式输出到 UI 层,同时保持单同步控制流
- 工具结果以
tool_result消息加入历史,循环继续
三、核心源码:5 层上下文压缩管线
这是 Claude Code 最精密的子系统------每次模型调用前,5 层"shaper"按递增成本顺序执行:
typescript
// src/query.ts (lines 365-453) --- 每次模型调用前执行
async function prepareContext(state: State): Promise<Message[]> {
let messages = await getMessagesAfterCompactBoundary(state);
// ── 第 1 层: Budget Reduction ──
// 对工具结果施加每条消息的大小限制
messages = applyToolResultBudget(messages);
// 超大输出替换为 content reference (可按需恢复, 类似 RecallToolResult)
// ── 第 2 层: Snip ──
// 轻量裁剪旧历史段
if (needsSnip(messages)) {
const result = snipCompactIfNeeded(messages);
messages = result.messages;
state.tokensFreed += result.tokensFreed;
}
// ── 第 3 层: Microcompact ──
// 细粒度压缩, 有两条路径:
// - 时间路径: 始终运行 (基于时间戳淘汰)
// - 缓存路径: 可选, 使用真实 cache_deleted_input_tokens
messages = microcompact(messages);
// ── 第 4 层: Context Collapse ──
// 只读投影 --- 不修改存储的历史
// 摘要消息替换详细轮次, 原始历史保留可重建
messages = applyCollapsesIfNeeded(messages);
// 摘要存在 collapse store 中, 跨轮次持久
// ── 第 5 层: Auto-Compact ──
// 最后手段: 触发模型生成摘要
if (stillTooLong(messages)) {
const summary = await compactConversation(messages);
messages = buildPostCompactMessages(summary);
}
return messages;
}
为什么需要 5 层? 因为单层压缩要么太粗暴(丢失关键信息),要么太精细(成本太高)。5 层递进策略确保:先用最便宜的方式腾空间,只有不够时才用更贵的。
四、核心源码:工具调度与并行策略
typescript
// StreamingToolExecutor --- 流式工具执行器
class StreamingToolExecutor {
private parallelPool: ThreadPool;
private serialQueue: SerialBarrier;
// 当模型响应流式返回 tool_use 时, 立即开始执行
onToolUseStreamed(toolUseBlock: ToolUse): void {
if (this.isReadOnly(toolUseBlock)) {
// 只读操作 → 并行池 (最多 N 个同时)
this.parallelPool.submit(toolUseBlock);
} else {
// 状态修改操作 → 串行屏障 (独占执行)
this.serialQueue.enqueue(toolUseBlock);
}
}
// 协调机制:
// 1. Sibling Abort Controller
// 任一 Bash 工具出错 → 立即终止所有飞行中的子进程
onToolError(toolUse: ToolUse): void {
if (toolUse.name === 'Bash') {
this.siblingAbortController.abort();
}
}
// 2. 顺序保持
// 结果按"接收顺序"而非"完成顺序"缓冲发射
// 确保模型收到结果的顺序与请求顺序一致
getResults(): ToolResult[] {
return this.resultBuffer.getInOrder();
}
}
// src/tools.ts --- assembleToolPool
function assembleToolPool(): ToolSchema[] {
// 1. 枚举基础工具 (最多 54 个)
const baseTools = getAllBaseTools();
// 2. 模式过滤 (SIMPLE 模式限制工具)
const filtered = getTools(baseTools, currentMode);
// 3. Deny 规则预过滤
const safe = filterToolsByDenyRules(filtered);
// 4. MCP 工具合并
const withMcp = mergeMcpTools(safe);
// 5. 去重
return deduplicate(withMcp);
}
五、真实案例:分析 GitHub 仓库代码质量
接下来用一个完整案例展示整个多 Agent 协作过程。
案例场景
用户需求 :"分析
my-repo这个 Python 仓库的代码质量,找出 bug,运行测试,然后写一份修复方案报告。"
这个需求包含 4 个子任务,存在并行和串行依赖:

任务分解
| 子任务 | Agent | 依赖 | 并行性 |
|---|---|---|---|
| 读取全部 .py 文件 + 提取 AST | CodeReader | 无 | 可并行 |
| 运行 pytest + 收集覆盖率 | TestRunner | 无 | 可并行 |
| 静态分析 + bug 定位 | BugAnalyzer | 依赖 CodeReader | 串行 |
| 生成 Markdown 修复方案报告 | ReportGenerator | 依赖 A+B+C 全部 | 汇聚 |
六、主 Agent CoT → 子任务拆解全过程
当用户需求进入 queryLoop() 后,主 Agent 的模型推理(CoT)如下:

6.1 主 Agent 的完整 CoT 推理日志
=== TURN 1: MainAgent ===
[system] 你是主 Agent (Orchestrator)。你有以下子 Agent:
- CodeReader: 读取代码文件, 提取 AST 和复杂度指标
- TestRunner: 运行测试, 收集覆盖率和失败用例
- BugAnalyzer: 静态分析, 定位 bug
- ReportGenerator: 生成 Markdown 报告
[user] 分析 my-repo 这个 Python 仓库的代码质量, 找出 bug,
运行测试, 然后写一份修复方案报告。
[assistant] (CoT 推理)
用户要分析 my-repo 仓库的代码质量。让我拆解这个需求:
1. 需要读取仓库中所有 Python 文件, 提取代码结构和复杂度
2. 需要运行测试套件, 了解测试通过率和覆盖率
3. 需要做静态分析, 找出潜在 bug
4. 需要汇总以上结果, 生成修复方案报告
依赖关系分析:
- 任务 1 (读代码) 和 任务 2 (跑测试) 之间无依赖 → 可以并行
- 任务 3 (找 bug) 需要基于任务 1 的代码分析结果 → 串行, 依赖 CodeReader
- 任务 4 (写报告) 需要任务 1+2+3 全部完成 → 汇聚点
我有 4 个子 Agent 可用。派发计划:
- Step 1: 并行派发 CodeReader + TestRunner (parallel_group = "P1")
- Step 2: CodeReader 完成后, 派发 BugAnalyzer (注入 CodeReader 结果)
- Step 3: 全部完成后, 派发 ReportGenerator (注入所有结果)
先执行 Step 1。
6.2 CoT 与 tool_use 的真实关系:模型直接输出结构化 JSON
这里需要澄清一个关键问题:从主 Agent 的 CoT 推理到 tool_use block 的转换,是大模型直接输出的,还是 harness 中转换的?
答案:大模型直接输出的。harness 不解析 CoT。
根据 Claude Code 源码分析论文的明确描述:
"The model emits tool_use blocks as part of its response, and the harness parses them, checks permissions, dispatches them to tool implementations, and collects results (query.ts)."
"In Claude Code, the model reasons about what to do; the harness is responsible for executing actions. The model never directly accesses the filesystem, runs shell commands, or makes network requests."
工作机制是这样的:
模型一次推理输出同时包含两部分:
┌─────────────────────────────────────────────┐
│ assistant response │
│ │
│ 1. thinking / CoT (自然语言推理) │
│ "用户要分析仓库... 读代码和跑测试可以 │
│ 并行... 找 bug 依赖读代码..." │
│ │
│ 2. tool_use block (结构化 JSON) │
│ ← 模型直接输出, 不是 harness 解析 CoT │
│ 后转换的! │
└─────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ harness 的职责 (query.ts): │
│ 1. 解析 tool_use JSON (验证格式/参数) │
│ 2. 权限检查 (permissions.ts, deny-first) │
│ 3. 分派到对应工具实现 │
│ 4. 收集 tool_result, 加入历史 │
│ 5. 进入下一轮循环 │
└─────────────────────────────────────────────┘
关键区分 :CoT 是模型的"思考过程"------帮助模型自身决定该调用什么工具、传什么参数。但 CoT 本身不触发任何动作。真正触发动作的是模型在同一个 response 中同时输出 的 tool_use block。harness 不需要"理解" CoT 的内容,它只负责执行 tool_use 中指定的动作。
在 Claude Code 中,主 Agent 派发子 Agent 使用的是 Agent tool(元工具)。模型输出如下:
[assistant response] --- 模型一次输出, 同时包含 CoT 和 tool_use
(thinking block)
用户要分析 my-repo 仓库的代码质量。让我拆解需求:
1) 读代码 2) 跑测试 3) 找 bug 4) 写报告
读代码和跑测试无依赖 → 并行
找 bug 依赖读代码 → 串行
写报告依赖全部 → 汇聚
先派发 CodeReader 和 TestRunner...
(tool_use block) ← 模型直接输出结构化 JSON
{
"type": "tool_use",
"name": "Agent", ← Claude Code 的元工具名
"input": {
"subagent_type": "code-reader", ← 对应 .claude/agents/ 中的定义
"prompt": "读取 my-repo 目录下所有 .py 文件, 提取 AST, 计算圈复杂度"
}
}
(tool_use block) ← 第二个并行调用, 同一 response 中
{
"type": "tool_use",
"name": "Agent",
"input": {
"subagent_type": "test-runner",
"prompt": "运行 pytest, 收集覆盖率和失败用例列表"
}
}
harness 收到这两个 tool_use block 后:
- 解析 JSON → 验证
subagent_type是否存在 → 权限检查 - 对每个
Agenttool 调用,重新进入queryLoop(),创建隔离的上下文窗口 - 子 Agent 执行完成后,返回摘要给父 Agent
- 两个
tool_result注入主 Agent 历史 → 下一轮推理
七、子 Agent 的 system prompt 从哪来?
7.1 关键澄清:子 Agent 的 system prompt 是预设的,不是主 Agent 生成的
这是第二个关键问题:子 Agent 的 system prompt 是主 Agent 产生的吗?还是 harness 中已有预设好的常用子 Agent 库?
答案:子 Agent 的 system prompt 是在 .claude/agents/*.md 文件中预定义的,不是主 Agent 在运行时动态生成的。
Claude Code 使用 Markdown 文件 + YAML frontmatter 的格式定义子 Agent:
markdown
# .claude/agents/code-reader.md
---
name: code-reader
description: "读取代码文件, 提取 AST 和复杂度指标"
tools: [Read, Glob, Grep, Bash] # 该子 Agent 可用的工具
disallowedTools: [Edit, Write] # 禁止的工具
model: haiku # 可指定不同模型
permissionMode: default # 权限模式
---
你是 CodeReader Agent。你的职责是:
1. 读取指定目录下的代码文件
2. 提取 AST (抽象语法树) 结构信息
3. 计算圈复杂度 (cyclomatic complexity)
4. 返回结构化的代码指标报告
你只负责读取和分析, 不负责修改文件或运行测试。
7.2 子 Agent 定义文件的结构
| 部分 | 作用 | 说明 |
|---|---|---|
| YAML frontmatter | 元数据配置 | name, description, tools, model, permissionMode 等 |
| Markdown body | system prompt | frontmatter --- 之后的全部文本成为子 Agent 的 system prompt |
关键规则(来自 Claude Code 官方文档):
- 子 Agent 接收的是自己的 system prompt + 基本环境信息 (如工作目录),不包含主 Agent 的 Claude Code system prompt
- 子 Agent 的
description字段决定了主 Agent 何时自动委派给它------主 Agent 根据描述匹配决定是否调用 tools字段限制了子 Agent 的工具集------可以比主 Agent 少(最小权限原则)model字段允许子 Agent 使用不同模型------例如代码读取用haiku(快且便宜),推理分析用opus
7.3 子 Agent 初始上下文的组成
当主 Agent 通过 Agent tool 派发子 Agent 时,子 Agent 的初始上下文由两部分组成:
子 Agent 初始上下文 = 固定部分 + 动态部分
固定部分 (来自 .md 文件):
┌──────────────────────────────────────┐
│ system prompt (md body) │
│ "你是 CodeReader Agent..." │
│ + 基本环境信息 (工作目录等) │
│ │
│ ← 不含主 Agent 的 system prompt │
│ ← 不含主 Agent 的对话历史 │
│ ← 不含主 Agent 的工具结果 │
└──────────────────────────────────────┘
动态部分 (主 Agent 通过 tool_use 注入):
┌──────────────────────────────────────┐
│ task message (delegation prompt) │
│ 主 Agent 在 Agent tool 的 │
│ "prompt" 参数中写的任务指令 │
│ │
│ "读取 my-repo/*.py, 提取 AST" │
│ │
│ ← 这是主 Agent 唯一注入的内容 │
│ ← 不是 system prompt, 是 user msg │
└──────────────────────────────────────┘
官方文档原文:"A non-fork subagent's initial context contains: System prompt: the agent's own prompt plus environment details. Task message: the delegation prompt Claude writes when it hands off the work."
7.4 内置子 Agent 与自定义子 Agent
Claude Code 有两类子 Agent:
内置子 Agent(harness 预设):
| 名称 | 描述 | 场景 |
|---|---|---|
explore |
搜索和理解代码库,不做修改 | 主 Agent 需要了解代码结构时自动委派 |
plan |
规划任务执行方案 | 复杂任务拆解 |
自定义子 Agent (用户定义的 .md 文件):
- 项目级:
.claude/agents/*.md(仅当前项目可用) - 用户级:
~/.claude/agents/*.md(所有项目可用)
主 Agent 在 system prompt 中看到所有可用子 Agent 的 name + description,根据用户需求自动匹配 并调用 Agent tool。用户也可以通过 @子Agent名 显式指定。
八、子 Agent 独立执行日志(完整 CoT + 工具调用)
注意 :以下日志中
[system]部分来自.claude/agents/*.md文件预定义的 system prompt,[user]部分是主 Agent 通过Agenttool 的prompt参数注入的 task message。
8.1 子 Agent A: CodeReader 的完整执行日志
=== SUB-AGENT A: code-reader (来自 .claude/agents/code-reader.md) ===
[system] (← 来自 .md 文件, 非主 Agent 生成)
你是 CodeReader Agent。你的职责是:
1. 读取指定目录下的代码文件
2. 提取 AST 结构信息
3. 计算圈复杂度
4. 返回结构化的代码指标报告
你只负责读取和分析, 不负责修改文件或运行测试。
(可用工具: Read, Glob, Grep, Bash)
[user] (← 主 Agent 通过 Agent tool 的 prompt 参数注入)
读取 my-repo 目录下所有 .py 文件, 提取 AST, 计算圈复杂度
[user] === 任务说明 ===
CodeReader: 读取代码文件, 提取 AST 和复杂度
=== 相关上下文 (由主 Agent 裁剪注入) ===
仓库路径: ./my-repo
用户需求: 分析代码质量
=== 执行指令 ===
读取 my-repo 目录下所有 .py 文件, 提取 AST, 计算圈复杂度
[assistant] (CoT)
需要先列出仓库中所有 Python 文件, 然后逐个读取和分析。
[assistant → tool_use]
{"tool": "list_files", "args": {"path": "./my-repo", "pattern": "*.py"}}
[tool_result]
["main.py", "utils.py", "models.py", "api.py", "tests/test_main.py"]
[assistant] (CoT)
找到 5 个 Python 文件。先读取核心文件 main.py 和 utils.py。
models.py 和 api.py 稍后读取。
[assistant → tool_use]
{"tool": "read_file", "args": {"path": "./my-repo/main.py"}}
[tool_result]
42 lines: def process_data(data):
result = []
for item in data: # line 5: O(n) loop
if item > 0:
result.append(item * 2)
return result
# ... (完整文件内容)
[assistant → tool_use]
{"tool": "read_file", "args": {"path": "./my-repo/utils.py"}}
[tool_result]
28 lines: def parse_input(s):
return [int(x) for x in s.split(',')]
# ...
[assistant] (CoT)
已读取核心文件。现在用 analyze_ast 提取结构信息。
main.py 的 process_data 函数圈复杂度为 3 (一个 for + 一个 if)。
[assistant → tool_use]
{"tool": "analyze_ast", "args": {"files": ["main.py", "utils.py"]}}
[tool_result]
{
"main.py": {"functions": 2, "complexity": {"process_data": 3, "main": 1}},
"utils.py": {"functions": 1, "complexity": {"parse_input": 1}},
"total_lines": 70
}
[assistant] (最终回复)
代码分析完成:
- 5 个 Python 文件, 总计 70 行 (不含测试)
- main.py: 2 个函数, 最高复杂度 3 (process_data)
- utils.py: 1 个函数, 复杂度 1
- 建议关注 process_data 的循环嵌套
→ 返回给主 Agent
8.2 子 Agent B: TestRunner 的完整执行日志
=== SUB-AGENT B: TestRunner ===
[system] 你是 TestRunner Agent。职责: 运行测试, 收集覆盖率。
工具: bash, parse_coverage
[user] === 任务说明 ===
TestRunner: 运行测试, 收集覆盖率和失败用例
=== 相关上下文 ===
仓库路径: ./my-repo
=== 执行指令 ===
运行 pytest, 收集覆盖率和失败用例列表
[assistant] (CoT)
需要执行 pytest 并带上覆盖率参数。先检查是否有 pytest 配置。
[assistant → tool_use]
{"tool": "bash", "args": {"command": "cd ./my-repo && python -m pytest --cov=. --cov-report=term-missing -v"}}
[tool_result]
==================== test session starts ====================
collected 15 items
tests/test_main.py::test_process_data PASSED
tests/test_main.py::test_process_data_empty PASSED
tests/test_main.py::test_parse_input PASSED
tests/test_main.py::test_parse_input_negative FAILED
tests/test_main.py::test_api_get FAILED
...
================== 13 passed, 2 failed in 0.83s ==================
Name Stmts Miss Cover Missing
main.py 12 2 83% 15-16
utils.py 8 0 100%
models.py 15 5 67% 10-14
api.py 20 8 60% 5-12, 20-23
TOTAL 55 15 73%
[assistant] (CoT)
15 个测试, 13 通过 2 失败。覆盖率 73%。
失败用例: test_parse_input_negative 和 test_api_get。
api.py 覆盖率最低 (60%)。
[assistant] (最终回复)
测试执行完成:
- 15 个测试: 13 通过, 2 失败
- 总覆盖率: 73%
- 失败用例:
1. test_parse_input_negative (utils.py)
2. test_api_get (api.py)
- 低覆盖率文件: api.py (60%), models.py (67%)
→ 返回给主 Agent
8.3 子 Agent C: BugAnalyzer 的完整执行日志(串行,依赖 A)
子 Agent C 在 CodeReader 完成后才被派发。主 Agent 将 CodeReader 的结果裁剪注入到 BugAnalyzer 的上下文中:
=== SUB-AGENT C: BugAnalyzer (串行, 等待 CodeReader 完成) ===
[system] 你是 BugAnalyzer Agent。职责: 静态分析, 定位 bug。
工具: read_file, search, analyze_pattern
[user] === 任务说明 ===
BugAnalyzer: 静态分析, 定位 bug
=== 相关上下文 (由主 Agent 裁剪注入) ===
CodeReader 结果:
- 5 个文件, 70 行 (不含测试)
- main.py: process_data (复杂度3), main (复杂度1)
- utils.py: parse_input (复杂度1)
TestRunner 结果:
- 2 个失败: test_parse_input_negative, test_api_get
- api.py 覆盖率 60%
=== 执行指令 ===
基于代码分析结果, 定位 bug 和代码质量问题
[assistant] (CoT)
有 2 个测试失败。test_parse_input_negative 说明 utils.py 的 parse_input
可能不处理负数。test_api_get 说明 api.py 的 GET 处理有问题。
先看 utils.py 的 parse_input ------ 它用 int(x) 转换, 不应该报错负数。
但测试失败了, 说明可能是 ValueError 没有被捕获, 或者逻辑有误。
再看 api.py 的覆盖率缺失行 (5-12, 20-23), 这些行没被测试覆盖到。
[assistant → tool_use]
{"tool": "read_file", "args": {"path": "./my-repo/utils.py"}}
[tool_result]
def parse_input(s):
return [int(x) for x in s.split(',')]
# 测试 test_parse_input_negative 输入 "-1,-2,3"
# 期望输出 [-1, -2, 3] 但实际抛出 ValueError?
[assistant → tool_use]
{"tool": "read_file", "args": {"path": "./my-repo/tests/test_main.py"}}
[tool_result]
def test_parse_input_negative():
result = parse_input("-1,-2,3")
assert result == [-1, -2, 3]
# 检查发现: 实际输入有前导空格 " -1" → int(" -1") 抛 ValueError
[assistant] (CoT)
找到 bug! parse_input 没有做 strip() 处理, 带空格的输入会导致 int() 失败。
修复: [int(x.strip()) for x in s.split(',')]
接下来看 api.py 的问题。
[assistant → tool_use]
{"tool": "read_file", "args": {"path": "./my-repo/api.py"}}
[tool_result]
def get_handler(request):
if request.method == 'GET':
data = request.args.get('data') # line 5
if data is None: # line 6
return error_response(400) # line 7 (未被测试覆盖)
# ... 处理逻辑
# line 20-23: 异常处理 (未被测试覆盖)
[assistant → tool_use]
{"tool": "search", "args": {"pattern": "TODO|FIXME|HACK", "path": "./my-repo"}}
[tool_result]
main.py:15 # TODO: handle empty data case
api.py:22 # FIXME: should validate input type
[assistant] (CoT)
总结发现的问题:
Bug 1 (高): utils.py parse_input --- 不处理输入空格, 导致负数测试失败
Bug 2 (中): api.py get_handler --- line 5-12 未被测试覆盖, 缺少边界处理
Bug 3 (低): main.py line 15 --- TODO 未处理空数据情况
Bug 4 (低): api.py line 22 --- FIXME 应验证输入类型
[assistant] (最终回复)
Bug 分析完成, 发现 4 个问题:
1. [高] utils.py: parse_input 缺少 strip() → ValueError on " -1"
2. [中] api.py: get_handler line 5-12 未测试覆盖, 边界处理缺失
3. [低] main.py:15 --- TODO: handle empty data case
4. [低] api.py:22 --- FIXME: should validate input type
→ 返回给主 Agent
8.4 上下文注入对比
注意 BugAnalyzer 的输入中包含了 CodeReader 和 TestRunner 的结果摘要------但不是完整结果。主 Agent 在派发时做了裁剪:
| 信息 | 主 Agent 拥有 | BugAnalyzer 接收 |
|---|---|---|
| CodeReader 完整工具日志 | 4 次调用 + 完整输出 | 仅结果摘要(文件数、复杂度) |
| TestRunner 完整 pytest 输出 | 15 行测试详情 + 覆盖率表 | 仅摘要(通过/失败数、覆盖率%) |
| 主 Agent 对话历史 | 完整(含 CoT 推理) | 无 |
| 系统 prompt | Orchestrator 定义 | BugAnalyzer 定义 |
压缩比:主 Agent 上下文约 8,000 tokens → BugAgent 输入约 300 tokens(压缩 27x)。
九、结果合并与最终交付
9.1 主 Agent 收集所有子 Agent 结果
当 3 个子 Agent 全部完成后,主 Agent 的上下文中新增了 3 条 tool_result 消息(来自 Agent tool 的返回):
[tool_result] [Agent tool: code-reader 返回]
5 个文件, 70 行, 复杂度: process_data=3
[tool_result] [Agent tool: test-runner 返回]
15 测试, 13 通过 / 2 失败, 覆盖率 73%
[tool_result] [Agent tool: bug-analyzer 返回]
4 个 bug: [高] parse_input strip / [中] api.py 边界 / [低] 2 个 TODO/FIXME
9.2 主 Agent 派发 ReportGenerator
[assistant] (CoT)
全部子 Agent 完成。现在需要生成修复方案报告。
我有 report-generator 子 Agent, 调用 Agent tool 派发。
[assistant → tool_use] (← 模型直接输出, 同一 response 含 CoT + tool_use)
{
"type": "tool_use",
"name": "Agent",
"input": {
"subagent_type": "report-generator",
"prompt": "生成 Markdown 修复方案报告。
代码分析: 5文件/70行, 最高复杂度3
测试结果: 13通过/2失败, 覆盖率73%
Bug清单:
1. [高] utils.py parse_input 缺少 strip()
2. [中] api.py get_handler 边界处理缺失
3. [低] main.py TODO
4. [低] api.py FIXME
"
}}
9.3 ReportGenerator 生成报告
=== SUB-AGENT D: report-generator (来自 .claude/agents/report-generator.md) ===
[system] (← 来自 .md 文件, 非主 Agent 生成)
你是 ReportGenerator Agent。生成结构化 Markdown 报告。
(可用工具: Write, Read)
[user] (← 主 Agent 通过 Agent tool 的 prompt 参数注入)
生成 Markdown 修复方案报告。
代码分析: 5文件/70行, 最高复杂度3
测试结果: 13通过/2失败, 覆盖率73%
Bug清单: 4个问题 (1高/1中/2低)
[assistant → tool_use]
{"tool": "write_file", "args": {
"path": "./my-repo/fix_report.md",
"content": "# 代码质量分析与修复方案\n\n..."
}}
[tool_result] 已写入 fix_report.md, 1.2KB
[assistant] 报告已生成: fix_report.md
→ 返回给主 Agent
9.4 主 Agent 转述给用户
[assistant] (最终回复)
分析完成! 已生成修复方案报告 (fix_report.md)。
概要:
- 代码规模: 5 个 Python 文件, 70 行
- 测试: 15 个用例, 13 通过 / 2 失败, 覆盖率 73%
- 发现 4 个问题:
1. [高] utils.py: parse_input 缺少 strip() → 负数输入报错
2. [中] api.py: get_handler 边界处理缺失, 60% 覆盖率
3. [低] main.py: TODO 未处理空数据
4. [低] api.py: FIXME 应验证输入类型
报告路径: ./my-repo/fix_report.md
十、核心洞察总结
10.1 两个关键澄清
Q1: 从主 Agent 的 CoT 推理到 tool_use block 的转换,是大模型直接输出的还是 harness 转换的?
答:大模型直接输出的。
Claude Code 的架构严格遵循"模型推理 + harness 执行"的分离设计:
- 模型在一次 response 中同时输出 thinking(CoT)和 tool_use(结构化 JSON)
- harness 不解析 CoT 的自然语言内容------CoT 是模型内部推理,帮助模型自身决定调用什么工具
- harness 只负责:解析 tool_use JSON → 权限检查 → 执行 → 返回 tool_result
- 这种分离的安全意义:即使模型被对抗性操纵,也无法绕过 harness 的权限系统
论文原文:"The model emits tool_use blocks as part of its response, and the harness parses them, checks permissions, dispatches them to tool implementations, and collects results."
Q2: 子 Agent 的 system prompt 是主 Agent 生成的吗?还是 harness 中已有预设好的常用子 Agent 库?
答:harness 中预设的,通过 .claude/agents/*.md 文件定义。
- 子 Agent 的 system prompt 来自
.md文件的 Markdown body(frontmatter---之后的文本) - 主 Agent 不生成 子 Agent 的 system prompt,只通过
Agenttool 的prompt参数注入任务指令(task message) - 子 Agent 初始上下文 =
.md文件定义的 system prompt + 基本环境信息 + 主 Agent 注入的 task message - 子 Agent 不继承主 Agent 的 system prompt、对话历史或工具结果
- Claude Code 有内置子 Agent(
explore、plan),用户也可自定义.md文件扩展
官方文档原文:"A non-fork subagent's initial context contains: System prompt: the agent's own prompt plus environment details. Task message: the delegation prompt Claude writes when it hands off the work."
10.2 CoT 与 tool_use 的关系图
CoT 推理 (自然语言) tool_use (结构化)
───────────────────── ─────────────────────
"读代码和跑测试可以并行" → Agent({subagent_type: "code-reader", ...})
"找 bug 依赖读代码" → Agent({subagent_type: "bug-analyzer", ...})
"写报告需要全部完成" → Agent({subagent_type: "report-gen", ...})
CoT 不是被"解析"的------它是模型在 tool_use 之前的思考过程 ,与 tool_use 在同一个 response 中输出。harness 不理解 CoT 的内容,它只执行 tool_use 中指定的动作。但模型的 CoT 帮助模型自身决定该调用什么工具、传什么参数。
10.3 上下文隔离的意义
| 维度 | 不隔离的问题 | 隔离后的好处 |
|---|---|---|
| 上下文大小 | 子 Agent 继承 50K tokens → 成本爆炸 | 裁剪到 ~500 tokens → 成本可控 |
| 工具干扰 | 子 Agent 看到主 Agent 的 Bash 历史 → 可能混淆 | 独立工具历史 → 清晰的执行边界 |
| 并行安全 | 共享消息历史 → 并行写入冲突 | 各自独立 → 无冲突 |
| 错误隔离 | 子 Agent 崩溃 → 污染主上下文 | 结果以 tool_result 注入 → 干净 |
10.4 与 Claude Code 的映射
| 案例中的概念 | Claude Code 对应 |
|---|---|
| 主 Agent queryLoop | queryLoop() in query.ts |
| 模型输出 tool_use | 模型 response 中直接包含(harness 不解析 CoT) |
| Agent tool 派发子 Agent | AgentTool.tsx → 重新进入 queryLoop() 隔离上下文 |
| 子 Agent system prompt | .claude/agents/*.md 文件预定义 |
| 上下文裁剪注入 | 5 层压缩管线 (Budget→Snip→Microcompact→Collapse→Auto) |
| 权限检查 | permissions.ts deny-first 网关 |
| 结果注入为 tool_result | history.append(action, result) |
| 子 Agent 独立 messages | State 对象每次整体赋值 + sidechain transcript |
10.5 最终统计
案例执行统计
═══════════════════════════════════════════════
主 Agent 轮次: 3 (规划 → 派发C+D → 转述)
子 Agent A 轮次: 4 (列文件 → 读2文件 → AST → 回复)
子 Agent B 轮次: 2 (执行pytest → 回复)
子 Agent C 轮次: 5 (CoT → 读utils → 读test → 读api → 搜索 → 回复)
子 Agent D 轮次: 2 (写文件 → 回复)
─────────────────────────────────────────
总工具调用: ~16 次
最大并行度: 2 (A+B 并行)
DAG 层数: 4
上下文压缩比: ~27x (主 Agent 8K → 子 Agent 300 tokens)
交付物: fix_report.md (1.2KB)
═══════════════════════════════════════════════
本文基于 Claude Code 源码分析论文 (Dive into Claude Code) 和 ideaGoal harness 真实工作原理整理。案例中的 CoT 推理和工具调用日志为基于真实工作原理的演示性还原。