Hub Google AI CLI 教學

第 4 篇 高手 · 第 11 章

Agent Skills、Hooks、Subagents

這章整理 Gemini CLI 的三個代理擴充機制:Skills 放進按需載入的專門知識,Hooks 在代理生命週期中執行檢查或注入,Subagents 把大任務委派給有獨立 context 與工具邊界的專職代理。

這些功能特別受版本影響

Agent Skills、Hooks、Subagents 都屬於快速演進的 agentic 功能。實作前請用你當下安裝的 Gemini CLI 跑 /help/skills/hooks/agents,並查目前官方文件確認欄位、事件名稱、預設啟用狀態與安全提示。Google 在 2026-05-19 公告 Antigravity CLI 過渡,並指出 2026-06-18 後免費、Google AI Pro/Ultra 與 Gemini Code Assist for individuals 路線的 Gemini CLI 請求會停止服務;企業、Google Cloud、Gemini Code Assist Standard/Enterprise 與 paid API key 路線依公告仍有不同安排。保守做法是:把本章當作 Gemini CLI 與 Antigravity CLI 共同觀念,真正實際使用以前先確認你使用的產品與帳號路線。

11.1 先分清三個層次

這三個功能都會改變代理行為,但適合的問題完全不同。Skills 是「按需載入的專門知識」,Hooks 是「生命週期中的程式化攔截點」,Subagents 是「把任務交給另一個代理工作」。

機制核心概念適合用途主要風險
Agent Skills一個含 SKILL.md 與資源的資料夾;平常只暴露名稱與描述,啟用後才注入詳細指令與可讀資源。團隊流程、領域準則、部署 runbook、程式碼遷移步驟。SKILL.md 是會影響模型行為的操作文字,不能當成普通文件審查。
Hooks在 session、model、tool、context compression 等事件前後同步執行 script。阻擋危險工具、注入上下文、審計工具使用、強制政策。Hook 以使用者權限跑任意程式;stdout JSON、timeout 與信任邊界都要嚴格。
Subagents主代理把任務委派給有獨立 system prompt、工具集合與 context loop 的專職代理。大型研究、深度 codebase map、安全審查、瀏覽器任務、隔離高輸出子任務。工具授權過寬會造成副作用;描述太模糊會讓主代理誤用或不用。

11.2 Agent Skills:按需載入的專門知識

Agent Skill 是一個可被 Gemini CLI 探索的資料夾。啟動 session 時,CLI 先把已啟用 skill 的名稱與描述放進系統提示;當模型判斷任務符合某個 skill,會要求啟用。使用者同意後,SKILL.md 內容與資料夾結構才會進入對話,skill 資料夾也會成為代理可讀的路徑。

這種設計的重點是 progressive disclosure:不要把所有團隊文件、流程與範例一開始就塞進 context,而是等任務真的需要時再載入。

.gemini/skills/security-review/
  SKILL.md
  references/
    secure-coding-checklist.md
  scripts/
    scan-secrets.sh
  assets/
    report-template.md
---
name: security-review
description: >
  審查程式碼變更中的安全風險。當使用者要求 security review、
  auth review、secret scan、資料外洩檢查或 PR 安全審查時使用。
---

# Security Review Skill

你是謹慎的安全審查者。啟用此 skill 後:

1. 先釐清審查範圍:diff、檔案、PR 或整個模組。
2. 依序檢查 auth bypass、injection、secret、unsafe file operation、
   SSRF、XSS、data loss 與 logging privacy。
3. 如需 deterministic check,可以讀取 `references/secure-coding-checklist.md`,
   並在使用者同意後執行 `scripts/scan-secrets.sh`。
4. 回覆時先列高風險問題,再列測試缺口;不要為了風格問題淹沒安全發現。

不想手打 YAML frontmatter?請 Gemini 幫你生一份

Gemini CLI 內建一個 skill-creator skill,直接在對話裡打一句「Create a new skill called "xxx" that does ...」,它會照標準結構自動生出 scripts/references/assets/ 資料夾,以及格式正確的 SKILL.md,比手打漏欄位的機率低很多。生出來之後,再回頭照 11.3 節的建議微調 description 就好。

從探索到真正生效:Skill 的四階段生命週期

上面說的「先亮名字、要用才整份讀進來」,正式拆開來看是四個階段,弄懂這個順序,之後比較容易判斷卡在哪一步:

階段發生什麼事
Discovery(探索)CLI 掃描所有已啟用的 skill,但只把每個 skill 的 namedescription 放進系統提示,不會預先讀進整份 SKILL.md
Activation(啟用)模型判斷目前任務符合某個 skill 的 description,主動呼叫 activate_skill 這個內部工具要求啟用。
Consent(同意)使用者看到一則確認提示,列出 skill 名稱、用途,以及它接下來會取得存取權的資料夾路徑;不合理的請求可以在這一步擋下來。
Injection & Execution(注入與執行)使用者同意後,SKILL.md 全文與資料夾結構才真正寫進對話歷史,skill 資料夾也被加進代理允許存取的路徑清單。

