Ian Chou's Blog

CtxFST CH27 - 沒有 World State 時 Agent 怎麼壞掉的:五個真實場景

CtxFST CH27:沒有 World State 時,Agent 怎麼壞掉的

CH26 講了 world state 是導航系統。地圖(graph)告訴你路在哪,導航(planner + state)告訴你下一步怎麼走。

但「為什麼需要導航」這個問題,用比喻只能講到一半。真正有說服力的,是看沒有導航的時候會發生什麼事。

這篇列五個場景。每個都是用 agent 跑長任務時真實會遇到的狀況。不是理論推演,是你用 Claude Code 或 OpenClaw 跑個一小時就會碰到的事。

每個場景對比兩種做法:


場景一:重複推薦已完成的步驟

狀況

你叫 agent 幫你建一個網站。它先幫你裝了 Node.js,然後跑了 npm init,然後開始裝 dependencies。

裝到第三個 package 的時候,它突然說:

"首先,讓我們確認一下 Node.js 環境是否已經安裝。"

你剛才不是裝過了嗎?對話歷史裡明明有。

但 LLM 不是「記得」對話歷史,它是每次都重新讀。當對話變長、中間穿插了 error 訊息和 debug 過程,LLM 的注意力會被最近的內容拉走,忘記前面的進度。

LLM 自管 state

對話歷史(50 條訊息後):
  ... 裝 Node ✓ → npm init ✓ → 裝 express ✓ → 裝 dotenv 時報錯 →
  debug 了 10 條訊息 → 修好了 → ...

Agent:"好的,讓我們先確認 Node.js 環境..."
User:"你剛才已經裝過了。"
Agent:"抱歉!你說得對,讓我繼續..."

LLM 不是故意的。它是在重新處理 prompt 時,被中間的 debug 訊息干擾,丟失了更早的進度。

這不會致命,但會:

系統層 world state

db.getWorldState(sessionId, "state:node-installed")
// → { value: true, updated_at: "2026-04-01T10:15:00" }

db.getCompletedSkills(sessionId)
// → ["install-node", "npm-init", "install-express"]

// Planner 看到 install-node 已完成,直接跳過
// 不管對話歷史裡有多少 debug 訊息

差異: 狀態存在 DB 裡,不受對話長度影響。Planner 不需要從 50 條訊息裡「回憶」進度,直接查表。


場景二:跳過 precondition,幻覺出不存在的輸入

狀況

你叫 agent「分析我的 resume」。但你還沒上傳任何檔案。

一個好的 agent 應該說:「請先上傳你的 resume。」

但 LLM 有時候會直接開始「分析」——用幻覺生成一份看起來像分析結果的東西,但完全沒有根據。

LLM 自管 state

User:"分析我的 resume"
Agent:"好的,根據你的 resume,我看到你有 5 年的 Python 經驗,
       曾在 Google 實習..."

(你從來沒上傳過 resume。這些全是幻覺。)

這個場景的問題不只是「答錯了」。而是:

  1. 下游步驟會基於這個幻覺結果繼續執行
  2. Agent 可能接著說「基於分析結果,我建議你強調 Python 經驗」
  3. 整條 chain 都被 poison 了,而且使用者不一定能發現

LLM 為什麼會這樣?因為它被訓練成「盡量回答問題」。如果 prompt 裡沒有明確的「resume 尚未上傳」狀態,LLM 傾向於假設需要的東西都有,然後開始回答。

系統層 world state

const check = checkPreconditions("analyze-resume", sessionId);
// → { ok: false, missing: ["state:resume-uploaded"] }

// 系統直接擋住,不讓 LLM 進入分析流程
// 回應使用者:"需要先上傳 resume。目前缺少:state:resume-uploaded"

差異: 不是靠 LLM 「判斷」resume 有沒有上傳,而是系統查表。state:resume-uploaded 不存在就是不存在,不會幻覺。

這是最關鍵的場景。因為 precondition 跳過造成的不是「重複」(場景一),而是「毒害」——下游所有步驟都建立在錯誤的基礎上。


場景三:長對話後遺忘中間進度

狀況

你和 agent 花了一小時建一個 app。前 20 分鐘連好了 GitHub repo,中間 30 分鐘在寫 code 和 debug,最後 10 分鐘你想部署。

Agent 突然問你:

"你有 GitHub repo 嗎?請提供 repo 名稱。"

