Ian Chou's Blog

CtxFST CH11 - 從 v1.0 到 v2.0:把 Semantic Graph 升級成可操作的 World Model

CtxFST CH11:從 v1.0 到 v2.0,把 Semantic Graph 升級成可操作的 World Model

到第十章為止,我們已經把 CtxFST 做成一個很強的 GraphRAG 基礎層了。

它已經能做到:

如果你回頭看 CH10,那一章的核心突破其實很清楚:

系統已經不只是找相似段落,而是能沿著 entity graph,把使用者原本不知道的相關概念主動拉出來。

也就是說,到 CH10 為止,我們已經解決了這個問題:

哪些概念彼此相近?哪些 chunk 可以沿著圖被重新打撈出來?

但接下來自然會冒出另一個更大的問題:

如果 graph 已經不只是拿來檢索,那 agent 能不能直接把這張圖當成「世界模型」來行動?

這就是 CH10CH11 之間真正的分水嶺。

前一章回答的是:

這些東西彼此像不像?

這一章要往前推成:

如果我現在在這個圖上的某個位置,下一步最適合做什麼?

這一套很強,但它還主要是在回答一種問題:

哪些內容彼此有關?

如果你想再往前走一步,讓 agent 不只會「找資料」,還會「在語意世界裡採取下一步行動」,那只靠 v1.x 的 semantic graph 就不夠了。

這就是第十一章要做的事:

CtxFST 從 GraphRAG 的資料格式,升級成 agent 可以操作的 semantic world model。

更白話地說:


先講結論:v2.0 不是推翻,而是加一層

這章最重要的觀念只有一句:

CtxFST v2.0 不是把舊的 schema 打掉重練,而是在原本的 chunk + entity + graph 骨架上,再加一層 world model 語意。

所以它不是:

而是:

這樣一來,同一份資料可以同時支援兩種用途:

  1. 舊世界:GraphRAG / Hybrid Search / Entity Graph Expansion
  2. 新世界:Agent Planning / Skill Selection / World State Transition

這就是 v2.0 最重要的設計原則:嚴格超集(strict superset)


為什麼 v1.0-v1.3 還不夠?

前面的版本已經很適合做:

但如果你想讓 agent 根據當前狀態選 skill、執行 skill、再更新世界狀態,v1.x 會遇到四個明顯缺口。

Gap 1:Entity type 幾乎都是描述性的,缺少操作型節點

目前常見的 entity type 比較像這些:

這些 type 很適合描述世界裡「有什麼東西」,但不夠表達:

如果要做 world model,至少還需要這些節點型別:

type 用途 例子
state 世界狀態節點 state:resume-parsed, state:user-goal-defined
action 可執行動作節點 action:analyze-resume, action:career-mapping
goal 任務目標節點 goal:learn-kubernetes-path
agent 行動者節點 agent:ian-chou
evidence 證據或觀察結果 evidence:docker-3yr-experience

也就是說,v1.x 比較像在描述「地圖上的物件」,而 v2.0 必須開始描述「地圖上的狀態、動作與目標」。


Gap 2:邊幾乎只有 SIMILAR,但 world model 需要的是可轉移關係

在前面幾章,我們的 graph 很大一部分建立在 similarity 上:

這種邊非常適合做:

但它不夠回答下面這些問題:

所以 v2.0 至少需要擴充到這類 relation vocabulary:

relation 語意 例子
SIMILAR 語意相近 FastAPI -> Hono
REQUIRES 前置條件 Kubernetes -> Docker
LEADS_TO 後繼行動 / 因果 analyze-resume -> career-mapping
EVIDENCE 已觀察到的證據 agent:ian -> Docker
IMPLIES 邏輯蘊含 Docker -> containerization
COMPLETED 執行完成 agent:ian -> analyze-resume
BLOCKED_BY 被阻擋 deploy -> missing-cert

這裡的關鍵差別是:

SIMILAR 主要是語意導航。
REQUIRES / LEADS_TO / COMPLETED 才是世界狀態轉移。


Gap 3:SKILL.md 還是技能說明書,不是 graph-aware action contract

現在的 SKILL.md 比較像:

這已經很有用了,但對 world model 來說還少了一層:

也就是說,SKILL.md 需要從「文字技能說明」再升級成「可執行 action 的 declarative contract」。

