很多團隊第一次把 agent 接進產品時,會很自然地先做一層 adapter。
今天接 Claude Code,明天接 Codex,後天再接另一個 cloud agent。上層都叫 runTask(),輸入一段 prompt,最後拿回 stream、檔案修改或執行結果。看起來很乾淨。
這個想法本身沒錯。錯的是把 agent harness 當成 model provider 的親戚。
換 model provider,通常是在換推理能力、價格、latency、context window、tool calling 格式。這些都會影響產品,但邊界還算清楚。
換 harness 就麻煩多了。
你換掉的不是「誰回答問題」,而是「一段任務到底怎麼在環境裡被執行」。這句話如果沒先講清楚,後面會出事。
Model provider abstraction 處理的是 model call。它的問題比較像「我要怎麼把不同模型的輸入輸出整理成同一種產品介面」。
你可能會統一這些東西:
這層抽象的目標很清楚:產品不要被單一模型 API 綁死。今天換 provider,理想上大部分產品邏輯不用重寫。
Harness abstraction 處理的是另一層問題:agent 拿到任務之後,怎麼操作環境。這裡開始就不是單純的 request / response。
它關心的東西通常長這樣:
這些不是包在 model call 外面的雜項。這些就是產品行為本身。
如果 abstraction 只把不同 harness 包成同一個 function signature,卻沒有定義 runtime contract,那只是把風險藏到比較漂亮的 API 後面。
現在的 coding agent 已經不是「模型加幾個工具」。那個階段很快就過去了。
一個完整 harness 可能會管理 skills、sandbox、session、permission flow、runtime config、sub-agent、context compaction,甚至負責把工作切成多個可恢復的步驟。它也可能決定哪些檔案能被讀、哪些命令要 approval、哪些 artifact 會留下來。
所以「支援 Codex / Claude Code / Pi」不應該被理解成單純的 provider list。
假設你的產品有一個內部 automation:
input issue
-> run agent
-> edit repo
-> open PR
-> attach evidence
上層介面可以都叫 runAgent()。但底下的語意可能完全不同。
有的 harness 預設會要求互動式 permission,有的比較像 cloud automation,事前給定 tool scopes 後就自己跑。
有的 session 可以恢復,有的只留下 log。有的 sandbox 是平台承諾,有的只是你在執行環境裡自己約定。
同一個 prompt,在不同 harness 裡不一定代表同一個操作權限。這才是最容易被 adapter 掩蓋的地方。
我會把這句話寫在 agent platform 的設計文件第一頁:一致 API 不等於一致安全語意。
一致 API 很有價值。它讓產品可以替換 harness、做 A/B test、按任務類型選不同 runner,也讓團隊不用在業務邏輯裡塞滿 provider-specific code。
但一致 API 也最容易製造錯覺:上層看起來一樣,底層就真的一樣。
不會。
例如 tools: ["git", "shell", "browser"] 這種設定,看起來很直覺。可是不同 harness 對這些工具的定義可能不一樣:
shell 是否能碰 network?git push 是否需要 approval?如果這些問題沒有答案,你其實沒有 adapter。你只有一個會讓大家誤會的轉接頭,而且誤會會在事故發生時才爆開。
我比較建議反過來做:先寫 contract,再接 adapter。這很不性感,但它會省掉很多後面的猜謎。
不用一開始就做很重的規格書。先把最小共同契約寫清楚:
| 契約項目 | 要回答的問題 |
|---|---|
| Filesystem boundary | 可讀、可寫、禁止碰的路徑各是什麼? |
| Tool allowlist | 每個工具能做什麼?哪些工具需要 approval? |
| Approval policy | denied tool、使用者拒絕、approval timeout 時怎麼處理? |
| Session lifetime | session 什麼時候建立、何時銷毀、能不能 resume? |
| Timeout / retry | timeout 後是否重試?重試會不會重複副作用? |
| Artifact retention | prompt、diff、log、screenshot、evidence 存在哪裡? |
| Telemetry fields | 最少要留下哪些欄位才能除錯和稽核? |
| Cleanup behavior | partial file、背景 process、dirty workspace 怎麼收尾? |
這張表不漂亮,但很有用。它逼你承認一件事:agent harness 的抽象邊界不是 TypeScript interface,而是整個執行現場。
如果 adapter 寫完後,這張表還是空的,表示整合還沒完成。你只是把第一個 happy path 跑通。
現在一些 agent SDK 已經開始把 runtime 相關能力放到檯面上,例如 typed runtime context、scoped tools context、tool approvals、durable workflow、timeout、sandbox、telemetry。這些功能不該只被看成新玩具,也不用急著包裝成「最佳實踐」。
我會把它們當成一份檢查表。
你可以問:
這裡要小心一點:有些 harness package 和 API 還在 experimental 或 canary 階段,不適合被寫成穩定教學照抄進 production。比較務實的用法,是拿它們提醒自己哪些 runtime 問題不能省。
程式碼會變,契約問題不會。
Agent harness 的整合測試,不應該只測「可以成功產生一個 PR」。
那太順了,也太危險。
我會至少補這些不太討喜、但很接近真實事故的測試:
這些測試很無聊。可是 agent runtime 最常出事的地方,本來就不是 demo 裡那條順路。
真正的差異會出現在拒絕、卡住、中斷、重試、清理失敗的時候。你如果沒有故意測它,等於把最危險的分支交給運氣。
企業環境裡,這件事會更尖銳。
以前你可以說「公司開了 Copilot」或「公司禁了某個 AI 工具」。這種講法現在太粗,粗到會誤導決策。
同一個品牌底下可能有 IDE client、CLI、cloud agent、app automation、issue trigger。它們支援的 settings、tool scopes、approval 行為、資料流向不一定相同。某個設定在互動式 client 有效,不代表 cloud automation 也用同一套語意。
所以治理不是問「AI 有沒有開」。治理要問:
這聽起來像管理問題,其實是工程問題,而且是很實際的那種。
當 issue 可以觸發 agent,agent 可以改 metadata、開 PR、推 branch,ticket template、repo permission、tool scope、approval gate、telemetry pipeline 就會黏在一起。你不能只在 UI 上放一個「Enable AI」開關,然後假裝 runtime 差異不存在。
如果明天要在產品裡支援可替換的 agent harness,我會先做這幾件事:
這些事情做完,adapter 才比較像 adapter。
不然你只是把不同 agent 都塞進同一個按鈕,然後希望它們在最糟的時候剛好表現一致。
Swappable harness 的價值,不是讓你忘記 runtime。
它真正逼你的,是把 runtime 語意寫清楚。寫清楚之後,產品才知道自己到底在替換什麼,也知道哪些東西根本不該被抽象掉。