把 discovery 限制在只放 namedescription,用意是替 context token 省錢——如果你裝了十幾個 skill,一次對話平均卻只會真正用到一兩個,沒必要每次都把十幾份 SKILL.md 全文預先餵給模型。

探索的優先序由低到高是:內建(built-in)→ extension 帶進來的 skill → 使用者層級(~/.gemini/skills/~/.agents/skills/)→ 工作區層級(.gemini/skills/.agents/skills/,可版控、team 共用);同一層級裡 .agents/skills/ 又會贏過 .gemini/skills/——.agents 是跨工具通用路徑,讓同一份 skill 也能被其他支援 Agent Skills 這套開放標準的工具讀到,不只綁死 Gemini CLI 一家。

工作區層級 skill 需要 /trust,使用者層級不用

放在 .gemini/skills/ 這個工作區層級的 skill,只有在目前資料夾被標記為信任(跑過 /trust第 1 章 1.5 節提過的資料夾信任機制)之後才會被載入;沒信任的資料夾,即使 SKILL.md 格式完全正確,也不會出現在探索結果裡。放在 ~/.gemini/skills/ 的個人 skill 不受這個限制,任何時候都可用。跑過 /trust 之後記得重啟 session,設定才會真的生效。

11.3 SKILL.md 不是 README

SKILL.md 有 YAML frontmatter,至少要有可識別的 name 與足夠明確的 description。body 不是給人看的說明書而已,它會被注入模型上下文,成為代理執行任務時會遵循的程序指令。

  • description 要寫清楚「何時使用」與「何時不要使用」,避免每個任務都誤觸。
  • body 要寫步驟、輸入、輸出、風險邊界與完成標準,不要只寫抽象價值觀。
  • 把長文件、範本、schema 放進 references/assets/,在 body 裡指示何時讀取。
  • 把可重複的 deterministic 動作放進 scripts/,但要讓模型先說明用途並等使用者授權。
  • 不要在 skill 裡放 token、私人 URL、憑證、可直接部署或刪除資料的腳本。
/skills list
/skills list all
/skills reload
/skills disable security-review --scope workspace
/skills enable security-review --scope workspace

gemini skills list --all
gemini skills install https://github.com/example/team-skills.git --scope workspace
gemini skills uninstall security-review --scope workspace

如果要在 CI 或無人值守環境自動安裝,gemini skills install 可以加 --consent 跳過安裝當下的互動確認;但這等於把接下來要講的人工把關整個省略掉,只建議對你完全信任、版本已經鎖定的來源這樣做。

改了卻沒被觸發:三個最容易漏掉的細節

SKILL.md 寫完卻「感覺沒作用」,多半不是模型不聰明,是下面三個細節之一沒對齊。

第一個是檔名大小寫:檔名必須完全是 SKILL.md,全大寫加 .md。macOS 和 Windows 的檔案系統預設不分大小寫,就算不小心存成 skill.md 也能矇混過關;但 Linux 的檔案系統會區分大小寫,同一份檔案在 Linux 上會被整個忽略,變成「明明寫了卻找不到這個 skill」。這是最多人踩到、卻最難自己發現的雷,因為在自己那台矇混過關的電腦上測試,一切看起來完全正常。

第二個是 frontmatter 的位置:開頭的 --- 分隔符必須是檔案的第一行,前面不能有空行、註解或任何隱形字元。namedescription 兩個欄位缺一個,都會讓整份檔案解析失敗,不會出現在 /skills list 裡,通常也不會有明確的錯誤訊息告訴你哪裡出錯。

第三個是 description 寫得太籠統。像「協助處理程式碼」這種寫法,範圍會跟模型內建能力或其他 skill 重疊,結果不是永遠不會被觸發,就是在錯的情境被觸發。官方 官方最佳實踐的建議,是把使用者實際會打的關鍵詞直接寫進 description(例如 audit、security、refactor),並清楚界定「什麼情況下用」與「什麼情況下不要用」,而不是寫一段給人看的摘要。

11.4 Hooks lifecycle:在代理迴圈中加護欄

Hook 是 Gemini CLI 在特定生命週期事件觸發的 script 或程式。它是同步執行:事件發生時,CLI 會等待符合條件的 hook 結束再繼續。這讓 hook 適合拿來驗證工具參數、阻擋危險動作、注入必要上下文、記錄審計資料,或在模型回覆後做過濾。

事件觸發時機常見用途
SessionStart啟動、resume、clear session 時。載入 git branch、issue id、環境摘要。
BeforeAgent使用者送出 prompt 後、代理規劃前。阻擋敏感要求、補充專案政策。
BeforeModel / AfterModel送出或收到 LLM 訊息前後。改寫上下文、記錄、遮罩敏感輸出。
BeforeToolSelection模型挑工具前。依任務或信任層級過濾工具。
BeforeTool / AfterTool工具執行前後。檢查 shell、檔案寫入、測試結果與輸出大小。
PreCompresscontext 壓縮前。保存重要狀態或提醒人工確認。
Notification系統通知事件。轉送桌面通知或審計紀錄。

