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。
這件事看起來像一個小補丁,其實不是。
它代表的是:
- planner 不再只是看 YAML 做靜態路由
- executor 不再只是跑完就算了
- graph 不再只是背景知識
三者要正式接成一個閉環。
而這一章要講的,就是這個閉環如何成立。
先講最重要的一句話
很多人會把下面兩件事混在一起:
- 讓
SKILL.md驅動 agent loop - 把 skill 執行結果回寫到 graph 與 world state
它們很像,但不是同一件事。
更準確地說:
- 前者是 Graph -> Agent
- 後者是 Agent -> Graph
如果只做前者,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,我們那時候已經有:
這兩個檔案解決了:
- 世界狀態如何表示
- 技能如何依 preconditions 被篩選
所以 CH13 其實已經做到了 loop 的前半段:
read state -> scan skills -> select next skill
但它還缺一個真正的 orchestrator,把下面幾件事串成一條線:
- 選 skill
- 執行 skill
- 寫回 postconditions
- 記錄 completion
- 視情況回寫 graph edges
- 再進下一輪
而這個缺口,現在就是由 agent_loop.py 補上。
agent_loop.py 到底補了什麼?
如果用一句最短的話講:
agent_loop.py是把world_state.py和skill_selector.py接起來的 orchestrator。
它不是在重寫既有邏輯,而是在重用現有 API:
- 從
world_state.py讀 state、寫 state - 從
skill_selector.py掃 skill、選 candidate、取 best skill - 再加上一層 execution protocol 與 loop termination 控制
也就是說,它補上的不是新格式,而是 runtime glue。
這一點很重要,因為這表示整個 v2.0 設計不是一團耦合在一起的黑盒,而是:
- 狀態管理一層
- skill 選擇一層
- loop orchestration 一層
這樣很乾淨,也很好測。
一張圖看懂三個 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:
- 建立 session
- 新增 active states
- 移除 states
- 記錄 skill 完成
- 顯示狀態摘要
skill_selector.py
負責 deterministic planning:
- 掃描
SKILL.md - 檢查
preconditions - 依
cost與postconditions數量排序 - 選出 best candidate
agent_loop.py
負責閉環 orchestration:
- 檢查 goal 是否已達成
- 挑下一個 skill
- 執行 skill
- 寫回新 states
- 記錄 history
- 視需要寫回
COMPLETEDedges - 決定何時停止
這一層一補上,系統就從「零件已備」變成「真的會跑」。
SKILL.md 在這裡到底扮演什麼角色?
到了這一章,SKILL.md 的角色就更清楚了。
它不是:
- 純文件
- 純註解
- 純示意 YAML
它現在已經是 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 訊號:
- 能不能執行:看
preconditions - 執行後會發生什麼:看
postconditions - 值不值得先做:看
cost - 可不可以重跑:看
idempotent
也就是說,從這一章開始,SKILL.md 已經不只是 human-readable skill card,而是 machine-usable action schema。
agent_loop.py 的三種 executor,很關鍵
這次我覺得設計得很好的地方,是 agent_loop.py 沒有把「執行 skill」硬寫死,而是切成了三種 executor 模式:
1. DryRunExecutor
用來模擬執行。
它會:
- 預設 skill 成功
- 自動套用
postconditions - 幫你驗證整個 loop 會不會跑通
這很適合:
- 測試規格是否合理
- 驗證 skill chain 是否能走到 goal
- 做 CI / regression test
2. InteractiveExecutor
用來逐步人工確認。
每一步會停下來問你:
- 要不要真的執行
- 跳過
- 或直接中止
這很適合:
- demo
- 本地探索
- 做半人工 agent workflow
3. CallbackExecutor
這是最有擴展性的版本。
它允許你把一個外部 callable 包成 executor。
這代表未來你可以很自然地接:
- 真正的工具調用
- 真正的
SKILL.md執行器 - LLM-based function runner
- 任何自訂 runtime
這層抽象其實很重要,因為它讓 agent_loop.py 不會一開始就被某個 execution backend 綁死。
真正的閉環:postconditions 自動回寫 world state
現在來看最關鍵的一步。
在 agent_loop.py 裡,當一個 skill 執行成功或 partial success 時,loop 會做下面這些事:
- 取出
ExecutionResult.new_states - 如果 executor 沒回傳,則退回使用 skill 本身的
postconditions - 把這些 states 加進
active_states - 記錄
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
適合回答:
- 當前有哪些 states 是 active 的?
- 哪些 skills 已完成?
- 這次 session 目前進度到哪?
entity-graph.json
適合回答:
- 哪個 skill 產生了哪些 state?
- 某類 completion 常常往後導向哪些路徑?
- agent 行為歷史在圖上長什麼樣子?
所以最完整的設計不是二選一,而是兩邊都寫:
world-state.json保留 session runtime snapshotentity-graph.json保留 graph-native execution trace
這兩層放在一起,系統才真的完整。
目前這個 loop 已經驗證了什麼?
根據目前的最小測試鏈:
analyze-resumematch-skillsgenerate-plan
這套 loop 已經驗證了幾件事:
- 可以從初始 state 出發,一步一步走到 goal
- skill eligibility 會隨 active states 改變
postconditions會真的累積進 state- loop 可以在 goal 達成時停止
- graph 可以被自動寫入
COMPLETEDedges
而且這不是理論上的設計,是真的已經有 runtime 與測試資料支撐:
這表示 CtxFST 的 world model loop 已經從:
- 「可以這樣設計」
變成:
- 「現在已經真的這樣運作」
這章真正回答了什麼問題?
我覺得這章最核心回答的,其實不是「怎麼寫一個 loop」。
而是這句話:
什麼時候
SKILL.md才算真的驅動了 graph-aware agent loop?
答案就是:
- planner 真的讀它的
preconditions來決定下一步 - executor 真的把它的
postconditions寫回世界 - graph 真的留下 execution trace
- 下一輪規劃真的會受這些更新影響
只有這四件事都成立,SKILL.md 才不是裝飾,而是 agent runtime 的核心介面。
這件事為什麼比單純 GraphRAG 更進一步?
因為 GraphRAG 大多數時候還是在回答:
- 哪些 chunk 和 query 有關?
- 哪些 entity 與某個概念相近?
- 應該沿著哪些邊擴展檢索?
但到了這一章,問題已經變成:
- 現在世界狀態是什麼?
- 哪個 skill 可以合法執行?
- 執行後世界會怎麼變?
- 這次行為要不要留下結構化痕跡?
也就是說,系統的核心任務從:
retrieval
進一步升級成:
stateful action planning
這就是 semantic world model 真正和普通 graph-enhanced retrieval 分道揚鑣的地方。
目前還缺的最後幾塊
這章雖然已經把閉環接起來了,但如果你想讓它更完整,還有幾個自然的下一步。
1. SKILL.md 真正的執行器
現在 DryRunExecutor 和 InteractiveExecutor 已經很好用,但真正完整的版本,還需要:
- 讀 skill 指令
- 執行實際工具
- 解析結果
- 自動產生
ExecutionResult
2. 更豐富的 graph writeback
現在主要寫的是 COMPLETED edges。
未來還可以補:
EVIDENCELEADS_TOBLOCKED_BY- state deactivation edges
3. goal-aware / graph-aware 排序
目前 selector 還是偏 rule-based baseline。
未來可以加入:
- 距離 goal 的 graph proximity
- current subgraph 覆蓋率
- skill 對特定 edge type 的推進效果
4. execution rollback
如果 skill 執行失敗,未來更完整的版本還可以處理:
- 哪些 states 要撤回
- 哪些 edge 要標記
failed - 哪些 path 暫時 blocked
這會讓 world model 更接近真實 workflow。
結語:從這一章開始,CtxFST 不只會想,還會留下行動痕跡
如果 CH13 的重點是:
CtxFST已經能跑出最小 agent loop
那 CH14 的重點就是:
這個 loop 現在終於真正閉起來了。
因為到了這一章:
SKILL.md真的被拿來做決策- 執行結果真的會改變
world-state.json - graph 真的會留下
COMPLETEDedges - 下一輪規劃真的會受前一輪影響
這就是我認為最關鍵的分水嶺。
從這一刻開始,CtxFST 不再只是能描述一個語意世界,也不只是能在這個世界裡挑下一步技能。
它開始具備了另一件更重要的能力:
agent 在這個世界裡做過什麼,會被正式寫回世界本身。
而這,才是真正的 graph-aware agent loop。
參考實作
📌
CH13證明 world model loop 已經能跑,CH14則補上最後一塊:讓 agent 的執行結果回寫成真正可查詢的世界狀態與圖邊。
- ← Previous
CtxFST CH13 - 第一個可跑的 Agent Loop:用 world_state.py 與 skill_selector.py 驅動 World Model - Next →
CtxFST CH15 - Goal-Aware Skill Routing:讓 skill_selector.py 真的朝目標前進