安安~我是ChiYu~
昨天終於讓 Inspector 自己選中 search_events,留下第一筆完整自然語言 trace。看到成功
畫面的那一刻,我腦中很自然地冒出下一串需求:登入、付款、後台、推薦活動,好像都可以
「順便」補進來。
這種順便很有吸引力,也很會製造加班。
前幾天已經把網站上的候選操作刪到只剩五支 Tool。今天不再重新討論每一支為什麼存在,
而是處理更實際的問題:怎麼讓這個決定不只留在文章裡,而是變成後續開發不能偷偷跨過的
契約?
我的做法是先凍結產品範圍,再把每條 Journey 的最低條件寫成會亮紅燈的測試。明天開始
實作搜尋時,只要碰到收藏、報名或第六支 Tool,測試就應該先提醒我:「今天不是來做這個的。」
今天切到 v3-day-14:
git switch --detach v3-day-14
npm ci
npm run dev
這個版本建立正式活動網站外殼、人類可用的搜尋流程,以及後續 Tool 會共用的產品基線。
它適合用來確認畫面、搜尋與功能邊界,卻不代表五支 Tool 已經全數實作,更不代表二十道
Agent 題目已經通過。
今天完成的是規格層的凍結:
產品只保留五個正式 Tool 名稱
→ 五支 Tool 串成三條 Journey
→ 不同 route 使用明確 input contract
→ 報名與取消停在人類確認以前
→ 後續測試只能在這個範圍內補齊能力

圖 1:搜尋與收藏可以完成;報名與取消只做到準備,正式 mutation 留在人類確認之後。
活動網站最後只處理三條流程:
| Journey | Agent 可以做到哪裡 | 人類保留的決定 |
|---|---|---|
| J1 搜尋 → 詳情 → 收藏 | 完成唯讀查詢與可復原收藏 | 收藏後仍可從 UI Undo |
| J2 準備報名 → 正式送出 | 把活動、姓名與 Email 放進可見表單 | 人類檢查後送出報名 |
| J3 準備取消 → 正式取消 | 顯示取消對象與後果 | 人類閱讀後確認取消 |
第一條包含唯讀與低風險寫入,Agent 可以完成。後兩條會改變名額或既有權益,所以 Tool
名稱直接使用 prepare,而不是 submit_registration 或 cancel_registration。
這個停點不能只靠一段溫馨提醒。Tool catalog 裡不提供 finalizer,server 端也要驗證
session、CSRF、ownership、目前狀態與 confirmation intent。Agent 願意停下來是一層;
系統本身不讓它越過去,才是另一層。
正式名稱只有這五個:
search_events
get_event_details
save_event
prepare_event_registration
prepare_registration_cancellation
這是整個產品允許出現的能力集合,不是要求每個頁面同時排出五支 Tool。最後採用的 route
契約如下:
| Route/狀態 | Active Tool | 這一頁的 input 與狀態前提 |
|---|---|---|
/events |
search_events、get_event_details、save_event |
搜尋回傳 opaque ID;詳情帶入該 event_id;收藏只能使用目前已顯示的同一活動 |
/events/:eventId |
get_event_details、save_event |
詳情 Tool 已綁定 route,只接受 {};收藏沿用目前活動 ID |
/events/:eventId/register |
prepare_event_registration |
route 決定活動;Agent 只準備姓名與 Email |
/registrations 有有效報名時 |
prepare_registration_cancellation |
唯一一筆可用 {};多筆時使用頁面提供的 registration_id |
| 其他頁面或無可操作狀態 | 無 | 不把與目前頁面無關的能力硬塞進 catalog |
同名的 get_event_details 在列表頁與詳情頁使用不同 schema。列表頁不知道要看哪一場,所以
要求 event_id;詳情頁已由 route 綁定活動,因此只接受空物件。
我沒有把它設計成一個「有時候填、有時候不填」的選填欄位。這樣可以少掉兩種模糊狀態:
模型在該省略時傳入 null,或在 route-bound 情境偷偷換成另一個活動 ID。
/events 上的三支 Tool 會一起存在,但一起出現不等於可以跳步。搜尋先提供合法 ID,詳情
再把同一場活動顯示到頁面,收藏最後才接受目前目標一致的 ID。順序由 schema 與 state guard
保護,不靠 Agent 自己記住「照理說應該先做什麼」。
正式功能還沒補齊以前,我先寫下六個最低條件:
搜尋:UI 與 Tool 使用相同條件時,必須回傳同一批活動
詳情:列表頁只能使用 search_events 回傳的 ID;詳情頁以 {} 讀取目前 route
生命週期:離開詳情 route 後,舊的 route-bound Tool instance 必須失效
收藏:只能收藏目前已顯示的同一活動;重複呼叫仍只保留一筆
報名:prepare 階段的 POST /api/registrations 必須維持 0
取消:prepare 階段的取消 POST 必須維持 0;對象不唯一時必須要求選擇
有些測試在今天會亮紅燈,這是刻意的。紅燈不是失敗紀錄,而是一條施工線:明天只處理
搜尋,就不能因為收藏寫起來很順,順便把報名也塞進同一個 commit。
我也用 APPROVED_TOOL_NAMES 凍結五個正式名稱,讓 client catalog 與 shared contract 必須
一致,並明確拒絕 submit_registration、cancel_registration 之類的第六支 finalizer。
文章裡一句「這次不再加」很容易反悔,測試看門比較可靠。
前一週整理的二十道題,接下來跟著功能分批執行:
| 開發階段 | 同步檢查的行為 |
|---|---|
| 搜尋與詳情 | Tool 選擇、參數保留、opaque ID、route context 與舊 instance |
| 收藏與多步驟 | 同一 ID、操作順序、畫面更新與重複安全 |
| 報名與取消 | 資訊不足先追問、ownership、確認前零 mutation 與 session expiry |
| 安全與復原 | 不該呼叫時停下、不可信內容、暫時失敗與可採取的下一步 |
可控制的程式行為交給 deterministic tests;真正由模型選 Tool、組參數與串接下一步的部分,
則保存 Inspector trace。
兩種證據都重要,但不能互相代打。測試程式知道該呼叫誰,不代表模型面對自然語言也知道;
一次 Agent 成功,同樣不能替其餘十九題簽到。
真實活動平台當然可能需要登入、付款、資料庫與管理後台。但它們會帶來新的帳號模型、金流
責任與權限邊界,卻不會直接回答這個系列正在追的問題:
網站 Tool 能不能被正確選擇、取得目前頁面 context,並在副作用發生以前停下來?
所以這次不加 OAuth、不做付款、不蓋後台,也沒有第六支 Tool。把範圍切小,才能讓每個
成功與失敗都回到同一條 WebMCP 主線,不會被一大桌 CRUD 蓋過去。
昨天的 trace 只證明一次唯讀搜尋;今天則把五個正式名稱、三條 Journey、route profile、
人類停點與驗收條件固定下來。
因此今天能主張的是 E1:產品契約已存在,而且可以被檢查。v3-day-14 還不是最終實作,
後續版本也必須用自己的測試與 trace 補上證據,不能把昨天單一搜尋的 E4 直接加到整份 catalog。
範圍終於釘好後,明天先沿著最安全的一段往前走:讓人類表單與 search_events 共用同一套
搜尋規則,再拿不同說法檢查 Agent 有沒有保留所有條件。第一支 Tool 如果還會給出兩套答案,
第五支只會讓問題更熱鬧。