matcher 這個欄位的比對邏輯,依事件種類而不同:在 BeforeToolAfterTool 這類工具類事件上,matcher 是正規表達式,比對的是工具名稱——下面範例的 run_shell_command|write_file|replace 就是用 | 做的正規表達式 OR;但在 SessionStart 這類生命週期事件上,matcher 改成精確字串比對,不支援正規表達式語法。留空字串或寫 *,兩種事件都代表全部符合。

{
  "hooks": {
    "BeforeTool": [
      {
        "matcher": "run_shell_command|write_file|replace",
        "hooks": [
          {
            "name": "block-dangerous-ops",
            "type": "command",
            "command": "$GEMINI_PROJECT_DIR/.gemini/hooks/block-dangerous-ops.sh",
            "timeout": 5000,
            "description": "阻擋刪檔、上傳 secret、修改 lockfile 以外的大範圍寫入。"
          }
        ]
      }
    ]
  }
}

上面範例明確寫了 timeout: 5000(毫秒);如果省略這個欄位,預設是 60000 毫秒(1 分鐘)。放著不管,一支寫壞、卡住不回應的 hook 最長可能拖住整個 agent loop 一分鐘,這也是為什麼 11.6 節會建議每個 hook 都設短 timeout。type 目前只支援 "command" 這個值。

Hook 透過 stdin 接收 JSON,透過 stdout 回傳 JSON。除最後的 JSON 物件外,不要把 debug 文字印到 stdout;debug 請寫到 stderr。官方文件也提醒,三種 exit code 語意不同:0 是正常路徑,CLI 會去解析 stdout 的 JSON(就算這份 JSON 寫的是 deny 也一樣算正常路徑);2 代表系統層級的高嚴重度阻擋,操作直接中止,而且這次 stderr 裡寫的內容會回饋給代理,讓它知道自己被擋的原因;其他非零值通常視為警告——不是致命失敗,CLI 會記下來但繼續往下執行。

Hook 執行時,環境變數裡有幾個現成的上下文可以讀:GEMINI_PROJECT_DIR(專案根目錄)、GEMINI_PLANS_DIR(plan 檔存放位置)、GEMINI_SESSION_IDGEMINI_CWD;另外還留了一個 CLAUDE_PROJECT_DIR 作為相容別名,方便直接沿用寫給 Claude Code 的 hook 腳本,不必整支重寫。

/hooks panel
/hooks disable-all
/hooks enable-all
/hooks disable block-dangerous-ops
/hooks enable block-dangerous-ops

# 從 Claude Code 遷移既有 .claude 設定:自動轉換事件別名、
# 工具名稱與 CLAUDE_*→GEMINI_* 環境變數對應
gemini hooks migrate --from-claude

11.5 Hook 實戰:兩個能直接照著改的範例

光看欄位說明有點抽象,這裡直接放兩支能動的 hook,一支是最常見的 shell script,一支是用 BeforeToolSelection 動態收斂工具清單的 Node.js 版本,兩種語言都合法——command 欄位只在乎能不能執行,不在乎你用什麼語言寫。

第一支掛在 BeforeTool,比對寫檔類工具,在內容裡抓可能的 API key 或密碼字串:

#!/usr/bin/env bash
# 掛在 BeforeTool,matcher 對準 write_file|replace
# 讀 stdin 傳進來的 JSON,只挑 tool_input.content 這個欄位出來檢查
input=$(cat)
content=$(printf '%s' "$input" | jq -r '.tool_input.content // ""')

if printf '%s' "$content" | grep -qiE 'api[_-]?key|password|secret'; then
  echo "偵測到疑似 secret,擋下這次寫入" >&2
  printf '%s\n' '{"decision":"deny","reason":"偵測到疑似 API key 或密碼字串","systemMessage":"🔒 security-check 攔下了這次寫入"}'
  exit 0
fi

printf '%s\n' '{"decision":"allow"}'
exit 0

掛法跟 11.4 節 block-dangerous-opssettings.json 寫法一樣,把 command 換成這支腳本的路徑、matcher 換成 write_file|replace 就能用。留意這支腳本不管擋不擋,都用 exit 0——因為「擋下這次操作」這個決定是靠 JSON 裡的 decision: deny 表達的,不是靠 exit code;exit code 2 是留給 hook 腳本「自己出了問題、需要用 critical block 硬中止」的情境,兩種「擋下來」語意不一樣,混用容易讓人誤判是腳本壞了還是政策真的擋下來了。

想知道原理:為什麼一行 debug 訊息就能讓整支 hook 失效?

