[AGENT] 14 分鐘閱讀OraCore 編輯部

Zero 把編譯錯誤變成 JSON

我拆 Vercel Zero 的 agent-first 編譯器設計,順手給你一份可直接抄走的結構化診斷模板。

分享 LinkedIn
Zero 把編譯錯誤變成 JSON

我拆 Vercel Zero 的 agent-first 編譯器設計,順手給你一份可直接抄走的結構化診斷模板。

我用一陣子編譯器,也看過一堆「AI 寫 code」demo,老實說,很多都卡在同一個爛點。模型寫完,工具跑完,編譯器吐一大串人類看得懂、機器看不懂的廢話,然後 agent 開始像熬夜第九小時的菜鳥一樣亂猜。不是模型笨,是我們一直拿給它一套給人類看的工具。人類可以掃一眼錯誤訊息、看 caret、自己腦補上下文;agent 不行,它只能硬把文字拆開再猜一次。我以前覺得這是模型問題,後來才發現,多半是介面設計太爛。

所以我看到 Vercel Zero 的介紹時,第一個反應不是「又一個語言」,而是「終於有人把底層假設講白了」。重點不是語法多漂亮,而是它把編譯器的第一個客戶改成 AI agent,而不是我。這件事聽起來很小,但其實很臭很真實:今天很多 agent 失敗,不是因為不會寫 code,是因為它們被迫讀一套人類中心的輸出格式。

這篇我不是在幫 Zero 做宣傳。它還很早期,資訊也很有限;我只是拿它當切入口,拆它背後的方法論。你如果在做 agent loop、CI bot、或任何需要自動修 code 的東西,這套想法其實很值得抄。

編譯器現在不是吐錯誤,是在跟 agent 送 API

訂閱 AI 趨勢週報

每週精選模型發布、工具應用與深度分析,直送信箱。不定期,不騷擾。

不會寄垃圾信,隨時可取消。

“The compiler does not print error messages — it emits structured JSON with stable error codes and typed repair IDs that an agent can act on without parsing English.”

翻譯一下就是:編譯器不再是對人類抱怨的公告欄,它變成 API 了。這個角度一換,很多以前看起來很怪的設計,瞬間就合理了。

Zero 把編譯錯誤變成 JSON

傳統流程是:模型寫 code,compiler 回一坨 prose,模型再去讀 prose、猜意思、決定修哪裡。這流程我自己手動看都嫌煩,何況是 agent。因為它要先判斷這是 syntax error、type error、依賴問題,還是剛剛那次修改造成的連鎖反應。只要 compiler 還在講人話,agent 就永遠得做翻譯工作

我之前在 TypeScript 跟 Rust 的自動修復 loop 裡真的踩過這種雷。模型看到一長串 compiler dump,會抓第一個「聽起來像答案」的建議去改,結果修錯地方還很有自信。那不是推理能力不夠,是輸入格式太吵。Zero 的核心主張就是:如果你想讓機器修 code,就別再丟給它一段 terminal 散文。

實操上,我會這樣做:把 compiler output 當成合約,不是當成 log。能改 compiler 的話,就直接定義 JSON schema;不能改,就在外面包一層 adapter,先把文字轉成結構化診斷,再餵給 agent。不要讓 agent 直接讀 stderr,真的很蠢。

  • 穩定的 code,比「看起來很像人話」的訊息有用太多。
  • typed repair ID 比「你可以試試看」這種建議更適合機器。
  • agent 能用欄位分支,就不要叫它讀段落。

穩定錯誤碼,比漂亮訊息更值錢

Zero 這類設計真正重要的,不是它把錯誤訊息寫得多精緻,而是它把錯誤碼固定住了。像原文提到的 NAM003 這種 code,如果跨版本還是同一件事,我就能把修復邏輯綁上去,不會因為下一版 wording 改了,整個自動化就失靈。

這點我一直覺得很多工具作者沒想清楚。人類可以容忍模糊語句,因為我們會自己補上下文;agent 不行。它不是「理解」文字,它是拿文字做分類、做決策、做 patch。只要輸出格式一飄,修復 loop 就很容易變脆。穩定 code 的價值就在這裡:它讓模型有固定錨點,不用每次都重新猜。

