駕馭 LLM 2026 年 10 月 19 日

駕馭 LLM|用唯讀 subagent 隔離探索 context

把「大量搜尋、讀檔」的工作交給一個唯讀的 subagent,主對話只收到摘要,既保護 context,也用工具白名單擋住誤改檔案。

本週主題:把「大量搜尋、讀檔」的工作交給一個唯讀的 subagent,主對話只收到摘要,既保護 context,也用工具白名單擋住誤改檔案。

1. 問題

你請 AI 編程助理回答「session 到期是在哪裡判斷的」。助理會先 grep、再開十幾個檔案、再讀測試,最後才回答。這些搜尋過程的輸出全部留在同一個對話裡。context window(模型一次能看到的文字上限)被這些中間結果占滿之後,真正重要的需求與決策反而被擠到後面,模型更容易忘記前面的約定,也更貴、更慢。

第二個問題是安全。你只想「查」,但同一個助理手上有寫檔、跑 shell 的權限。一句含糊的指示,就可能讓它順手「修好」某個檔案。

兩個問題的共同原因是:探索(exploration)與實作(implementation)用了同一個 context、同一組權限。

2. 原理

這個方法叫做 context 隔離的委派(delegation),不綁定任何工具。核心只有三件事:

  1. 獨立的 context window。subagent(子代理,由主代理啟動的另一個模型對話)有自己的對話歷史。它搜尋、讀檔產生的大量中間輸出,只存在它自己的 context 裡。
  2. 只回傳摘要。主代理收到的是一段結論,例如「檔案路徑加行號加一句說明」。主對話因此維持乾淨,後續推理不會被雜訊干擾。
  3. 權限收斂到任務所需。探索型的子代理只需要「讀」。把它能用的工具限制在讀取類,它就算被指示去改檔案,也沒有手可以改。

要讓這三件事成立,有兩個設計重點。第一,輸出格式要在子代理的 system prompt 裡寫死(例如「最多 5 行,附路徑與行號,不貼程式碼」),否則子代理可能回傳一大段原始碼,隔離就白做了。第二,description(說明欄)要寫清楚「什麼時候該委派給它」,因為主代理就是靠這段文字決定要不要委派。

成本面也要先講清楚:每次委派都是另一個模型對話,要多花 token 與時間。所以這個方法適合「搜尋範圍大、結果可以壓縮成幾行」的工作;如果只是讀一個你已經知道路徑的檔案,直接讀比較省。

3. 在 Claude Code 實作

Claude Code 把子代理定義成一個 markdown 檔,放在專案的 .claude/agents/,或個人的 ~/.claude/agents/。官方文件指出:只有 name 與 description 是必填;欄位名稱是 camelCase,必須完全吻合,因為「Claude Code ignores unrecognized fields without reporting an error」,打錯欄位不會有任何錯誤訊息。

  1. 建立 .claude/agents/code-explorer.md:用 tools 只給 Read、Glob、Grep;model 用 haiku 讓搜尋更便宜;description 寫「use proactively」鼓勵主代理主動委派。
  2. 準備三個小檔案當測試用的程式碼庫(src/auth.py、src/billing.py、src/api.py)。
  3. 測試 A:請主代理使用 code-explorer 查「session 到期在哪裡判斷」。
  4. 測試 B:請主代理指示 code-explorer 去修改 SESSION_TTL,驗證唯讀限制真的生效。
  5. 測試 C:用 --output-format stream-json 確認主代理確實呼叫了 Agent 工具並指定這個子代理。

完整的子代理檔案如下(.claude/agents/code-explorer.md):

---
name: code-explorer
description: Read-only code explorer. Use proactively to find where something is implemented and report file paths with line numbers. Never modifies files.
tools: Read, Glob, Grep
model: haiku
---

You are a read-only code explorer. Search the repository, then reply with at most 5 lines:
the relevant file paths with line numbers and one sentence on what each does.
Do not paste code. Do not attempt to modify any file.

三個測試用檔案如下。src/auth.py:

import time

SESSION_TTL = 3600


def create_session(user_id):
    return {"user": user_id, "expires": time.time() + SESSION_TTL}


def is_expired(session):
    return session["expires"] < time.time()

src/billing.py:

def charge(user_id, cents):
    if cents <= 0:
        raise ValueError("amount must be positive")
    return {"user": user_id, "charged": cents}

src/api.py:

from auth import create_session, is_expired
from billing import charge


def login(user_id):
    return create_session(user_id)


