系列導(dǎo)讀
這是《12課拆解Claude Code架構(gòu)》系列的第 4 課。
前三課我們?cè)炝艘粋€(gè)有完整工具鏈和規(guī)劃能力的 Agent:第 1 課建了 Agent Loop,第 2 課加了 Tool Use dispatch,第 3 課用 TodoWrite 引入了顯式規(guī)劃。
但有一個(gè)問題一直在惡化——上下文膨脹。
第 4 課的格言:
"大任務(wù)拆小,每個(gè)小任務(wù)干凈的上下文"
這一課,我們給 Agent 裝上 task 工具,讓它能把子任務(wù)派發(fā)給獨(dú)立的 Subagent,用完即棄。
上下文膨脹:一個(gè)真實(shí)的痛點(diǎn)
假設(shè)你讓 Agent 回答一個(gè)簡(jiǎn)單問題:
"這個(gè)項(xiàng)目用什么測(cè)試框架?"
Agent 的工作過程:
第1輪: bash → cat package.json (輸出: 200行JSON)
第2輪: bash → cat jest.config.js (輸出: 30行配置)
第3輪: bash → cat tsconfig.json (輸出: 50行JSON)
第4輪: bash → ls tests/ (輸出: 15行文件列表)
第5輪: bash → head -20 tests/setup.ts (輸出: 20行代碼)
5 輪下來,messages 數(shù)組里塞進(jìn)了 315 行工具輸出。但父 Agent 真正需要的答案只有一個(gè)詞:"Jest"。
這不是個(gè)例。Agent 執(zhí)行的每一步——讀文件、跑命令、查日志——輸出都永久留在 messages 數(shù)組里。10 輪下來上下文可能漲到幾萬 token。50 輪下來,你可能已經(jīng)逼近上下文窗口的上限。
更要命的是,這些歷史輸出不只是占空間——它們會(huì)干擾模型的注意力。模型需要在一堆已經(jīng)過時(shí)的文件內(nèi)容中找到當(dāng)前任務(wù)的關(guān)鍵信息,準(zhǔn)確率和推理質(zhì)量都會(huì)下降。

