設定改了到底有沒有變好?拆解 affaan-m/ECC 的「有界評估 + 自動回滾」迴圈,並抄回自己的 coding agent 流程
本文大綱
你改了 CLAUDE.md,然後呢?
老實講,我們調 coding agent 設定的方式基本上是「感覺」。改幾行 system prompt、加三個 skill、把一個 subagent 拆成兩個,然後跑幾次覺得順,就留著了。
這裡有三個洞:沒有 baseline(跟什麼比?)、沒有指紋(三週後你不知道那個「感覺很好」的版本到底是哪一份設定)、沒有退路(發現變爛了,退回哪一版、憑什麼證據退的,沒人記得)。
2026-08-05,Affaan Mustafa(@affaan-m)在他的 ECC repo 合併了 PR #2686,標題就叫 Add bounded harness evaluation and rollback loop。這個 PR 做的事情很窄,但很值得抄:它把「agent 設定要不要換掉」這件事,變成一條有邊界、確定性、可稽核、而且健康檢查失敗會自動回滾的 CLI 流程。
先講清楚身份:這個 repo 原名 everything-claude-code,現在已經改名成 affaan-m/ECC(舊網址會 redirect)。以下所有描述來自我實際讀的 repo 程式碼、PR diff 和官方 README,不是二手摘要——我沒有在本機跑過 cargo test,涉及測試數字的地方我會標明是誰報的。

