076

SKILL.state:長任務 Agent 不再重播整段對話,改用顯式執行狀態

SKILL.state:長任務 Agent 不再重播整段對話,改用顯式執行狀態 封面圖

SKILL.state 用 structured state 取代長任務 Agent 的完整對話重播。本文拆解 runtime、Claude/Codex/Pi 接法與導入判斷。

Seer

2026-08-31

LLM Agent 跑十幾步時,對話歷史通常還能撐住;跑到上百步,runtime 本身就會變成問題。每次呼叫模型都重送先前的 observation、reasoning、tool output 與 action,prompt 越來越長,舊資訊也會一直留在上下文裡。模型不只要決定下一步,還得先從整段歷史重建「現在到底是什麼狀態」。

SKILL.state: Scalable Long-Horizon Agent Skills 提出的做法很直接:把 append-only conversation 換成一份可修改、可驗證的 structured execution state。每一步只送三樣東西給模型:固定的 skill specification、目前 state、最新 observation。模型當步產生的 reasoning 用完即丟,只留下經 runtime 驗證過的 state patch。[1][2]

研究範圍聚焦 Agent runtime architecture,模型本身與長期記憶資料庫維持不變。它要處理的是長任務執行時,agent 如何保留「未來還需要的事實」,同時不讓整段執行歷史持續膨脹。

論文版本:arXiv:2608.26263v1,2026-08-26 提交;作者為 Sanket Badhe、Priyanka Tiwari、Jonghyun Chung。arXiv 註記為 accepted at EMNLP,授權為 CC BY 4.0。[1][3]

先看重點

  • SKILL.state 每一步只輸入 PΣ_tO_t:固定程序、目前狀態、最新觀察。
  • 模型仍可在單一步驟內做多步 reasoning,但 reasoning 不會進入下一輪 prompt。
  • 模型輸出 state_patchaction;runtime 負責 schema validation、merge、rollback/retry。
  • null 代表刪除 state key,patch 需要用 merge semantics,不能把整份 state 覆蓋掉。
  • 在論文的 Warehouse 測試中,T=100 時 SKILL.state 使用 65,408 tokens,Stateful baseline 使用 1,062,387,約為 16.2 倍。[2]
  • 在 InterCode CTF,SKILL.state pass@1 為 54.2%,同時比 ReAct 少約 60.4% total tokens。[2]
  • 這套方法成立的前提,是 structured state 足以代表未來決策需要的資訊。
  • 論文沒有釋出 SKILL.state/SkillExecBench 官方程式碼或完整 schema;目前能做的是依論文 prompt、演算法與附錄自行實作,不能宣稱完整重現作者實驗。

問題出在 Agent 一直把歷史當成狀態

常見 Agent runtime 會把每輪內容接在同一段 conversation 後面:

  1. 使用者或環境送出 observation。
  2. 模型推理並選 action。
  3. tool 執行 action,回傳 output。
  4. 下一輪把前面所有內容再送一次。

這種設計的好處是簡單。模型看得到完整軌跡,不需要 runtime 先定義 state。缺點也很明確:第 t 步的 prompt 包含前 t-1 步內容,單步 prompt 隨時間成長,累積 token 會接近 O(T²)。[2]

更麻煩的是資訊語意。舊 observation 可能已經失效,早期推理也可能只是當時的假設。它們留在 prompt 裡後,模型每一輪都要重新判斷:

  • 哪個狀態還有效?
  • 哪個 tool output 已被後續動作推翻?
  • 哪個 hypothesis 已經測過?
  • 哪段文字只是過去的 reasoning,不是目前事實?

摘要、retrieval memory、sliding window 能縮小 prompt,但它們仍以「壓縮或挑選歷史」為主。論文的切入點是:如果 Agent 下一步真正需要的是 current state,那就直接把 current state 設成 runtime 的 canonical representation,不要每次從歷史文字重建。[2]

SKILL.state 的核心資料流

論文把每一步輸入寫成:

A_t = (P, Σ_t, O_t)
  • P:immutable procedural specification,也就是 skill instructions、可用 action、環境規則與角色限制。
  • Σ_t:第 t 步的 structured execution state。
  • O_t:環境最新回傳的 observation。

模型產生:

(R_t, ΔΣ_t, a_t)
  • R_t:當步 reasoning。
  • ΔΣ_t:JSON state patch,只描述要新增、修改或刪除的 state。
  • a_t:下一個 action。

runtime 驗證 patch 後更新:

Σ_(t+1) = Σ_t ⊕ ΔΣ_t

是 dictionary merge;patch 中的 null 表示刪除。完成 transition 後,R_t 永久丟棄,下一輪也不會收到先前的 observation 或 action。[2]

flowchart TB
    A["Agent Adapter<br/>Claude: Hooks / one-shot<br/>Codex: Hooks / exec JSONL<br/>Pi: Extension events"]
    U["User Intent Contract<br/>goal · constraints · approval"]
    X["External Authority<br/>Git · Issue / ADR · CI · Runtime"]

    subgraph Paper["論文核心:Bounded State Loop"]
        R["Authority Refresh + Bounded Prompt<br/>P + Intent + Σt + Ot"]
        C["Candidate Transition<br/>state_patch + action"]
    end

    subgraph Production["本文建議:Production Governance"]
        G["Governance Gate<br/>schema · intent · freshness · sensitivity"]
        Q["User Confirmation / Corrective Observation"]
        T["Tool Execution"]
        V["Evidence Validation<br/>Git · CI · Runtime"]
        S["CAS Commit<br/>task state + audit pointer"]
    end

    A --> R
    U --> R
    X --> R
    R --> C --> G
    G -->|"intent change / stale / reject"| Q
    Q --> R
    G -->|"allow"| T --> V
    V -->|"failure"| Q
    V -->|"success"| S
    S --> R

