每一步都個別允許時,Tool composition 為何仍可能形成原本未授權的高風險能力?
一個 support Agent 可以 export_ticket,也可以 send_internal_message。另一個 policy 也允許 send_external_email,因為它本身看起來只是寄信。若 Agent 先 export confidential ticket,再把結果接到 external email,兩個單步 decision 都可能是 allow,但整個 workflow 已形成「資料搬運出境」的能力。
這是 stateful Tool composition:前一步的結果、次數、累積 impact 與 workflow state 會改變下一步的風險。無狀態 PDP 每次只看當前 request,無法看見這個 sequence。
傳統 API authorization 常假設 request 相互獨立;Agent 卻會自主規劃、重試、平行呼叫與把 output 串成 input。安全性取決於路徑(sequence)、累積 side effect 與跨 Tool capability,而非單一 endpoint 的 allowlist。
PEP 允許 export(每次最多 100 筆)和 send(每次最多 1 個 recipient)。Agent 分 10 次 export,再透過外部 send;每步都低於限制,總量卻超出 Task 的 purpose。另一種風險是先 approve_payment 再 change_bank_account,單獨看都合法,但順序造成 separation-of-duties 破壞。
Task state machine 保存 state、已執行 action types、resource labels、cumulative counters、last outputs 的 metadata 與 nonce。Policy 不只回 allow/deny,也可要求 transition、max count、must-follow/must-not-follow、cooldown 或 second approver。每次成功 side effect 後原子地更新 state,避免兩個平行 Agent step 使用同一舊 state。
NIST SP 800-53 AC-5 的 separation of duties 可轉成 Agent workflow constraint:提出與批准、讀取與外送、修改收款資料與付款等能力由不同 actor 或不同 state 分隔。這不是把所有組合硬編碼,而是明確標示哪些 capability composition 會產生新的 authority。
每個 Tool 各自有 stateless PDP;只看 actor、tool、scope,不傳 Task state,也不記錄累積數量或前一步 result label。
Workflow PEP 先以 state transition policy 檢查候選 Action,再呼叫一般 resource PDP。成功後以 compare-and-swap 更新 state;不符合 sequence、budget、label 或 SoD 的 call 在 side effect 前拒絕。長流程的 state record 由 Orchestrator/Policy service 維護,Agent 只能讀取受限 view。
Agent 提議 plan 但不能自行宣告「這是第一步」或清除 history。Workflow state store、PDP、PEP 與 Tool outcome 是受信任來源;Tool 若發生 partial failure,必須回報明確狀態,不能讓 Agent 自行重置。
每個 event 關聯 actor、subject、task_id、state_version、action_digest、predecessor_event 與 outcome。下一步 grant 的 audience/constraints 由目前 state 產生,而非由 Agent 從上一個 token 複製。
allow 需滿足目前 state、allowed transition、cumulative budget、data-flow label、SoD 與 action policy;若 state version 不符則 reject/retry。PDP 回傳的新 state transition obligation 由 PEP 原子套用。

def next_state(state, action, label="public"):
if state == "ready" and action == "export": return "exported"
if state == "exported" and action == "send_internal": return "sent"
if state == "exported" and action == "send_external" and label == "public": return "sent"
return None
assert next_state("ready", "export") == "exported"
assert next_state("exported", "send_internal", "confidential") == "sent"
assert next_state("exported", "send_external", "confidential") is None
assert next_state("sent", "send_external", "public") is None
print("export/internal-composition=allow confidential-external/invalid-order=deny")
Day 25 會收束本 Part:同一個 Tool 即使介面相同,也不能把 Research、Support、Operations Agent 當成相同 principal。