我看過 CI bot 自動修 build failure,結果 warning 文案一改,heuristic 就抓錯。最後 bot 去改錯檔案,還以為自己很聰明。那根本不是 AI 問題,是介面問題。Zero 的意思其實很直白:如果你要機器幫你修 code,就不要讓它從寫給終端機看的文字裡猜意思。

實操上我會建議你把 diagnostics 當 API version 來管。不要只印一句話,至少要有 code、severity、location、machine-actionable category。能穩定就穩定,因為 agent 需要的是可預期,不是文案美感。

  • 用 code 當 key,不要用 message 當 key。
  • diagnostic schema 要版本化,也要文件化。
  • 修復邏輯先查 code,再看 text。

typed repair ID 才是真正的修復語彙

原文提到 Zero 會附像 declare-missing-symboladd-capability 這種 typed repair ID。這個東西很關鍵,因為它把 compiler help 從一段描述,變成一個 enum。

Zero 把編譯錯誤變成 JSON

白話講,就是 compiler 不只是說「有問題」,它還告訴 agent「問題屬於哪一類修法」。這會大幅縮小搜尋空間。你不用叫模型從零發明修法,而是先把它丟進一個受限的動作集合裡。對 agent loop 來說,這差很多,因為每多一次瞎猜,就多燒 token、多花時間、還可能把 branch 弄壞。

我在 Rust 跟 TypeScript 的 agent 修復裡也看過類似狀況。像「consider adding a type annotation」這種話,對人類還行,對機器太鬆了,因為它可能代表三種不同改法。typed repair ID 不會消滅推理,但它會把起點縮小到一個可控範圍。模型還是要看 code,但它先知道自己是在修哪一類錯。

實操上,我會自己定一套 repair taxonomy,而且不要貪多。先做小而可執行的類別:missing import、wrong arity、capability absent、type mismatch、invalid config key。然後讓 agent 先根據類別分流,再決定要不要開檔。這種東西很 boring,但通常很有效。

如果你覺得這很像 lint rule,你沒看錯。差別在於 Zero 把這套修復語彙直接塞進 compiler contract 裡,不是事後補一層工具。這就是我在意的地方。

capability 讓副作用變得看得見

Zero 的 capability-based I/O model 也很有意思。函式如果沒拿到 capability,就不能做那個 effect。沒有隱藏 global,沒有 helper 背後偷跑副作用。

也就是說,signature 本身就在講實話。作為 agent,我不用猜這個函式會不會碰網路、改狀態、寫檔案。capability 直接寫在那裡。這能少掉一個很煩的失敗模式:模型以為某個 helper 是 pure 的,結果它其實會開 socket、寫磁碟、或打外部服務。

我自己在 code review 被這種事陰過,agent 也一樣。它看到一個很乾淨的 helper,就會假設它安全。結果下一秒它把資料寫進檔案、打到 service、或者偷偷改了狀態。人類靠經驗能抓到,agent 最好靠 compiler 直接提醒。

實操上,如果你在設計內部 API,最好把 side effect 明確化。可以放進 type system,也可以放進 function signature metadata。就算做不到那麼硬,也至少用 convention 把 I/O、network、mutation 包起來,別讓它們藏在一般 helper 裡。

這也是我覺得 Zero 不是單純語言新奇,而是設計壓力的展示。Rust、Go、TypeScript 都有一點這種味道,但 Zero 把話講得更直接:對機器來說,顯式 effect 不是加分題,是基本題。

結構化輸出,才是 agent loop 的正解

原文一直在對比現在這套 stack:agent 呼叫 rustc 或 tsc,然後再去 parse terminal text。這就是我最常看到的隱形稅。

翻譯一下就是,agent loop 每一步都在做轉譯:讀 source、跑 compiler、解析 prose、推理修法、改 source、再跑一次。每多一層轉譯,就多一個 drift 的機會。Zero 的做法,是先把 compiler output 變成 machine-readable,直接砍掉最大那層噪音。