這張 Mermaid 把兩個範圍分開:Bounded State Loop 是論文核心;使用者確認、freshness、sensitivity、CAS commit 與外部 authority reconciliation 是本文建議的 production governance。完整版本、決策與執行證據留在 Git、Issue/ADR、CI、runtime telemetry 與 audit log;Prompt 只帶目前需要的 bounded snapshot。

這裡的重點不是「禁止模型思考」。模型在單次 generation 裡仍可做完整 reasoning;runtime 只是不把 reasoning 當成跨步記憶。需要跨步保留的內容,必須投影進 Σ_t

Schema 才是這套方法真正的工程成本

SKILL.state 把執行可靠性從 history reconstruction 移到 schema authoring。論文主張 schema 以 domain 為單位定義,不需要每個 task 重寫。

論文的 sufficient-statistic assumption 沒有自動保證 schema 真的代表使用者在意的事。只讓模型自己摘要,仍可能把使用者的優先順序、禁止事項、版本決策與驗收條件壓掉。 Production runtime 必須把「誰定義重要性」做成獨立治理層。

怎麼確保 Schema 保存的是使用者真正關心的事

第一步不是請模型自由歸納,而是先建立一份 使用者意圖契約(User Intent Contract)。這份契約由使用者需求、任務規格與明確確認組成,模型只能引用或提出修改,不能自行刪除。

至少要包含:

  • goal:最後要完成什麼。
  • success_criteria:什麼結果才算完成,要如何驗證。
  • locked_requirements:使用者要求原文保留、不可降級或不可省略的內容。
  • constraints:技術、安全、成本、時間與範圍限制。
  • non_goals:這一輪明確不做什麼,避免 Agent 自行擴張範圍。
  • approval_boundaries:發布、付款、刪除、部署、merge 等哪些動作要先取得許可。
  • must_remember:即使壓縮 working state 也不能消失的資訊。
  • priority_order:衝突時要先保哪個目標。

每個重要 state field 還要帶治理 metadata:

{
  "value": "使用者要求所有版本迭代都要可回溯",
  "source": "user-message:2026-08-31T...",
  "owner": "user",
  "retention": "task-lifetime",
  "freshness": "until-superseded",
  "change_policy": "explicit-user-approval",
  "supersedes": null
}

這會帶來六條硬規則:

  1. User-owned 欄位不可由一般 state patch 刪除。 模型只能提出 change_request
  2. 重要資訊不能只剩摘要。 要保留 source pointer,能回到原始訊息、Issue、commit 或文件。
  3. 不確定就標記 unknown。 不可因模型判斷「可能不重要」而直接遺失。
  4. 衝突要進 confirmation queue。 新 observation 與 locked requirement 衝突時,由使用者或 policy resolver 決定。
  5. Schema 本身要 versioned。 每次增刪欄位都要有 migration、相容性與回滾方式。
  6. 定期做反向檢查。 從 success criteria 回查目前 state:完成判斷所需的證據是否都還找得到。

所以保留策略應分成不同層,而不是把所有資料塞進同一份 JSON:

資料層保存內容是否進每輪 Prompt權威來源
使用者意圖契約目標、驗收、限制、approval、must-remember是,精簡且受保護使用者/任務規格
Working State目前步驟、事實、未解問題、下一個 action是,boundedRuntime state store
Source Authoritybranch、HEAD、diff、tag、正式文件只放 pointer/current snapshotGit/文件系統
Runtime Evidencetest、CI、telemetry、deployment state只放摘要與最新證據 pointerCI/GSC/監控/部署平台
Decision/Audit History為何修改、誰批准、tool calls、失敗紀錄預設不進Issue/ADR/audit log
Ephemeral Reasoning當步推理過程否,用完丟棄不作持久權威

程式開發的版本迭代要記,但由 Git 與證據系統保存完整歷史

程式開發不能只留下「目前正在改登入功能」這種摘要。版本迭代至少有兩種權威,handoff 時要分開:

Source Authority

  • repository path/remote
  • current branch
  • base commit、HEAD commit
  • staged/unstaged diff 與 working-tree digest
  • release tag/package version
  • schema migration/database migration version
  • 目前哪些檔案是這輪 change scope

Runtime Evidence

  • 已執行哪些 tests,完整 command 與結果
  • CI run/artifact/coverage
  • 實際 runtime telemetry、logs、GSC 或 production state
  • deployment environment、deployed revision
  • 尚未執行的 production authority
  • rollback target 與最後已知穩定版本

完整 commit history、diff、CI log 與 deployment event 留在外部 source of truth。Σ_t 保存的是目前權威快照與可解引用 pointer,例如:

{
  "user_intent": {
    "goal": "完成登入重構並保持既有 session 相容",
    "success_criteria": [
      "unit and integration tests pass",
      "staging can resume existing sessions"
    ],
    "locked_requirements": [
      "不可變更公開 API response shape"
    ],
    "approval_boundaries": ["production deploy"]
  },
  "source_authority": {
    "repo": "/workspace/app",
    "branch": "feature/auth-refactor",
    "base_commit": "abc1234",
    "head_commit": "def5678",
    "working_tree": "dirty",
    "diff_ref": "git-diff:sha256:..."
  },
  "current_change": {
    "issue": "AUTH-42",
    "plan_step": 3,
    "files_touched": ["src/auth.ts", "tests/auth.test.ts"],
    "open_questions": ["舊 refresh token 是否要一次性遷移"]
  },
  "runtime_evidence": {
    "tests": [{"command": "npm test", "status": "passed", "artifact": "ci://run/812"}],
    "staging_revision": "def5678",
    "production_revision": "abc1234",
    "production_authority_executed": false
  },
  "decision_ledger": [
    {
      "decision": "保留舊 session cookie 名稱",
      "source": "issue://AUTH-42#comment-18",
      "approved_by": "user",
      "supersedes": null
    }
  ],
  "next_action": "驗證 staging 舊 session 恢復"
}

