OpenAI 在 4 月 15 日更新了 Agents SDK,加入了 sandbox 隔離、Harness/compute 分離、Manifest 工作區描述、Capabilities 分層。看完技術文件後我的第一反應是「這跟我們在做的事情不一樣」,第二反應是「但裡面有幾個結構性概念可以偷」。

這篇記錄我們從中借了什麼、怎麼落地到 openclaw-workspace-template v3.0.0,以及為什麼大部分東西我們選擇不抄。

兩個系統的定位差異

先把前提講清楚:OpenAI Agents SDK 跟我們的 workspace template 解決的是完全不同的問題。

OpenAI Agents SDK我們的 workspace template
目標使用者企業,多租戶 SaaS個人,單使用者
執行環境雲端 sandbox(Modal / E2B / Cloudflare / Vercel)本機 Mac / Linux,直接 filesystem access
權限模型Tool calls 在 unprivileged container 裡跑,隔離網路和 secret跟使用者同權限,能碰 git / cron / Telegram / Obsidian
Stateserialize_session_state() / resume(),snapshot 整個 workspace檔案系統就是 state,git 就是 snapshot
Memory內建 Memory() capability,session close 自動 summarize → consolidate自建 MemPalace:hall-tagged journal + 主題筆記 + weekly reflection + knowledge graph

OpenAI 在解的是「怎麼讓 agent 安全可靠地跑在生產環境」。我們在解的是「怎麼讓一個人的知識和自動化系統持續累積和整合」。拿來直接比就像比 AWS Lambda 跟家裡的 crontab。

所以 sandbox 隔離、multi-tenant、container snapshot 這些我們完全不需要。我們的 agent 能直接 git commit、改 crontab、發 Telegram 通知、rebuild knowledge graph——把它塞進 sandbox 等於砍掉核心價值。

但有三個結構性概念跟 runtime 無關,純粹是「怎麼描述和組織你的系統」的問題。這些值得借。

借鏡 1:Manifest → workspace.spec

OpenAI 怎麼做

OpenAI 的 Manifest 是一個 Python object,宣告式描述 agent 的工作區應該長什麼樣:

Manifest(
    entries=[
        GitRepo(url="...", path="repo"),
        LocalDir(host_path="~/notes", workspace_path="notes"),
        S3Mount(bucket="my-kb", workspace_path="kb"),
    ],
    environment={"PYTHONPATH": "/workspace/repo"},
)

跑 agent 前,sandbox 照這份 spec 組出環境。換機器、換 provider,同一份 Manifest 就能重建。

我們之前怎麼做

bootstrap.sh 裡面一堆 mkdir -pcopy_tree 呼叫:

mkdir -p "$WORKSPACE_PATH/memory"
mkdir -p "$WORKSPACE_PATH/notes/00-Inbox"
mkdir -p "$WORKSPACE_PATH/notes/01-Projects/Active"
# ...13 個目錄
copy_tree "$SCRIPT_DIR/templates"
copy_tree "$SCRIPT_DIR/scripts" "scripts"
copy_tree "$SCRIPT_DIR/cron" "cron"

要知道「工作區應該有什麼」,得讀整份 bash。

我們怎麼改

新增 templates/workspace.spec,44 行的 line-based DSL:

# workspace.spec — Declarative workspace layout
copy_tree templates
copy_tree scripts scripts
copy_tree cron cron

dir memory
dir notes/00-Inbox
dir notes/01-Projects/Active
dir notes/01-Projects/Archive
dir notes/02-Areas
dir notes/03-Resources
dir notes/04-Archive
dir .learnings
dir scripts
dir .claude/skills
dir cron/logs
dir reference
dir tmp

bootstrap.sh 裡新增一個 process_workspace_spec() function 讀這份檔案。條件邏輯(welcome flag、chmod、next-steps banner)留在 bash。

為什麼不用 YAML?不想加 parser dependency。這份 spec 只有兩個 verb(dircopy_tree),純 bash 的 parameter expansion 就能解析。遇到不認識的 verb 會直接 exit 1 並印出 workspace.spec:42: unknown verb 'bogus'

驗證方式:用舊版 bootstrap 建一個 workspace,再用新版建一個,find . | sort | diff 兩邊。結果完全一致(97 個路徑)。

借鏡 2:Capabilities → settings.capabilities.toml

OpenAI 怎麼做

OpenAI 的 Agents SDK 把 agent 的能力分成幾個 Capability 層:

Capabilities(
    Shell(),
    Filesystem(),   # apply_patch + view_image
    Skills(),
    Memory(),
    Compaction(),
)

預設是 Filesystem + Shell + Compaction。要加能力就顯式 opt-in。

我們之前怎麼做

settings.json 裡有一個 flat array:

"allow": [
  "Bash(python3 scripts/*)",
  "Bash(bash scripts/*)",
  "Bash(git status:*)",
  "Bash(git diff:*)",
  "Read(*)",
  "Grep(*)",
  "Write(memory/*)",
  "Edit(notes/*)"
]

25 個 entry 混在一起,看不出「這個 agent 到底能幹嘛」的大圖。

我們怎麼改

新增 settings.capabilities.toml 作為 source of truth,分成 6 個 capability bucket:

