# 策展 · X (Twitter) 🔥🔥🔥🔥

> 作者：Gregor Zunic (@gregpr07) · 平台：X (Twitter) · 日期：2026-04-24

> 原始來源：https://x.com/gregpr07/status/2047358189327520166

## 中文摘要

# Agent harness 的苦澀教訓

不要封裝 LLM，也不要封裝它的工具。

你只需要一個 `SKILL.md` 和一些 Python 輔助函式。讓 LLM 擁有完全的自由，如果缺少什麼，就讓它自己寫出來。

## 學習心得

幾個月前，我們寫了《Agent 框架的苦澀教訓》（The Bitter Lesson of Agent Frameworks）。當時的論點是：不要用抽象層來封裝 LLM，應先給予最大的行動空間，再進行限制。

但當時我們仍在封裝它的工具。

每一個 `click()`、`type()`、`scroll()` 輔助函式，都是你自以為模型需要而強加的抽象層。每一個輔助函式，都是經過強化學習（RL）訓練的模型必須費力克服的限制。

## 為什麼選擇原生 CDP

當我們開發 Browser Use 的第一個版本時，我們發布了數千行的元素提取器、DOM 索引器以及點擊封裝器。

但 LLM 本身就懂 CDP（Chrome DevTools Protocol）。它們在訓練過程中已經讀過數百萬個 `Page.navigate`、`DOM.querySelector` 和 `Runtime.evaluate` 的 token。

![](https://pub-75d4fe1e4e80421b9ecb1245a7ae0d1a.r2.dev/curated/1776994518263-iaHGmtEbabEAAv1QAjpg.jpg)

CDP 是 Chrome 暴露出的最底層介面。直接把它交給模型：

- **跨來源 iframe**：直接附加到目標，無需對抗任何框架抽象層。
- **Shadow DOM**：像模型過去見過無數次那樣，直接遍歷 `shadowRoot.querySelectorAll`。
- **反機器人檢測**：這本質上是 Chrome 在與自己對話。

## 我們錯在哪裡

幾個月前，我們在這個部落格寫了《更接近底層：從 Playwright 轉向 CDP》（Closer to the Metal: Leaving Playwright for CDP）。那篇文章的結論是：「我們的 Agent 不應該為了完成工作，而去了解 CDP Target 的細節。」

事實證明我們錯了。

那篇文章列出了 Chrome 分頁崩潰的十種方式。我們建立了監控服務來捕捉每一種情況——分頁崩潰、目標分離、渲染器記憶體溢出（OOM）、zygote 死亡、GPU 進程崩潰。每一個情況都有對應的處理程式，而每一個處理程式都必須與 Chrome 的內部機制保持同步。

只要給予 LLM 直接存取 CDP 的權限，並讓它有能力編輯自己的 harness，它就能自行處理這一切。分頁死掉、目標掛載錯誤、Chrome 卡住——Agent 會讀取錯誤訊息、重新掛載到新的目標、然後重試。它不需要監控服務，因為它已經讀過一萬篇關於 Chrome 崩潰的討論串，它早就知道該怎麼做。

我們試圖隱藏的那些「CDP 的複雜性」，根本不該被隱藏，而是應該讓模型直接看見。

## 四個檔案

這就是整個 harness 的全部內容：

- `run.py` (13 行) - 執行原生 Python 並預先載入輔助函式。
- `helpers.py` (192 行) - 對 CDP 的輕量封裝，且 Agent 可以自行編輯它們。
- `daemon.py` (220 行) - 保持 CDP websocket 連線存活。
- `SKILL.md` - 告訴 Agent 如何使用上述內容。

總共約 600 行程式碼。

![](https://pub-75d4fe1e4e80421b9ecb1245a7ae0d1a.r2.dev/curated/1776994518269-iaHGmtLq6awAA3yohjpg.jpg)

Agent 撰寫 Python 程式碼，Python 匯入這些輔助函式，輔助函式呼叫 CDP，Chrome 執行指令。Chrome 之上的所有東西都是可重寫的。

## 自我修復迴圈

當工具缺失時，會發生這種情況：

![](https://pub-75d4fe1e4e80421b9ecb1245a7ae0d1a.r2.dev/curated/1776994518250-iaHGmtOORboAAE4tKjpg.jpg)

當缺少某個輔助函式時，Agent 會做任何 Claude Code 使用者都會做的事：搜尋 `helpers.py`，新增該函式，然後重新執行。

我們並沒有教它這麼做。我們只是給了它 Claude Code 標準的「讀取/編輯/寫入」能力加上 CDP 存取權。程式開發 Agent 本來就知道如何修復缺失的匯入（import）。

關鍵在於：Agent 並不是從零開始撰寫新程式碼，它只是編寫了缺失的那一個函式，就像它在任何程式庫中修復缺失的匯入一樣。

## 魔法時刻

**檔案上傳**：我們忘了加入 `upload_file()`。任務執行到一半，Agent 遇到一個檔案輸入框，它搜尋了 `helpers.py`，發現沒有這個功能，於是使用原生的 `DOM.setFileInputFiles` 寫出了這個函式並上傳了檔案。我們是在閱讀 git diff 時才發現的。

**分塊上傳**：在寫完 `upload_file` 後，Agent 嘗試上傳一個 12MB 的檔案。CDP websocket 的封包上限大約是 10MB。它觸發了限制、讀取了錯誤訊息，隨即切換到分塊上傳模式。

**Gusto 到日曆**：任務是將每位員工的生日放入我們的共享日曆中。這需要導覽 Gusto 的員工頁面、從 DOM 提取日期，然後建立 Google 日曆活動。

**Azure 管理員角色**：Azure 的管理入口網站是一堆嵌入在 iframe 中的面板。透過座標級別的 `Input.dispatchMouseEvent`，原生 CDP 可以直接在合成器（compositor）層級進行操作。

## 親自試試

為 Claude Code 或 Codex 設定 Prompt：

```markdown
Set up https://github.com/browser-use/browser-harness for me.
```

第一個找到它無法完成的任務（非驗證碼/雙重驗證）的人，可以獲得一台 Mac Mini。認真的。我已經試著搞垮它一週了，但完全做不到。

儲存庫：github.com/browser-use/browser-harness

Agent harness 的苦澀教訓：你的輔助函式也是一種抽象層。刪掉它們，讓 Agent 自己寫出它需要的東西。

## 標籤

Agent, Skills, 教學資源, Agent