20 分鐘前就設好了。但對話已經有 80 條訊息,中間的 code 和 error log 佔了大量 token。早期的「已連接 GitHub repo: my-app」被壓到了 context window 的邊緣,或者已經被 context compression 截掉了。

LLM 自管 state

// 訊息 #12:已連接 GitHub,repo = my-app
// 訊息 #13-79:寫 code、debug、跑測試...
// 訊息 #80:

Agent:"要部署到 GitHub Pages 嗎?請先設定 repo..."
User:"my-app,我們 20 分鐘前設的。"
Agent:"好的,讓我連接 my-app..."  // 重新連一次

這個問題有一個技術原因:LLM 的 context window 有限。就算模型支援 200K token,agent runtime 通常會做 context compression——把舊訊息摘要化或截斷。一旦截斷,那些「已完成」的進度就從 LLM 的可見範圍消失了。

你可以在 prompt 裡加規則:「每 10 條訊息就總結一次進度。」但這只是把問題推遲了,不是解決了。因為:

系統層 world state

db.getWorldState(sessionId, "entity:github-repo")
// → { value: "my-app", updated_at: "2026-04-01T10:20:00" }

db.getActiveStates(sessionId)
// → ["state:github-connected", "state:repo-created",
//     "state:code-pushed", "state:tests-passing"]

// Planner 注入 prompt:
// "GitHub 已連接(repo: my-app)。Code 已 push。Tests passing。"
// 不管對話有 80 條還是 800 條,這段 summary 永遠存在

差異: 進度不存在對話歷史裡,存在 DB 裡。Context compression 截不掉它,因為它根本不是對話的一部分。每次 prompt 組裝時,從 DB 讀取當前狀態,注入 prompt。


場景四:跨 session 狀態完全丟失

狀況

昨天你和 agent 花了兩小時建了半個 app。今天打開新 session,想繼續。

Agent 說:

"你好!有什麼我可以幫你的嗎?"

它完全不知道昨天的事。

LLM 自管 state

這個場景裡,「LLM 自管 state」其實根本管不了。因為新 session = 新 context = 什麼都沒有。

有些 agent 會用 memory 檔案(像 MEMORY.md 或 PROJECT-STATE.yaml)來跨 session 保留資訊。但這依賴 LLM 在上一個 session 結束前主動寫入,而且:

# PROJECT-STATE.yaml(LLM 上次寫的)
project: my-app
status: in-progress
last_action: fixed CSS layout
next_step: add auth

# 問題:
# 1. "in-progress" 太模糊,哪些步驟做了哪些沒做?
# 2. "fixed CSS layout" 是最後一個 action 嗎?還是之後又做了其他事?
# 3. "add auth" 是 LLM 上次的建議,但 preconditions 滿足嗎?
# 4. 這個檔案是 LLM 自己寫的,格式可能不一致

系統層 world state

// 新 session 開始時
const state = db.loadUserState("user-ian");
// → {
//   project: "my-app",
//   active_states: [
//     "state:repo-created",
//     "state:frontend-scaffold-done",
//     "state:css-layout-fixed",
//     "state:backend-api-started"
//   ],
//   completed_skills: [
//     "create-repo", "scaffold-frontend", "fix-css",
//     "start-backend-api"
//   ],
//   blocked_by: [],
//   goal: "state:app-deployed"
// }

// Planner 立刻知道:
// 1. 做到哪了(active_states)
// 2. 做過什麼(completed_skills)
// 3. 下一步是什麼(查 goal 的 skill chain,跳過已完成的)

差異: 狀態是結構化的、schema-enforced 的、queryable 的。不是 LLM 寫的自由文字,而是系統在每次 skill 執行後自動 writeback 的。


場景五:Error retry 後迷失方向

狀況

Agent 在跑 npm install 時遇到 proxy error。它嘗試了幾種修法:

  1. 換成 yarn → 同樣的 proxy error
  2. 設定 npm config proxy → 設錯了格式
  3. 檢查 .npmrc → 發現有舊設定衝突
  4. 刪掉 .npmrc 重來 → 還是錯

到第四次 retry 之後,agent 開始建議「試試看重裝 Node.js」。

問題是:這和 proxy 完全無關。Agent 已經忘了它在 debug 什麼。

LLM 自管 state

嘗試 1:yarn → proxy error
嘗試 2:npm config set proxy → 格式錯
嘗試 3:檢查 .npmrc → 發現衝突
嘗試 4:刪 .npmrc → 還是錯