這份 state 不複製完整 Git history,但不會遺失版本迭代。Agent 每輪開始前要向 Git、Issue、CI 與 runtime 平台做 reconciliation:

  1. 讀取目前 branch/HEAD/diff 與 deployed revision。
  2. 比對 Σ_t 裡的 pointer 是否仍新鮮。
  3. 發現外部變更時,先更新 authority snapshot,再規劃下一步。
  4. patch 若改動 locked requirement 或 approval boundary,停止執行並詢問使用者。
  5. action 完成後,把完整 evidence 寫回 Git/CI/audit,再更新 state pointer。

這樣「丟棄對話歷史」只代表不把整段歷史重送給模型,不代表刪除程式版本、使用者決策或執行證據

套用到 Claude Code、Codex CLI 與 Pi

三個工具都能接入這套方法,但不能只把同一份 prompt 貼進去。SKILL.state 要成立,runtime 必須掌握四個邊界:

  1. 每輪只注入 Skill Spec、User Intent Contract、目前 state 與最新 observation。
  2. 模型輸出的 state_patch 只能先成為 candidate。
  3. action 通過 policy gate 後才執行。
  4. tool result 經環境驗證後,才以 compare-and-swap commit state。

三者應共用同一份 adapter contract,而不是各自發明不同的 schema:

UserInput
  → AuthorityRefresh(Git / Issue / CI / runtime)
  → PromptRender(P + intent + Σt + Ot)
  → AgentStep
  → StatePatchCandidate + Action
  → Schema / Intent / Action Gate
  → ToolExecution
  → EvidenceValidation
  → StateCommit(expected_version)

建議把資料拆成兩個目錄:

.agent-contract/                 # 可進 Git;不可放敏感資訊
  skill-spec.md
  intent-contract.json
  state.schema.json
  transition.schema.json
.agent-runtime/                  # gitignored 或外部 SQLite/KV
  state.json
  audit.jsonl
  observations/
  state.lock

AGENTS.mdCLAUDE.md 適合放穩定指令,不適合每輪覆寫 mutable state。完整 tool output 進 audit/artifact store;state.json 只留有上限的決策快照與外部 pointer。

工具最接近完整 SKILL.state 的作法Native 接點主要限制
Claude Codeone-shot claude -p+外部 controllerHooks、structured output互動 session 仍保留 transcript
Codex CLIcodex exec --ephemeral --json --output-schema+外部 controllerHooks、JSONL events、App Server/SDKoutput schema 只約束 final response
Piproject extension+自訂 transition toolbefore_agent_startcontexttool_calltool_result平行 tool call 需要 single-writer/CAS

Claude Code:one-shot controller 最接近論文語意

Claude Code 的 Hooks 已提供 SessionStartUserPromptSubmitPreToolUsePostToolUsePostToolUseFailurePreCompactStopSessionEnd 等事件。Hook 可以補 additionalContext、拒絕 tool call,或在 tool 完成後收集 evidence。[7]

如果要忠實實作 SKILL.state,不建議直接讓一個互動 session 無限跑。互動 session 仍會保存 transcript,hooks 只增加治理,沒有自動把每輪輸入縮成 P + Σ_t + O_t。較完整的方式是外部 controller 每一步啟動一次不保存 session 的 structured-output call:[8]

TRANSITION_SCHEMA="$(jq -c . .agent-contract/transition.schema.json)"
STEP_PROMPT="$(python3 runtime/render_step.py)"

claude -p "$STEP_PROMPT" \
  --no-session-persistence \
  --tools "" \
  --output-format json \
  --json-schema "$TRANSITION_SCHEMA"

這一輪 Claude 只產生 state_patchaction,不直接執行 shell。Controller 接著:

  1. 驗證 transition schema、protected fields、state version 與 action policy。
  2. 由 controller 的 tool adapter 執行 action。
  3. 讀 Git/CI/runtime 驗證結果。
  4. 成功才 commit candidate state;失敗則寫 corrective observation。
  5. 下一輪重新啟動 one-shot call,不 resume 舊 session。

需要保留 Claude Code 原生工具體驗時,可以改成 hooks+MCP state service:

Claude HookSKILL.state 工作
SessionStart載入 task state、schema version、authority pointer
UserPromptSubmit刷新 Git/Issue/CI,注入 bounded state
PreToolUse檢查 protected path、approval boundary、stale state version;必要時 deny
PostToolUse正規化 tool result,寫 audit,產生 candidate observation
PostToolUseFailure記錄失敗,不 commit optimistic patch
Stop驗證最後一筆 tool result 已 reconciliation,否則阻止 task 被標成完成
SessionEnd封存或清除 session-scoped state

MCP service 可提供 state_readstate_transitionstate_confirmauthority_refresh 四個工具。CLAUDE.md 只寫「何時必須呼叫這些工具」與穩定 invariant;mutable state 留在 MCP service/external store。這是容易落地的 retrofit,但 session 內歷史仍可能進 context;若目標是論文所說的固定 footprint,仍要使用 one-shot controller 或自己掌握 agent loop。

Codex CLI:用 JSONL event stream 做 controller 邊界

Codex 的 non-interactive mode 可以用 --json 輸出 JSONL events,包含 thread/turn lifecycle、command execution、file change、MCP tool call 與完成狀態;--output-schema 則約束 final response。最保守的模式同樣讓 Codex 只提議 transition:[10]

STEP_PROMPT="$(python3 runtime/render_step.py)"

