iT邦幫忙

2026 iThome 鐵人賽

DAY 14
0
ChatGPT & Codex

當 Codex 開始自己工作:從 Prompt Engineering 到 Agent Governance系列 第 14 篇

Day 14|Collision Check:看到 dirty working tree,到底該停、繞開,還是繼續?

  • 分享至 

  • xImage
  •  

Codex Day 14|Collision Check

Day 13 把兩個可寫入的任務(mutation-capable task)拆進不同工作樹(worktree),解掉「共用中間狀態」的污染。

但 Agent 不會永遠從乾淨 Repository 開始。

真實工作裡更常見的,是工作目錄本來就有尚未提交的變更(dirty working tree):

git status
→ modified files
→ untracked files
→ 某些變更不是這次任務產生的

我一開始最直覺的安全規則是:

看到有未提交變更的工作目錄(dirty working tree)就停。

這很保守,卻很快遇到另一個問題:只要使用者原本就在工作,Agent 幾乎永遠得停。

問題可以再拆成兩層。

Dirty 是狀態;Collision 才是風險判斷。

真正要判斷的不是 Repository 乾不乾淨,而是既有變更與本次 mutation 之間,是否存在行為衝突、歸屬(ownership)不明或高風險依賴。


第一個修正:既有變更不是錯誤

這條也寫進了 ap-safe-preflight 的前置檢查(preflight)規則:

Pre-existing user changes are evidence, not defects.

preflight 先讀取:

git status
git diff
git diff --cached
relevant untracked files

再建立起始狀態清冊(start-state ledger),把工作狀態至少拆成:

pre-existing user changes
current-task expected changes
stale tests / expectations
unknown or potentially conflicting changes

如果 Agent 一看到既有 diff 就假設它是錯的,最容易做出的「安全動作」反而可能是 reset、revert 或 clean,直接破壞使用者尚未完成的工作。

預設不再是「先清乾淨」,而是:

先證明哪些變更原本就存在,再判斷它們和這次任務有沒有衝突。


同一個檔案,不一定就是 Collision

這也是 collision 定義最重要的一次修正。

ap-safe-preflight 明確要求:直接衝突(direct collision)指的是既有變更與本次要求的行為(requested behavior)發生實際衝突,或既有變更本身不清楚、只做了一半;不是只要碰到既有使用者變更(existing user change)就算 collision。

假設這次要修改:

src/auth/router.ts

而這個檔案原本就有使用者尚未 commit 的調整。

只看到 path overlap 還不夠判 STOP。

如果既有修改和本次需求可以同時成立,Agent 應該保留既有意圖(intent),只在必要範圍內繞著它修改。

反過來,如果 existing diff 正在把某個 auth rule 改成 A,而本次需求要求 B,這才是直接 collision。這種情況不能靠模型猜哪個 intent 比較新,應該停在那一段並回報衝突。

我現在會把判斷順序寫成:

Path overlap?
↓
Behavior overlap?
↓
Intent clear?
↓
Risk acceptable?

而不是:

dirty?
↓
stop

Repo 裡已經有一個 hard collision

這個判斷沒有只停在文字規則。

agent-platform/tools/sync-consumer-skills.ps1 在同步 consumer skill 前,會先取得 consumer 已存在的 Git status paths,再拿它們和準備同步的 target prefixes 比對。

核心邏輯很直接:

pre-existing status path
        ↓
falls inside target sync path?
        ↓
YES → throw collision

實作會直接擋下:

pre-existing consumer target modification collision

理由不是「consumer repo 不能 dirty」,而是這次 lifecycle 準備寫入的 target,已經有一份不屬於本次 lifecycle 的既有 mutation。

同步工具沒有足夠授權(authority)決定誰該覆蓋誰,因此採取失敗即阻擋(fail closed)。

這讓一個重要分界變得很具體:

unrelated dirty change
≠
target collision

而 repo 還多做了一步:允許把「預期要保留的 dirty paths」明確寫進 ledger。

new-shared-skill-preflight-evidence.ps1 的 ExpectedConsumerDirtyPaths 預設是空陣列;如果 consumer 本來就有需要保留的修改,就必須明確列出,而不是假裝 worktree 是 clean。

README 甚至直接寫明:

consumer clean
→ omit ExpectedConsumerDirtyPaths

consumer has preserved changes
→ provide a non-empty explicit ledger

這個設計把「容忍 dirty」從一句原則變成可驗證契約。

new-shared-skill-preflight-evidence.ps1 同時記錄 HEAD、實際 changed paths、預期 changed paths 與 targetCollision。如果 Agent 開始工作後,實際 dirty paths 和 ledger 不一致,preflight validation 直接擋下:

changed paths do not match expected evidence

也就是說,允許既有變更存在,不等於允許 working state 任意漂移。

Collision Check 到這裡才從「我看過了,應該沒問題」,變成一個可重跑的問題:

Agent 開始工作前,Repository 到底已經有哪些變更?哪些是預期的?哪些不是?


STOP、AVOID、PROCEED

不是每個 dirty tree 都值得中止。我會先把 mutation 怎麼處理 收斂成三種:

明確 collision / intent unknown
→ STOP

既有變更與本次任務無關
→ AVOID

已知且相容的既有變更
→ PROCEED

這裡有兩條不同的判斷軸:

STOP / AVOID / PROCEED
→ 既有變更要不要碰

FAST / STANDARD / HIGH_RISK
→ 這次需要多深的執行與驗證

AVOID 不等於 escalation。FAST 只有在沒有 target collision、unrelated changed file、unowned change 等阻礙時才成立;條件破壞就記錄原因並提高處理層級,高風險 Evidence 再進入 HIGH_RISK。

例如 working tree 有一個和本次任務無關、但已知是使用者正在做的修改:

mutation strategy
→ AVOID that region

execution / review depth
→ FAST may no longer be eligible

所以「不能走 FAST」不等於「整個任務必須停止」;它只代表自動化信任程度要下降,而既有區域仍應被保留。

Codex Day 14|Collision Check mutation strategy and review depth

STOP

適合這些情況:

  • 既有變更直接和 requested behavior 衝突;
  • target file 有修改,但 intent 不清楚或只做到一半;
  • schema、migration、auth、data integrity 等高風險區域出現 unknown change;
  • 接下來需要 reset、checkout、clean 等可能破壞既有工作的操作。

這些情況下,多問一次比「自動整理乾淨」便宜。

AVOID

Repository 可以繼續工作,但某些既有區域不屬於這次任務。

例如 working tree 已有:

docs/notes.md       modified
assets/cover.png    untracked

而本次只修改:

src/auth/router.ts
tests/auth.test.ts

這時可以把 unrelated region 視為 immutable,明確避開,而不是要求使用者先把所有工作 commit 掉。

PROCEED

即使 target 或 adjacent file 已經有修改,只要能確認:

  • intent 清楚;
  • 與 requested behavior 相容;
  • 不需要覆寫既有工作;
  • 能把修改限制在必要範圍;
  • 沒有額外高風險依賴;

就可以在保留既有變更的前提下繼續。


Negative-path test 要證明「撞到時真的停得住」

如果 collision rule 只存在文件裡,我仍然不會太放心。

agent-platform 的負向路徑測試(negative-path tests)有一個 ConsumerCollision scenario。測試會刻意製造 consumer target collision,再執行 lifecycle runner。

預期結果是:

status = BLOCKED
exit = 20
consumer target collision blocked before mutation

而且 canonical 與 consumer fixture snapshot 都必須維持不變。

這個測試證明的不是「Agent 很小心」,而是:

當 target ownership 不清楚時,mutation path 真的會在寫入前被擋下。

到這裡,Collision Check 才從一條建議,變成可驗證的 guardrail。


確定性工具(Deterministic tool)能收集事實,但不能替你發明 intent

Collision Check 很適合把客觀資訊固定化:

  • current branch;
  • worktree root;
  • HEAD;
  • changed paths;
  • expected paths;
  • target prefixes。

但有一層不能假裝完全自動化:

這份既有變更到底代表什麼 intent?

File path 可以比對;behavioral ownership 有時仍需要讀 diff、handoff、issue 或最近決策,必要時直接問使用者。

所以 Collision Check 既不是:

if dirty:
  stop

也不是:

if no path overlap:
  safe

比較接近實際的版本是:

deterministic state collection
+
explicit task scope
+
existing-change ownership
+
semantic overlap decision

前半段盡量固定;後半段只有證據足夠時才繼續。


Day 12–14:Authority 的三層

Day 12|Bounded Scope
→ 被授權改什麼

Day 13|Worktree Isolation
→ 中間 mutation 放在哪裡

Day 14|Collision Check
→ 既有變更哪些可保留、避開,哪些必須停止

三層共同避免兩種誤判:「看得到」不等於「有權改」,dirty 也不等於「不可工作」。

Day 14 最後要回答的是:

既有 mutation 的 intent 是否清楚?本次 mutation 會不會跨過 ownership boundary?


Authority 做完,下一個問題才是「怎麼證明做完了?」

如果 Scope 正確、Worktree 隔離完成、Collision 也已經處理,至少能降低改到不該碰內容的機率。

但這仍然只證明 Agent 在合理邊界裡工作,還沒有證明它做對了。

所以下一階段會把問題從 Authority 換成 Evidence。

Agent 說:

Implemented successfully.

下一個要追問的是:

你實際驗證到哪一層?


上一篇
Day 13|Worktree Isolation:平行工作不等於 Multi-Agent
下一篇
Day 15|Verification Core:我為什麼不再接受「Implemented successfully」
系列文
當 Codex 開始自己工作:從 Prompt Engineering 到 Agent Governance 共 17 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言