Ian Chou's Blog

CtxFST CH14 - 讓 SKILL.md 真正驅動 Graph-Aware Agent Loop,並自動回寫 Graph

CtxFST CH14:讓 SKILL.md 真正驅動 Graph-Aware Agent Loop,並自動回寫 Graph

到了 CH13,我們已經證明了一件很重要的事:

CtxFST 不只是有 world model 的 spec,
它已經能跑出一個最小可用的 agent loop。

但如果你真的想讓它變成一個完整的 graph-aware agent runtime,還有最後一個關鍵步驟要補上:

SKILL.md 不只被讀來做決策,還要讓 skill 執行結果自動回寫成新的 state 與 graph edges。

這件事看起來像一個小補丁,其實不是。

它代表的是:

三者要正式接成一個閉環。

而這一章要講的,就是這個閉環如何成立。


先講最重要的一句話

很多人會把下面兩件事混在一起:

  1. SKILL.md 驅動 agent loop
  2. 把 skill 執行結果回寫到 graph 與 world state

它們很像,但不是同一件事。

更準確地說:

如果只做前者,agent 會選 skill,但世界不會變。

如果只做後者,世界會變,但 agent 不會依狀態做下一步規劃。

只有兩者一起成立,CtxFST 才會從「有 metadata 的 skill system」變成真正的 graph-aware world model loop


先拆開來看:這是閉環的兩半

可以先把它拆成這個表:

概念 方向 在做什麼 少了會怎樣
SKILL.md 驅動 planner Graph -> Agent preconditions / postconditions / cost,決定下一步 skill 只會有 metadata,沒人真的拿它來做決策
執行結果回寫 graph Agent -> Graph 執行後把新狀態、新邊、新紀錄寫回世界 skill 執行完就消失,不會推進下一輪

把這兩半合在一起,才會變成下面這個真正的 loop:

flowchart LR
    A[World State Graph
entities + states + edges] --> B[Planner / Selector] B --> C[Choose next SKILL.md] C --> D[Executor] D --> E[Write postconditions] E --> A

這張圖的重點只有一句:

Planner 讀世界,Executor 改世界。

少任何一邊,系統都不會真正前進。


CH13 做到了什麼?還差什麼?

回頭看 CH13,我們那時候已經有:

這兩個檔案解決了:

  1. 世界狀態如何表示
  2. 技能如何依 preconditions 被篩選

所以 CH13 其實已經做到了 loop 的前半段:

read state -> scan skills -> select next skill

但它還缺一個真正的 orchestrator,把下面幾件事串成一條線:

而這個缺口,現在就是由 agent_loop.py 補上。


agent_loop.py 到底補了什麼?

如果用一句最短的話講:

agent_loop.py 是把 world_state.pyskill_selector.py 接起來的 orchestrator。

它不是在重寫既有邏輯,而是在重用現有 API:

也就是說,它補上的不是新格式,而是 runtime glue。

這一點很重要,因為這表示整個 v2.0 設計不是一團耦合在一起的黑盒,而是:

這樣很乾淨,也很好測。


一張圖看懂三個 runtime 檔案的分工

flowchart LR
    A[world_state.py
load save add remove complete] --> C[agent_loop.py] B[skill_selector.py
scan parse rank select] --> C C --> D[Executor
dry-run interactive callback] D --> E[Postcondition writeback] E --> A E --> F[entity-graph.json
COMPLETED edges optional]

這張圖其實已經把 v2.0 的 runtime backbone 畫出來了。

三個檔案各自做的事情很清楚:

world_state.py

負責世界狀態的 CRUD:

skill_selector.py

負責 deterministic planning:

agent_loop.py

負責閉環 orchestration:

這一層一補上,系統就從「零件已備」變成「真的會跑」。


SKILL.md 在這裡到底扮演什麼角色?

到了這一章,SKILL.md 的角色就更清楚了。

它不是:

它現在已經是 planner 會直接吃進去的 action contract。

例如:

---
name: analyze-resume
description: "Parse raw resume and extract skill evidence"
preconditions:
  - "entity:has-raw-resume"
  - "NOT entity:has-parsed-resume"
postconditions:
  - "entity:has-parsed-resume"
  - "entity:has-skill-evidence"
cost: low
idempotent: true
---

這份 YAML 至少提供了四種 runtime 訊號:

  1. 能不能執行:看 preconditions
  2. 執行後會發生什麼:看 postconditions
  3. 值不值得先做:看 cost
  4. 可不可以重跑:看 idempotent

也就是說,從這一章開始,SKILL.md 已經不只是 human-readable skill card,而是 machine-usable action schema。


agent_loop.py 的三種 executor,很關鍵

這次我覺得設計得很好的地方,是 agent_loop.py 沒有把「執行 skill」硬寫死,而是切成了三種 executor 模式:

1. DryRunExecutor

用來模擬執行。

它會:

這很適合:

2. InteractiveExecutor

用來逐步人工確認。

每一步會停下來問你:

這很適合:

3. CallbackExecutor

這是最有擴展性的版本。

它允許你把一個外部 callable 包成 executor。

這代表未來你可以很自然地接:

這層抽象其實很重要,因為它讓 agent_loop.py 不會一開始就被某個 execution backend 綁死。


真正的閉環:postconditions 自動回寫 world state

現在來看最關鍵的一步。

agent_loop.py 裡,當一個 skill 執行成功或 partial success 時,loop 會做下面這些事:

  1. 取出 ExecutionResult.new_states
  2. 如果 executor 沒回傳,則退回使用 skill 本身的 postconditions
  3. 把這些 states 加進 active_states
  4. 記錄 complete_skill(...)

這代表:

postconditions 從靜態 YAML 欄位,變成真正會改變下一輪世界狀態的 runtime 指令。

這件事的意義非常大。

因為沒有這一步的話,postconditions 只是宣告。

有了這一步,它才是世界模型中的狀態轉移。


另一半:自動回寫 COMPLETED edges 到 graph

這次更進一步的地方,是 agent_loop.py 還支援把執行結果寫回 graph。

也就是當你提供 --graph entity-graph.json 時,loop 會在執行成功後 append 類似這樣的邊:

{
  "source": "analyze-resume",
  "target": "entity:has-parsed-resume",
  "relation": "COMPLETED",
  "score": 1.0,
  "shared_chunk_count": 0,
  "properties": {
    "timestamp": "2026-03-13T15:22:15.536270+00:00",
    "status": "active",
    "result_summary": "[dry-run] Would execute 'analyze-resume'"
  }
}

這裡的關鍵不只是「多一條邊」。

真正重要的是:

agent 的行為開始留下可查詢、可追蹤、可回放的 graph 痕跡。

這讓 graph 不再只是背景知識,而是 runtime 行為歷史的一部分。


為什麼 COMPLETED edge writeback 很重要?

因為 world state 和 graph 其實各自解不同問題。

world-state.json

適合回答:

entity-graph.json

適合回答:

所以最完整的設計不是二選一,而是兩邊都寫:

這兩層放在一起,系統才真的完整。


目前這個 loop 已經驗證了什麼?

根據目前的最小測試鏈:

這套 loop 已經驗證了幾件事:

  1. 可以從初始 state 出發,一步一步走到 goal
  2. skill eligibility 會隨 active states 改變
  3. postconditions 會真的累積進 state
  4. loop 可以在 goal 達成時停止
  5. graph 可以被自動寫入 COMPLETED edges

而且這不是理論上的設計,是真的已經有 runtime 與測試資料支撐:

這表示 CtxFST 的 world model loop 已經從:

變成:


這章真正回答了什麼問題?

我覺得這章最核心回答的,其實不是「怎麼寫一個 loop」。

而是這句話:

什麼時候 SKILL.md 才算真的驅動了 graph-aware agent loop?

答案就是:

  1. planner 真的讀它的 preconditions 來決定下一步
  2. executor 真的把它的 postconditions 寫回世界
  3. graph 真的留下 execution trace
  4. 下一輪規劃真的會受這些更新影響

只有這四件事都成立,SKILL.md 才不是裝飾,而是 agent runtime 的核心介面。


這件事為什麼比單純 GraphRAG 更進一步?

因為 GraphRAG 大多數時候還是在回答:

但到了這一章,問題已經變成:

也就是說,系統的核心任務從:

retrieval

進一步升級成:

stateful action planning

這就是 semantic world model 真正和普通 graph-enhanced retrieval 分道揚鑣的地方。


目前還缺的最後幾塊

這章雖然已經把閉環接起來了,但如果你想讓它更完整,還有幾個自然的下一步。

1. SKILL.md 真正的執行器

現在 DryRunExecutorInteractiveExecutor 已經很好用,但真正完整的版本,還需要:

2. 更豐富的 graph writeback

現在主要寫的是 COMPLETED edges。

未來還可以補:

3. goal-aware / graph-aware 排序

目前 selector 還是偏 rule-based baseline。

未來可以加入:

4. execution rollback

如果 skill 執行失敗,未來更完整的版本還可以處理:

這會讓 world model 更接近真實 workflow。


結語:從這一章開始,CtxFST 不只會想,還會留下行動痕跡

如果 CH13 的重點是:

CtxFST 已經能跑出最小 agent loop

CH14 的重點就是:

這個 loop 現在終於真正閉起來了。

因為到了這一章:

這就是我認為最關鍵的分水嶺。

從這一刻開始,CtxFST 不再只是能描述一個語意世界,也不只是能在這個世界裡挑下一步技能。

它開始具備了另一件更重要的能力:

agent 在這個世界裡做過什麼,會被正式寫回世界本身。

而這,才是真正的 graph-aware agent loop。


參考實作


📌 CH13 證明 world model loop 已經能跑,CH14 則補上最後一塊:讓 agent 的執行結果回寫成真正可查詢的世界狀態與圖邊。