codex exec \
  --ephemeral \
  --json \
  --output-schema .agent-contract/transition.schema.json \
  --cd "$REPO" \
  --sandbox read-only \
  "$STEP_PROMPT" > .agent-runtime/events.jsonl

Controller 從 JSONL 取得 thread.startedturn.item. 與 error,最後解析符合 schema 的 response,再執行 action。--ephemeral 只是不把 session 存到磁碟;真正的 bounded context 仍取決於每個 transition 都開新 invocation,且不要把上一輪 JSONL 全部塞回下一輪。

Codex 現在也有官方 Hooks,包括 SessionStartUserPromptSubmitPreToolUsePermissionRequestPostToolUsePreCompactStopSessionEnd。對應方式與 Claude 類似:[9]

  • SessionStartUserPromptSubmit:authority refresh,透過 additionalContext 注入 bounded snapshot。
  • PreToolUse:檢查 state version、approval boundary;可回 permissionDecision: "deny"
  • PostToolUse:保留 tool evidence,更新 latest observation,不直接把整段 output 寫入 state。
  • Stop:確認 tests/CI/Git status 與 state 已對帳。
  • SessionEnd:關閉 lock,封存 task state。

如果把 sandbox 改成 workspace-write,Codex 可能在 final structured response 前已經執行多個 tool。這時 --output-schema 只能保證最後輸出形狀,不能提供 transaction isolation。需要每一步都先驗 patch 再執行時,應使用 hooks+MCP transition service,或由 Codex App Server/SDK 的外部 orchestrator 管理 tool execution。

AGENTS.md 只放穩定流程、repo invariant 與 state tool 使用規則。Branch、HEAD、CI run、暫時 workaround 留在 task state/外部權威,避免每次迭代都改寫 AGENTS.md。[11]

Pi:用 extension API 直接改造 agent loop

Pi 的 extension API 更接近這篇論文需要的 runtime seam:[12][13]

  • before_agent_start 可注入 state 或改 system prompt。
  • context 在每次 LLM call 前可重建 messages。
  • tool_call 可修改參數或 block。
  • tool_result 可正規化 result、標記 error。
  • pi.appendEntry() 可把 extension state 寫入 session JSONL,且 custom entry 不會自動進 LLM context。

Project extension 可放在 .pi/extensions/skill-state.ts。概念骨架如下:

import { Type } from "typebox";
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";

export default function (pi: ExtensionAPI) {
  let state = loadState();
  let latestObservation = "task started";

  pi.on("session_start", async (_event, ctx) => {
    state = restoreLatestState(ctx.sessionManager.getBranch()) ?? state;
    state = await refreshAuthorities(state);
  });

  pi.on("before_agent_start", async () => ({
    message: {
      customType: "skill-state-current",
      content: renderBoundedSnapshot(state, latestObservation),
      display: false,
    },
  }));

  pi.on("context", async (event) => ({
    messages: buildBoundedContext(event.messages, state, latestObservation),
  }));

  pi.on("tool_call", async (event) => {
    const denied = validateToolPolicy(event.toolName, event.input, state);
    if (denied) return { block: true, reason: denied };
  });

  pi.registerTool({
    name: "skill_state_transition",
    label: "Validate and commit one state transition",
    parameters: Type.Object({
      expectedVersion: Type.Integer(),
      statePatch: Type.Record(Type.String(), Type.Unknown()),
      action: Type.Object({
        type: Type.String(),
        args: Type.Record(Type.String(), Type.Unknown()),
      }),
    }),
    async execute(_id, params, signal, _onUpdate, ctx) {
      const candidate = validateAndMerge(state, params);
      const observation = await executeDomainAction(params.action, signal, ctx);
      state = await reconcileAndCommit(candidate, observation);
      latestObservation = observation.summary;
      pi.appendEntry("skill-state", { version: state.version, state });
      return {
        content: [{ type: "text", text: latestObservation }],
        details: { stateVersion: state.version },
        terminate: true,
      };
    },
  });
}

context handler是關鍵:它可讓 LLM 只看到目前 state 與最新 observation,完整 session history 仍保留在 Pi 的 JSONL,供 audit、branch、resume 使用。這比把 history 真正刪掉安全,也最符合論文「不再把完整 trajectory 當推理輸入」的意思。

Pi 預設可以平行執行同一 assistant message 中的 tool calls,因此 state transition 要強制 single writer:每個 patch 帶 expectedVersion,寫入時做 CAS;檔案更新則用同一把 state lock 或 withFileMutationQueue()。未通過 project trust 時,project-local extension 不應被自動載入;non-interactive run 也要明確處理 trust policy。[13]

三者共同的 commit 規則

不論 adapter 用哪一套 API,都應遵守同一條 transaction boundary:

模型提議 patch/action
  → schema + intent + freshness gate
  → stage candidate state
  → 執行 action
  → 驗證 Git/CI/runtime evidence
  → CAS commit 或產生 corrective observation

只掛 hooks、每輪把 state 附加到既有 session,能得到治理與 observability,但不必然得到論文的固定 prompt footprint。要重現 SKILL.state 的核心效益,controller 還必須限制每輪實際送入模型的 messages,並把完整歷史留在模型 context 之外。

是否值得引入:值得做有限導入,不適合直接取代所有 Agent Memory

對 Claude Code、Codex CLI、Pi 這類 coding agent,這套架構值得用一個真實長任務流程試點。原因很直接:程式開發已有 Git、Issue、CI、filesystem 與 deployment platform 當外部權威,state 可以保存 pointer 與未完成事項,不必自己冒充版本資料庫。