解決方案:父子隔離架構(gòu)
核心思路極其簡(jiǎn)單:把子任務(wù)的臟活放到一個(gè)獨(dú)立的上下文里做,只把干凈的結(jié)果帶回來。
┌────────────────────────────────────────────────────────┐
│ 父 Agent (messages[]) │
│ │
│ User: "重構(gòu)這個(gè)模塊,先弄清楚測(cè)試框架" │
│ Assistant: 我先調(diào)查測(cè)試框架 → tool_use: task │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ 子 Agent (sub_messages[] — 獨(dú)立,用完即棄) │ │
│ │ │ │
│ │ User: "這個(gè)項(xiàng)目用什么測(cè)試框架?" │ │
│ │ Assistant: → bash cat package.json (200行) │ │
│ │ Assistant: → bash cat jest.config.js (30行) │ │
│ │ Assistant: → bash ls tests/ (15行) │ │
│ │ Assistant: "項(xiàng)目使用 Jest 測(cè)試框架,配置..." │ │
│ │ │ │
│ │ ★ 整個(gè) sub_messages[] 在這里丟棄 │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ tool_result: "項(xiàng)目使用 Jest 測(cè)試框架,配置在..." │
│ Assistant: 好的,現(xiàn)在開始重構(gòu)... (繼續(xù)干凈地工作) │
│ │
└────────────────────────────────────────────────────────┘
關(guān)鍵機(jī)制:
-
父 Agent 擁有
task工具 —— 可以把一個(gè)提示詞派發(fā)成子任務(wù) -
子 Agent 用獨(dú)立的
sub_messages[]啟動(dòng) —— 完全干凈的上下文 -
子 Agent 擁有除
task外的所有工具 —— 能干活,但不能遞歸 - 只有最終文本返回給父 Agent —— 幾百行的中間過程,壓縮成幾句話
- 子 Agent 的消息歷史直接丟棄 —— 不污染父上下文
效果:父 Agent 的 messages 里只多了一條 tool_result,內(nèi)容是精煉的摘要。那 315 行中間輸出?消失了。
核心機(jī)制拆解
機(jī)制一:task 工具定義
給父 Agent 注冊(cè)一個(gè) task 工具,參數(shù)就是一個(gè)提示詞:
TASK_TOOL = {
"name": "task",
"description": (
"Run a subtask in an isolated context. "
"Use this for research, analysis, or any work whose "
"intermediate output the parent does not need to see. "
"Returns only the final text summary."
),
"input_schema": {
"type": "object",
"properties": {
"prompt": {
"type": "string",
"description": "The task description for the subagent",
}
},
"required": ["prompt"],
},
}
這就是父 Agent 發(fā)起子任務(wù)的唯一接口。描述里明確告訴模型——這個(gè)工具適合"不需要看中間過程"的任務(wù)。
機(jī)制二:Subagent 循環(huán)
Subagent 本質(zhì)上就是一個(gè)迷你版的 Agent Loop,但有三個(gè)關(guān)鍵差異:
def run_subagent(prompt: str) -> str:
"""在隔離上下文中執(zhí)行子任務(wù),僅返回最終文本。"""
sub_messages = [{"role": "user", "content": prompt}]
# 子 Agent 可用的工具:除了 task 之外的所有工具
sub_tools = [t for t in ALL_TOOLS if t["name"] != "task"]
for turn in range(MAX_SUBAGENT_TURNS): # 硬上限,防止失控
response = client.messages.create(
model=MODEL,
system=SUBAGENT_SYSTEM,
messages=sub_messages,
tools=sub_tools,
max_tokens=8000,
)
sub_messages.append({
"role": "assistant",
"content": response.content,
})
# 子 Agent 決定不再調(diào)工具 → 任務(wù)完成
if response.stop_reason != "tool_use":
break
# 執(zhí)行工具,收集結(jié)果
results = []
for block in response.content:
if block.type == "tool_use":
output = TOOL_HANDLERS[block.name](**block.input)
results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": output,
})
sub_messages.append({"role": "user", "content": results})
# ★ 關(guān)鍵:只提取最終文本,sub_messages 整個(gè)丟棄
return extract_text(response)
三個(gè)關(guān)鍵差異:
| 對(duì)比項(xiàng) | 父 Agent | 子 Agent |
|---|---|---|
| 消息列表 | 全局 messages,持續(xù)累積 |
局部 sub_messages,用完即棄 |
| 可用工具 | 所有工具,包括 task
|
所有工具,排除 task
|
| 輪次限制 | 無硬上限(教學(xué)版) | MAX_SUBAGENT_TURNS = 30 |
機(jī)制三:結(jié)果提煉
extract_text 函數(shù)極其簡(jiǎn)單——從 response 的 content blocks 里只取文本:
def extract_text(response) -> str:
"""從響應(yīng)中提取純文本,丟棄工具調(diào)用塊。"""
parts = []
for block in response.content:
if hasattr(block, "text"):
parts.append(block.text)
return "\n".join(parts) if parts else "(subagent produced no text output)"
子 Agent 的最后一輪回復(fù)通常是自然語(yǔ)言摘要——"項(xiàng)目使用 Jest 測(cè)試框架,配置了 TypeScript 轉(zhuǎn)換器,覆蓋率閾值 80%"。這就是父 Agent 需要的全部信息。
完整代碼:父 Agent 的 dispatch 更新
在第 2 課的 dispatch map 基礎(chǔ)上,只需要加兩行:
# 工具列表:在原有工具基礎(chǔ)上加入 task
PARENT_TOOLS = BASE_TOOLS + [TASK_TOOL]
# dispatch map:加入 task → run_subagent 映射
TOOL_HANDLERS = {
"bash": run_bash,
"read": read_file,
"write": write_file,
"edit": edit_file,
"todo": todo_write,
"task": lambda prompt: run_subagent(prompt), # 新增
}
父 Agent Loop 本身一行不改。當(dāng)模型決定調(diào)用 task 工具時(shí),dispatch map 自動(dòng)路由到 run_subagent,執(zhí)行完把結(jié)果字符串作為 tool_result 追加到父 messages。
這就是第 2 課 dispatch 架構(gòu)的威力——加新能力只加注冊(cè),循環(huán)不碰。
實(shí)際運(yùn)行示例
給 Agent 一個(gè)復(fù)合任務(wù):
"分析這個(gè)項(xiàng)目的技術(shù)棧,然后在 README 里補(bǔ)充技術(shù)棧說明"
Agent 的執(zhí)行鏈路:
父 Agent:
思考: 這個(gè)任務(wù)有兩步——先調(diào)研技術(shù)棧,再寫文檔。
調(diào)研部分會(huì)產(chǎn)生大量中間輸出,用 task 隔離。
tool_use: task(prompt="分析這個(gè)項(xiàng)目的技術(shù)棧,
包括語(yǔ)言、框架、測(cè)試工具、構(gòu)建工具")
┌─ 子 Agent (獨(dú)立上下文) ─────────────────────┐
│ bash → cat package.json (200行) │
│ bash → cat tsconfig.json (50行) │
│ bash → ls src/ (20行) │
│ bash → cat vite.config.ts (30行) │
│ bash → cat jest.config.js (25行) │
│ bash → cat Dockerfile (15行) │
│ │
│ 最終回復(fù): "技術(shù)棧分析: │
│ - 語(yǔ)言: TypeScript 5.3 │
│ - 前端: React 18 + Vite 5 │
│ - 測(cè)試: Jest + Testing Library │
│ - 構(gòu)建: Docker + GitHub Actions │
│ - 包管理: pnpm" │
│ │
│ ★ 340行中間輸出在此丟棄 │
└──────────────────────────────────────────────┘
tool_result: "技術(shù)棧分析:語(yǔ)言 TypeScript 5.3..."
(只有5行摘要進(jìn)入父上下文)
思考: 拿到技術(shù)棧信息了,現(xiàn)在編輯 README。
tool_use: read(path="README.md")
tool_use: edit(path="README.md", ...)
最終回復(fù): "已在 README.md 中補(bǔ)充了技術(shù)棧說明。"
對(duì)比效果:
| 指標(biāo) | 不用 Subagent | 用 Subagent |
|---|---|---|
| 父上下文增長(zhǎng) | +340 行工具輸出 | +5 行摘要 |
| 模型注意力 | 被過時(shí)輸出稀釋 | 始終聚焦當(dāng)前任務(wù) |
| Token 消耗 | 每輪都帶歷史 | 子歷史隔離不累積 |

洞見:兩個(gè)關(guān)鍵設(shè)計(jì)決策
為什么禁止遞歸
子 Agent 沒有 task 工具,所以它不能再派生子子 Agent。這是刻意的限制。
表面原因:防止失控。 如果子 Agent 能再派子 Agent,一個(gè)寫得不好的提示詞可能導(dǎo)致無限遞歸,每一層都消耗 API 調(diào)用和 token。
深層原因:兩層就夠了。 實(shí)際使用中,父 Agent 拆解的子任務(wù)粒度已經(jīng)足夠小——"查測(cè)試框架"、"分析依賴關(guān)系"、"統(tǒng)計(jì)代碼行數(shù)"。這些任務(wù)不需要再拆解,一個(gè) Subagent 在 30 輪內(nèi)完全能搞定。
Claude Code 的生產(chǎn)實(shí)現(xiàn)也是這個(gè)設(shè)計(jì):Task 工具的子 Agent 禁止使用 Task。如果你需要更深的分解,正確的做法是讓父 Agent 拆出更多并列的子任務(wù),而不是讓子任務(wù)嵌套下去。
? 正確: 父 → [子A, 子B, 子C] (寬度擴(kuò)展)
? 錯(cuò)誤: 父 → 子 → 孫 → 曾孫 (深度遞歸)
為什么丟棄子歷史
子 Agent 結(jié)束后,sub_messages[] 直接被垃圾回收。為什么不保留?
不是因?yàn)闆]用,而是因?yàn)樾詢r(jià)比不夠。
保留子歷史有兩個(gè)成本:
- Token 成本:子歷史留在父上下文里,后續(xù)每輪 API 調(diào)用都要帶上,反復(fù)付費(fèi)。
- 注意力成本:模型的注意力是有限資源。無關(guān)的歷史信息會(huì)稀釋模型對(duì)當(dāng)前任務(wù)的聚焦度。
而保留子歷史的收益呢?幾乎為零。父 Agent 在后續(xù)工作中需要引用"子 Agent 第 3 輪讀的那個(gè)文件的第 47 行"的概率極低。它需要的是結(jié)論,不是過程。
這和現(xiàn)實(shí)中的管理一樣——你派一個(gè)工程師去調(diào)研技術(shù)方案,你要的是一份摘要報(bào)告,不是他瀏覽過的每一個(gè)網(wǎng)頁(yè)的截圖。
Subagent 的系統(tǒng)提示詞
子 Agent 有自己的系統(tǒng)提示詞,比父 Agent 更簡(jiǎn)潔聚焦:
SUBAGENT_SYSTEM = """You are a focused research and analysis agent.
Your job is to complete the specific task given to you, then provide
a clear, concise summary of your findings.
Guidelines:
- Stay focused on the given task
- Be thorough but efficient
- End with a clear summary of findings
- Do not ask for clarification — work with what you have
"""
注意最后一條——"不要問澄清問題"。子 Agent 沒有和用戶交互的通道,它只能和工具交互。所以它必須根據(jù)提示詞自行判斷、自行行動(dòng)、自行總結(jié)。
輪次上限:為什么是 30
MAX_SUBAGENT_TURNS = 30
30 不是拍腦袋的數(shù)字:
- 太小(<10):復(fù)雜調(diào)研任務(wù)可能需要讀多個(gè)文件、跑多個(gè)命令,10 輪不夠
- 太大(>50):如果 30 輪都沒搞定,大概率是任務(wù)拆解粒度不對(duì),繼續(xù)跑只是浪費(fèi) token
- 30 是經(jīng)驗(yàn)甜點(diǎn):覆蓋 95%+ 的合理子任務(wù),同時(shí)在失控時(shí)及時(shí)止損
生產(chǎn)環(huán)境可以根據(jù)任務(wù)類型動(dòng)態(tài)調(diào)整,但 30 是一個(gè)穩(wěn)健的默認(rèn)值。
五分鐘跑起來
# 進(jìn)入項(xiàng)目目錄
cd learn-claude-code
# 啟動(dòng)第四課
python agents/s04_subagent.py
啟動(dòng)后你會(huì)看到 s04 >> 提示符。以下是真實(shí)的運(yùn)行記錄:
任務(wù) 1:用子任務(wù)做調(diào)研
s04 >> Use a subtask to find what testing framework this project uses
> task (Find the testing framework used in this project):
(子 Agent 獨(dú)立執(zhí)行:讀取 package.json、requirements.txt、
tests/ 目錄、多個(gè)測(cè)試文件……全部在隔離上下文中完成)
子 Agent 返回摘要 ↓
## Results
The project uses **two Python testing frameworks**:
| Framework | Version | Role |
|-----------|---------|------|
| **pytest** | 9.0.2 | Primary — used across tests/ and mypackage/tests/ |
| **unittest** | stdlib | Secondary — used in one file with TestCase style |
Notably, the web/ subdirectory has **no JavaScript testing
framework** configured.
父 Agent 只拿到了一段精煉摘要。子 Agent 讀了多少文件、跑了多少命令?父 Agent 不知道,也不需要知道。
任務(wù) 2:大規(guī)模文件摘要(并行子任務(wù))
s04 >> Delegate: read all .py files and summarize what each one does
> bash: (列出所有 25 個(gè) .py 文件)
> task (Summarize top-level .py files): ...
> task (Summarize mypackage .py files): ...
> task (Summarize agents s01-s06 .py files): ...
> task (Summarize agents s07-s12 and s_full .py files): ...
> task (Summarize tests and skills .py files): ...
Here's a complete summary of all 25 `.py` files:
## ?? Top-Level
| File | Summary |
|------|---------|
| hello.py | greet(name) function returning "Hello, {name}!" |
| greet.py | Minimal module with a single greet(name) function |
## ?? agents/ — Progressive Agent Harness (s01–s12)
| File | Summary |
|------|---------|
| s01_agent_loop.py | The Agent Loop — minimal core |
| s02_tool_use.py | Tool Dispatch — dispatch map |
| s03_todo_write.py | TodoWrite — planning system |
| s04_subagent.py | Subagents — context isolation |
| ... | (后續(xù) 8 個(gè) agent 文件的完整摘要) |
父 Agent 把 25 個(gè)文件分成 5 批,派出 5 個(gè)子任務(wù)分別處理。每個(gè)子 Agent 讀取多個(gè)文件并生成摘要,所有中間的文件內(nèi)容(幾千行)都在子上下文中丟棄,父 Agent 只拿到結(jié)構(gòu)化的匯總表格。
任務(wù) 3:創(chuàng)建并驗(yàn)證
s04 >> Use a task to create a new module, then verify it from here
> task (Create a new JavaScript utility module):
(子 Agent 獨(dú)立創(chuàng)建 src/utils/stringUtils.js,
包含 capitalize、reverseString、isPalindrome、truncate 四個(gè)函數(shù))
子 Agent 返回摘要 ↓
> bash: cat src/utils/stringUtils.js
(父 Agent 自己讀取文件驗(yàn)證內(nèi)容)
> bash: node -e "..." (逐個(gè)測(cè)試每個(gè)函數(shù))
? Module created and verified successfully!
| Function | Test Input | Expected | Actual | Status |
|---|---|---|---|---|
| capitalize | 'hello' | 'Hello' | 'Hello' | ? |
| reverseString | 'hello' | 'olleh' | 'olleh' | ? |
| isPalindrome | 'racecar' | true | true | ? |
| truncate | 'Hello, World!', 5 | 'Hello...' | 'Hello...' | ? |
注意分工:子 Agent 負(fù)責(zé)創(chuàng)建,父 Agent 負(fù)責(zé)驗(yàn)證。 創(chuàng)建過程中的所有中間輸出(文件寫入、目錄創(chuàng)建)留在子上下文里;父 Agent 拿到摘要后,在自己干凈的上下文中獨(dú)立驗(yàn)證結(jié)果。這就是"隔離但協(xié)作"。
變更表
| 組件 | 第 3 課 (TodoWrite) | 第 4 課 (Subagent) |
|---|---|---|
| 上下文模型 | 單一共享 messages[] | 父 messages[] + 子 sub_messages[] |
| 子任務(wù)機(jī)制 | 無 |
run_subagent() 函數(shù) |
| 工具列表 | bash, read, write, edit, todo | +task (僅父 Agent) |
| 上下文隔離 | 無 | 子任務(wù)獨(dú)立上下文,用完即棄 |
| 遞歸控制 | 無 | 子 Agent 禁止使用 task |
| 輪次限制 | 無 | 子 Agent 30 輪上限 |
| 結(jié)果傳遞 | 無 | 僅最終文本返回父上下文 |
| 新增代碼 | ~30 行 | ~50 行 |
下一課預(yù)告
第 4 課解決了上下文膨脹,但 Agent 的能力范圍還是固定的——它只能用 system prompt 里寫死的知識(shí)。
第 5 課:Skills —— 動(dòng)態(tài)加載技能。讓 Agent 能根據(jù)任務(wù)需要,從外部加載專業(yè)知識(shí)和操作指南。就像一個(gè)工程師遇到陌生領(lǐng)域時(shí)翻文檔,而不是什么都靠腦子記。
# 預(yù)告:s05 的技能加載
def load_skill(skill_name: str) -> str:
skill_path = f"skills/{skill_name}.md"
return read_file(skill_path)
# 技能內(nèi)容注入系統(tǒng)提示詞
system = BASE_SYSTEM + "\n\n" + load_skill("python-testing")
從"硬編碼知識(shí)"到"按需加載",Agent 的知識(shí)邊界第一次變得可擴(kuò)展。
這是《12課拆解Claude Code架構(gòu):從零掌握Agent Harness工程》系列的第 4 課。關(guān)注Claw開發(fā)者,不錯(cuò)過后續(xù)更新。
完整代碼和交互式學(xué)習(xí)平臺(tái):github.com/shareAI-lab/learn-claude-code
如果這篇文章對(duì)你有幫助,歡迎轉(zhuǎn)發(fā)給你的技術(shù)團(tuán)隊(duì)。
系列目錄
- 第1課:用20行Python造出你的第一個(gè)AI Agent
- 第2課:給Agent加工具 —— dispatch map模式詳解
- 第3課:TodoWrite —— 讓Agent先想后做:規(guī)劃系統(tǒng)
- 第4課:Subagent —— 拆解大任務(wù),上下文隔離(本文)
- 第5課:按需加載領(lǐng)域知識(shí)——Skill機(jī)制
- 第6課:無限對(duì)話——上下文壓縮三層策略
- 第7課:任務(wù)持久化——文件級(jí)DAG任務(wù)圖
- 第8課:后臺(tái)執(zhí)行——異步任務(wù)與通知隊(duì)列
- 第9課:Agent Teams——多Agent協(xié)作:團(tuán)隊(duì)與郵箱系統(tǒng)
- 第10課:團(tuán)隊(duì)協(xié)議——狀態(tài)機(jī)驅(qū)動(dòng)的協(xié)商
- 第11課:自治Agent——自組織任務(wù)認(rèn)領(lǐng)
- 第12課:終極隔離——Worktree并行執(zhí)行