[[我也希望安全第一]]|第 4/30 天
前三篇一路看資料、對齊與模型內部訊號。到這裡先往外走一層:當模型開始讀檔、執行指令,成敗就不再只由權重決定。
Prompt、Context、Harness 常被當成三波流行語。我比較想知道的是:同一個模型接到同一項任務時,這三層分別改變了什麼?
這篇就看一個修 bug 的示範。模型第一次沒有找到檔案,卻照樣交出答案;第二次只多了一小段工作原則,結果便完全不同。把過程按順序排出來,三個名詞也會清楚不少。
這個示範讓 Google 的 Gemma 4 2B 修正 parser.py 裡的 bug,並用 verify.py 驗證結果。兩個檔案都放在模型可以操作的工作目錄中,但第一次沒有提供額外的工作引導。
Gemma 沒有先查看目錄。它從題目裡看到 parser.py 和函式名稱,便自行想像檔案內容,重新寫了一份,最後回報任務完成。
它知道 email parser 大概該怎麼寫,實際卡住的是工作順序:它沒有採取人類工程師很自然的第一步,先看看手邊到底有什麼。
第二次,示範者加入不到八十個字的通用原則:開始前先查看目錄、修改前先讀檔、完成後執行驗證。同一個模型這次依序找到檔案、閱讀內容、修改程式並跑完測試。
模型沒有變聰明,工作環境替它補上了一套做事方式。
把剛才的任務排在一起看,大致會是這個樣子:
Prompt 「修好 parser.py,讓 verify.py 通過」
↓
Context parser.py、verify.py、專案說明、先前操作結果
↓
Harness 查看目錄 → 讀檔 → 修改 → 跑測試 → 根據結果重試
Prompt 給模型一個目標。早期大家最常調整的是問法:角色怎麼設定、步驟怎麼描述、要不要要求模型逐步推理。這些技巧能改善單次回答,卻不會自動把工作目錄和測試結果送進模型。
Context 補上完成任務需要的資訊。對話紀錄、檔案內容、搜尋結果與 RAG 找回來的文件,都屬於這一層。Gemma 第一次只知道檔名,不知道檔案實際內容;有名稱和拿得到內容,是兩回事。
Harness 負責讓整個過程運作起來。它把使用者需求、context、模型與工具接在一起,也安排每輪之間要保留什麼、何時執行工具、失敗後要不要重試,以及最後拿什麼當作完成證據。
三者沒有互相取代。Prompt 仍然說明目標,Context 仍然提供資訊;只是任務從「回答一句話」變成「連續操作一個環境」之後,越來越多成敗落在 Harness。
Harness 聽起來像一套很大的平台,實際上可以從一個很小的 agent loop 開始:把任務與當前狀態交給模型,模型選擇回答或呼叫工具,系統執行後把結果放回下一輪。一直到模型說完成,或是用完步數、時間與預算。
User / Task
↓
Runner / Orchestrator ── Session、State、Checkpoint
↓ ↑
Context Builder → Model → Tool Request
↓
Tool Adapter / MCP
↓
Shell、Browser、API、Database
↓
Result / Error / Evidence
整條路徑另外留下 Trace,必要時停下來交給人。
Runner 是跑這個迴圈的程式;State 是任務現在走到哪裡;Checkpoint 則是可以恢復的中間點。MCP(Model Context Protocol)這類介面解決的是「工具怎麼接進來」,不會自動決定模型該不該用它。
真正實作時,這張圖會往不同方向長。目前常見的不是單一標準架構,而是下面幾種取向:
| 取向 | 架構重點 | 什麼時候有用 |
|---|---|---|
| 線性 agent loop | 對話歷程一路往後加,模型在每輪選擇下一個動作 | 想看懂每一步、做 baseline 或測試模型時 |
| SDK / runner | 把工具呼叫、session、handoff 與 trace 收進通用執行層 | 一般對話、工具使用與多 agent 分工 |
| 圖狀工作流 | 節點、分支、狀態與 checkpoint 都明確表示 | 任務會跑很久、需要中斷後續跑,或特定步驟要人介入 |
| Agent server + 執行環境 | agent 與真正動手的 workspace、container 或 VM 分開 | 寫程式、用瀏覽器或處理多人長時間任務 |
為什麼要分這麼細?因為「agent 卡住」的解法差很多。三步就做完的工具呼叫,用線性 loop 最好查;如果任務會跑幾個小時,半夜因 API 逾時斷在第二十七步,有沒有 checkpoint 就會決定 on-call 的人是按一次恢復,還是重跑整件事。
要看 Harness,我反而不會先選功能最多的框架。SWE-agent/mini-swe-agent 的預設 agent 類別只有約兩百行,核心迴圈更短,正好可以對照前面的三層各自落在哪裡。
如果把前面的任務交給它,過程大致如下:
system template +「修好 parser.py,讓 verify.py 通過」
↓
model.query(messages)
↓
action: ls
↓
environment.execute(action)
↓
observation: parser.py verify.py
↓
append to messages
└───── 回到下一輪
run() 開始時會先清空 messages,加入 system template 與任務,然後反覆呼叫 step()。而 step() 實際只做兩件事:先用目前全部 messages 詢問模型,再把模型回傳的 action 交給 environment 執行。執行結果會被格式化成 observation,追加回同一份 messages。
前一輪的 ls 結果,就是後一輪的 context。
| 這篇的名詞 | 在 mini-swe-agent 裡的位置 |
|---|---|
| Prompt | system template 與帶入任務的 instance template |
| Context | 線性增長的 messages,內含模型回應、action 與 observation |
| Harness | run() / step() 迴圈、model 與 environment 介面、上限檢查、錯誤處理與 trajectory 儲存 |
這個實作還有一個容易被忽略的部分。每次查詢模型前,它會先檢查步數、費用與總執行時間;格式連續出錯也會停下來。這些都不是模型的能力,是 Harness 從外面管住一次 run 的方式。
它的好處也是限制。線性歷程很好追,本地、container 或其他執行環境也能替換;但整個工具面主要是 shell,能做什麼很大程度取決於那個環境原本擁有的權限。messages 一直變長時,也得自己處理 context 成本。它會儲存 trajectory,不等於已有圖狀工作流那種明確的分支與 checkpoint 恢復。
這也是我選它當範例的因素:它沒有用大量框架名詞遮住 Harness 的基本工作。等基本迴圈看懂了,再去看 OpenAI Agents SDK 的通用 runner 與 handoff、LangGraph 的狀態與 checkpoint,或 OpenHands 如何在 agent server、workspace 與多後端控制中心之間分工,會比較知道自己在找什麼。
Pi 也是刻意做小的 coding harness,但它比 mini-swe-agent 更接近每天真的會用的終端工具。核心預設只交給模型 read、write、edit、bash,同時提供互動、JSON、RPC 與 SDK 模式。任務迴圈沒有因此變神祕:模型讀 context、選工具、取得結果,再決定下一步。
值得看的反而是它選擇不把什麼塞進核心。Sub-agent、plan mode、權限 gate、git checkpoint、sandbox 等能力可以透過 extension 或外部環境加入。這讓基本路徑比較容易追,也把責任露得很清楚:預設有 bash,並不等於預設有安全邊界;extension 又是會在本機執行的程式碼,安裝第三方 package 等於新增一個供應鏈信任決定。
Pi 的 session 用 JSONL tree 保存,可以分支、回到先前節點,也會在 context 接近上限時做 compaction。完整歷程仍在檔案裡,送給下一輪模型的摘要卻是有損的。這個差異後面會再出現:保存過紀錄,不代表模型當下看到的是完整紀錄;能恢復工作,也不代表能安全地恢復原本的權限與意圖。
如果模型只在聊天視窗回答,猜錯一個檔案頂多得到一段不可靠的文字。當它能寫檔、執行程式或操作外部服務,同一種猜測就可能真的改變系統。
因此,Harness 會影響任務能否完成,以及錯誤能走多遠。它可以要求模型先讀再改、跑完測試才回報,也可以決定哪些資訊和操作結果會在下一輪重新送回模型。
但這裡先留一條界線。寫在工作原則裡的「不要修改正式資料」,仍然只是一句給模型看的指示;資料庫帳號究竟能不能寫入,要由模型外面的權限系統決定。第 5 篇會分別看工作指引、工具邊界與驗證流程;第 6 篇再看即使沒有人惡意下指令,agent 為了完成任務還是可能自己越界。
前四篇從訓練資料開始。資料治理能留下來源並降低已知風險,對齊能改變模型在測試中的行為分布,可解釋性開始提供內部訊號。到了 Harness,工程師才開始能在每一次任務外圍安排資訊、工具與驗證流程。
這四層都能降低風險,卻沒有一層可以單獨保證「這一次 tool call 一定安全」。第 5 篇不再問 Harness 是什麼,而是開始逐一看它的工作指引、能力邊界與行為驗證。接著兩篇分別看 agent 自己越界,以及外部內容把它帶走;第 9 篇會再回頭看看,這些限制為什麼不可能把所有路都封死。
前四篇從模型裡面一路走到執行迴圈。把它們放在一起看,最大的差別是:有些鎖只能改變模型「比較可能怎麼做」,有些則開始留下可檢查的工程邊界。
| 鎖 | 放在哪裡 | 想攔住什麼 | 失效時的跡象 | 怎麼驗證 |
|---|---|---|---|---|
| 資料與 adapter lineage | 訓練資料、微調產物 | 來源不明、污染或錯版資料進入行為 | 找不到某項行為來自哪批資料與版本 | 固定資料/權重 hash,重跑回歸集 |
| 對齊與行為評估 | post-training、eval | 模型把代理指標當真正目標 | 換一種說法或環境,安全行為就消失 | 對抗切片、未見情境與外部效果測試 |
| SAE/activation monitoring | 模型內部 | 危險傾向只在輸出後才被看見 | 特徵漂移、誤報暴增或危險行為沒有訊號 | 綁定模型 revision,比較介入前後及副作用 |
| Context 與工作指引 | prompt、專案文件、記憶 | 沒讀環境便猜、忽略工作順序 | 指令衝突、舊摘要覆蓋新規則 | 固定任務回放,檢查實際讀取與引用來源 |
| Harness 執行迴圈 | 模型與工具之間 | 未取得證據便宣稱完成 | 回答說成功,卻沒有 diff、測試或工具結果 | 強制完成證據,故意讓工具失敗再觀察行為 |
這裡還沒有真正限制 shell、網路和正式系統。後面才開始碰那些比較硬的控制。