Lab:Capability-based agent harness
前三課都在拆觀念。這一課把 capability、tool gate、audit log 接成一段真的能跑的最小骨架。
Hook
到這裡, 你手上其實已經有幾塊拼圖:
- Lesson 02 的 per-agent capability set
- Lesson 03 的拆 autonomy / data / tools 心智模型
- Lesson 04 的 tool wrapper 與 audit log 欄位 如果這些東西一直分開看, 你很容易覺得: 「概念我懂, 但落地時到底長什麼樣子?」 所以這堂 lab 的目標很直接。 我們不做完整平台。 不接真 GitHub。 不接真資料庫。 不做完整 RBAC service。 我們只做一個 大約 80 行等級的 最小 capability-based harness。 它要證明四件事:
- agent 啟動時有自己的
required_caps - runtime 真的會檢查 capability
- read-only agent 在 write 步驟真的會被擋下
- audit log 真的會留下 blocked 與 success 的差異
換句話說, 這不是展示語法。 這是一個最小可執行證明。 你可以把它抄進自己的內部工具裡, 再慢慢把 mock tool 換成真實 side effect。
Learn
這個 lab 在驗什麼
先把成功標準講清楚。 這個 lab 不是在證明:
- 你已經做出完整權限平台
- 你已經解完 multi-tenant RBAC
- 你有 production-ready policy engine 它要驗的其實更小, 但也更關鍵: | 驗證點 | 你要看到什麼 | |---|---| | per-agent caps | 三個 agent 拿到不同 capability set | | runtime gate | tool call 進來時會比對 capability | | blocked write | read-only agent 讀得到,但寫不到 | | audit visibility | 成功與阻擋都會留下 audit entry | 做到這四件事, 你就已經把: 「權限邊界很重要」 變成一段可 review、 可測試、 可重複執行的程式。
我們只需要兩個 dataclass 跟三個核心 function
這份 lab 的骨架非常小。 只有:
Capability
Agent
check_capability(...)
audit_log(...)
run_tool(...)
其中:
Capability描述一項能力Agent描述誰持有哪些能力check_capability(...)判斷某個需求是否被滿足audit_log(...)記錄每次工具呼叫的 outcomerun_tool(...)把檢查與執行包在一起 你可以把它想成: 最小 policy engine 加上 最小 tool executor。
啟動時宣告 required_caps
這一點特別重要。
很多團隊的 tool gate
只有在執行瞬間才硬編碼需求。
但較好的做法是:
任務一開始就把自己需要的 capability
宣告成資料。
也就是:
required_caps
不是散在各處的字串。
而是一份可以被檢視的需求表。
這樣做的好處是:
- 任務缺權限時提早看得出來
- review 時能直接比對「持有」與「需求」
- 後續想接 approval 或 policy report 比較容易
Reference code:最小 capability harness
下面這段 code 是完整可跑的。 只用 Python 標準函式庫。 沒有假 import。 沒有外部相依。 你會看到:
- 三個 agent:read-only / write / full-access
- 同一個 task:先
github.read,再github.write - read-only agent 在 write 被擋下
- write 與 full-access agent 成功通過
- audit log 最後統計出 blocked 與 success
如果一切正常, 你應該看到:
== agent-read-only ==
audit = {'agent_id': 'agent-read-only', 'tool': 'github.read', 'args_hash': 'cd235278fb6b', 'outcome': 'success'}
read = README snapshot
audit = {'agent_id': 'agent-read-only', 'tool': 'github.write', 'args_hash': 'b10688d5d592', 'outcome': 'blocked'}
write_error = agent-read-only lacks github.write on repo:abd-ids-class
== agent-write ==
audit = {'agent_id': 'agent-write', 'tool': 'github.read', 'args_hash': 'cd235278fb6b', 'outcome': 'success'}
read = README snapshot
audit = {'agent_id': 'agent-write', 'tool': 'github.write', 'args_hash': 'b10688d5d592', 'outcome': 'success'}
write = comment-created
== agent-full-access ==
audit = {'agent_id': 'agent-full-access', 'tool': 'github.read', 'args_hash': 'cd235278fb6b', 'outcome': 'success'}
read = README snapshot
audit = {'agent_id': 'agent-full-access', 'tool': 'github.write', 'args_hash': 'b10688d5d592', 'outcome': 'success'}
write = comment-created
audit_summary = {'blocked': 1, 'success': 5}
comments = [{'repo': 'repo:abd-ids-class', 'text': 'LGTM after capability check'}, {'repo': 'repo:abd-ids-class', 'text': 'LGTM after capability check'}]
這就是這堂 lab 的最小證明。 不是用口頭說 read-only agent 不能寫。 而是讓它真的在 runtime 被擋掉。
check_capability(...) 真正做了什麼
這個 function 很小。 但它做了整個權限模型最核心的一件事: 把「持有的能力」 和 「要求的能力」 做比對。 這份 demo 的規則只有兩條:
name必須相同scope必須相同,或持有的是*真系統裡, 你還可以再長出:
- branch prefix match
- namespace hierarchy
- expires_at
- environment bound 但這個最小版本已經夠表達重點。
audit_log(...) 的價值,不只是除錯
很多人看到 audit_log
第一反應是除錯方便。
但在權限邊界裡,
它其實也是 policy 可觀測性。
如果沒有 audit,
你很難回答:
- 哪個 agent 最常被 block
- 哪個 tool 常被誤用
- 哪種 capability 其實配置錯了 所以 audit 不是最後才補。 而是 capability harness 本來就該帶著的東西。
為什麼 run_tool(...) 比把檢查散在各 tool 裡更好
你當然也可以 在每個 tool function 裡 自己寫 permission check。 但那樣很容易變成:
- A tool 有記 audit
- B tool 沒記
- C tool 有 scope check
- D tool 忘了 blocked outcome
把這些邏輯收進
run_tool(...)的好處是: 所有 tool 都走同一條 enforcement path。 這就是 harness 的意義。 它不是多一層抽象而已。 它是把邊界收斂成一個可重用入口。
為什麼 full-access agent 仍然值得保留在 demo
有些人會問:
既然 write_agent
已經能成功寫了,
為什麼還要 full_access_agent?
因為它提醒你另一個重要現象:
同一個 task
在高權限 agent 身上看起來也能正常通過。
這正是高權限最危險的地方。
你平常看不到問題。
功能都會成功。
直到某一天,
它碰到另一個更危險的 tool,
或被帶偏到另一條路徑。
所以 full_access_agent
不是拿來證明「它也會成功」。
而是提醒你:
成功執行 不代表權限設計合理。
你帶回真系統時,最先該升級哪三件事
這份 lab 刻意做得小。 所以它還沒處理很多現實問題。 如果你要把它帶回專案, 我會建議先升級這三個地方:
Capability增加時間邊界,例如expires_ataudit_log(...)加上principal與request_idcheck_capability(...)把 scope 比對做成真正的 resource matcher 這三步做完, 你就會從 demo 跨到一個相當像樣的內部 harness。
這堂 lab 還沒做哪些事
這裡也要誠實。 這份 code 還沒做:
- 真正的 identity binding
- 真的 external side-effect tool
- capability source of truth 來自 DB 或 YAML
- rich policy reason code
- denied event 的告警或自動分析 但這不影響它的教學價值。 因為它的目標很明確: 先把 capability check 做成一個最小可執行單位。
這一課你應該帶走的三個名字
如果只能記三個名字, 就記:
check_capability(...)audit_log(...)run_tool(...)第一個負責判斷。 第二個負責留下痕跡。 第三個把兩者包進實際執行。 這三個一起工作, 才叫 capability-based harness。 不是因為你在 README 裡 寫了「我們遵循 least privilege」。
AI 協作:學了這個,跟 AI 怎麼配合?
這個技能在 AI 協作中的定位, 是讓你可以要求 AI 幫你生成一個最小可跑的權限骨架, 然後你再用真實 capability 與真實 policy 把它補上。 你的人類優勢:
- 只有你知道哪些 capability 在你的系統裡需要 wildcard,哪些其實必須綁到單一 repo、單一 branch、單一 namespace。
- 只有你能判斷 audit log 裡哪些欄位足夠追責,哪些欄位若記原文反而會造成新的資料暴露面。 可以這樣跟 AI 說:
請幫我做一個最小 capability-based harness。需要有
Capability、Agent、check_capability、audit_log、run_tool。請用三個 agent 測同一個 task:read-only、write、full-access。read-only 要在 write 被擋下,其他兩個成功,最後輸出 blocked / success 的 audit summary。只用 Python 標準函式庫。
Do
互動示範
挑戰任務
請修改這堂 lab 的 Capability,加上一個 expires_at: datetime 欄位,並讓 check_capability(...) 額外檢查 capability 尚未過期。測試情境:建立一個 github.write capability,但 expires_at 是昨天;對它做檢查時,程式最後只印出 False。