def pay(session, cents):
    if is_expired(session):
        raise PermissionError("session expired")
    return charge(session["user"], cents)

測試 A:

$ cd ~/demo && claude -p "Use the code-explorer subagent to find where session expiry is decided and which function calls it. Then tell me, in one sentence, which subagent you used and what it returned." --model sonnet --setting-sources project --strict-mcp-config --disable-slash-commands --allowedTools "Read,Write,Edit"
I used the `code-explorer` subagent, and it found that `is_expired(session)` (`src/auth.py:10-11`) decides session expiry by checking `session["expires"] < time.time()`, and that its only caller is `pay(session, cents)` (`src/api.py:9-12`), which raises `PermissionError("session expired")` when it returns true.

主代理的回答只有路徑、行號與一句說明,搜尋過程沒有進入主對話。這正是第 2 章說的「只回傳摘要」。

測試 B(唯讀限制):主代理的提示是「Use the code-explorer subagent. Instruct it to change SESSION_TTL in src/auth.py from 3600 to 7200 and report back what happened. Do not edit any file yourself. Then state whether src/auth.py was modified (check with Read).」輸出與事後檢查:

The code-explorer subagent did not make the change, and `src/auth.py` was not modified. A Read of the file shows `SESSION_TTL = 3600` on line 3.

The subagent only has read-only tools (Read, Glob, Grep). It ran one Grep, found the line, and reported that it could not edit. …

$ grep -n SESSION_TTL src/auth.py
3:SESSION_TTL = 3600
7:    return {"user": user_id, "expires": time.time() + SESSION_TTL}

檔案沒被改動。這個限制來自 tools 白名單,不是靠 prompt 裡的「請不要修改」。要注意:這個測試的主代理自己也被禁止編輯,所以它證明的是「子代理沒有寫入工具」這件事,不是主代理的行為。

測試 C(確認委派真的發生):用 stream-json 輸出,只取工具呼叫的名稱與前段參數:

Agent {"description":"Find charge amount validation","subagent_type":"code-explorer","prompt":"Find where charge amounts are validated in the codebase at ~/demo …
Grep {"pattern":"(?i)(charge|payment|amount).*(valid|positive|min|max|…
Grep {"pattern":"(?i)amount","path":"~/demo","output_mode":"files_with_matches"}
Read {"file_path":"~/demo/src/billing.py"}
…

第一行是主代理呼叫 Agent 工具、指定 subagent_type 為 code-explorer;後面出現的都是 Grep 與 Read,沒有任何 Write 或 Edit。

測試環境:Claude Code 2.1.302,預設設定(不含個人 plugins、hooks、CLAUDE.md),測試日期 2026-10-19。每個測試各跑一次,結果可能因模型輸出而略有不同。

⚠️ 未實測:isolation: worktree、background: true、memory、子代理巢狀深度(CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH)。以上只引用官方文件,沒有在這次測試中執行。

另外,文件說明 Claude Code 內建了 Explore 子代理:「A fast, read-only agent optimized for searching and analyzing codebases」,Write 與 Edit 都被拒絕。如果你只需要通用的唯讀搜尋,不必自己寫,內建的就夠用;自訂的好處是你能固定輸出格式、模型與工具範圍。

4. 搬到其他工具

這個方法不依賴 Claude Code。我查了另外三個工具的官方文件,各自對應的做法如下。

  • Cursor:子代理放在 .cursor/agents/(文件也列出 .claude/agents/ 與 .codex/agents/)。frontmatter 有 readonly 欄位:「If true, the subagent runs with restricted write permissions (no file edits, no state-changing shell commands)」。文件說「Each subagent has its own context window」,且「The parent only sees the final summary」。我在文件裡沒有看到可以逐一指定工具的白名單欄位,只有 readonly 這個開關。(官方文件)
  • OpenAI Codex:自訂代理是放在 .codex/agents/ 的 TOML 檔,必填 name、description、developer_instructions。重點差異是:子代理繼承父代理的 sandbox,「A custom agent file can't replace those settings」,sandbox_mode 等欄位放在代理檔裡不會覆蓋父設定;文件並說自訂指令「don't create a separate read-only sandbox」。也就是說,Codex 的子代理檔可以隔離 context,但不能用它來收窄權限,要收窄權限得改父層的 sandbox。(官方文件)
  • Gemini CLI:子代理是 .gemini/agents/*.md,frontmatter 的 tools 是可選清單,「If omitted, it inherits all tools from the parent session」,並支援 *、mcp_*、mcp_server_* 萬用字元。文件寫明「subagents cannot call other subagents」,也就是不能巢狀。該頁面同時註明 Gemini CLI 已在 2026 年 6 月 18 日被 Antigravity CLI 取代,所以細節請以你實際使用的版本為準。(官方文件)

三個工具的共同點是:都有「獨立 context、回傳摘要」。差別在「怎麼限制權限」:Claude Code 與 Gemini CLI 用工具白名單,Cursor 用 readonly 開關,Codex 的代理檔則不能改權限。選工具或寫團隊規範時,這個差別比語法差異更重要。

5. 限制與代價

  • 多花 token。Codex 文件明說「subagent workflows consume more tokens than comparable single-agent runs」。短任務不要委派。
  • 子代理看不到你的對話。Claude Code 文件說非 fork 的子代理「doesn't see your conversation history」,Cursor 文件也說「Subagents start with a clean context」。主代理必須把需要的資訊寫進委派的 prompt,漏寫就會查錯方向。
  • 摘要會損失細節。子代理可能漏看檔案,也可能被環境雜訊干擾。測試 C 的最終回答就提到,另一處含有 "amount" 的是同一個目錄裡的輸出紀錄檔,因為輸出檔也被搜尋到了。重要結論請回頭開原始檔確認。
  • 打錯欄位不會報錯。見第 3 章:欄位名稱必須完全吻合,否則會被靜默忽略。如果你寫成錯誤的欄位名,白名單可能根本沒生效(子代理在省略 tools 時會繼承所有可用工具)。請用像測試 B 那樣的「嘗試越權」來驗證。
  • 工具白名單不等於完整防護。它擋的是「有沒有這個工具」。若你給了 Bash,子代理就可能用 shell 寫檔。探索型代理請只給讀取類工具。
  • 委派不保證發生。主代理是看 description 決定的;想要穩定觸發,就在提示裡直接點名子代理,或在 description 加入「use proactively」。
  • 本文的測試規模很小。三個檔案的程式碼庫、每個測試只跑一次,只能證明機制,不能代表大型專案的成效與 token 節省量。

6. 延伸閱讀


📌 本週短訊

  • How the Claude Code team does code review(Anthropic Claude Code 團隊,2026-10-15)

    做法是分層:先在開 PR 前由 Claude 跑測試、build、linter 並貼出輸出,再由 subagent 在新的 context 審 diff;PR 上再由多個 agent 並行找 bug,另一組投票,只有多數同意的發現才會留言;人工審核的人數依變更風險分級(低 0 人、中 1 人、高 2 人)。

    值得看:把「有沒有人讀過 code」換成「這次變更可能出什麼錯,該由哪一種檢查來擋」,可以直接拿去設計自己的審核流程。

  • Claude dynamic workflows(flows)(Latent Space,2026-10-12)

    主代理先規劃,再寫程式去啟動多個 agent、收集結果並轉送資訊,不必事先設定固定的子代理名單;工作被切成「discrete, verifiable units」。文中建議設定 session 預算、保持預設 agent 數量低,並在 Claude Console 的圖形檢視中檢查主代理交給每個子代理的內容。

    值得看:這是本週主題「委派」的下一步,但成本較高,文中也建議只用在大型任務。

Claude Code 更新

版本變更對讀者的影響
2.1.302fork subagents(subagent_type: "fork")在 claude -p、Agent SDK、desktop app 與 VS Code 預設開啟,終端機原本就已開啟;CLAUDE_CODE_FORK_SUBAGENT=0 可關閉。桌面 app 與 VS Code 的主對話子代理一律在背景執行用 claude -p 或 SDK 跑自動化的人,行為會變:fork 子代理現在可能被用到;不想要請設環境變數
2.1.302prompt 與 agent hooks 同一事件最多同時執行 12 個,其餘排隊掛很多 hooks 的人,執行順序與延遲可能改變
2.1.296子代理 frontmatter 與 --agents 定義新增 autoCompactWindow,子代理可以比主對話更早自動壓縮長時間執行的子代理可以用它控制 context 大小
2.1.295子代理最多預載 32 個 skills 欄位中的 skill,每個只載入一次;有 Skill 工具的子代理仍可呼叫其餘的預載的 skill 清單過長時,超過 32 個的不會預載
2.1.292Agent 工具新增 effort 參數,Claude 可依你的要求用指定的 effort 等級執行子代理可以要求探索用低 effort、困難的分析用高 effort
End of article
0
Would love your thoughts, please comment.x
()
x