八篇走完,我們把一個 AI Agent 的 harness 從第 0 層拆到第 7 層,最後停在一台會自己轉的機器上。系列本來就收官了——這篇是加映,因為原書在 2026-07-30 又長出了一個新章節。

Layer 7 原本只有一個 section(21 · Loop engineering,也就是第 8 篇的內容)。作者在 7/30 補上了 22 · Graph engineering,34 個檔案、+2,871 行,隔天 merge 進 main。它開頭第一句話是:「第 21 章在一個 agent 外面堆疊 loop。這一章整理的是 model 呼叫和呼叫之間的流程。」——擺明就是第 8 篇的續集,那就把它補完。

一句話講完這章在幹嘛:別再問 model 下一步跑什麼。把你已經知道的流程寫進程式碼,model 只用在需要判斷的地方。

你早就知道流程長怎樣,卻每次都讓 model 重新猜一遍

很多任務的流程,你在還沒呼叫 model 之前就已經知道了:先分類工單再處理、先 review diff 再 commit、先拿到核准才做對外的動作。這些不是「要不要這樣做」的選擇題,是規定。

但普通的 agent loop 不知道這件事。它每跑一次,都要問 model「下一步做什麼」,把一套你早就背得出來的流程重新摸索一遍。代價有三個:慢(多一趟 round trip)、貴(連 routing 都要算 token)、而且每次跑都不一樣(同樣的輸入,這次先 review、下次先 commit)。

Graph engineering 的處方很直接:把已知的流程用程式碼寫成一張有向圖(directed graph),只有真的需要判斷的地方才留給 model。

四個零件:node、edge、cycle、state

可以先拿捷運路網圖來對。node 是站、edge 是軌道、cycle 是環狀線,而 state 是你手上那張一路刷過去的悠遊卡——每站都讀它,也都往上寫東西。

  • Node 負責做事。 一個 node 可以是純程式碼、一次 model 呼叫,或一整趟 agent 執行。
  • Edge 決定下一個 node。 由 harness 用程式碼判斷,不用 model 呼叫。
  • Cycle 是允許的。 重試、review 後修改、人工暫停,都需要一條往回走的路。
  • State 是一筆在圖上流動的紀錄。 每個 node 讀它,再把自己的更新寫回去。

第 8 篇那個驗證 loop,其實就是這種圖的最小版本:兩個 node(worker 和 checker)加一條往回的 edge。這一章做的事,是把「兩站的環狀線」擴展成一整張路網。

整套機制只有 12 行

最簡單的版本只需要三樣東西:一個 dict 把 node 名稱對到要跑的函式,另一個 dict 記著每個 node 跑完接誰,再加一個一路傳下去的 state。

1
2
3
4
5
6
7
8
9
10
11
12
def run_graph(nodes, edges, state, start, budget=20):  # src/graph.py
state = dict(state)
trace = []
node = start
for _ in range(budget): # 上限:由 harness 強制執行
state.update(nodes[node](state) or {}) # node 只回傳自己改動的部分
trace.append(node)
step = edges.get(node, END)
node = step(state) if callable(step) else step # 程式碼判斷的 edge:不花 model 呼叫
if node == END:
return {"ok": True, "state": state, "trace": trace}
return {"ok": False, "state": state, "trace": trace} # budget 用完 → 交給人接手

四個值得看的細節:

  • nodes 是一張 dispatch map,就是第 2 篇那個工具分派表的同一招。node 讀 state,只回傳自己改動的 key,不是整份紀錄。
  • edge 有兩種寫法:固定的 node 名字(決定性的),或一個吃 state 的函式(條件式的)。兩種都是 harness 用程式碼判斷,routing 不花任何 token。
  • 沒有 edge 的 node 就是圖的終點。budget 是第 8 篇那個上限:cycle 撞到上限就停,回傳 ok: False 交給人。
  • trace 依序記下跑過哪些 node,就是這趟執行留給可觀測性的紀錄。哪些 edge 從來沒 fire 過,一看就知道——那是死掉的分支。

真正該停下來看一眼的是這行:node = step(state) if callable(step) else step。整章的重點都在這裡——決定下一步的是 Python 的三元運算子,不是一次 API 呼叫。

