Ian Chou's Blog

CtxFST CH13 - 第一個可跑的 Agent Loop:用 world_state.py 與 skill_selector.py 驅動 World Model

CtxFST CH13:第一個可跑的 Agent Loop,用 world_state.pyskill_selector.py 驅動 World Model

如果說:

那這一章要做的事情就很直接:

證明這套東西不是只停在 spec 和 README,而是真的已經能跑。

而且不是「概念上能跑」,而是:

這代表什麼?

代表 CtxFST v2.0 已經不只是把資料切乾淨、圖建乾淨而已,而是已經有了第一個最小可用的 agent loop


這章要回答的問題

很多人在看到 World Model First 之後,直覺都會問:

好,那到底哪裡開始算「真的能動」?

答案就是這兩個 script:

它們的重要性很高,因為這兩個檔案剛好對應了 world model loop 裡最核心的兩件事:

  1. 世界現在處於什麼狀態?
  2. 在這個狀態下,下一個最適合執行哪個 skill?

一旦這兩件事能穩定跑起來,後面接 LLM、接工具、接 graph update 都只是往上疊而已。


先講結論:這兩個 script 已經是最小可跑版本

我先把結論講在前面:

world_state.pyskill_selector.py 已經構成了一個真正可執行的最小 agent loop。

不是半成品,也不是純草稿。

它們目前已經能做到:

world_state.py

skill_selector.py

也就是說,最基本的 selector loop 已經完整了。


這就是 CH12 的真正落地

回頭看 CH12,我那時候講的是:

CtxFST 不再只是資料格式,而是 agent 可操作的 semantic world model substrate。

這句話如果要落地,至少要有三樣東西:

  1. 一個 runtime state 表示層
  2. 一個 deterministic 的 skill selector
  3. 一個能讓 state 在 action 後改變的 loop

而現在這三件事其實都已經出現了:

這也是為什麼我覺得 CH13 很重要。

因為從這一章開始,你不再是在想像一個 world model,而是在操作一個 world model。


這兩個 script 在整個系統裡各自扮演什麼角色?

先用一張最簡單的圖來看:

flowchart LR
    A[world-state.json
current goal + active states] --> B[skill_selector.py] C[SKILL.md files
preconditions + postconditions + cost] --> B B --> D[selected skill] D --> E[skill execution] E --> F[world_state.py complete-skill / add-state] F --> A

這張圖其實已經很接近最小 agent runtime。

用白話講就是:

  1. 先看世界現在是什麼狀態
  2. 再看有哪些 skills 符合現在的狀態
  3. 挑一個最適合的
  4. 執行它
  5. 把結果寫回世界
  6. 再進入下一輪

這就是 loop。


Step 1:建立 world state

先從最基本的開始。

world_state.py 的第一步,就是初始化一個 session:

python3 scripts/world_state.py init \
  --goal "entity:learn-kubernetes-path" \
  --output /tmp/test-state.json

這一步會產生一份乾淨的 world state,大致長這樣:

{
  "session_id": "33018093-f029-47b2-8c01-f55928952cc1",
  "goal": "entity:learn-kubernetes-path",
  "active_states": [],
  "completed_skills": [],
  "current_subgraph": {
    "nodes": [],
    "edges": []
  },
  "created_at": "2026-03-13T15:21:48.851380+00:00",
  "updated_at": "2026-03-13T15:21:48.851407+00:00"
}

這個格式很重要,因為它把 agent loop 最需要的幾個 runtime 資訊正式獨立出來了:

也就是說,從這一步開始,世界已經不是只有靜態 graph,而是有了「這次任務的當前存檔」。


Step 2:啟動第一個 state

只有 goal 還不夠,agent 還需要知道目前有哪些條件成立。

例如先加入:

python3 scripts/world_state.py add-state \
  /tmp/test-state.json \
  "entity:has-raw-resume"

然後用:

python3 scripts/world_state.py show /tmp/test-state.json

你會看到像這樣的摘要:

Session:  33018093-f029-47b2-8c01-f55928952cc1
Goal:     entity:learn-kubernetes-path

Active states (1):
  ✓ entity:has-raw-resume

Completed skills (0):
  (none)

Subgraph: 0 nodes, 0 edges

這裡雖然還很簡單,但概念上已經非常關鍵了。

因為 agent 現在的判斷依據,不再只是「query 是什麼」,而是:

當前世界已經有哪些 state 被激活。

這正是 world model 和普通 prompt routing 的差別。


Step 3:準備兩個最小的 SKILL.md

接著準備兩個最小 skill:

skill A:analyze-resume

---
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
---

skill B:docker-overview

---
name: docker-overview
description: "Docker fundamentals overview"
preconditions: []
postconditions:
  - "entity:has-docker-knowledge"
cost: medium
idempotent: false
---

這兩個例子很小,但已經能展現完整邏輯:

這表示 SKILL.md 已經不是純說明文件,而是真的在提供 selector 可計算的 planning signal。


Step 4:跑 skill_selector.py

現在把 world state 和 skill directory 一起丟進 selector:

python3 scripts/skill_selector.py \
  /tmp/test-state.json \
  --skill-dir /tmp/test-skills/