最小版本可以長這樣:

---
name: analyze-resume
description: "Parse raw resume and extract skill evidence"
preconditions:
  - "state:has-raw-resume"
  - "NOT state:has-parsed-resume"
postconditions:
  - "state:has-parsed-resume"
  - "state:has-skill-evidence"
related_nodes:
  - "entity:resume-parsing"
  - "entity:skill-extraction"
related_skills:
  - "career-mapping"
  - "skill-gap-analysis"
cost: low
idempotent: true
---

這時候 SKILL.md 就不只是文件,而是 agent loop 裡的 action schema。


Gap 4:目前 pipeline 是靜態 export,不是 runtime state machine

這是最大的差別。

前面的 CtxFST pipeline 主要是:

.md
-> validate
-> export
-> profiles
-> entity graph

這是一個很乾淨的靜態流程,但 world model 還需要另一層:

換句話說,v1.x 主要在建「資料」,
v2.0 還要開始處理「執行中的世界」。

這一層不能只靠 frontmatter,本質上一定要有 runtime layer。


v2.0 的核心定義:Semantic Graph + World State + Skill Loop

如果用一句最短的話來定義 CtxFST v2.0,我會這樣寫:

CtxFST v2.0 = Semantic Graph 作為外部世界結構,SKILL.md 作為可執行動作集合,runtime state layer 作為世界演化記錄。

也就是這個結構:

[Semantic Graph] <-> [World State] <-> [Skill Selector] <-> [SKILL.md Execution]

如果把它畫成比較完整的架構圖,會像這樣:

flowchart LR
    A[CtxFST Documents
chunks + entities + context] --> B[Semantic Graph
Entity nodes + relation edges] B --> C[World State
goal + active states + completed skills] C --> D[Skill Selector
precondition matching + cost ranking] D --> E[SKILL.md Execution
run one action] E --> F[Execution Result
evidence + status + summary] F --> C F --> B G[Entity Embeddings
SIMILAR edges] --> B H[Manual or Runtime Edges
REQUIRES / LEADS_TO / COMPLETED] --> B I[SKILL.md YAML
preconditions + postconditions + related_nodes] --> D I --> E

這張圖剛好也可以拿來看 CH10CH11 的差別:

所以兩章不是斷開的,而是很自然地前後相接:

CH10 把圖建出來。
CH11 開始思考 agent 怎麼在圖上移動。

每一層負責的事情不同:

1. Semantic Graph

負責存世界的相對穩定結構:

這一層比較像地圖與規則骨架。

2. World State

負責記錄「這次 session 現在走到哪裡」:

這一層比較像遊戲存檔。

3. Skill Selector

負責根據當前 world state 去篩選:

這一層不一定要一開始就用 LLM,rule-based 其實就能先跑起來。

4. SKILL Execution

真正執行 action,執行後再更新世界。

例如:

  1. analyze-resume
  2. 執行 skill
  3. 產生新 evidence
  4. state:has-parsed-resume 寫回 world state
  5. 解鎖下一批可執行 skill

這樣就形成了一個簡化版的 world model loop。


v2.0 的 schema 該怎麼升級?

最重要的是:結構不重排,只做 additive upgrade。

1. schema.json:擴充 entity types,不破壞既有形狀

entities[] 的 shape 不用改掉,還是維持:

type 的 enum 需要擴充:

[
  "skill", "tool", "library", "framework", "platform",
  "database", "architecture", "protocol", "concept",
  "domain", "product",
  "state", "action", "goal", "agent", "evidence"
]

除此之外,entity 可以新增 optional 欄位:

這幾個欄位都應該是 optional,不能讓舊文件瞬間失效。

2. chunks[]:補一個 state-aware 錨點欄位

例如新增:

用途是讓某些 chunk 能明確掛到世界狀態,例如:

這對之後做 state explanation 或 world-state-grounded retrieval 很有幫助。

3. entity-graph.json:從單一 similarity graph 升級成多關係 operational graph

現在的 graph 可以繼續保留,但 edge schema 應該擴充成:

{
  "source": "entity:docker",
  "target": "entity:kubernetes",
  "relation": "REQUIRES",
  "score": 0.91,
  "properties": {
    "timestamp": "2026-03-13T10:00:00+08:00",
    "confidence": 0.92,
    "result_summary": "Docker experience unlocks Kubernetes learning path",
    "status": "active"
  }
}

注意這裡的設計重點:


build_entity_graph.py 應該怎麼改?

我不建議一開始就讓它自動「學因果」。

這一點非常重要。

因為 similarity 跟 causality 不是同一件事。

所以更穩的做法是:

  1. build_entity_graph.py 自動產生 SIMILAR
  2. 額外提供 --extra-edges 參數
  3. 讓手動定義或上層 runtime 產生的 REQUIRES / LEADS_TO / EVIDENCE 邊 merge 進同一份 graph

也就是說:

相似關係可以自動學。
因果與操作關係,先由人或上層 policy 明確定義。

這是我覺得最實際的 v2.0 路線。


為什麼我不建議一開始就自動推斷因果邊?

因為你會很快掉進「把相關當因果」的坑。

例如:

如果這些邊一開始就由模型自己亂長,agent 在上面規劃時會非常難 debug。

所以比較好的順序是:

Phase 1

先做成「手動 + runtime 更新」版本:

Phase 2

等你累積了一批穩定邊與 execution logs,再讓系統做:

這樣的半自動策略,比一開始就讓模型亂補世界規則安全得多。


SKILL.md 在 v2.0 的地位會變得非常關鍵

到了 v2.0SKILL.md 不再只是 agent 的說明文件。

它開始變成這三件事的交集:

  1. Action spec
  2. Policy hint
  3. World transition contract

也就是說,SKILL.md 一方面還保留人類可讀的操作說明,另一方面又能被機器當成:

這裡我會建議把 SKILL.md 裡的 world model 欄位定成 optional,但明確文件化。

例如:

欄位 型別 用途
preconditions string[] 哪些 state entity 必須先存在
postconditions string[] 執行後要建立或更新哪些 state
related_nodes string[] skill 在 semantic graph 的錨點
related_skills string[] 前後相接或互補的 skill
cost low | medium | high 給 selector 排序用
idempotent boolean 是否安全重跑

這樣未來上層 orchestrator 或 agent runtime 就能很自然地做:

read current world state
-> filter skills by preconditions
-> rank by cost / expected gain
-> execute one skill
-> write back postconditions

v2.0 一定要補一個 runtime state layer

這層我認為是這次升級真正的核心。

因為沒有 runtime state,你永遠只是在看一張靜態圖,而不是一個會演化的世界。

最小可用的 world-state.json 可以長這樣:

{
  "session_id": "uuid",
  "goal": "goal:learn-kubernetes-path",
  "active_states": [
    "state:resume-parsed",
    "state:has-skill-evidence"
  ],
  "completed_skills": [
    {
      "skill": "analyze-resume",
      "timestamp": "2026-03-13T10:00:00+08:00",
      "result": "success",
      "summary": "Extracted Docker and backend API evidence from resume"
    }
  ],
  "current_subgraph": {
    "nodes": ["entity:docker", "entity:kubernetes"],
    "edges": [
      {
        "source": "entity:docker",
        "target": "entity:kubernetes",
        "relation": "REQUIRES"
      }
    ]
  }
}

這份檔案的價值在於,它把「知識圖」和「當前任務進度」真正接起來了。


我會怎麼拆實作:library + CLI 雙模式

如果真的要做 v2.0,我很建議不要只做 shell script。

最穩的切法是:

例如:

world_state.py

提供可重用 API:

然後最下面再留:

if __name__ == "__main__":
    main()

這樣你同時得到兩個好處:

skill_selector.py

我也不建議一開始就把 LLM 綁死在這一層。

比較好的設計是:

然後真正要不要用 LLM 做更高階規劃,留給上層 agent loop。

這樣底層保持:


這會不會破壞原本的 GraphRAG?

這是最重要的相容性問題。

答案是:不會,只要你把 v2.0 做成嚴格超集。

也就是要守住四個原則。

原則 1:不要改掉既有欄位的語意

下面這些核心欄位都不要動:

只要這些維持原樣,現有的 hybrid RAG pipeline 就能繼續跑。

原則 2:新欄位全部 optional

像是:

都應該先做成 optional。