目前不適合直接升成所有任務的全域 memory layer。論文提供的 benchmark 訊號很強,但截至本文查核時沒有官方 SKILL.state/SkillExecBench code;使用者意圖治理、transaction semantics、multi-agent conflict 也不是論文已解決的部分。正確決策是先做 task-scoped pilot,再用實際指標決定是否擴大。

適合導入的訊號

工作特徵為什麼有價值
任務跨很多 tool steps、session 或 agent handoff降低重送完整 history 與重新理解成本
下一步主要由明確 state 決定Schema 能保存 branch、issue、驗證與 pending work
Git/CI/API 能驗證 action 結果Candidate patch 可以等 evidence 通過後再 commit
常重跑已失敗命令或忘記先前假設tested_hypotheses/evidence pointer 可阻止重複工作
外部 actor 會改 repo、issue 或 deploymentAuthority refresh 能處理 stale state
需要可稽核 handoffTask state 與完整 audit 分離,接手者先看 canonical snapshot

不適合導入的訊號

工作特徵主要問題
一兩輪就完成的問答、簡短修改Adapter、schema、state store 的成本高於收益
創作、研究探索,早期素材的價值要到後面才知道固定 schema 容易過早丟掉內容
成功條件與 state structure 持續改變Schema migration 會變成主要工作
Tool result 無法驗證,也沒有 source of truthRuntime 無法判斷何時能 commit
使用者真正要的是完整過程與脈絡原始 trajectory 本身就是產出,不能只留 state

真正要付的成本

  • 替每個 domain 設計 schema、invariant、migration 與 size bound。
  • 維護 Claude/Codex/Pi adapter,處理版本與 hook 行為差異。
  • 建 state store、lock、CAS、audit 與 authority refresh。
  • 處理 optimistic patch、tool failure、timeout、retry 與 partial success。
  • 建 protected intent、confirmation queue、sensitivity 與 retention policy。

這些成本說明了導入邊界:它適合「長、可建模、可驗證」的 workflow;短任務與開放式探索繼續使用原本的 Agent session。

建議 rollout

  1. Sidecar state:先不裁剪 context、不接管 tool。每輪只做 authority refresh、task snapshot、structured handoff 與 audit pointer,確認 schema 是否能描述真實工作。
  2. Gated transition:挑一個長任務流程,加入 candidate patch、protected fields、PreToolUse policy、tool 後 evidence validation 與 staged commit。完整 history 仍保留,方便對照。
  3. Bounded-turn runtime:只有前兩階段證明 state 足夠,才改成 Claude/Codex one-shot controller,或 Pi context handler,把每輪模型輸入限制為 P + Intent + Σ_t + O_t

第一階段應先做平台無關的 state service,再接 Claude、Codex、Pi adapter。直接維護三套各自的 state logic,schema 與 commit semantics 很快會分岔。

Pilot 要量什麼

指標想確認的問題
每步 prompt tokens、state bytesContext 是否真的維持 bounded
Task success、人工修正次數壓縮後有沒有犧牲正確性
重複 command/重複 hypothesisStructured state 是否減少重工
Validator reject/retrySchema 與模型輸出是否穩定
Stale pointer/CAS conflict外部 drift 與並行寫入是否可控
Recovery stepsGit/CI/runtime 被外部修改後多久能恢復
Protected-intent violation使用者要求是否被模型覆寫;此項應作 hard gate
延遲與維護工時節省的 token 是否被 runtime 複雜度抵消

擴大導入的條件是:prompt 與重複工作下降,task success 不退步,外部 drift 能更快恢復,且沒有 protected-intent violation。若 schema migration、人工 reconciliation、validator retry 或 adapter 維護成為主要成本,就停在 sidecar/handoff 層,不必追求完整 bounded-turn runtime。

對三個工具的實際判斷

  • Claude Code:先做 hooks+MCP state service;有明確 token 或 context drift 問題後,再把特定 workflow 改成 one-shot controller。
  • Codex CLI:最適合先用 exec --json 做 observer/controller,保留 JSONL evidence;高風險 action 維持 read-only proposal+外部執行。
  • Pi:extension API 能直接控制 context 與 tool lifecycle,最適合做完整 prototype;但要先處理 project trust、平行 tool 與 state migration。

總結判斷:值得引入 task-scoped execution state,不值得把它包成全域永久記憶。 先選一個會跨 session、會跑大量工具、又能由 Git/CI 驗證的 coding workflow。試點成功後,再決定是否擴到其他 Agent 或 domain。

論文中的 Domain Schema 範例

例如 InterCode CTF 的 100 個 challenge 共用五個 state 欄位:[2][4]

  • discovered_flags
  • tested_hypotheses
  • active_files
  • working_dir
  • cmd_summary

這五個欄位對應 CTF Agent 下一步真正要知道的資訊:找到了什麼、哪些路線已失敗、目前有哪些檔案、工作目錄在哪、先前命令做了什麼。完整 terminal transcript 不再是 primary reasoning substrate。

但論文只列出欄位名,沒有公開完整 JSON Schema 與型別。下面這份是依論文概念整理的實作建議,不是作者官方 schema:

{
  "discovered_flags": ["flag{...}"],
  "tested_hypotheses": [
    {
      "hypothesis": "binary contains a hard-coded key",
      "result": "rejected",
      "evidence": "strings and objdump found no candidate"
    }
  ],
  "active_files": ["/tmp/challenge/app", "/tmp/challenge/output.txt"],
  "working_dir": "/tmp/challenge",
  "cmd_summary": [
    "file app: ELF 64-bit executable",
    "strings app: no obvious flag"
  ]
}

設計 schema 時要控制四件事:

  1. 只保留會改變未來 action 的資訊:純敘事、重複 tool output 不應進 state。
  2. 值要有上限:schema 欄位固定,不代表 list 與字串不會無限長。要設定長度、筆數與淘汰規則。
  3. 區分事實與假設tested_hypotheses 應帶結果與 evidence,避免把猜測升格成世界狀態。
  4. 寫出 invariant:例如一個 warehouse shelf 最多一個 item、working_dir 必須存在、同一 action 不可同時標示成功與失敗。