這時 selector 會掃描所有 SKILL.md,檢查誰符合 preconditions,並依規則排序。

在這個例子裡,輸出結果的重點是:

為什麼?

因為它:

這裡最值得注意的,不是排序本身,而是:

這個決策完全不需要 LLM。

也就是說,CtxFST 現在已經有一個 deterministic、可測、可解釋的 planning baseline。

這非常重要,因為你不會想在最底層就把所有邏輯都做成黑盒。


Step 5:完成 skill,讓世界狀態改變

接下來才是這章最關鍵的一步。

如果 analyze-resume 執行成功,那世界應該要改變。

例如:

python3 scripts/world_state.py complete-skill \
  /tmp/test-state.json \
  --skill analyze-resume \
  --result success \
  --summary "Parsed 3 skills"

python3 scripts/world_state.py add-state \
  /tmp/test-state.json \
  "entity:has-parsed-resume"

python3 scripts/world_state.py add-state \
  /tmp/test-state.json \
  "entity:has-skill-evidence"

做完之後再 show 一次,就會看到世界狀態已經改變:

Active states (3):
  ✓ entity:has-raw-resume
  ✓ entity:has-parsed-resume
  ✓ entity:has-skill-evidence

Completed skills (1):
  ✅ analyze-resume [success] Parsed 3 skills

這個結果看起來很普通,但其實它代表了一件大事:

執行 skill 之後,世界不是只有多一段 log,而是真的多了新的可計算 state。

這就是 world model loop 成立的關鍵。


Step 6:重新選 skill,觀察 eligibility 變化

現在再次執行:

python3 scripts/skill_selector.py \
  /tmp/test-state.json \
  --skill-dir /tmp/test-skills/ \
  --auto

這時輸出的重點會變成:

為什麼 analyze-resume 被排除了?

因為它有一條條件:

NOT entity:has-parsed-resume

但前一步我們已經把 entity:has-parsed-resume 加進 active states 裡了。

所以這條 precondition 現在失敗,analyze-resume 就不再 eligible。

這件事非常關鍵,因為它證明:

postconditions 不是裝飾欄位,而是真的會改變下一輪 action selection。

這一步一成立,整個系統就從「規格上支援 world model」變成「行為上已經是一個 world model」。


這個最小 loop 為什麼意義重大?

因為它證明了三件事。

1. CtxFST 已經有 runtime memory

以前的 pipeline 比較像:

document -> chunk -> entity -> graph

現在多了:

world state -> skill selection -> execution -> state update

這代表系統第一次真的擁有:

這已經不是靜態資料工程了。

2. SKILL.md 已經是 machine-usable policy contract

以前講 SKILL.md,很多人會覺得它比較像人類可讀指南。

但在這個 loop 裡,它已經可以被機器直接拿來做:

所以它已經從文件變成一種 action schema。

3. agent loop 已經不必靠 LLM 才成立

這點我特別想強調。

現在很多 agent 系統最大問題是:

而這個最小 loop 告訴你:

即使完全不接 LLM,CtxFST 也已經能先跑出一個可測試、可除錯、可解釋的 planning loop。

這對真正要做工程的人,價值非常大。


這跟 GraphRAG 的關係是什麼?

這章也順便把一件事情釐清了:

GraphRAG 沒有消失,只是它現在變成 world model 的觀察層。

也就是說:

只是到了 v2.0 之後,graph 不再只負責「找回更多相關 chunk」,還可以進一步支援:

所以不是從 GraphRAG 跳到另一個完全不同的世界,而是:

把原本的 semantic graph,往可執行的 world state 系統再推進一步。


現在這個 loop 還缺什麼?

這章也要誠實一點。

雖然最小 loop 已經成立,但它還不是完整 agent。

目前還缺幾層更高階的能力:

1. skill 真正的執行器

現在的範例裡,我們是手動模擬 skill 成功後的狀態更新。

未來更完整的版本,應該要讓:

變成一條連續流程。

2. graph mutation 自動化

目前 world state 已經能更新,但 graph edge 更新還可以更進一步自動化,例如:

這樣 runtime state 和 graph 會更緊密連動。

3. goal-aware ranking

現在 selector 的排序規則主要是:

這已經很好,但未來還可以加入:

4. LLM 作為上層 planner

這是最後才要加的,不是最先加的。

因為比較好的切法是:

這樣系統才穩。


我會怎麼定義這一章的真正成果?

不是「多了兩個 script」。

而是:

CtxFST 已經第一次具備了從 state 出發、選 skill、執行、再回寫 state 的最小閉環能力。

這句話很重要,因為它說明了:

這些東西現在已經接成一個真正會動的 loop。


結語:從這一章開始,CtxFST 才真正像一個 agent substrate

如果回頭看整個系列的節奏:

所以這章最重要的意義不是「實作了一個 CLI」。

真正的意義是:

從這一章開始,CtxFST 不再只是能描述語意世界,而是已經能讓 agent 在這個世界裡做出下一步選擇。

而這,正是 semantic world model 和普通 GraphRAG 格式之間最本質的差別。


參考實作


📌 CH11 講 world model 的方向,CH12 講 World Model First 的重構,而 CH13 終於讓這個世界真的動了起來。