Node 的光譜:從純程式碼到一整隻 agent

每個 node 都在「純程式碼」和「完整 agent」之間挑一個位置站:

類型 是什麼 判斷力 成本
Code node 解析、驗證、固定的 API 呼叫 決定性的,沒有判斷 不花 token
Model node 一次 LLM 呼叫,例如分類器 有限度的判斷 一次呼叫
Agent node 一整個第 2 篇的 agent loop,帶著 tool 開放式判斷,但被釘在一個位置上 一整趟執行

Agent node 是這三個裡面最有意思的。agent_node 把內層 loop 掛成圖上的一個 node,每次經過都用 state 組出 prompt,在全新的 messages[] 上跑一次 run_turn

1
2
3
4
5
6
7
8
def agent_node(build_prompt, model, registry, key="output"):
# 每次經過都用全新的 messages[] 跑,所以這個 node 只看得到
# build_prompt 餵給它的東西,看不到整趟執行的全部脈絡
def node(state):
text = run_turn([{"role": "user", "content": build_prompt(state)}],
model, registry, Session(mode=DEFAULT))
return {key: text}
return node

這個「只看得到別人餵給它的部分」正是第 3 篇 subagent 的 context 隔離,換個地方用而已。整章新增的元件其實只有 edge map 一個,其餘全是前面幾層零件的重新排列。

怎麼決定一個 node 要放在光譜的哪裡?原則只有一句:分支條件寫得出來的,就交給程式碼;model 呼叫只留給真的需要判斷的 node。

你叫得出名字的那些 workflow pattern,其實都是圖

  • Prompt chaining:一串 node 排成一條路,中間用程式碼把關。
  • Routing:一條條件式 edge,分流到各個專門的 node。
  • Parallelization:幾條同時跑的分支在一個 node 會合。可以是拆工作(sectioning),也可以是同一件事跑多次投票(voting)。
  • Orchestrator-workers:一個 node 在執行時決定要派出多少工作,再由另一個 node 收攏。edge 是動態的,但形狀仍然是圖。
  • Evaluator-optimizer:一個 worker node、一個 checker node,加一條往回的 edge——這就是第 8 篇的驗證 loop,放進圖裡就變成一個子圖。

這五個名字如果你讀過 Anthropic 那篇 Building effective agents 應該都不陌生。這一章的貢獻是指出它們不是五種各自獨立的架構,而是同一個資料結構的五種接法。

什麼時候不要畫圖

這節在原文裡是獨立的一節,我認為它比前面所有內容都值錢。

開放式的工作沒辦法預先定好流程。深度研究、難查的 bug,都得邊跑邊規劃;事先畫死的圖,反而擋住解法真正需要走的那條路。你以為自己在加結構,其實是在砍掉搜尋空間。

原書給的判準有兩條,都很緊:只把你本來就要強制執行的結構寫進圖裡(先分類再處理、先 review 再 commit、先核准再送出),而且只在確實改善結果時才加。其他一律交給普通的 loop,讓 model 自己規劃。

實務上最常見的是混合式:把 agent 當成固定圖裡的一個 node。圖負責保證 review 一定會發生,agent 負責決定在自己那一格裡怎麼把事做完。該硬的地方硬,該軟的地方軟:

1
2
3
4
5
6
7
8
9
10
11
12
nodes = {                                          # src/demo.py
"classify": lambda s: {"route": "math" if any(c.isdigit() for c in s["task"]) else "prose"},
"math": agent_node(prompt, model, math_reg), # 一整趟 agent 執行,當成一個 node
"prose": agent_node(prompt, model, Registry()),
"check": check_node, # 第 8 篇那個 checker,現在是一個 node
}
edges = {
"classify": lambda s: s["route"], # 程式碼判斷的 edge:選路不花 token
"math": "check",
"prose": "check",
"check": lambda s: END if s["verdict"]["passed"] else s["route"], # 往回的 cycle
}

四個 node、四條 edge,就把「先分類、分流給對的 agent、一定要過檢查、沒過帶著 feedback 繞回去」這套規矩焊死在程式碼裡。model 只在 mathprose 這兩格裡有發言權。

三套系統怎麼決定下一步