論文實際怎麼要求模型回傳

附錄中的 SKILL.state prompt 會放入:[2]

  • skill.instructions
  • compact JSON state
  • latest observation
  • 回傳格式約束

模型必須輸出當步 reasoning,接著給一個 fenced JSON block,而且頂層只能有兩個 key:

{
  "state_patch": {
    "key": "new value",
    "deleted_key": null
  },
  "action": "exact command to execute"
}

這個格式把「思考內容」與「runtime 可執行輸出」分開。真正寫進 persistent state 的只有 state_patch;真正送給 tool adapter 的只有 action

Warehouse 範例更清楚。最新 observation 是:

Customer ordered item_12.

目前 state 顯示 item_12shelf_42。模型推理後回傳:

{
  "state_patch": {
    "inventory": {
      "shelf_42": null
    }
  },
  "action": "Ship item_12 shelf_42"
}

下一筆 observation 才是:

Success: Shipped item_12 from shelf_42.

這個例子也揭露一個 production 設計問題:論文 pseudo-code 是先 merge patch,再 execute action。也就是 item 在 tool 確認 Ship 成功前,已先從 state 移除。

正式 runtime 至少要選一種語意:

  • Optimistic state update:照論文先更新;action 失敗時,依新 observation 產生 corrective patch。
  • Staged commit:先驗證出 candidate state,tool 成功才 commit;失敗則丟棄 candidate 或進 reconciliation。
  • 雙狀態desired_stateconfirmed_state 分開,適合遠端 API、交易或非同步 workflow。

論文實驗使用第一種。涉及付款、刪除、部署、權限或其他高風險 action 時,第二或第三種會更安全。

Runtime 要怎麼實作

論文的 Algorithm 1 可以整理成以下流程:

for t = 0 ... T:
    接收最新 observation
    用 skill spec、current state、observation 組 prompt
    LLM 產生 reasoning、state patch、action
    驗證 state patch
    合併 state patch
    執行 action

真正落地時,validator 不能只檢查 JSON 能否 parse。至少要有六層:

  1. Envelope validation:頂層只能有 state_patchaction
  2. Schema validation:欄位、型別、enum、長度與 nesting depth 符合定義。
  3. Patch validation:只能修改 allowlist path;null deletion 不得移除必要欄位。
  4. Intent-policy validation:一般 patch 不得修改 user-owned/locked fields;變更改送 confirmation queue。
  5. Invariant validation:patch 套用後的 candidate state 仍符合 domain 規則。
  6. Action policy validation:action grammar、權限、參數與目前 state 相容。

下面是一個從論文推導的 Python implementation sketch。它不是作者程式碼,重點是呈現 runtime boundary:

from copy import deepcopy
from dataclasses import dataclass
from typing import Any, Callable


@dataclass
class ModelStep:
    state_patch: dict[str, Any]
    action: str


def deep_merge_with_null_delete(
    current: dict[str, Any],
    patch: dict[str, Any],
) -> dict[str, Any]:
    result = deepcopy(current)

    for key, value in patch.items():
        if value is None:
            result.pop(key, None)
            continue

        if isinstance(value, dict) and isinstance(result.get(key), dict):
            result[key] = deep_merge_with_null_delete(result[key], value)
        else:
            result[key] = deepcopy(value)

    return result


def run_skill_state(
    skill_spec: str,
    initial_state: dict[str, Any],
    first_observation: str,
    call_llm: Callable[[str], str],
    parse_model_step: Callable[[str], ModelStep],
    reconcile_authorities: Callable[[dict[str, Any]], dict[str, Any]],
    validate_intent_change: Callable[[dict[str, Any], dict[str, Any]], None],
    validate_state: Callable[[dict[str, Any]], None],
    validate_action: Callable[[str, dict[str, Any]], None],
    execute_action: Callable[[str], str],
    max_steps: int = 100,
):
    state = deepcopy(initial_state)
    observation = first_observation

    for _ in range(max_steps):
        # Git/CI/runtime authority 才是完整版本與證據的來源。
        state = reconcile_authorities(state)

        prompt = build_prompt(
            skill_spec=skill_spec,
            state=state,
            observation=observation,
        )

        raw = call_llm(prompt)
        step = parse_model_step(raw)

        candidate = deep_merge_with_null_delete(
            current=state,
            patch=step.state_patch,
        )

        validate_intent_change(state, candidate)
        validate_state(candidate)
        validate_action(step.action, state)

        # 論文語意:先 commit patch,再執行 action
        state = candidate
        observation = execute_action(step.action)

        write_audit_log(
            state=state,
            action=step.action,
            observation=observation,
        )

    return state

parse_model_step() 若失敗,persistent state 不能被修改。論文描述的行為是 rollback-retry;實作時還要補:

  • retry 上限
  • retry prompt 是否回傳 validation error
  • 相同錯誤連續發生時的 fallback
  • malformed JSON 的 constrained decoding/grammar
  • action timeout、idempotency key 與重試語意
  • state snapshot/checkpoint
  • audit log retention

最重要的一條是:audit log 可以保留,但不要自動塞回模型 prompt。 安全稽核、除錯、token 計費與 provenance 仍需要完整紀錄。SKILL.state 丟棄的是「作為下一輪模型輸入的歷史」,不是要求整個系統不留 log。

為什麼 token 會從 O(T²) 變成 O(T)

傳統 history runtime 在第 t 步送入的 context 長度約為 O(t)

Σ |C_t| = O(T²)