我不會把這件事講得像魔法。結構化 diagnostic 不會幫你解邏輯 bug、不會補測試、也不會救爛架構。它只是讓 agent 不要被 compiler 的文案絆倒。老實說,光這樣就很值了。我寧可要一個無聊但可預期的修復 loop,也不要一個很聰明但常常瞎修的系統。

實操上,我會建議你把整條工具鏈都往 machine-readable 推。能吐 JSON 就吐 JSON,log 先 normalize,free-form suggestion 轉成欄位。如果你自己能控制 compiler 或 linter,就先把 machine interface 做好,再考慮 terminal 美化。順序真的有差。

  • machine-readable output 會直接降低修復歧義。
  • 漂亮的 terminal output 可以有,但只能當 view,不該是 source of truth。
  • 你少掉一個 parse step,agent 就少一個幻覺修法的機會。

Zero 不是重點,壓力才是重點

我不覺得 Zero 會取代 Rust、Go 或 TypeScript。原文也講得很清楚,它還是 experimental、版本也還在 0.1.2,現在拿去做 payments、auth、核心 infra,基本上是在賭命。我不會這樣幹。

但它厲害的地方在於,它把設計壓力直接攤出來。當一個語言把 structured diagnostics、stable repair IDs、explicit capabilities 當預設,舊工具鏈就會開始顯得有點鈍。不是爛,是舊。Rust 已經有 JSON error output,但還是 opt-in;TypeScript 有 compiler options 跟 error codes,但沒有 typed repair IDs;Go 的工具鏈在某些地方很結構化,可是它不是先把 agent 當第一使用者。Zero 其實是在提醒大家:現在要先問,這套東西是給誰看的?

實操上,我不會等新語言來救我。我會先把同樣的原則補進現有 stack:structured diagnostics、stable codes、explicit effects、repair categories。你如果在跑 Claude CodeCursor,或自己寫 agent loop,就盡量讓 compiler output 對模型來說比對人類更好吃。

這才是我從 Zero 拆出來最有用的地方:編譯器現在已經是 agent API 的一部分了。你一旦接受這件事,很多以前看起來很怪的工具選擇,就突然不怪了。

可抄的模板

# Agent-first compiler contract template

## 1) Diagnostic schema
{
  "ok": false,
  "diagnostics": [
    {
      "code": "NAM003",
      "severity": "error",
      "message": "unknown identifier",
      "line": 3,
      "column": 12,
      "repair": {
        "id": "declare-missing-symbol",
        "confidence": 0.94,
        "candidates": ["import-symbol", "declare-local", "rename-reference"]
      }
    }
  ]
}

## 2) Stable repair taxonomy
- declare-missing-symbol
- import-symbol
- add-capability
- narrow-type
- fix-arity
- remove-invalid-effect
- update-config-key

## 3) Agent loop rules
1. Call compiler with machine-readable output only.
2. Branch on diagnostic.code first.
3. Use diagnostic.repair.id as the primary fix class.
4. Never infer a repair from free-form prose if a structured field exists.
5. Re-run compile after each edit and stop on the first clean pass.

## 4) Capability-style API pattern
// bad: hidden side effect
fn write_report(data) {
  save_to_disk(data)
}

// better: explicit capability
fn write_report(data, io: FileIO) {
  io.save_to_disk(data)
}

## 5) Wrapper for existing tools
# Example: normalize human compiler output into JSON
compiler --format json 2>/dev/null | agent-normalizer

## 6) What to store in your playbook
- compiler version
- stable error codes
- repair categories
- known false positives
- safe auto-fix rules
- human escalation rules

## 7) Copy-paste policy for teams
If the tool can emit structured output, the agent must consume structured output.
If the tool cannot emit structured output, add a parser layer before the agent sees it.
If the repair is not backed by a code or typed category, do not auto-apply it.

這段不是 Zero 本身,也不是照抄原文;是我把它背後的方法論整理成你真的能拿去用的版本。你如果在做 agentic coding、CI 自動修、或內部工具鏈,這份模板可以直接開工。

來源我放在這裡:AI Automation Global 的 Vercel Zero 文章,以及參考對照的 VercelVercel GitHub orgRust JSON diagnosticsTypeScript docs。前面拆解是我自己的整理,模板是我根據這個方向重寫的實作版。