背景:ECC 是什麼,ecc2/ 又是什麼
ECC(affaan-m/ECC,MIT 授權,2026-01-18 建立)自稱是「agent harness performance optimization system」。README 標的規模是 67 個 agents、281 個 skills、94 個 commands,支援 Claude Code、Codex、Cursor、OpenCode、Gemini、Zed、Kimi 等多個 harness。GitHub API 在 2026-08-06 查到的數字是 238,025 stars / 36,150 forks(這種數字會變,自己去看)。
重點是 repo 裡的 ecc2/ 目錄:那是用 Rust 寫的 ECC 2.0 控制平面。作者在 ecc2/README.md 裡自己標得很白:alpha quality、not yet a public GA release,甚至留了一條 repo rule:
Do not market
ecc2/as done just because the scaffold builds.
這個態度值得先記著,因為它一路貫穿到這個 PR 的限制清單。
另外,ECC 本來就有兩個相關的 skill:skills/verification-loop(六階段驗證:build → typecheck → lint → 測試+覆蓋率 → secret 掃描 → diff review,輸出一份 READY / NOT READY 報告)和 skills/eval-harness(eval-driven development,capability eval / regression eval、四種 grader、pass@k 與 pass^k)。
這兩個 skill 是純 markdown,靠 agent 自己照著做。 PR #2686 的意義是:把「評估 → 晉升 → 回滾」從「提示 agent 去做」變成「SQLite transaction 保證它成立」。這個差別就是整篇文章的重點。
運作原理:逐段拆
PR 規模是 +2,328 / −170、15 個檔案。核心是三塊:新檔 ecc2/src/harness_eval.rs(579 行)、ecc2/src/main.rs 新增 harness-eval 子指令(+236)、ecc2/src/session/store.rs 的資料層(+1,237 / −84)。
1. 候選設定 = 內容定址的不可變物件
CandidateSpec::new(config, trace_refs, evidence_refs) 做三件事:
- 正規化 JSON:遞迴走訪,把每個 object 的 key 丟進
BTreeMap排序後重建。 - 正規化參照:
trace_refs/evidence_refs各自 trim、排序、去重。 - 算指紋:把
{config, trace_refs, evidence_refs}序列化成一個 canonical artifact,取 SHA-256 當 id。
測試 candidate_id_addresses_canonical_config_and_normalized_references 直接驗證這件事:{"model":"fixed","limits":{"steps":3,"tools":["read"]}} 和 key 順序顛倒的同一份設定,算出同一個 id;" trace://two " 這種前後有空白、順序也反過來的參照,正規化後也一樣。
反過來,candidate_id_changes_when_any_immutable_reference_changes 確認:只要換掉任何一個 trace 或 evidence ref,id 就變。
為什麼 refs 也要算進指紋?因為 id 代表的不是「這份設定」,是「這份設定,加上它宣稱依據的那批證據」。只 hash 設定的話,同一份 prompt 配不同批評估紀錄會撞成同一個 id,稽核就失效了。(順帶一提,v1 的 id 只 hash canonical config,v2 才把 refs 算進去;舊資料靠 harness_candidate_aliases 這張表做別名相容,不改也不刪舊列。)
2. 「有界」是真的每個欄位都畫了界
這是我覺得最能直接抄的一段。這套的每一個輸入都有硬上限:
- 候選設定 JSON ≤ 1 MiB
- trace / evidence refs:至少 1 個非空、最多 100 個、每個 ≤ 4096 bytes
- seeds:必須唯一、最多 10,000 個
- 記錄的分數總數 ≤ 20,000;每個分數必須是有限數且落在 [0, 1]
- health 斷言必須剛好一個 candidate key
- measurements 檔 ≤ 8 MiB
讀檔的 read_bounded_file 更細:Unix 上帶 O_NONBLOCK 開檔、檢查 metadata.is_file()、用 take(limit + 1) 讀再判斷有沒有超標。測試裡真的用 libc::mkfifo 造一個 named pipe 來確認會被拒絕——因為 --measurements 指到 FIFO 會讓 CLI 直接卡死。
你自己寫 agent eval script 的時候,上面這幾條大概就涵蓋了會出事的全部情境。抄走。
3. 配對評估:同一組 seed,兩邊各跑一次
evaluate_paired 的迴圈短到幾乎沒東西可講,但設計是對的:對每個 seed,先跑 candidate、再跑 baseline,組成一個 PairedSample { seed, candidate_score, baseline_score }。
測試 evaluator_is_called_for_each_explicit_seed_in_order 直接斷言呼叫序列是 (candidate,4) → (baseline,4) → (candidate,2) → (baseline,2)。
關鍵在 paired:不是「candidate 跑 20 題、baseline 跑另外 20 題」,而是同一題兩邊各跑一次再比。這樣任務本身的難度變異會被抵銷掉,你量到的才比較接近設定差異。而且 seed 必須明確給、不能重複——測試 policy_requires_explicit_unique_seeds_and_minimum_samples 裡,重複的 seed 讓 compare() 直接回 Err,不是回一個 false。
4. 三道門檻,全過才算過
PromotionPolicy { min_samples, min_mean_delta, min_win_rate }。compare() 算四個量:candidate 平均、baseline 平均、平均差(candidate 平均 − baseline 平均)、勝率(candidate 分數嚴格大於 baseline 的 seed 佔比)。三個門檻各自檢查,沒過的把原因塞進 failures: Vec<String>,passed = failures.is_empty()。
測試 thresholds_are_deterministic_and_all_must_pass 給了一個很好的實例:四組樣本 (0.9,0.7)、(0.8,0.7)、(0.6,0.7)、(0.8,0.7),勝率 3/4 = 0.75,剛好達標,但平均差只有 0.075,沒到 0.1 → 整體不通過。同一個測試還跑兩次 compare() 斷言結果完全相等:整條路徑沒有隨機、沒有時間依賴。
這就是為什麼要三道而不是一道:平均分會被一兩題大勝拉高、勝率會被「每題只贏 0.001」灌水、樣本數不夠時前兩個都沒意義。三個一起才擋得住不同形狀的假進步。

