! 本篇文章將會介紹 安全煞車:全自動系統的邊界設計,期望大家都能放心讓 Agent 開車——因為煞車是你親手裝的 :D
昨天(D26)讓系統學會了 self-healing:偵測、隔離、修復、驗證,結尾還答應要補一把「別跟還沒跑完的實例打架」的鎖。寫完之後我盯著螢幕想了五分鐘,突然意識到一件有點毛的事:我花了 26 天,認真地把一個 Agent 教到會自己寫文章、自己開瀏覽器、自己按發佈鍵、甚至自己修自己——但它到目前為止沒闖禍,純粹是因為它「還沒想闖」。
自癒是油門,今天是煞車。一台會自己動手的機器,最危險的不是它不會動,而是它什麼都能動。這篇把界線畫清楚:它能去哪裡(allowed-domains)、能做哪些動作(action policy)、以及界線全部失守時,人類的搶救通道長什麼樣。
讀完這篇你會學到:
不用裝任何新東西,今天依然是盤點既有程式碼:
$ cd /Users/benben/ai/automations/ironman
$ grep -E 'MODE|NEW_ARTICLE_URL|BROWSER_PROFILE' config.sh # 煞車的開關都在 config.sh
$ ls ~/.config/opencode/opencode.jsonc # opencode 的權限規則在這
本文引用的程式碼,除了標注「概念示意」的那一段,都是從 scripts/ 和實際設定原封不動搬出來的。
邊界的第一問:Agent 的雙腳能踩到哪?以這套系統來說,答案應該只有一個地方——iThelp 的鐵人賽發文頁。但「應該」不算數,要用程式碼釘死。第一道閘門:開的網址不是寫在腳本裡散落各處,而是 config.sh 裡唯一一個變數:
# config.sh:唯一准許開啟的網址(9571 = 鐵人賽系列 id)
NEW_ARTICLE_URL="https://ithelp.ithome.com.tw/2026ironman/create/9571"
第二道閘門:開完頁面後驗證自己還在哪裡。被導去登入頁代表 profile 失效、頁面已經不在預期範圍,立刻煞車:
# publish.sh(節錄):URL 跑到登入頁 = 離開允許範圍,die 中止
URL=$("${AB[@]}" get url 2>/dev/null)
[[ "$URL" == *sign_in* || "$URL" == *login* ]] && die "iThelp 登入已失效,請重新登入 profile"
第三道閘門:就算網址對了,還要確認頁面的「身分」。發文入口如果設定錯誤,可能開出一般文章草稿——發了不計入鐵人賽,等於白發。所以 D 校準時就埋了防呆:草稿必須帶 article_type=ironman,不是就 die。三道閘門疊起來,Agent 的活動範圍被收斂成「這個網址、這個頁面、這種草稿」,一個更都進不去。
還有兩個容易漏掉的範圍。一是最小權限的 profile:BROWSER_PROFILE 指向的 ~/.agent-browser-profiles/ithelp 只登入過 iThelp,裡面只有這一個站方的 cookie。profile 就是 Agent 的錢包,錢包裡只放這趟要用的現金,被偷了損失也有限。二是時間的邊界——範圍不只是空間:
# generate.sh:賽期外直接跳過,不生成也不報錯
(( DAY >= 1 && DAY <= TOTAL_DAYS )) || { log "Day=${DAY} 不在賽期,跳過"; exit 0; }
這行就是 D10 安裝排程時「賽期外自動跳過」的來源,旁邊還有一個 Day 為負值時額外發 warn 的防呆(9/10 真的發生過 Day=-20,因為 START_DATE 忘了更新)——排程在賽季結束後還醒著,本身就是一種越界。
畫好了活動範圍,下一問是:在範圍之內,Agent 的雙手能做什麼?我的分法很簡單:可逆的放行(allow)、不可逆的先問(ask)、踩紅線的擋死(deny)。
deny 級最好認,就是那幾道命中即中止的守衛。機敏掃描是最紅的一條線:
# publish.sh:deny 級守衛——機敏掃描命中即中止,絕不送出
if secret_scan "$BODY_FILE" || secret_scan <<<"$TITLE"; then
die "機敏掃描命中!請人工檢查內容後手動重發"
fi
allow 級佔日常操作的絕大多數:填表、截圖、寫 log、摸 flag——這些動作做壞了都能重來,全放行,系統才跑得快。
ask 級最有趣:在 headless 的世界裡「問人類」很奢侈,所以這套系統把 ask 做成了開關。MODE="dry-run" 就是具象化的 ask——發佈前先降級成存草稿,人類驗收過才切 MODE="live"。D2 之前生成的品質判準沒過時,generate.sh 保留現狀、不讓備稿覆蓋、只發 warn 叫人來看(D23 的教訓),也是同一種思路:把決定權升級給人類,而不是替人類做決定。
opencode 本身也有同款機制,我自己的全域設定裡就放了三條:
// ~/.config/opencode/opencode.jsonc(節錄):規則依序評估,命中即套用
"permissions": [
{ "action": "shell", "resource": "git push --force*", "effect": "ask" },
{ "action": "shell", "resource": "rm -rf /*", "effect": "ask" },
{ "action": "shell", "resource": "rm -rf ~*", "effect": "ask" }
]
不可逆的破壞性指令升級成 ask,其他照常——跟 publish.sh 的 MODE、generate.sh 的備稿紀律,是同一套哲學在不同層的實現。
邊界還有一種常被忘記:不能跟自己打架。還債時間到——d26 預告的鎖,就是為了擋「20:00 的 job 還沒跑完、20:15 的補發 job 又殺進來」這種自相殘殺(概念示意,這是我接下來要加進 publish.sh 的):
# 概念示意:d26 預告的互斥鎖。mkdir 在 POSIX 上是原子操作,成功與否天然二值
LOCK="$ROOT/state/.publish.lock"
if ! mkdir "$LOCK" 2>/dev/null; then
log "上一個實例還在跑(鎖被佔用),本次直接跳過"
exit 0
fi
trap 'rmdir "$LOCK" 2>/dev/null' EXIT # 正常收工或 die,鎖都會自動解開
小小小測驗:你知道這套系統的第一次「全自動發佈」,實際上是誰按下的發佈鍵嗎?(答案:人類。9/11 D1 那天 20:00 的自動發佈失敗,20:22 是人工重試成功的——
screenshots/publish-d01-manual-retry-success.png這張截圖的檔名,就是歷史的供詞)
前面兩節都是「希望界線守得住」,但工程師的浪漫是假設它守不住。搶救通道不是出事再來想,要平時就鋪好,我鋪了三層。
第一層:出事要看得見。die() 這個全腳本最忙碌的函式,每次出場都自帶 Discord error 告警:
# publish.sh:die = 寫 log + Discord error,人類手機一定收得到
die() { log "FATAL: $1"; "$NOTIFY" -t "鐵人賽發文" -s error "$1"; exit 1; }
配合 ok / warn / error 三級通知,人類只在收到 warn 和 error 時才需要動手——這是 D13 告警系統留下的地基。
第二層:看懂要夠快。每次 die 都留下截圖(screenshots/),log 逐項記原因(d02 的教訓:不能只說失敗)。人類收到告警後不用重跑一次才能猜哪裡炸,看圖看 log 就能下診斷——D1 那晚 20:22 能收工,靠的就是這個。
第三層:接手要安全。人類在慌張中做的操作,要假設他會按兩次。所以搶救通道的第一原則是冪等優先:
# 手動搶救:重跑完全安全——開頭的 flag 守衛擋住重複發文
IRONMAN_DAY=27 ./scripts/publish.sh
flag 在就 exit 0,文章在就跳過生成。人類介入不需要先讀完腳本才能安全操作,這才是合格的搶救通道。而如果有一天連搶救都來不及——還有最後一格煞車:./scripts/install-launchd.sh remove,把排程整組拆掉,讓機器明天不要自己醒。煞車的最後形式,是把鑰匙拔走。
Q:煞車裝這麼多,會不會把「全自動」變成「半自動」?
A:關鍵在分級,不是數量。allow 級佔日常操作的九成九,ask 只留給不可逆動作(發佈、覆蓋、備稿),deny 只守紅線(機敏、身份錯誤)。26 天跑下來,煞車沒有一次擋到正常流程——被它擋下的全是真的有問題的:d09 的五篇機敏文章、D1 的失效登入。煞車的設計目標不是讓車變慢,是讓你敢把油門踩到底。
Q:機敏掃描會不會誤殺正常內容?
A:會,而且幾乎每天都會——本系列天天在教讀者怎麼寫 webhook 設定,範例 URL 本身就長得像機敏值。解法在 config.sh 的 secret_scan() 裡:掃描進場前,先把 discord.com/api/webhooks/<ID>/<TOKEN> 這種教學用佔位符置換成白名單字串再掃。先收好自己的假鑰匙,再讓掃描器進門。沒有這行置換,這個系列會被自己的教學文章塞滿隔離區。
Q:人類介入的時候,會不會反而把系統弄得更亂?
A:所以搶救通道的全部設計都圍繞「假設人類會手殘」:重跑有 flag 擋重複、備稿有 used 標記擋二次消耗、品質不足的文章保留現狀等人類判斷而不是被自動流程覆蓋。人類是這個系統裡唯一沒有冪等守衛的角色——所以才要用系統的冪等,把人類的每次出手都包成安全的。
article_type=ironman 防呆、最小權限 profile,四道閘門把活動範圍收斂到一條車道;連時間都要有邊界(賽期外自動跳過)install-launchd.sh remove 這個拔鑰匙開關下一篇我們要介紹 高手玩法:多 Agent 平行寫作,敬請期待!煞車裝好、界線畫清,明天開始放心踩油門:多個 agent 平行生成、互相評審、再由主編彙整的一人編輯部架構。
參考資料:
有任何疑問但沒有 iT 邦幫忙帳號,或是想匿名提問?
歡迎到 https://dev.benben.me/q/Z5442T 提問或加油打氣,沒意外的話會在完賽之後一起回答 :D