面向 Claude Code Hermes Agent mini-swe-agent
怎麼選路 階段之間用普通的 script 程式碼:條件、迴圈、pipeline、平行分派 model 用 tool call 選路,沒有寫在程式碼裡的 edge 一個固定的 cycle,跑到 model 提交或 budget 用完
node 是什麼 一個 node 一個 subagent,回傳通過 schema 驗證的結構化輸出 委派出去的 subagent,深度和並行數都有上限 兩個:一個 model step、一個 environment step
state 怎麼流 階段的回傳值往下傳;journal 記下每個 node 的輸出供續跑 結果經過 completion queue 回到呼叫端 message list 就是全部的 state
代價 圖活在單次執行的 script 裡,不是可重用的宣告式圖 routing 花 model 的 token,每次跑可能不一樣 所有任務共用同一個形狀,沒有分支可以特化

順帶一提,mini-swe-agent 是原書在 7 月下旬才加進來的第三個對照系統,前面幾篇還沒提到它。它的定位是 baseline:整張圖只有兩個 node 加一個固定 cycle,所有選擇都留在 model 裡。拿它跟 Claude Code 對照特別清楚——一個把編排全寫進程式碼,一個把編排全交給 model,中間那條光譜就是這一章在講的東西。

六個會翻車的地方

  • Model 當 router:把選路交給 model,燒 token、增加延遲,而且每次跑不一樣。最上游選錯一次,後面全部跟著錯。→ 轉移改用程式碼判斷,model 呼叫只留給需要判斷的 node。
  • 過度畫圖:需要探索的任務被固定的圖框住,解法要走的路被擋掉。→ 只把本來就要強制執行的結構寫進圖裡。
  • 沒有失敗的路:檢查用的 node 判了 FAIL 卻無路可送,爛輸出就一路流到下游。→ 每個檢查 node 都要配一條帶 budget 的往回 edge。
  • 沒有上限的 cycle:往回的 edge 沒有上限,就會永遠繞下去。→ harness 強制執行的 step budget,用完就交給人。
  • State 膨脹:每個 node 都把完整輸出倒進共用的 state,後面的 node 被淹沒。→ 嚴格的 state 邊界,node 只讀需要的子集、只回傳自己的更新(第 3 篇的 context 管理)。
  • 跑到一半掛掉:一張長圖在第七個 node 掛掉,重來卻從第一個 node 開始。→ 記下每個 node 的輸出,續跑時跑完的 node 從紀錄重放(第 45 篇)。

⚠️ 前兩個是選型錯誤,後四個是工程細節。真正讓專案翻車的通常是前兩個——它們在架構定案的那一刻就決定了,後面再怎麼補都補不回來。「這個流程到底該不該畫成圖」值得你在寫第一行程式之前先想清楚。

動手跑

1
2
3
4
5
6
# 離線檢查,不用金鑰:驗串接順序、state 合併、程式碼 routing、cycle 撞 budget 會停
python sections/22-graph-engineering/src/test.py

# 即時 demo,需要金鑰:code node 分類 → 程式碼 edge 選路 → agent node 作答
# → 第 21 章的 checker 評分 → 沒過就帶著 feedback 繞回去
uv run python sections/22-graph-engineering/src/demo.py

這章的 src/ 一樣是接著第 21 章長出來的,loop.py 完全沒動——什麼時候輪到它跑,改由圖決定。想確認「多一張圖」在程式碼裡長什麼樣,diff 這兩章的 src/ 就看得到,新增的實質上只有 graph.py 一個檔。

收尾

第 8 篇的結論是:別再想下一句 prompt 要打什麼,去設計那個不需要你也能把 agent 跑起來的 loop。這一篇是那句話的下半段——loop 設計好之後,下一個問題是「loop 和 loop 之間的順序,要交給誰決定」。

答案不是全部交給 model,也不是全部寫死。是把你已經知道的部分寫進程式碼、把你不知道的部分留給 model,然後很小心地別把「不知道」的那部分也一起寫死了。

原文與可執行程式碼在這裡:hardness1020/awesome-agent-architecturesections/22-graph-engineering/,有繁體中文版。這一章的三個出處也值得直接讀:LangChain · 3 years of graph engineering with LangGraphAnthropic · Building effective agentsGoogle · Why we built ADK 2.0