即使 spec 文案說「推薦」,schema 也不應該立刻把它們變成 required。

原則 3:保留 SIMILAR

舊的 GraphRAG 很可能只在乎:

那就讓它繼續這樣用就好。

新的 relation 只是多出來,不應該讓舊流程壞掉。

原則 4:保留 v1-style export

舊文件在完全沒有 world model 欄位的情況下,也應該能輸出跟今天幾乎一樣的:

只要做得到這件事,v2.0 就是一次健康升級,而不是破壞性重構。


所以 v2.0 實際上要改哪些檔?

如果用工程角度來看,我會把它切成下面幾塊。

檔案 改動量 要做什麼
schema.json entity type enum 擴充,新增 world model optional 欄位
ctxfst-spec.md 規格升級到 v2.0,補 layer matrix、relation vocabulary、migration notes
entity-graph-schema.json 正式定義多 relation 的 graph schema
world-state-schema.json 定義 runtime session state 的格式
SKILL.md 新增 graph-aware YAML fields 說明
export_to_lancedb.py 透傳新欄位
build_entity_profiles.py 讓 action/state/goal 類型進 representation text
build_entity_graph.py 保留自動 SIMILAR,支援 --extra-edges merge
validate_chunks.py 驗證新的 optional 欄位格式
world_state.py runtime state manager
skill_selector.py skill candidate filtering 與排序

你會發現,真正大的新增其實只有兩塊:

因為這兩個檔案代表的是從「靜態資料格式」進入「執行中系統」。


一個最小可跑的 v2.0 POC 長什麼樣?

如果你只想先做最小可用版,我會建議先跑這個 loop:

Step 1:建立 graph

Step 2:初始化 world state

goal: learn-kubernetes-path
active_states: [state:has-raw-resume]
completed_skills: []

Step 3:掃描 skills

讀所有 SKILL.md 的 YAML header,找出:

Step 4:執行一個 skill

例如執行 analyze-resume

Step 5:更新世界

把執行結果寫回:

Step 6:進入下一輪

這時候下一輪可用的 skill 可能就變了,例如:

這樣就形成一個真正可運作的 agent planning loop。


這個升級真正改變了什麼?

如果只用一句話講:

前面的 CtxFST 主要在回答「知識怎麼組織」。
v2.0 開始回答「agent 接下來該做什麼」。

這個差別非常大。

v1.x

你有的是:

v2.0

你開始多出:

所以它不是單純多幾個欄位,而是系統定位改變了:

從 GraphRAG document format,升級成 agent-operable semantic operating layer。


我的建議:v2.0 要先 symbolic,再逐步引入 learned world model

最後講一個最實務的建議。

很多人一看到 world model,就會很想立刻做:

這些不是不能做,但不適合當第一步。

CtxFST 來說,比較合理的順序是:

  1. 先把 symbolic world model 打穩
  2. 先讓 state / action / goal / evidence 有清楚表示
  3. 先讓 SKILL.md 有 pre/postconditions
  4. 先讓 runtime state manager 跑起來
  5. 先讓 deterministic selector 能選 skill
  6. 最後再用 learned model 去建議邊、預測結果、做更強規劃

這樣你的系統會有一個很大的好處:

即使沒有最花俏的模型,它也已經能工作。

而且每一層都容易 debug。


結語:v2.0 的真正意義,不是更複雜,而是更可行動

如果回頭看整個系列:

所以這章最重要的結論不是「多了幾個 enum」或「多了幾種 edge relation」。

真正的重點是:

CtxFST v2.0 讓 semantic graph 不再只是用來檢索的知識地圖,而是變成 agent 可以在其中採取行動、更新狀態、逐步推進目標的世界模型骨架。

也因此,v2.0 的最佳設計不是推翻 v1.x,而是保留原本對 GraphRAG 很有價值的 chunk/entity/similarity 能力,再往上加一層:

當這一層補上去之後,CtxFST 就不只是「會切 chunk 的格式」,也不只是「會建 entity graph 的格式」。

它會開始變成一個更有野心的東西:

一個讓語意圖、agent skills、world state 三者真正接起來的操作介面。

這,才是我認為 CtxFST v2.0 最值得做的地方。


📌 CtxFST v2.0 的核心不是讓圖更大,而是讓圖可以被行動。