SKILL.state 每一步只保留:

|P_t| = O(|P| + |Σ| + |O|)

只要 skill spec、state、observation 大小有上限,單步 prompt 就不隨已執行步數增加,總成本為:

Σ |P_t| = O(T)

這個 O(1) prompt footprint 有明確前提。若 tested_hypothesescmd_summary 或 observation 仍無限累積,state 只是換一個地方變長。可用的 implementation 必須替每個欄位設定容量、聚合與淘汰策略。[2]

實驗怎麼設計

論文用 SkillExecBench 與兩個公開 benchmark 測試:[2][4][5]

Benchmark任務主要測試點
SkillExecBench Warehouse500 個 shelf 的 Store/Ship/Move/Wait長 horizon、多個獨立 state variables
SkillExecBench Software Repositorybranch、commit、PR、CI transition關聯 state 與相依 action
InterCode CTF100 個 Linux bash CTF challengeopen-ended search、tool use、hypothesis tracking
Sierra τ-BenchRetail、Airline customer serviceDB API、政策限制、交易型 action

對照 runtime 包含:

  • ReAct-style full history
  • 三步 rolling window+自然語言 summary
  • structured state+full transcript 的 LangGraph-style baseline
  • sliding-window truncation
  • capped summary
  • ReAct+LLMLingua compression[6]

模型涵蓋 Gemini-3-Flash、Gemma-4-31B-it、Qwen-3-8B-it。解碼設定是 temperature 0.0、top-p 1.0。合成實驗使用五個 generator seeds,論文稱 T ≥ 50 時與 baseline 的差異在 paired t-test 下達 p < 0.01。[2]

主要結果:Prompt 維持固定,長任務差距才拉開

Warehouse long-horizon scaling

Gemini-3-Flash 的 Warehouse 結果:

HorizonRuntimeScoreAvg PromptTotal Tokens
100ReAct0.8436,3621,245,413
100Memory0.8729,6071,082,154
100Stateful0.9131,3541,062,387
100SKILL.state0.941,90565,408
200Memory0.8484,3646,175,509
200SKILL.state0.941,811122,384

T=100 時,Stateful baseline 的 total tokens 約為 SKILL.state 的 16.2 倍。到 T=200,Memory baseline 約為 50.5 倍。SKILL.state 的 average prompt 仍落在約 1,700–1,900 tokens。[2]

Noise robustness

Warehouse 固定 T=50,每步加入 5、20、50 個背景事件:

Noise events/stepReActMemoryStatefulSKILL.state
50.681.001.001.00
200.611.000.980.97
500.530.960.980.98

SKILL.state 在產生 patch 時把無關 telemetry 排除,這些內容不會進入下一輪 prompt。[2]

但這個測試的 noise 有嚴格邊界:附錄說它們是隨機、與任務完全無關、且不改變 ground-truth state 的 telemetry。這不等同於 prompt injection、惡意 tool output、互相衝突的 relevant evidence,也不能直接延伸成「可抵抗 context poisoning 攻擊」。

State recovery

論文模擬外部 actor 在 Agent loop 之外改變真實環境。三個可恢復的 Warehouse scenario 中,Prompt/Memory/Stateful 需要 5–8 步才修正;SKILL.state 收到 corrective observation 後立即更新,recovery steps 為 0。[2]

另一個 Canceled Order scenario 則是所有 runtime 都失敗。顯式 state 能降低舊資訊干擾,沒有保證 observation 不完整時也能恢復。

InterCode CTF 與 τ-Bench

RuntimeInterCode CTF Pass@1Retail Pass RateAirline Pass Rate
ReAct43.2%48.2%21.8%
Memory46.4%29.9%23.6%
Stateful41.8%51.7%28.1%
SKILL.state54.2%58.3%32.4%

InterCode CTF 中,SKILL.state total tokens 是 387k,ReAct 為 977k,降低約 60.4%。Airline 則從 ReAct 的 4.85M 降到 2.88M,降低約 40.6%。[2][4][5]

這組結果的意義不只在 token。顯式記錄 tested_hypothesesdiscovered_flags 等狀態,也讓 Agent 少重跑已失敗命令。

相同 token budget 下,壓縮歷史仍不夠

Warehouse T=100、平均 prompt 約 1,800 tokens 的 budget-matched control:

RuntimeScoreAvg PromptTotal Tokens
Sliding Window0.181,80062,100
Summary-capped0.521,84063,400
ReAct+LLMLingua0.221,81062,350
SKILL.state0.941,90565,408

把歷史截短或壓縮到相同 budget,仍可能刪掉 shelf ID、inventory allocation 這類看起來重複、實際上決定 action 的 exact relation。SKILL.state 的優勢來自資料結構與 state ownership,不只是 prompt 比較短。[2][6]

Open-weight 模型仍會被 JSON 與 merge semantics 絆倒

Gemma-4-31B 在 T=100 的 SKILL.state score 是 0.42。作者分析 failure log 後分成:[2]

  • 68%:過早 overwrite/delete,沒有做 in-place merge。
  • 20%:schema comprehension/type coercion。
  • 12%:JSON delimiter、trailing comma 等格式問題。

作者據此把主要問題歸因於 structured output adherence,並提出 grammar-constrained decoding 作為後續方向。論文沒有做 constrained-decoding ablation,所以目前只能說「failure log 主要落在結構化輸出錯誤」,還不能證明 reasoning capacity 完全不是因素。

這也提醒實作者:response_format=json 只解決語法,不會自動解決錯誤 deletion、錯誤 nested merge 或語意上不合法的 state transition。validator 仍然是核心元件。

這套方法不適合哪些任務

