第 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 的 name 與 description 放進系統提示,不會預先讀進整份 SKILL.md。 |
| Activation(啟用) | 模型判斷目前任務符合某個 skill 的 description,主動呼叫 activate_skill 這個內部工具要求啟用。 |
| Consent(同意) | 使用者看到一則確認提示,列出 skill 名稱、用途,以及它接下來會取得存取權的資料夾路徑;不合理的請求可以在這一步擋下來。 |
| Injection & Execution(注入與執行) | 使用者同意後,SKILL.md 全文與資料夾結構才真正寫進對話歷史,skill 資料夾也被加進代理允許存取的路徑清單。 |
把 discovery 限制在只放 name 和 description,用意是替 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 的位置:開頭的 --- 分隔符必須是檔案的第一行,前面不能有空行、註解或任何隱形字元。name 與 description 兩個欄位缺一個,都會讓整份檔案解析失敗,不會出現在 /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、檔案寫入、測試結果與輸出大小。 |
PreCompress | context 壓縮前。 | 保存重要狀態或提醒人工確認。 |
Notification | 系統通知事件。 | 轉送桌面通知或審計紀錄。 |
matcher 這個欄位的比對邏輯,依事件種類而不同:在 BeforeTool/AfterTool 這類工具類事件上,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_ID、GEMINI_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-ops 的 settings.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_file、list_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.json 用 enableAgents: false 整組關掉(實際 schema 細節請以官方 remote agents 文件為準,這裡示範的是概念性寫法)。
11.9 什麼時候用哪一個?
| 需求 | 建議 | 理由 |
|---|---|---|
| 團隊有一套固定 PR review 流程與 checklist | Agent Skill | 流程與參考資料按需載入,不必長駐主 context。 |
| 每次工具寫檔前都要阻擋特定路徑或命令 | Hook | 這是 deterministic policy,應在工具執行前攔截。 |
| 要請代理花很多步驟理解大型模組,再回報摘要 | Subagent | 長調查可以留在獨立 context,只把結論帶回主代理。 |
| 要把安全審查流程、範本、掃描腳本一起交給專職安全代理 | Skill + Subagent | Skill 提供程序與資源,Subagent 提供角色、工具與 context 隔離。 |
要防止任何 agent 執行 rm -rf、上傳 secret 或 push 到 protected branch | Hook + 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 遷移規則已將 temperature、top_p、top_k 列為 deprecated:目前可能被忽略,未來模型可能直接回 400;不要再把「低溫度=穩定」寫成這兩個模型的治理策略。直接寫 Gemini API 時改用 thinking_level(minimal/low/medium/high),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 容易踩雷。
| Hooks | Gemini CLI | Claude Code |
|---|---|---|
| 通訊協定 | JSON over stdin/stdout、exit code 語意、matcher 語法,刻意模仿 Claude Code 以降低遷移成本。 | 同一套協定(被模仿的原型)。 |
| 獨有事件 | BeforeModel(可攔截、修改甚至偽造 LLM 請求與回應)、BeforeToolSelection(篩選當下可用的工具集)。 | prompt/agent 類型的 hook(把「這個指令安不安全」這類模糊判斷直接交給 LLM 自己決定)、SubagentStart/SubagentStop(反映多代理架構的事件)。 |
| 遷移工具 | gemini hooks migrate --from-claude 一鍵轉換既有 .claude 設定。 | — |
順帶一提,Codex CLI 目前沒有正式的 hook 系統,只有 notify 這種單向通知機制;如果你的工作流同時涉及三套 CLI,hook 這塊的可攜性目前只在 Gemini CLI 與 Claude Code 之間成立。
| Subagents | Gemini CLI | Claude 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 阻擋危險工具行為。
.gemini/skills/security-review/SKILL.md:定義審查順序、報告格式與可讀 checklist。.gemini/agents/security-auditor.md:限制為 read/search/test 類工具,明確要求只報告不修改。.gemini/settings.json的BeforeToolhook:阻擋刪檔、外傳資料、修改憑證檔與未經確認的網路命令。
# 互動中先確認目前版本支援與啟用狀態
/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 +x;settings.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 和官方文件再確認。
動手試試
- 設計一個只含
SKILL.md的測試 skill,description 明確寫出何時使用與何時不要使用。 - 把一份 checklist 放進
references/,在SKILL.md中要求只有在審查任務需要時才讀取。 - 用
/skills list與/skills reload確認 discovery 與啟用狀態。 - 設計一個
BeforeToolhook 的 pseudo policy,列出它要阻擋的命令與允許的例外,不必一開始就執行真 script。 - 寫一個
.gemini/agents/researcher.md草稿,只允許 read/search 類工具,並用@researcher描述你希望委派的任務。 - 找一個 extension 或團隊設定,審查其中是否包含 skills、hooks 或 subagents,記錄你會接受與拒絕的原因。
- 把 11.5 節的 secret-scan bash hook 接到
BeforeTool,matcher 只鎖write_file與replace,實際觸發一次,確認它真的能擋下含api_key字樣的寫入。 - 刻意把一個 skill 的檔名存成
skill.md(小寫),觀察它會不會出現在/skills list;改回SKILL.md再確認一次差異,實際感受檔名大小寫的影響。
官方參考
Gemini CLI Agent Skills overview、Creating Agent Skills、Agent Skills best practices、Gemini CLI hooks、Hooks reference、Writing hooks、Gemini CLI subagents、Remote subagents、Gemini CLI model selection、Gemini 3.6 API migration、Trusted folders、Google Gemini CLI GitHub repo、Google Antigravity transition announcement