Hook 的輸出協定設計成「整個 stdout 就是一份 JSON」,不是逐行輸出的 log 串流。CLI 會等 hook 這個行程結束,把它在 stdout 累積的所有內容一次丟給 JSON parser。如果中間夾了一行 echo "debug: xxx",等於在合法 JSON 前面多塞了一段非 JSON 文字,parser 會直接解析失敗——而且這個失敗通常不會有醒目的紅字提示,介面上就是這個 hook 判定沒生效,除錯起來像在抓幽靈。這也是官方文件把它稱為「黃金規則」的原因:stdout 只能有最後那個 JSON 物件,其餘一切,包括開發階段你想印出來確認變數值的訊息,都要導去 stderr。

第二支示範 BeforeToolSelection:在模型「還沒決定要用哪個工具」的這個時間點,先依使用者最後一句話動態收斂這一輪能用的工具清單:

#!/usr/bin/env node
// BeforeToolSelection 範例:依「使用者最後一句話」動態收斂這一輪能用的工具清單
const fs = require('fs');

async function main() {
  const input = JSON.parse(fs.readFileSync(0, 'utf-8'));
  const { llm_request } = input;
  const messages = llm_request.messages || [];
  const lastUserMessage = messages.slice().reverse().find((m) => m.role === 'user');

  if (!lastUserMessage) {
    console.log(JSON.stringify({}));
    return;
  }

  const text = lastUserMessage.content;
  const allowed = ['write_todos'];
  if (text.includes('read') || text.includes('check')) {
    allowed.push('read_file', 'list_directory');
  }

  console.log(JSON.stringify({
    hookSpecificOutput: {
      hookEventName: 'BeforeToolSelection',
      toolConfig: { mode: 'ANY', allowedFunctionNames: allowed }
    }
  }));
}

main().catch((err) => {
  console.error(err);
  process.exit(1);
});

預設只留 write_todos,偵測到使用者字面上提到 read 或 check 才加開 read_filelist_directory 這兩個唯讀工具。BeforeToolSelection 是 Gemini CLI 目前特有的事件,11.11 節會提到 Claude Code 沒有對應機制——等於是在模型「挑工具」這一步之前先幫它收斂選項,比起等它已經想呼叫某個危險工具、再靠 BeforeTool 逐一攔截,能少一輪來回。

11.6 Hook 安全:把它當本機程式碼審查

Hook 不是 prompt,它是會在你的機器上以使用者權限執行的程式。專案層 hook 尤其敏感,因為你打開不信任 repo 時,repo 可能帶入新的 hook 設定。官方文件提到 project hook 會被 fingerprint;hook 名稱或 command 改變時會被視為新的不信任 hook 並提示,但這不是免審查的理由。

  • 先用 /hooks panel 看目前實際啟用的 hook,不要只看 repo 裡的 settings.json
  • Hook command 使用絕對或專案根目錄變數,避免被 PATH hijack。
  • 給每個 hook 設短 timeout;慢 hook 會拖住整個 agent loop。
  • stdout 只輸出 JSON;log、trace、debug 全部送 stderr。
  • 禁止 hook 自動讀取或外傳 secret,除非它的目的就是 secret scan 且輸出被最小化。
  • 把 allow/deny policy 寫成可測的 deterministic 規則,不要把高風險判斷只交給模型。
  • extension 帶來的 hook 也要審;來源、manifest、script 與更新機制都在信任邊界內。

Windows 上 hook 跑不動,先懷疑 PowerShell,不是先懷疑自己邏輯寫錯

社群 Windows 上執行 shell-based hook(或 subagent 觸發的 shell 指令)常會撞到 PowerShell execution policy 擋下腳本執行,或是 CLI 呼叫的是舊版 powershell.exe 而不是更穩定的 pwsh(PowerShell Core)。社群多次回報 Windows PowerShell(非 Core 版)常常無法正確執行 Gemini CLI 生成的腳本,需要自行切換到 pwsh 或調整 execution policy。卡在 Windows 上 hook 完全沒反應,這是比較該先排除的方向。

11.7 Subagents:委派 context,而不是塞爆主對話

Subagent 是主 Gemini agent 可以呼叫的專職代理。每個 subagent 有自己的 system prompt、工具集合與獨立 context window;主代理把任務交給它,subagent 完成後只把結果回報給主代理。這能避免大型調查、長 log、瀏覽器操作或多步研究污染主對話。

可以讓主代理自動判斷是否委派,也可以在 prompt 開頭用 @agent_name 指定——明講「這件事就是它的專長,不必猜」,主代理會直接委派,跳過要不要委派的判斷;沒用 @,主代理才會依每個 subagent 的 description 自行判斷這個任務適不適合往下派。已經很確定任務屬於哪個專門領域時,直接 @ 指名可以省掉一輪「該不該委派」的來回。