[capabilities.run_scripts]
description = "Execute project-owned scripts under scripts/"
allow = [
  "Bash(python3 scripts/*)",
  "Bash(bash scripts/*)",
]

[capabilities.inspect_git]
description = "Read-only git inspection"
allow = [
  "Bash(git status:*)",
  "Bash(git diff:*)",
  "Bash(git log:*)",
  "Bash(git show:*)",
  "Bash(git branch:*)",
]

[capabilities.write_memory]
description = "Append/edit memory palace artifacts"
allow = [
  "Write(memory/*)",
  "Write(MEMORY.md)",
  "Edit(memory/*)",
  "Edit(MEMORY.md)",
]
# ...

tools/build-settings.py(stdlib tomllib,零外部 dependency)讀 TOML、生成 settings.json,同時保留 hooks 區塊不動。跑兩次產出 byte-identical 的 JSON。

Permission set 完全沒變:25 個 entry,0 新增 0 移除。這不是加權限,是把既有權限整理成人類可讀的分組。

借鏡 3:serialize_session_state → save/load YAML schema

OpenAI 怎麼做

client.serialize_session_state() 把 agent 的 session 狀態序列化成結構化資料,client.resume() 在新的 sandbox 裡恢復。重點是:序列化的格式是有 schema 的,load 端可以靠固定欄位做型別化處理。

我們之前怎麼做

/save 指令產出一個 markdown 檔到 tmp/<slug>.md,裡面是自由格式的 H2 sections:

# Session State: ...
## Context
(自由發揮)
## Completed
(自由發揮)
## Pending
### High
- ...

/load 讀這份檔案,靠 Claude 的語意理解來解析。大部分時候可以用,但「哪些檔案被改過」「哪些 pending 是 high priority」這些資訊藏在 prose 裡面,跨 session 會 drift。

我們怎麼改

加入 YAML frontmatter 作為機器可解析的 schema:

---
slug: refactor-espresso-notes
saved_at: 2026-04-16T14:30:00+08:00
project: cc-memory-project
title: Refactor espresso dial-in notes
summary: >
  把散落在各處的 espresso dial-in 筆記整理成 per-bean template。

completed:
  - task: "建立 bean template"
    files: ["notes/02-Areas/Coffee/bean-template.md:1"]
    decision: "用 frontmatter field 而非 heading hierarchy"

pending:
  - priority: high
    task: "現有 5 篇 bean note 還沒套 template"
    files: ["notes/02-Areas/Coffee/ethiopia-guji.md"]
    blocker: null
    suggested_fix: "逐篇跑 regex replace + 人工校對"
---

10 個 top-level key 全部 required,空值寫 null[],不能省略 key。/load 先解析 frontmatter 做結構化摘要(pending 幾個 high / medium / low、touched 哪些檔案),再對 files[] 做 glob 檢查確認檔案還在。

舊格式的 save 檔(沒有 frontmatter)走 legacy fallback,讀 H2 sections,標記 (legacy schema)

沒抄的東西

列一下明確選擇不做的:

  • Sandbox isolation:我們的 moat 就是深度整合本機環境。隔離 = 砍功能。
  • Manifest cloud mounts(S3 / GCS / Azure Blob):state 全在本機 + git,不需要。
  • OpenAI Memory() capability:它是自動 summarize → consolidate 到 MEMORY.md。我們有自建的 MemPalace(hall tag journal、weekly reflection、pattern promotion、knowledge graph),功能複雜一個數量級。套它等於退化。
  • Harness/compute 分離:Claude Code 本來就是我們的 harness,它跟 filesystem 的耦合是 feature 不是 bug。
  • Multi-tenant 架構:我們是單使用者系統。為不存在的需求加 isolation tax 不值得。

實際效果

改完之後的差異是維護體驗:

以前現在
要知道 workspace 長什麼樣,讀 255 行 bash讀 44 行 workspace.spec
要知道 agent 有什麼權限,看 25 行 flat array 然後心算看 TOML 6 個 bucket,每個有 description
settings.json 手動改,改完忘記改了什麼改 TOML → 跑 build script → commit 兩份
/save 產出自由格式 prose,/load 靠語意猜frontmatter 有 schema,/load 做型別化摘要 + 檔案存在檢查
bootstrap 完告訴你「edit IDENTITY/USER/SOUL/TOOLS」bootstrap 完告訴你「type hi」,agent 一步一步問你

都不是大改動。workspace.spec 多了 44 行,build-settings.py 121 行,TOML 82 行,save/load schema 是 prompt 指令的修改。整個 PR 加起來 375 行新增。

但這些改動讓下次要動 template 的人(通常是三個月後的我自己)不用重新讀 bash 才知道系統長什麼樣。

結尾

OpenAI Agents SDK 在做的是幫企業把 agent 生產化的通用工具鏈。我們在做的是幫一個人極致整合生活和工作的垂直系統。兩者的 runtime 和安全需求差很遠,但「怎麼描述你的系統」這個問題是共通的。

Manifest 教我們把 workspace layout 從 bash 裡抽出來變成宣告式 spec。Capabilities 教我們把 flat permission list 分層成可 reason about 的 bucket。serialize_session_state 教我們把跨 session 的 handoff 從 prose 收緊成有 schema 的結構。

三個都不是 rocket science,但都是「痛了才知道要做」的那種改善。趁看到別人做了,順手補上。