Agent:"可能是 Node 版本問題,試試重裝?"

(proxy 問題完全沒解決,但 Agent 已經跑偏了)

Error retry 是 LLM agent 最脆弱的場景。因為每次 retry 都會在對話裡加入 error log,這些 log 通常很長、很雜。幾輪之後,LLM 的注意力被錯誤訊息淹沒,丟失了「我們在 debug 什麼」和「哪些方法已經試過了」。

系統層 world state

db.getDebugState(sessionId, "npm-install-proxy-error")
// → {
//   root_cause: "proxy-config",
//   attempts: [
//     { method: "switch-to-yarn", result: "same-error" },
//     { method: "npm-config-proxy", result: "format-error" },
//     { method: "check-npmrc", result: "found-conflict" },
//     { method: "delete-npmrc", result: "still-failing" }
//   ],
//   untried: ["system-proxy-env", "direct-connection-test"],
//   status: "in-progress"
// }

// Planner 看到:
// 1. 問題是 proxy,不是 Node 版本
// 2. 已經試了 4 種方法,都失敗
// 3. 還有 2 種沒試的方案
// → 推薦 "system-proxy-env",不會跑偏到 "重裝 Node"

差異: Debug 的上下文不存在對話歷史裡,存在結構化的 state 裡。不管 error log 有多長多雜,planner 永遠知道「我們在 debug 什麼」和「還有什麼沒試」。


五個場景的共同模式

把五個場景放在一起看,有一個共同的結構:

場景 失敗原因 核心問題
重複推薦 進度被 debug 訊息淹沒 完成狀態沒有被 explicitly track
跳過 precondition LLM 傾向假設條件滿足 沒有強制檢查機制
長對話遺忘 Context compression 截掉舊資訊 狀態存在對話裡,不在外部
跨 session 丟失 新 session 沒有歷史 狀態生命週期綁定 session
Retry 迷失 Error log 干擾注意力 Debug 進度沒有結構化記錄

共同模式:狀態存在 LLM 的 context 裡,而不是 DB 裡。

只要狀態存在 context 裡,就會受到:

這些都是 LLM 架構的結構性限制,不是模型不夠聰明。GPT-5 或 Claude 5 也不會完全解決這些問題,因為問題不在模型能力,在架構。


這不是「LLM 不夠聰明」

這一點需要特別強調。

上面五個場景,不是在說 LLM 笨。LLM 的推理能力很強,單步執行能力更強。問題在於我們把一個不該由 LLM 負責的工作交給了它——記住狀態

LLM 的設計是:給定 context,產生 response。它不是一個有持久記憶的系統。每次呼叫都是無狀態的,所有的「記憶」都是靠把歷史塞進 prompt 來模擬的。

用 LLM 來追蹤狀態,就像用計算機來當鬧鐘。計算機算數很強,但它不是設計來追蹤時間的。你可以 hack 出一個方法讓它做到,但那不是它的強項,而且很容易壞。

正確的分工是:

┌──────────────────────────────┐
│  LLM                         │
│  強項:推理、生成、判斷       │
│  不該做:記住進度、追蹤狀態   │
└──────────────┬───────────────┘
               │ 查詢 state / 提議 action
               ▼
┌──────────────────────────────┐
│  World State (DB)            │
│  強項:持久、精確、可查詢     │
│  不該做:推理、判斷           │
└──────────────────────────────┘

LLM 問 DB:「現在狀態是什麼?」

DB 回答:「做到第三步了,還缺一個 precondition。」

LLM 決策:「那先去滿足這個 precondition。」

DB 記錄:「precondition 已滿足,進入第四步。」

各做各的強項。


「但我從來沒遇過這些問題啊?」

如果你的 agent 用途是:

那你確實不需要 world state。LLM 臨場判斷完全夠用。

但只要你開始做這些事情:

這五個場景就會開始出現。不是機率問題,是確定性問題。用得越多,遇得越多。


收尾

一句話總結:

LLM 臨場判斷的失敗,不是因為模型不夠聰明,而是因為我們讓它做了它不該做的事——記住狀態。把狀態從 LLM 的 context 移到系統的 DB,不是優化,是架構修正。

CH26 說 world state 是導航系統。這一章說的是:沒有導航系統的時候,你會在第三個路口開始迷路,在第五個路口開回原點,在第十個路口完全忘記你要去哪裡。

不是因為你不會開車。是因為你沒有 GPS。