[Declarative Safety Gate] Part 2:把那道閘做到會自我修正
GDE《Agentic Architect》系列三部曲的**最後一篇**。前言講「為什麼是現在」,Part 1 把那道 **Declarative Safety Gate** 做出來給你看。這篇要回頭補上一個我在 Part 1 結尾自己招認的洞 —— 那道閘只判一次,不會學。我們要把它升級成一個**會自我修正的 loop**。
先承接 Part 1 。
我在上一篇白紙黑字寫了:這道閘是一個**單次判定(single-pass evaluation)**。engine 提一個動作、閘判 `APPROVE / DENY / ↓DOWNGRADE / ASK_USER`,判完就結束。**沒有獨立的 verifier、沒有自我修正。** 它很會把關,但它不會「下一回合做得更好」。
這就是一道**淺 loop**。
淺在哪?淺在它沒有時間維度。閘擋掉一個太衝的動作,下一回合 engine 還是可能再提一次一樣衝的東西 —— 因為**沒有人把「剛剛那筆被擋了」這件事記下來、餵回去**。閘是個盡責的海關,但它每天上班都失憶,不認得昨天被它退件的那個人。
Part 1 我說那道閘是 Harness:規則開火的那個關節。這篇要把鏡頭轉到 Loop:那個一圈一圈跑、每一圈都把 spec 跟 harness 吃進去、而且知道什麼時候該停的時間控制流。
所以這篇的概要只有一句:
淺 loop(閘判一次就結束)→ 會自我修正的 loop(提案 → 閘 → 驗證 → 寫回記憶 → 下一輪變好)。
🧩 解法:補三件東西,不動那道閘
閘還是 Part 1 那道閘: 照樣每一步判斷。但是在它外面加了三圈:
一個獨立的 verifier sub-agent(maker ≠ grader)
提案的人,不能是打分的人。
critique 寫回記憶、下一回合注入
讓「上一筆學到的事」真的影響「這一筆的提案」。
goal-conditioned 終止 + budget cap
loop 不是跑到天荒地老,是達標或燒完預算就停,而且交代為什麼停。
Let's break it down.
🧪 maker ≠ grader 是「結構上」做到的,不是嘴上說
`verifier.py` 裡是一個 `Verifier` Protocol,配一個 deterministic、offline 的 `RuleBasedVerifier`(跟 engine 那邊的 rule-based fallback 同一個 pattern)。它吐一個 `Critique` dataclass:一個 `score ∈ [0,1]`,外加一個 `advise_lighter` 的下一步轉向訊號。
score 只看三項:
advance: 這筆有沒有真的把 bond 往上推?
appropriate: 閘的判決對不對得上動作的壓力?
`DENY` / `ASK_USER` 代表 engine **衝過頭**了(inapprop
iate`DOWNGRADE` 是正確的 recovery(app
opriate)乾淨的 `APPROVE` 也是 appropriate。
quality: 這對 (agent) 合不合得來、強度合不合理。
重點不在這三個軸多聰明,重點在**它怎麼跟 engine 隔開的**:
verifier 只看得到「結果」—— 事件、閘的判決、互動前後的 bond。
跟 engine 不共用任何 state。所以 maker ≠ grader 是結構性的。
一個會自我打分的系統,永遠有 self-validation bias。把打分的人從提案的人手上拆開、讓它只能看 outcome、看不到 engine 的內部,這個偏誤才被結構擋掉。這正是 loop engineering 講的 Sub-agents(獨立驗證)。
💾 critique 寫回 + 注入:讓 loop「記得」
光打分沒用,分數要能影響下一筆。
`memory_store.py` 多了一張 durable 的 `critiques` SQLite table。`record_critique(...)` 把每一筆 critique 存進去;然後每個 actor**下一回合一開頭**,`latest_critique(actor)` 把它讀回來,折進 `EngineInput` 的 `adviseLighter` / `critiqueNote` —— 於是下一個提案就**自我修正**了。
而且它是 durable 的。critique 跟關係一樣寫進 SQLite,re-run 之後那個轉向訊號還在,就像現實裡的關係,你不會因為睡了一覺就忘記昨天踩到對方的雷。
⏱️ 終止:有目的/負責的結束,包括達標 / 燒完 / 卡住
`orchestrator.py` 裡的 `SelfCorrectingSimulation` 不會無限跑。它跑到下面**哪個先發生**就停:
**goal_reached** —— 任何一對關係爬到 `walk_buddy`。
**budget_engine_calls** —— engine 呼叫上限(預設 60)。
**stalled** —— 連續 4 回合沒有任何 approved 進展。
**budget_turns** —— 回合上限(預設 12)。
停的時候印一行 `STOP: <kind> — <detail>`。一個負責任的 loop,不只要會跑,要**會停、而且交代為什麼停**。這就是 harness 五維裡 Resource Management 在 loop 層的長相。
🔍 Traverse:來看 self-correction 實際案例
直接看 `p4_demo_output.txt` 裡 **Jordan → Theo** 這條線。
世界設定:場景在 `park`、下午、`maxIntensity = 4`。Jordan 是個 energy 8、playful 的衝組。
Turn 1 —— Jordan 提了一個大膽的 `visit`(intensity 8)。閘一比 `cap_intensity`:`8 > 4`,**DENY**,然後自動 **DOWNGRADED** 成 `leave_a_note`(intensity 2)過關。接著 verifier 進場打分:
`verifier (grader): score=0.87 advance=True appropriate=True | recovered via downgrade to ‘leave_a_note’; bond advanced (+1)`
critique 帶著 `advise_lighter=True` 寫回記憶。翻譯成人話:**「你剛剛衝過頭被降級救回來了,下一次溫和點。」**
Turn 2 —— Jordan 一開頭就把這筆 critique 注入了。輸出裡那行 `<- inected critique` 就是它:
[APPROVE ] a_jordan -> a_theo: gentle_intro (intensity 3)
<- injected critique : recovered via downgrade to ‘leave_a_note’; bond advanced (+1)
proposal rationale : prior critique advised a gentler move; start light at the park.
gate : APPROVE (social_action)注意那句 rationale —— *”prior critique advised a gentler move”*。Jordan 這次不提 `visit` 了,改提 `gentle_intro`(intensity 3)。
把兩回合擺一起:
**一個回合之內,self-correction 看得見。** 不是「跑了一百次平均下來變好」那種統計式進步,是這一筆,你指得出它為什麼比上一筆好。
有沒有反例?有,而且很重要。**Quinn → Sasha** 這條線,Quinn 是害羞、energy 2 的安靜組,它**從來沒衝過頭** —— 每一回合都提 `gentle_intro`、每一回合都 `APPROVE`。verifier 也一路給它 `score≈0.88`。
**loop 只在「閘曾經回推過」的地方改變行為。沒被擋過的人,不會被無事生非地「修正」。** 自我修正不是一台到處亂改的機器,是一個只回應真實摩擦的回饋。
跑到大約 turn 9,Jordan↔Theo 爬到 `walk_buddy`,goal 達成,loop 停。
📊 負責任的 loop
loop 跑完印的最後一行 => 這一圈到底花了多少成本
responsible loop engineering: engine_calls=45 verifier_calls=45 gate_evaluations=49 sim_tokens=8100
How to read:
`engine_calls=45` / `verifier_calls=45` —— 每一步提案配一次獨立 review,一比一。
`gate_evaluations=49` —— 比 45 多 4,因為**降級的那幾回合閘會跑兩次**(先判原案 DENY、再判降級版)。Turn 1 有 4 筆 downgrade,剛好多 4。
`sim_tokens=8100` —— 提案 120 / 筆、review 60 / 筆的 mock 估算累加。
為什麼要知道這段:
因為一個會自我修正的 loop,每多一圈 verify 就是多一份成本。**autonomy 不是免費的
🧭 Spec / Harness / Loop:三部曲
到這裡,三篇剛好把主軸的三個高度走完一圈:
**〈前言〉= Why** : 為什麼需要這道治理層(Moltbook 那面 150 萬倍的鏡子)。
**Part 1 = Harness** : 規則在哪個關節開火(pre-tool-call 的那道閘)。
**Part 2 = Loop** : 那個一圈圈跑、每一輪把 spec + harness 吃進去、而且**知道何時該 STOP** 的時間控制流。
**Spec 是規則是什麼。
Harness 是規則在哪開火。
Loop 是這一切一圈圈跑、而且知道何時收手。**
▶️ How-to-Run
cd agentic-social-kit && uv run --with google-antigravity python p4_demo.py --reset`--reset` 會先清掉 persisted memory 再跑。整個 loop offline、deterministic,不用 API key、不用 live model。
先讀這幾個檔:
- `verifier.py` —— 獨立 verifier(`Verifier` Protocol + `RuleBasedVerifier` + `Critique`)。
- `p4_demo.py` —— 把 engine(maker)跟 verifier(grader)接起來跑 loop 的入口。
- `memory_store.py` / `orchestrator.py` —— critique 寫回 / 注入、`SelfCorrectingSimulation` 的終止 + metrics。
- 存好的輸出:`p4_demo_output.txt`。
✅ 總結
Part 1 的閘是**單次判定**;這篇把它升級成**會自我修正的 loop**:提案 → 閘 → 驗證 → 寫回記憶 → 下一輪變好。
加了一個 **maker ≠ grader 的獨立 verifier**:它只看 outcome、跟 engine 不共用 state,所以偏誤是被**結構**擋掉的,不是嘴上保證的。
critique **寫回 SQLite、下一回合注入**:Jordan 被降級救回後,下一回合主動提溫和版 `gentle_intro` 一次過關 —— rationale 直接寫著 *prior critique advised a gentler move*。
loop **負責任地停**:`goal_reached` / `budget` / `stalled` 擇一,並印出 `engine_calls / verifier_calls / gate_evaluations / sim_tokens` 把成本攤開。
🔗 相關資源
Repo: https://github.com/jimmyliao/agentic-social-kit(`verifier.py` / `p4_demo.py` / `memory_store.py` / `orchestrator.py` / `social_engine.py`)
範例輸出:`p4_demo_output.txt`
*Jimmy Liao|LeapDesign Co-Founder / CTO|Google Developer Expert*
`#GoogleAntigravity` `#AgenticArchitect`