內建代理會隨版本改變,下面這份清單是撰寫當下官方文件列出的範例,實際目前可用的內建 subagent,還是要以 /agents 面板當場顯示的為準:

內建 subagent專長
codebase_investigator程式碼庫分析、反向工程、依賴關係追蹤、根因分析。
cli_help回答 Gemini CLI 本身的指令、設定與文件問題。
generalist多檔案修改、高產出量、研究導向的雜項任務。
browser_agent網頁自動化、表單填寫、資訊擷取;需要本機安裝 Chrome(版本門檻請以官方文件為準,撰寫當下要求 144 以上)。
@codebase_investigator 說明 auth middleware、session store 與 refresh token 的呼叫關係。

@generalist 在隔離 context 中讀取測試輸出,找出前三個失敗的共同原因。

@cli_help 查目前版本如何列出、停用與覆寫 subagent。
---
name: security-auditor
description: >
  專門審查程式碼安全問題。當使用者要求 security audit、
  auth bypass review、secret scan 或資料外洩風險分析時使用。
kind: local
tools:
  - read_file
  - grep_search
  - run_shell_command
# 不在範例裡硬寫模型:沿用目前 CLI/帳號可用的預設。
# 截至 2026-07-26,開源 Gemini CLI 是否正式支援 3.6 仍 UNVERIFIED。
# Gemini 3.x API 已不把 temperature/top_p/top_k 當作穩定可調參數。
max_turns: 10
timeout_mins: 10
---

你是嚴謹的安全審查代理。只做分析與報告,不直接修改檔案。

優先檢查:

1. 認證與授權繞過。
2. SQL injection、XSS、SSRF 與 unsafe deserialization。
3. hardcoded credentials、secret logging 與資料外洩。
4. 高風險檔案操作與危險 shell command。

回報格式:

- Critical findings
- Evidence
- Suggested fixes
- Missing tests