論文自己列出三個 sufficient-state assumption 會失效的情況:[2]

  1. Schema 必須在執行中才知道:事前無法固定 relevant state structure。
  2. 早期 observation 的價值要到後面才看得出來:當時沒寫入 state,history 又已丟棄,後面無法取回。
  3. 歷史軌跡本身就是輸出:audit、debug provenance、解釋過去 action,都需要原始 trajectory。

另外還有幾個工程邊界:

  • 論文只測 single-agent;multi-agent shared state 會遇到 concurrent writes、版本衝突與 merge policy。
  • schema authoring 需要 domain knowledge,欄位設計錯誤會把重要資訊在入口處丟掉。
  • 論文沒有定義 user-owned protected fields、使用者確認流程、field-level provenance、retention policy 或 supersession 規則;前文的 User Intent Contract 是本文補上的 production governance,不是作者已實作的功能。
  • long-running state 仍要做 size bound、snapshot、migration 與 schema versioning。
  • tool action 可能失敗、timeout 或重複執行,state commit 必須配合 idempotency 與 reconciliation。
  • noise robustness 沒有涵蓋惡意或語意相關的衝突訊號。
  • 論文與 arXiv source package 沒有附 SKILL.state/SkillExecBench 官方 code、完整 schema 與實驗 runner。

因此,這篇論文現在適合拿來做 runtime design 與 independent implementation。若要宣稱 benchmark reproduction,仍需要作者公開 artifact,或自行重建環境後把差異完整列出。

一個可執行的重現順序

第一階段:先做 deterministic toy environment

先做 Warehouse 類環境,不急著接 web browser 或真實 API:

  • 20–500 個 state slots。
  • Store/Ship/Move/Wait。
  • tool adapter 回傳 deterministic observation。
  • 每次 action 都有 ground-truth transition。

這一階段先驗證 deep merge、null deletion、schema validation、retry 不污染 state。

第二階段:同時做四種 runtime

用同一模型、同一 skill instructions、同一事件序列跑:

  • full-history ReAct
  • rolling summary
  • structured state+history
  • SKILL.state

控制 temperature、top-p、max output tokens、action parser 與 tool environment,避免把模型或工具差異誤算成 runtime 差異。

第三階段:把計量寫進 runtime

每一步至少記錄:

step_id
prompt_tokens
completion_tokens
prompt_chars
state_bytes
observation_bytes
patch_bytes
action
validator_result
retry_count
tool_result
score_delta

Audit log 存在外部,不自動回填 prompt。

第四階段:測三種壓力

  1. Horizon scaling:10/25/50/100/200 steps。
  2. Irrelevant noise:每步增加 5/20/50 筆背景 telemetry。
  3. External drift:繞過 Agent 修改 ground truth,觀察幾步能恢復。

再加一組論文沒測完整的情境:相關但互相矛盾的 observation、malicious tool output、schema migration、action timeout 與 retry。

第五階段:再接 public benchmark

  • InterCode 可測 terminal action 與 hypothesis state。
  • τ-Bench 可測 transaction、policy 與資料庫 state。
  • LLMLingua 可作 budget-matched compression baseline。

通過 toy environment 之後再接這些 benchmark,才能分清是 runtime bug、schema 問題、model formatting,還是 benchmark adapter 出錯。

我的判斷

SKILL.state 最有價值的地方,是把 Agent 長任務問題從「如何讓模型記住更多對話」改成「runtime 應該保存哪一份 canonical state」。這個方向很適合程序明確、狀態可建模、tool action 可驗證的工作:CI/CD、客服交易、terminal workflow、庫存、資料處理 pipeline、長時間批次任務。

它也把責任分得更清楚:模型負責當步推理與提出 transition;runtime 負責 schema、validation、merge、action policy、checkpoint 與 audit。模型不再是唯一的狀態管理者。

真正的難點會落在 schema 品質、使用者意圖治理與 commit semantics。state 設計得太窄,早期資訊會永久遺失;設計得太寬,prompt 還是會成長。模型也不能自行決定哪些需求可以丟,user-owned constraints 與 approval boundary 要由 runtime 保護。tool action 先更新 state 或成功後才 commit,也會直接影響一致性與安全性。

程式版本、決策與執行證據繼續由 Git、Issue/ADR、CI、runtime telemetry 與 audit log 保存完整歷史;working state 帶目前 branch、HEAD、diff、test、deployment 等權威 pointer。這能讓 prompt 保持 bounded,同時維持可回溯性。

對 Claude Code、Codex CLI、Pi 的導入判斷很明確:先在一個長、可建模、可由 Git/CI 驗證的 coding workflow 做 sidecar state 與 gated transition。只有 task success、prompt cost、重複操作、drift recovery 與 protected-intent 指標都過關,才升到 bounded-turn runtime。

SKILL.state 值得作為 task-scoped execution state 驗證,不適合直接取代全域使用者記憶或所有 Agent session。它的價值在 canonical state、驗證與 handoff;完整 provenance 繼續留在外部 source of truth。

Sources

<!-- [1] https://arxiv.org/abs/2608.26263 [2] https://arxiv.org/html/2608.26263 [3] https://arxiv.org/pdf/2608.26263 [4] https://github.com/princeton-nlp/intercode [5] https://github.com/sierra-research/tau-bench [6] https://github.com/microsoft/LLMLingua [7] https://code.claude.com/docs/en/hooks [8] https://code.claude.com/docs/en/cli-reference [9] https://developers.openai.com/codex/hooks [10] https://developers.openai.com/codex/noninteractive [11] https://developers.openai.com/codex/guides/agents-md [12] https://github.com/earendil-works/pi [13] https://github.com/earendil-works/pi/blob/853a80d26c90a14c1886f0ebb8ffaae133ca2185/packages/coding-agent/docs/extensions.md -->

Visits

--

Waiting for Cloudflare metrics.