CtxFST CH15 - Goal-Aware Skill Routing:讓 skill_selector.py 真的朝目標前進
CtxFST CH15:Goal-Aware Skill Routing,讓 skill_selector.py 真的朝目標前進
如果說:
CH13證明了最小 agent loop 已經能跑CH14補上了 execution writeback,讓 loop 真正閉起來
那下一個很自然的問題就是:
現在 agent 會挑 skill 了,但它挑的是「最便宜的 skill」,還是「最接近目標的 skill」?
這兩者差很多。
因為如果 selector 只看:
costpostcondition_countname
那它其實還不知道 goal 在哪裡。
也就是說,它雖然會做 deterministic sorting,但還不算真正的 goal-aware planning。
這就是 CH15 要補的那一塊:
讓
skill_selector.py結合current_subgraph與 goal proximity,開始真的朝目標前進。
先講一句最短的定義
這次升級可以濃縮成一句話:
以前 selector 只知道哪個 skill 比較便宜;現在 selector 開始知道哪個 skill 離 goal 比較近。
這個變化看起來像只是排序 key 多一個欄位,但其實代表 planner 的性質變了。
以前它比較像:
rule-based skill picker
現在它開始更像:
graph-aware goal router
問題出在哪裡?舊排序其實不懂 goal
在加入 goal-aware routing 之前,skill_selector.py 的核心排序邏輯很單純:
candidates.sort(key=lambda c: (
COST_ORDER.get(c["cost"], 1),
-c["postcondition_count"],
c["name"],
))
這個排序其實很合理。
它偏好的候選 skill 是:
- 成本低
- 能產生更多 postconditions
- 名字排前面
但問題就在這裡:
它完全不知道 goal 在哪裡。
所以只要兩個 skill 都是 low cost,系統就會傾向挑 postconditions 比較多的那個,即使那些 postconditions 根本不在通往 goal 的路徑上。
這就是舊 selector 最大的盲點。
一個很白話的例子
假設現在 goal 是:
entity:goal
而 current_subgraph 長這樣:
entity:goal <-1 hop-> entity:mid <-1 hop-> entity:far
現在有兩個 skill:
skill-closecost = lowpostconditions = [entity:mid]
skill-farcost = lowpostconditions = [entity:far]
如果只看舊排序:
- 兩個都是
low - 兩個都只有 1 個 postcondition
最後只能靠名字決定。
這 obviously 不合理。
因為 entity:mid 離 goal 只有 1 hop,entity:far 卻有 2 hops。
如果 agent 真的是在朝目標前進,那應該優先選 skill-close。
這就是這次 goal-aware routing 想解決的問題。
這次到底加了什麼?
最核心的改動只有一個:
把 goal proximity 納入
select_candidates()的排序依據。
也就是新的排序邏輯變成:
cost
-> goal_proximity
-> postcondition_count
-> name
這裡的 goal_proximity 指的是:
某個 candidate skill 的 postconditions,離
state["goal"]最近的 hop 距離。
hop 越小,代表越接近 goal,排序就越前面。
這也表示:
cost還是重要postcondition_count還是有價值- 但現在真正拉開同 cost skill 差距的,變成「是否更靠近目標」
這比以前更像真正的 planner。
current_subgraph 在這裡終於真的派上用場
在前幾章裡,我們其實已經有 current_subgraph 這個概念了。
但老實說,在更早的版本裡,它比較像:
- 可用資訊
- 可以被讀
- 但還沒有真的進入 selector 的核心決策
這次改動之後,情況就不一樣了。
因為 skill_selector.py 會直接讀:
state["current_subgraph"]["edges"]
然後做兩件事:
- 建 adjacency map
- 從
state["goal"]做 BFS,算出每個 entity 到 goal 的 hop 距離
也就是說,current_subgraph 現在終於不再只是 runtime 附件,而是 selector 真正會用來決策的 graph context。
這一點非常重要。
因為從這裡開始,selector 不再只是讀 YAML,而是真的開始「看圖選路」。
BFS 怎麼算出 proximity?
這次實作的核心其實很樸素,但很有效。
做法大致是:
- 從
goal節點出發 - 沿著
current_subgraph.edges做 BFS - 算出每個 entity 的最短 hop distance
- 對每個 candidate skill,取其
postconditions中最小的 hop distance - 這個值就是該 skill 的
goal_proximity
所以如果:
entity:goal的距離是0entity:mid的距離是1entity:far的距離是2
那麼:
- 產生
entity:mid的 skill 會比產生entity:far的 skill 排得更前面
這種做法的好處是:
- 完全 deterministic
- 不需要 LLM
- 易懂
- 易測
- 很容易 debug
而且已經足夠讓 selector 開始出現「朝 goal 前進」的味道。
為什麼這件事比「postconditions 多寡」更重要?
因為 postconditions 多,不等於有用。
舉個簡單例子:
Skill A
cost = lowpostconditions = [entity:x, entity:y, entity:z]- 但這三個都離 goal 很遠
Skill B
cost = lowpostconditions = [entity:mid]- 但
entity:mid距離 goal 只差 1 hop
舊排序很可能會選 A,因為它 postconditions 比較多。
但真正有目標感的 planner,應該會選 B。
因為 world model 裡最重要的不是:
我一次產生多少 state
而是:
我有沒有把世界往 goal 推近
這就是 goal-aware routing 的本質。
這次實作有一個很好的細節:fallback 完全保留舊行為
我很喜歡這次設計的一點,是它沒有粗暴地破壞舊排序。
當 current_subgraph 是空的,或者某個 postcondition 根本沒有通往 goal 的路徑時,實作上會用一個 sentinel:
_UNKNOWN_PROXIMITY = sys.maxsize
這代表:
- 如果 proximity 可算,就按 hop 距離排
- 如果 proximity 不可算,就視為「無限遠」
這樣帶來一個很好的效果:
當 graph context 不足時,selector 會自然退回舊的
cost -> postcondition_count -> name行為。
也就是說,新功能不是硬覆蓋舊功能,而是:
- 有 graph 時,更聰明
- 沒 graph 時,不退化
這是很好的升級方式。
一個最小測試,剛好把這件事講清楚
這次驗證用的最小案例其實非常漂亮。
狀態
state = {
"goal": "entity:goal",
"active_states": ["entity:start"],
"completed_skills": [],
"current_subgraph": {
"nodes": ["entity:goal", "entity:mid", "entity:far"],
"edges": [
{"source": "entity:goal", "target": "entity:mid", "relation": "REQUIRES"},
{"source": "entity:mid", "target": "entity:far", "relation": "REQUIRES"},
]
}
}
候選 skills
skills = [
{"name": "skill-far", "preconditions": [], "postconditions": ["entity:far"], "cost": "low"},
{"name": "skill-close", "preconditions": [], "postconditions": ["entity:mid"], "cost": "low"},
]
算出的 hop distance
entity:goal -> 0
entity:mid -> 1
entity:far -> 2
最後排序結果是:
skill-close proximity=1
skill-far proximity=2
這正是我們要的行為。
因為它證明:
selector 已經會因為「更靠近 goal」而改變排序。
這代表 planner 變成什麼了?
我覺得這一步之後,skill_selector.py 的性質有了很明顯的提升。
以前它比較像:
- precondition filter
- rule-based ranker
現在它開始更接近:
- graph-conditioned selector
- shortest-path-aware router
- lightweight planner
雖然它還不是完整的 search-based planner,也還沒做到多步 lookahead,但它已經具備了一個很重要的特質:
它的排序邏輯開始對 world model 的拓樸結構敏感。
這就是從「會挑 skill」到「會挑方向正確的 skill」的差別。
這對 agent_loop.py 有什麼影響?
很直接。
因為 agent_loop.py 本來就是靠 select_candidates() 和 select_best() 來決定下一步。
現在每個 candidate 多了一個:
goal_proximity
所以 loop 可以自然得到幾個新好處:
- 選 skill 時更像在走向 goal,不只是貪便宜
- log 可以開始顯示 proximity,方便 debug
- 之後如果要做更強的 planner,也有一個很好的 baseline feature
換句話說,這次雖然只改了 selector,但受益的是整個 agent loop。
這是不是就等於「goal-aware skill routing」?
是,這兩個說法其實描述的是同一件事。
下面兩句話完全等價:
- 讓
skill_selector.py結合current_subgraph與 goal proximity,做更強的 routing - 做 goal-aware skill routing
因為它們都在講同一個核心能力:
selector 排序時,不只看技能本身,還看這個技能會不會把系統更接近 goal。
這就是 routing。
現在這個版本還有哪些限制?
雖然這次升級已經很關鍵,但它還不是終點。
至少還有幾個可以繼續往前走的方向。
1. 目前只看最短 hop,不看 edge type
現在 BFS 主要是在 graph 上算距離,但還沒分辨:
REQUIRESLEADS_TOSIMILARCOMPLETED
未來更強的版本可以做 relation-aware routing。
例如:
LEADS_TO權重更高SIMILAR權重較低
2. 目前只看單步 postconditions,不看多步收益
現在 selector 看的還是:
- 這個 skill 直接產生哪些 states
但未來可以更進一步評估:
- 這個 skill 是否能解鎖更多後續 skills
- 這個 skill 對整體路徑的 long-term gain 是多少
3. 目前 proximity 只來自 current_subgraph
這是好事,因為它很乾淨。
但也代表 graph 如果抽取得不夠完整,proximity 就不夠準。
未來可以結合:
- 全圖 BFS
- subgraph expansion
- edge confidence
讓 proximity 更穩。
4. 目前仍是 deterministic baseline,不是 learned policy
這點我反而覺得是優點。
因為比較好的演化順序本來就應該是:
- 先有 deterministic planner
- 再在上層疊 LLM 或 learned policy
而不是一開始就全靠模型猜。
為什麼我覺得這一章很關鍵?
因為 CH13 和 CH14 證明的是:
- loop 已經存在
- writeback 已經存在
但直到 CH15,我們才真正開始回答這個問題:
如果同時有多個可執行 skill,系統會不會優先走向正確的目標?
這個問題一旦成立,planner 就真的開始有方向感了。
也就是說:
CH13解的是「能不能跑」CH14解的是「會不會回寫」CH15解的是「會不會朝對的方向跑」
這三章合起來,才真正把 world model runtime 的核心骨架補完整。
結語:從這一章開始,selector 不再只是排序器,而是目標導向的路由器
如果只從程式碼 diff 看,這次改動也許只是:
- 多一個
_goal_hop_distances() - 多一個
_UNKNOWN_PROXIMITY - sort key 多插入一個
goal_proximity
但從系統設計角度看,真正發生的事情其實是:
skill_selector.py從單純的 rule-based sorter,進化成會參考 graph 結構與目標距離的 goal-aware router。
這個差別很大。
因為這代表 CtxFST 的 world model runtime 現在不只會:
- 判斷技能是否合法
- 把結果寫回世界
它還開始會:
優先挑選那些真正把世界推向 goal 的技能。
而這一步,正是從「有閉環」走向「有方向感」的關鍵升級。
參考實作
📌
CH13讓 loop 跑起來,CH14讓 loop 寫回世界,而CH15則讓 loop 開始真的朝 goal 前進。
- ← Previous
CtxFST CH14 - 讓 SKILL.md 真正驅動 Graph-Aware Agent Loop,並自動回寫 Graph - Next →
CtxFST CH16 - 先別急著變聰明:為 Agent Loop 補上 End-to-End Test Suite