5. 晉升是 CAS,不是 UPDATE
通過門檻之後那行 SQL 值得單獨看:
UPDATE active_harness_config
SET candidate_id = ?1, updated_at = ?2
WHERE slot = 'default' AND candidate_id = ?3 -- ?3 是 baseline
影響列數不等於 1 就 bail,錯誤訊息是 atomic promotion compare-and-swap failed。在這之前還會先檢查「你給的 baseline 是不是當下的 active pointer」,不是就直接拒絕。
翻成人話:你不能拿一個過時的 baseline 去覆蓋別人剛晉升的設定。 多人同時操作同一個 registry 時,這一行就是全部的併發安全。
6. 健康檢查在切換「之後」,失敗反向 CAS 回滾
順序刻意設計成:先切 pointer,再跑 health check。三種結果對應三種 audit event:
| health_check 回傳 | event_type | status |
|---|---|---|
Ok(true) |
promoted |
healthy |
Ok(false) |
promotion_rolled_back |
unhealthy |
Err(_) |
health_check_error_rolled_back |
error |
後兩種會執行反向 CAS(把 pointer 寫回 baseline,一樣要求剛好影響 1 列),並把回滾證據寫進 audit。
還有一層防呆:health check 的回傳值必須跟事先落盤的 HealthEvidenceSnapshot.asserted_healthy 一致,不一致就當成 error 處理、照樣回滾。這條防的是「事後修改證據來讓結果好看」。
最重要的是:從門檻比較、CAS 切換、健康檢查到寫 audit,全部在同一個 SQLite transaction 裡,最後才 commit。所以不可能出現「pointer 切了但 audit 沒寫」或「回滾了但紀錄裡看起來還是 promoted」。
7. Audit 是 append-only,而且是資料庫層擋的
四張表——harness_candidates、harness_candidate_aliases、harness_evaluations、harness_eval_audit——各自掛了兩個 trigger:BEFORE UPDATE 和 BEFORE DELETE,一律 RAISE(ABORT, '... are immutable')。不是靠應用層自律,是 SQLite 自己擋。
讀 audit 的時候 harness_audit_entries() 還會逐筆重算驗證:把 health_evidence_json 反序列化回結構、重新算 canonical JSON 和 SHA-256、跟落盤的 digest 比對,還要檢查 event_type 跟 health_check_status 是否自洽。對不上就整個查詢報錯。
舊資料怎麼辦?用 legacy_unverifiable 旗標。ALTER TABLE 補欄位時預設 1(不可驗證),新寫入一律 0。不假裝舊資料可驗證,也不刪掉它們——這種處理方式比大部分「遷移時直接 truncate」的做法誠實得多。
數據與限制(作者自己講的那部分最值錢)
先講數字,都標明來源:
- PR body 自報的驗證結果:ECC2 479 passed、ECC Itô bridge 21 passed、compute skill contract 5 passed。這是 Affaan 在 PR 上寫的,我沒有在本機重跑。
- diff 規模 +2,328 / −170、15 個檔案,2026-08-05T22:17:10Z merged。
再來是 ecc2/README.md 裡作者親手列的限制清單。我把它幾乎照譯,因為這段比程式碼本身更值得抄進你自己的 README:
這只做一次有界的確定性比較。它不會自動改寫 prompt 或
ecc2.toml、不訓練或微調模型、沒有也不宣稱 RL、不呼叫任何網路服務、不執行 shell command evaluator。它不會動到執行中的 session。Evidence reference 和分數是 operator 的斷言,不是經過驗證的事實。算術門檻不代表統計顯著性。 Active pointer 只是 registry 狀態,不等於自動部署進 harness runtime。
PR body 裡也重申了一次 scope:This is deterministic offline policy/config evaluation, not RL training or autonomous online mutation.
這裡有兩個實務上最要注意的點:
第一,最大的缺口是分數哪裡來。 CLI 目前唯一暴露的 evaluator 是 recorded-v1,也就是你得自己準備 measurements.json。這套工具完全不管評分怎麼產生,它管的是「有了分數之後,晉升和回滾這段不要出錯」。這是刻意的邊界,不是缺陷——但你不能誤以為裝了它就有評估能力。
第二,「算術門檻不代表統計顯著性」這句要放大。 --min-mean-delta 0.05 --min-win-rate 0.6 這種數字沒有任何 p 值撐腰。5 個 seed 跑出 60% 勝率,跟擲硬幣的差別非常有限。要嚴謹的話,自己補檢定,或者把 seed 數拉到幾十上百。
照著做:把這套跑起來
A. 先跑 ECC2 內建的四個指令
git clone https://github.com/affaan-m/ECC.git
cd ECC/ecc2
cargo test # PR 自報 479 passed
# 1. 登記候選設定,stdout 回傳 sha256 id
cargo run -- harness-eval record \
--config candidate.json \
--trace-ref trace://run-1 \
--evidence-ref evidence://review-1
# 2. 指定第一個 baseline(同一個 DB 只能做一次)
cargo run -- harness-eval activate-initial <sha256> \
--evidence-ref evidence://baseline-approval
# 3. 配對評估 + 條件晉升 + 健康檢查(失敗自動回滾)
cargo run -- harness-eval run \
--candidate <sha256> --baseline <sha256> \
--seed 1 --seed 2 \
--measurements measurements.json \
--evidence-ref evidence://evaluation-1 \
--min-samples 2 --min-mean-delta 0.05 --min-win-rate 0.5
# 4. 看 append-only 稽核紀錄
cargo run -- harness-eval audit
measurements.json 的格式(每個 --seed 都必須出現在裡面,否則 evaluate() 會找不到分數而報錯):
{
"evaluator": "recorded-v1",
"scores": {
"<candidate-sha256>": { "1": 0.9, "2": 0.8 },
"<baseline-sha256>": { "1": 0.7, "2": 0.7 }
},
"health": { "<candidate-sha256>": true }
}
三個很容易踩的坑:health 必須剛好一個 key(多筆會被 from_evidence 擋下);那個 key 必須是 candidate 的 id(不符時 health_check 直接報 health evidence does not match promoted candidate);分數必須落在 [0, 1]。
B. 分數自己怎麼生(工具不管的那半)
這是你真正要花時間的地方。可照做的流程:
- 挑 10–20 個真實任務當 fixture。 從
git log撿最近實際處理過的 issue 最實在,別自己編。 - 每個 task 給一個固定編號當 seed,寫進 fixture 檔,之後永遠不動。
- candidate / baseline 各是一份完整的 agent 設定——CLAUDE.md 內容、啟用的 skill 清單、subagent 配置、模型與工具權限——序列化成
candidate.json。 - 每個 task 跑兩次(candidate 一次、baseline 一次),輸出(diff、測試結果、工具呼叫序列)落檔保存。
- 評分。ECC 的
skills/eval-harness建議四種 grader:code grader(確定性斷言)、rule grader(regex / schema)、model grader(LLM-as-judge rubric)、human grader。優先用 code grader——build 過不過、測試綠不綠、那個 export 有沒有出現,這種二元訊號最不會騙人。 - 把結果組成
measurements.json。
如果非得用 model grader,這是一段可以直接改的 prompt 骨架:
你是嚴格的程式碼變更評審。只依據以下任務描述與 diff 評分,
不要臆測未提供的內容,不要因為程式碼「看起來合理」就給分。
<task>{{TASK}}</task>
<diff>{{DIFF}}</diff>
依序判斷,每項只能是 0 或 1:
1. 是否解決了任務描述的問題(部分解決算 0)
2. 是否沒有引入任務之外未被要求的變更
3. 錯誤路徑是否有處理(本來就沒有錯誤路徑則算 1)
4. 是否有對應的測試或可執行的驗證
5. 是否沒有留下 TODO、被註解掉的程式碼、debug print
只輸出一行 JSON,不要任何其他文字:
{"score": <五項總和除以 5,取兩位小數>, "failed": [<未通過的項次>]}
為什麼拆成五個 0/1 而不是直接要一個 1–5 分? 因為 LLM 給連續分數的一致性很差,同一份 diff 跑三次拿到 3 / 4 / 4 是常態。拆成二元子項再加總,重跑的變異會小很多;而且 failed 陣列讓你事後看得懂到底為什麼扣分——這在你要向同事解釋「為什麼這版設定沒過門檻」時是必要的。
C. 沒有 Rust 也能抄的最小版本
你不需要 build ecc2。這套設計真正可移植的是四個決定:
1. 設定要有內容指紋。 最小實作就一行:
jq -S . candidate.json | shasum -a 256
-S 就是 key 排序,等同 canonicalize。把這個 hash 寫進每一筆評估紀錄,之後「哪一版設定拿到哪個分數」才答得出來。
2. 配對,不要各跑各的。 同一個 task fixture,candidate 和 baseline 各跑一次再比。這一步不花錢,只花紀律。
3. 三道門檻,全過才換。 樣本數、平均差、逐題勝率。少一道就會被某種形狀的假進步騙過。
4. 切換之後才做健康檢查,失敗自動退回,而且把「為什麼退回」寫進 log。 對應到日常操作大概是:git switch 到新 CLAUDE.md 分支 → 跑一次 smoke(build + 最小 e2e)→ 沒過就 git switch - 退回 → 把失敗輸出 append 進紀錄檔。
ECC 現成的 markdown 資產可以直接接上:skills/verification-loop 的六階段報告很適合當 health check 的內容;commands/checkpoint.md 則是最土砲的 active pointer——它就是把 時間 | 名稱 | git short SHA append 進 .claude/checkpoints.log,然後 /checkpoint verify <name> 比對「檔案變了幾個、測試 +幾 −幾、覆蓋率差多少」。
什麼時候該用、什麼時候別用
該用:
- 你的 agent 設定被多人改,而且改動有爭議(「加這個 skill 到底有沒有比較好」吵不完)
- 你需要對外交代某個設定為什麼上線——append-only 且可重算 digest 的 audit trail 有實際的合規價值
- 你已經有一組穩定的 task fixture 和能自動打分的 grader
別用:
- 還在探索期,一天改十次 prompt。 這套的成本全在準備 fixture 和 measurements,探索期硬套只會拖慢你。先手動摸清楚方向,再把穩定下來的東西丟進閘門。
- 只有 3–5 個樣本就想下結論。 作者自己說了算術門檻不是統計顯著性。
- 期待它自動改進你的 agent。 它明確不做這件事:不改 prompt、不訓練、沒有 RL。它是一道閘門加一本帳。
- 期待切了 pointer 就自動生效。 active pointer 只是 registry 狀態,接到 runtime 那段還是你自己的事。
對工程團隊的意義
第一,把「agent 設定」當成有版本的部署物。 現在多數團隊的 CLAUDE.md、skills、subagent 配置是散在 repo 各處的 markdown,沒有版本語意、沒有指紋、改壞了靠 git blame 慢慢挖。內容定址 + append-only audit 是成本最低的補救。今天就能做的第一步:在 CI 加一個 job,每次 .claude/ 底下有變動就把 jq -S 過的設定 hash 記進一個 append-only 檔。
第二,健康檢查放在切換之後、失敗自動回滾。 這是 web 部署 canary / rollback 早就有的常識,agent 設定管理現在才開始補課。注意順序:先切再檢查,而不是檢查完才切——因為你要驗的是「切過去之後這套設定在真環境還能動」。
第三,三道門檻的設計可以脫離這個 repo 使用。 任何 A/B 決策都適用:只看平均會被離群值騙、只看勝率會被微小差距灌水、樣本不夠時兩者都沒意義。三個一起看,成本幾乎為零。
第四,也是我覺得最值得學的:誠實地標「我們量的不是統計顯著性」。 ecc2/README.md 那段限制清單,比整個 PR 的程式碼更值錢。在一個到處宣稱「self-improving agent」「autonomous optimization」的環境裡,一個作者主動寫下「沒有也不宣稱 RL」「這是 operator 的斷言,不是經過驗證的事實」,這件事本身就是可信度。抄程式碼之前,先抄這個。
來源
- affaan-m/ECC(原
everything-claude-code,已改名並 redirect),MIT,作者 Affaan Mustafa(@affaan-m):https://github.com/affaan-m/ECC - PR #2686 — Add bounded harness evaluation and rollback loop(2026-08-05 merged,+2,328 / −170,15 files):https://github.com/affaan-m/ECC/pull/2686
ecc2/README.md的 Bounded Harness Evaluation 段(CLI 用法與作者自列限制清單)ecc2/src/harness_eval.rs(CandidateSpec / PromotionPolicy / evaluate_paired,579 行)、ecc2/src/session/store.rs(SQLite schema、immutability triggers、evaluate_promote_and_health_check)、ecc2/src/main.rs(harness-eval子指令與read_bounded_file)skills/eval-harness/SKILL.md、skills/verification-loop/SKILL.md、commands/checkpoint.md
整理:DataAgent · Coding Agent 實戰教學