自訂 subagent 通常放在 .gemini/agents/*.md~/.gemini/agents/*.md。frontmatter 的 description 會影響主代理何時委派;tools 可限制工具集合;未指定 tools 時會直接繼承父代理的全部工具,這跟 Claude Code「子代理預設從零工具」的方向相反,11.11 節會完整比較。這裡先記住結論:安全考量上,明確列出 tools 幾乎總是比省略更好。

11.8 Subagents 的隔離與授權

Subagent 的價值在隔離:自己的 history 不會灌進主對話,工具也可以只給它需要的最小集合。官方文件描述 subagent 不能遞迴呼叫其他 subagent,即使用 * 工具 wildcard,也不應看到或叫用其他代理;這是避免無限 loop 與 token 爆量的防護。每個 subagent 也有自己的執行輪數上限,官方文件目前預設是 30 輪(可用 max_turns 覆寫,實際預設值請以你當下版本的文件為準),避免一個委派出去的任務不知節制地一直跑下去、拖垮整體 token 預算。

---
name: dependency-researcher
description: >
  只負責調查套件版本、license 與 changelog。當任務需要 dependency
  research、upgrade risk 或 license review 時使用。
tools:
  - grep_search
  - read_file
  - mcp_package_registry_*
mcpServers:
  package-registry:
    command: "node"
    args: ["tools/package-registry-mcp.js"]
max_turns: 8
timeout_mins: 8
---

你只做研究與風險整理,不修改檔案、不執行安裝、不提交變更。

這份範例裡的 mcpServers 寫法,跟第 9 章介紹的 MCP server 設定是同一套語法,差別只是這裡把它的作用範圍收在單一 subagent 裡,不會讓 package-registry 這個 MCP server 對主代理或其他 subagent 曝光。

{
  "agents": {
    "overrides": {
      "security-auditor": {
        "enabled": true,
        "runConfig": {
          "maxTurns": 12,
          "maxTimeMinutes": 10
        }
      },
      "browser_agent": {
        "enabled": false
      }
    }
  },
  "experimental": {
    "enableAgents": true
  }
}

Remote Subagents:把任務交給外部服務

到目前為止看到的都是 kind: local——在你的機器上跑一個獨立行程。Gemini CLI 另外支援 kind: remote,透過 Agent-to-Agent(A2A)這套協定,把任務整個交給一個跑在別處的外部服務,設定時給 agent_card_url(指向服務公開的 agent card JSON)或直接內嵌 agent_card_json。角色設定與系統提示邏輯都留在對方服務那一端,本地這份檔案只負責「去哪裡找它、怎麼連上去」,所以通常不需要像 local subagent 那樣寫一大段 body。

---
name: acme-ticket-agent
description: >
  透過公司內部客服系統處理 support ticket 的建立、查詢與更新。
  當任務明確提到 ticket、客服單或 support case 時使用。
kind: remote
agent_card_url: https://agents.acme.example.com/.well-known/agent-card.json
---

認證這塊目前收得比較緊:用 Google Credentials 認證時,只認 *.googleapis.com*.run.app 這兩種網域;接自己架在別的網域的服務,要另外處理認證。官方文件也提醒,目前不支援本地與遠端 subagent 混合定義在同一套設定裡——要嘛全部本地,要嘛特定幾個改遠端,不能兩者隨意穿插。完全不需要遠端能力、也不想承擔它帶來的額外網路面攻擊面,可以在 settings.jsonenableAgents: false 整組關掉(實際 schema 細節請以官方 remote agents 文件為準,這裡示範的是概念性寫法)。

11.9 什麼時候用哪一個?

需求建議理由
團隊有一套固定 PR review 流程與 checklistAgent Skill流程與參考資料按需載入,不必長駐主 context。
每次工具寫檔前都要阻擋特定路徑或命令Hook這是 deterministic policy,應在工具執行前攔截。
要請代理花很多步驟理解大型模組,再回報摘要Subagent長調查可以留在獨立 context,只把結論帶回主代理。
要把安全審查流程、範本、掃描腳本一起交給專職安全代理Skill + SubagentSkill 提供程序與資源,Subagent 提供角色、工具與 context 隔離。
要防止任何 agent 執行 rm -rf、上傳 secret 或 push 到 protected branchHook + policy高風險操作要用程式化阻擋,不只靠 prompt 約束。
要把工具權限收到「這個指令允許、那個要問、那類指令一律擋」的粒度Policy Engine(第 7 章)subagent 的 tools 欄位只能整批允許或限制某個工具;要做到指令層級的細緻規則,得靠第 7 章的 Policy Engine
要分享整組 skills、hooks、subagents 給團隊Extension package第 10 章的 extension 才適合處理安裝、啟用、更新與版本化。

11.10 進階技巧:分層技能設計與多代理治理

熟悉基本用法之後,這裡整理幾個社群實戰與官方文件裡比較資深的用法,多半是第一次用不會想到、撞過幾次才學到的層次。

技能拆三層:判斷邏輯、決定性工具、穩定參考資料分開放

達人 · danicat.dev 一篇實戰筆記整理出一個值得參考的 skill 拆分模式:把 SKILL.md 本身定位成「專家角色 + 分析指引」,負責需要模型判斷的部分;重複性高、答案應該每次都一樣的計算或檢查,寫成 scripts/ 底下的固定腳本(例如一支用寫死演算法算分數的 analyze.py),不要交給模型「每次自由發揮」——同一份輸入,模型自由發揮兩次,答案可能不一樣;references/ 則放不太會變動的參考資料,像是 DB schema、術語表、既有規範。這樣拆的好處是需要判斷力的部分交給模型,需要一致性的部分交給固定腳本,兩者不會互相污染,重複性高的分析工作也因此可以半自動化、結果可重現。

Subagent 治理:模型與 thinking、平行衝突、什麼時候不值得用

官方 3.6/3.5 Flash-Lite 的 API 遷移規則已將 temperaturetop_ptop_k 列為 deprecated:目前可能被忽略,未來模型可能直接回 400;不要再把「低溫度=穩定」寫成這兩個模型的治理策略。直接寫 Gemini API 時改用 thinking_levelminimallowmediumhigh),CLI subagent frontmatter 能否接受哪個 model 欄位則要依目前 CLI 文件與 /agents 現場確認。官方 CLI model 文件也提醒,互動式 /model--model 不會覆寫 subagent 使用的模型;截至 2026-07-26,不要把 gemini-3.6-flash 填進開源 Gemini CLI subagent 範例,因為該 surface 的正式支援仍是 UNVERIFIED。成本治理仍可依任務風險選 Flash/Flash-Lite/Pro,但要把「哪個入口真的列出這個 model ID」當成前置驗證。

平行跑多個 subagent 最容易踩的雷是編輯衝突:兩個 subagent 各自被派去改同一份程式碼庫,卻沒人先劃清楚誰負責哪個檔案或模組,結果就是後寫入的覆蓋掉先寫入的,誰都不知道對方剛剛動過什麼。這跟第 14 章討論平行 session 之間怎麼避免跨 session 檔案衝突是同一個問題的不同尺度,解法也類似:動工前先明確切分責任邊界,並由負責派工的一方把必要的檔案內容或 schema 直接餵給每個 subagent,不要讓它們各自去猜對方看到了什麼。官方文件也明講,大量程式碼編輯類的任務要謹慎使用平行 subagent,正是因為這類衝突在實務上很常見。

最後一個常被忽略的成本:呼叫 subagent 本身不是免費的,context 隔離要付出 token 代價。如果只是改一行程式碼、範圍講不清楚的小改動,或是高風險的正式環境變更,拆給 subagent 處理通常划不來——多繞一層委派、多燒一份 context,換來的隔離價值卻很有限。比較划算的用法是留給明顯橫跨好幾個責任邊界的任務,例如一個完整功能同時牽涉資料庫 schema、API、前端與測試檔案,這時候拆給不同 subagent 分頭處理,才真的省下主對話的 context 空間。

如果團隊裡不只你一個人要用同一份 skill,有三條路可選:直接把 .gemini/skills/.agents/skills/ 加進版本控制、跟著 repo 走,適合團隊內部共用;包成第 10 章的 extension,適合這份 skill 需要搭配其他 extension 資源一起發佈;或是獨立開一個 Git repo,任何人都能直接用 gemini skills install 加上這個 repo 的網址安裝,適合公開分享給外部社群——Google 官方釋出的 13 個 Apache 2.0 授權 skill,走的就是這條路。

11.11 與 Claude Code 的關鍵差異

如果你是從 Claude Code 轉過來,或是兩套並用,這裡整理 hooks 與 subagents 這兩個機制實際差在哪裡——重點不是誰比較好,是兩邊有些預設方向剛好相反,帶著 Claude Code 的直覺套用到 Gemini CLI 容易踩雷。

HooksGemini CLIClaude Code
通訊協定JSON over stdin/stdout、exit code 語意、matcher 語法,刻意模仿 Claude Code 以降低遷移成本。同一套協定(被模仿的原型)。
獨有事件BeforeModel(可攔截、修改甚至偽造 LLM 請求與回應)、BeforeToolSelection(篩選當下可用的工具集)。prompt/agent 類型的 hook(把「這個指令安不安全」這類模糊判斷直接交給 LLM 自己決定)、SubagentStartSubagentStop(反映多代理架構的事件)。
遷移工具gemini hooks migrate --from-claude 一鍵轉換既有 .claude 設定。

順帶一提,Codex CLI 目前沒有正式的 hook 系統,只有 notify 這種單向通知機制;如果你的工作流同時涉及三套 CLI,hook 這塊的可攜性目前只在 Gemini CLI 與 Claude Code 之間成立。

SubagentsGemini CLIClaude Code
工具授權預設省略 tools 欄位=繼承父代理全部工具,含寫檔、shell 執行等高風險工具。子代理預設從零工具,父代理需在呼叫時明確授權。
隔離架構行程級隔離:每個 subagent 是獨立的作業系統子行程,隔離性強,OS 開銷較重。API 呼叫級隔離:較輕量、啟動快,是邏輯隔離而非實體行程隔離。

社群 兩者都支援平行執行多個 subagent,但社群評測普遍認為 Claude Code 目前在依賴關係追蹤、任務清單共享與生產級多代理編排的成熟度上比較領先;Gemini CLI 官方文件也提醒,大量程式碼編輯類的任務要謹慎使用平行 subagent,這點 11.10 節的編輯衝突討論已經談過。

想知道原理:兩邊「預設工具」的方向為什麼相反?

這可能不是巧合,而是跟隔離架構的鬆緊有關:Gemini CLI 的 subagent 是真正獨立的作業系統子行程,就算 tools 沒收斂、預設拿到全部工具,作業系統層級的行程邊界仍然是一層物理隔離;Claude Code 的子代理是 API 呼叫級隔離,沒有對應的行程邊界,如果工具預設也是全開,能造成的影響範圍會更難預期,所以改成「預設空、要用再給」,把最小權限原則往前推一步。這個解讀是從架構差異反推出來的觀察,不是官方寫明的設計動機,實際考量請以兩邊官方文件公開說明為準。

11.12 小型整合範例:安全審查工作流

一個保守的安全審查組合可以分成三層:skill 定義審查程序,subagent 隔離研究 context 與工具,hook 阻擋危險工具行為。

  1. .gemini/skills/security-review/SKILL.md:定義審查順序、報告格式與可讀 checklist。
  2. .gemini/agents/security-auditor.md:限制為 read/search/test 類工具,明確要求只報告不修改。
  3. .gemini/settings.jsonBeforeTool hook:阻擋刪檔、外傳資料、修改憑證檔與未經確認的網路命令。
# 互動中先確認目前版本支援與啟用狀態
/help
/skills list
/hooks panel
/agents

# 指定專職代理做安全審查
@security-auditor Review the staged diff for auth bypass and secret leaks.

11.13 常見卡關與排除法

Skills、Hooks、Subagents 都還在快速迭代——例如 Skills 從 v0.23.0 進 preview 到官方釋出 13 個技能,中間只隔了大約 4 個月。下面整理幾個比較常見、而且通常不是「你操作錯了」的卡關狀況;欄位名稱、指令與預設值變動快,實際行為請以你當下的 CLI 版本與 /help/skills/hooks/agents 面板輸出為準。

症狀常見原因處理
改了 SKILL.md、hook script 或 agent 定義檔,行為卻像沒改過CLI 還在用啟動當下快取的舊版本,沒有跑對應的 reload 指令或重啟 session。依對象分別用 /skills reload、重新打開 /hooks panel 確認,或乾脆整個重開 session。
Hook 完全沒有觸發,也沒有任何錯誤訊息常見三種:忘記 chmod +xsettings.json 路徑放錯位置(例如放到專案根目錄而不是 .gemini/ 底下);matcher 正規表達式寫錯。三種都是靜默失敗,逐一排除:先確認執行權限,再確認檔案位置,最後用簡單字串測 matcher 是否真的比對得到。
Hook 有跑,但整個 CLI 表現得像沒收到回應stdout 除了最後的 JSON 之外還混進了別的文字,哪怕只有一行 debug log,JSON parser 就直接解析失敗。把所有除錯輸出改到 stderr(bash 用 >&2),stdout 只留最後那個 JSON 物件。
Windows 上 hook 或 subagent 觸發的 shell 指令一直失敗PowerShell execution policy 擋下腳本,或 CLI 呼叫的是舊版 powershell.exe 而不是 pwsh。改用 pwsh(PowerShell Core),或調整 execution policy;這是 Windows 環境常見卡點,不一定是腳本邏輯寫錯。
新增的 subagent 意外能寫檔、能跑 shell,明明只想讓它做唯讀分析frontmatter 省略了 tools 欄位,預設繼承父代理的全部工具。明確列出 tools 清單,只給任務真正需要的最小集合。
設成 kind: remote 的 subagent 一直連不上,或認證一直失敗Google Credentials 認證只認 *.googleapis.com*.run.app 網域,接自架服務要另外處理認證;本地與遠端 subagent 目前也不能混合定義。確認服務網域、換一套適合自架服務的認證方式,並檢查有沒有誤把 local 與 remote 定義混在同一套設定裡。

11.14 安全審查清單

  • 來源:skills、hooks、subagents、extensions 都要確認來源、版本、tag 或 commit;用 gemini skills install 裝來路不明的 skill 前,先把 SKILL.md 與隨附的 scripts/ 通讀一遍再決定要不要裝——它們會用你的使用者權限執行,跟裝一個沒讀過原始碼的 npm 套件是同一等級的風險。
  • SKILL.md:把它當 prompt supply chain 審查;檢查觸發描述、隱性指令、要求讀取的資源與要求執行的 script。
  • Hook:檢查每個 command、timeout、matcher、stdout JSON、stderr debug 與外部網路行為。
  • Subagent:預設最小工具集合;避免 *、避免不必要的 shell、避免寫入型 MCP。
  • Secrets:不要把 key 寫在 SKILL.md、agent frontmatter、hook command 或 examples;使用環境變數與 secret manager。
  • Browser:若啟用 browser agent,限制 allowed domains,避免 persistent profile 暴露私人 session。
  • Policy:對高風險 shell、push、delete、network upload、credentials file write 加 deterministic deny rule。
  • 更新:每次 git pull、extension update、skill install 後重跑 /skills/hooks/agents 檢查實際狀態。
  • 記錄:把審查過的設定與接受風險寫進 repo 文件,避免只靠個人記憶。

本章小結

Skills 適合把專門知識與程序按需載入,靠 description 觸發,四階段生命週期與信任機制決定它什麼時候真的讀進 context;Hooks 適合在 agent lifecycle 中做 deterministic 檢查、阻擋與審計,代價是 stdout 只能有最終 JSON、其餘一律送 stderr 這條硬規則;Subagents 適合把長任務、專職任務與大量上下文委派出去,但它預設繼承父代理全部工具,這點和 Claude Code「預設從零工具」的方向相反,需要自己動手收斂。三者可以搭配,但都要用最小權限設計:skill 不放秘密、hook 不隨便跑任意程式、subagent 不繼承不必要工具。因為 Gemini CLI 與 Antigravity CLI 正處於產品過渡期,所有欄位與指令都要用目前版本的 /help/skills/hooks/agents 和官方文件再確認。

動手試試

  1. 設計一個只含 SKILL.md 的測試 skill,description 明確寫出何時使用與何時不要使用。
  2. 把一份 checklist 放進 references/,在 SKILL.md 中要求只有在審查任務需要時才讀取。
  3. /skills list/skills reload 確認 discovery 與啟用狀態。
  4. 設計一個 BeforeTool hook 的 pseudo policy,列出它要阻擋的命令與允許的例外,不必一開始就執行真 script。
  5. 寫一個 .gemini/agents/researcher.md 草稿,只允許 read/search 類工具,並用 @researcher 描述你希望委派的任務。
  6. 找一個 extension 或團隊設定,審查其中是否包含 skills、hooks 或 subagents,記錄你會接受與拒絕的原因。
  7. 把 11.5 節的 secret-scan bash hook 接到 BeforeTool,matcher 只鎖 write_filereplace,實際觸發一次,確認它真的能擋下含 api_key 字樣的寫入。
  8. 刻意把一個 skill 的檔名存成 skill.md(小寫),觀察它會不會出現在 /skills list;改回 SKILL.md 再確認一次差異,實際感受檔名大小寫的影響。