---
slug: skill-state-scalable-long-horizon-agent-skills
title: "SKILL.state：長任務 Agent 不再重播整段對話，改用顯式執行狀態"
status: published
excerpt: "SKILL.state 用 structured state 取代長任務 Agent 的完整對話重播。本文拆解 runtime、Claude／Codex／Pi 接法與導入判斷。"
category: AI
tags: [agent, llm, runtime, state-management, paper]
author: Seer
author_role: Author
read_time: 19 min
cover: "/static/skill-state-scalable-long-horizon-agent-skills-cover.png"
closing_note: ""
published_at: "2026-08-31T08:12:20Z"
updated_at: "2026-08-31T08:12:20Z"
---

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

[SKILL.state: Scalable Long-Horizon Agent Skills](https://arxiv.org/abs/2608.26263) 提出的做法很直接：把 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`、`Σ_t`、`O_t`：固定程序、目前狀態、最新觀察。
- 模型仍可在單一步驟內做多步 reasoning，但 reasoning 不會進入下一輪 prompt。
- 模型輸出 `state_patch` 與 `action`；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 的核心資料流

論文把每一步輸入寫成：

```text
A_t = (P, Σ_t, O_t)
```

- `P`：immutable procedural specification，也就是 skill instructions、可用 action、環境規則與角色限制。
- `Σ_t`：第 `t` 步的 structured execution state。
- `O_t`：環境最新回傳的 observation。

模型產生：

```text
(R_t, ΔΣ_t, a_t)
```

- `R_t`：當步 reasoning。
- `ΔΣ_t`：JSON state patch，只描述要新增、修改或刪除的 state。
- `a_t`：下一個 action。

runtime 驗證 patch 後更新：

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

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

```mermaid
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：

```json
{
  "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 | 是，bounded | Runtime state store |
| Source Authority | branch、HEAD、diff、tag、正式文件 | 只放 pointer／current snapshot | Git／文件系統 |
| Runtime Evidence | test、CI、telemetry、deployment state | 只放摘要與最新證據 pointer | CI／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**，例如：

```json
{
  "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：

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

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

```text
.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.md`、`CLAUDE.md` 適合放穩定指令，不適合每輪覆寫 mutable state。完整 tool output 進 audit／artifact store；`state.json` 只留有上限的決策快照與外部 pointer。

| 工具 | 最接近完整 SKILL.state 的作法 | Native 接點 | 主要限制 |
|---|---|---|---|
| Claude Code | one-shot `claude -p`＋外部 controller | Hooks、structured output | 互動 session 仍保留 transcript |
| Codex CLI | `codex exec --ephemeral --json --output-schema`＋外部 controller | Hooks、JSONL events、App Server／SDK | output schema 只約束 final response |
| Pi | project extension＋自訂 transition tool | `before_agent_start`、`context`、`tool_call`、`tool_result` | 平行 tool call 需要 single-writer／CAS |

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

Claude Code 的 [Hooks](https://code.claude.com/docs/en/hooks) 已提供 `SessionStart`、`UserPromptSubmit`、`PreToolUse`、`PostToolUse`、`PostToolUseFailure`、`PreCompact`、`Stop` 與 `SessionEnd` 等事件。Hook 可以補 `additionalContext`、拒絕 tool call，或在 tool 完成後收集 evidence。[7]

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

```bash
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_patch` 與 `action`，不直接執行 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 Hook | SKILL.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_read`、`state_transition`、`state_confirm`、`authority_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](https://developers.openai.com/codex/noninteractive/) 可以用 `--json` 輸出 JSONL events，包含 thread／turn lifecycle、command execution、file change、MCP tool call 與完成狀態；`--output-schema` 則約束 final response。最保守的模式同樣讓 Codex 只提議 transition：[10]

```bash
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.started`、`turn.*`、`item.*` 與 error，最後解析符合 schema 的 response，再執行 action。`--ephemeral` 只是不把 session 存到磁碟；真正的 bounded context 仍取決於每個 transition 都開新 invocation，且不要把上一輪 JSONL 全部塞回下一輪。

Codex 現在也有官方 [Hooks](https://developers.openai.com/codex/hooks/)，包括 `SessionStart`、`UserPromptSubmit`、`PreToolUse`、`PermissionRequest`、`PostToolUse`、`PreCompact`、`Stop` 與 `SessionEnd`。對應方式與 Claude 類似：[9]

- `SessionStart`／`UserPromptSubmit`：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](https://developers.openai.com/codex/guides/agents-md/) 只放穩定流程、repo invariant 與 state tool 使用規則。Branch、HEAD、CI run、暫時 workaround 留在 task state／外部權威，避免每次迭代都改寫 AGENTS.md。[11]

### Pi：用 extension API 直接改造 agent loop

Pi 的 [extension API](https://github.com/earendil-works/pi/blob/853a80d26c90a14c1886f0ebb8ffaae133ca2185/packages/coding-agent/docs/extensions.md) 更接近這篇論文需要的 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`。概念骨架如下：

```typescript
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：

```text
模型提議 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 或 deployment | Authority refresh 能處理 stale state |
| 需要可稽核 handoff | Task state 與完整 audit 分離，接手者先看 canonical snapshot |

### 不適合導入的訊號

| 工作特徵 | 主要問題 |
|---|---|
| 一兩輪就完成的問答、簡短修改 | Adapter、schema、state store 的成本高於收益 |
| 創作、研究探索，早期素材的價值要到後面才知道 | 固定 schema 容易過早丟掉內容 |
| 成功條件與 state structure 持續改變 | Schema migration 會變成主要工作 |
| Tool result 無法驗證，也沒有 source of truth | Runtime 無法判斷何時能 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 bytes | Context 是否真的維持 bounded |
| Task success、人工修正次數 | 壓縮後有沒有犧牲正確性 |
| 重複 command／重複 hypothesis | Structured state 是否減少重工 |
| Validator reject／retry | Schema 與模型輸出是否穩定 |
| Stale pointer／CAS conflict | 外部 drift 與並行寫入是否可控 |
| Recovery steps | Git／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：

```json
{
  "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：

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

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

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

```text
Customer ordered item_12.
```

目前 state 顯示 `item_12` 在 `shelf_42`。模型推理後回傳：

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

下一筆 observation 才是：

```text
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_state` 與 `confirmed_state` 分開，適合遠端 API、交易或非同步 workflow。

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

## Runtime 要怎麼實作

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

```text
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_patch` 與 `action`。
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：

```python
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)`：

```text
Σ |C_t| = O(T²)
```

SKILL.state 每一步只保留：

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

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

```text
Σ |P_t| = O(T)
```

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

## 實驗怎麼設計

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

| Benchmark | 任務 | 主要測試點 |
|---|---|---|
| SkillExecBench Warehouse | 500 個 shelf 的 Store／Ship／Move／Wait | 長 horizon、多個獨立 state variables |
| SkillExecBench Software Repository | branch、commit、PR、CI transition | 關聯 state 與相依 action |
| InterCode CTF | 100 個 Linux bash CTF challenge | open-ended search、tool use、hypothesis tracking |
| Sierra τ-Bench | Retail、Airline customer service | DB 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 結果：

| Horizon | Runtime | Score | Avg Prompt | Total Tokens |
|---:|---|---:|---:|---:|
| 100 | ReAct | 0.84 | 36,362 | 1,245,413 |
| 100 | Memory | 0.87 | 29,607 | 1,082,154 |
| 100 | Stateful | 0.91 | 31,354 | 1,062,387 |
| 100 | SKILL.state | **0.94** | **1,905** | **65,408** |
| 200 | Memory | 0.84 | 84,364 | 6,175,509 |
| 200 | SKILL.state | **0.94** | **1,811** | **122,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／step | ReAct | Memory | Stateful | SKILL.state |
|---:|---:|---:|---:|---:|
| 5 | 0.68 | 1.00 | 1.00 | 1.00 |
| 20 | 0.61 | 1.00 | 0.98 | 0.97 |
| 50 | 0.53 | 0.96 | 0.98 | 0.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

| Runtime | InterCode CTF Pass@1 | Retail Pass Rate | Airline Pass Rate |
|---|---:|---:|---:|
| ReAct | 43.2% | 48.2% | 21.8% |
| Memory | 46.4% | 29.9% | 23.6% |
| Stateful | 41.8% | 51.7% | 28.1% |
| SKILL.state | **54.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_hypotheses`、`discovered_flags` 等狀態，也讓 Agent 少重跑已失敗命令。

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

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

| Runtime | Score | Avg Prompt | Total Tokens |
|---|---:|---:|---:|
| Sliding Window | 0.18 | 1,800 | 62,100 |
| Summary-capped | 0.52 | 1,840 | 63,400 |
| ReAct＋LLMLingua | 0.22 | 1,810 | 62,350 |
| SKILL.state | **0.94** | 1,905 | 65,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

每一步至少記錄：

```text
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](https://github.com/princeton-nlp/intercode) 可測 terminal action 與 hypothesis state。
- [τ-Bench](https://github.com/sierra-research/tau-bench) 可測 transaction、policy 與資料庫 state。
- [LLMLingua](https://github.com/microsoft/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：SKILL.state arXiv abstract 與 metadata](https://arxiv.org/abs/2608.26263)
- [2：SKILL.state HTML 全文與附錄](https://arxiv.org/html/2608.26263)
- [3：SKILL.state PDF](https://arxiv.org/pdf/2608.26263)
- [4：InterCode 官方 repository](https://github.com/princeton-nlp/intercode)
- [5：Sierra τ-Bench 官方 repository](https://github.com/sierra-research/tau-bench)
- [6：Microsoft LLMLingua 官方 repository](https://github.com/microsoft/LLMLingua)
- [7：Claude Code Hooks](https://code.claude.com/docs/en/hooks)
- [8：Claude Code CLI reference](https://code.claude.com/docs/en/cli-reference)
- [9：Codex Hooks](https://developers.openai.com/codex/hooks)
- [10：Codex non-interactive mode](https://developers.openai.com/codex/noninteractive)
- [11：Codex AGENTS.md guide](https://developers.openai.com/codex/guides/agents-md)
- [12：Pi coding agent repository](https://github.com/earendil-works/pi)
- [13：Pi extension API，固定 revision](https://github.com/earendil-works/pi/blob/853a80d26c90a14c1886f0ebb8ffaae133ca2185/packages/coding-agent/docs/extensions.md)

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