前幾篇介紹 Codex 和 Claude 時,我們追的是一個 Agent Run,OpenClaw 當然也有。
但如果真的想自己打造一個長期運作的 Agent,更值得看的問題是:
一個 Agent 到底怎麼從幾份 Markdown、Session、Tool,組成一次真正可以執行的工作?
這篇只用一個例子。
假設我先建立一個航空客服 Agent:
agentId = flight
名字 = 小航
之後再跟小航說:
幫我建立一個票規助理,專門檢查退改限制。
系統裡因此多一個:
agentId = fare_rules
名字 = 票規助理
最後 Alice 從 Slack 問:
BR123 可以改到明天下午嗎?
小航先取得訂位資料,再把票規檢查交給 fare_rules,最後整理結果回覆 Alice。
整篇只看四件事:
1. Markdown 怎麼真的進到 Model?
2. 自然語言怎麼真的建立第二個 Agent?
3. A 怎麼把工作交給 B,再拿回結果?
4. Runtime、Tool、Skill、SOP 到底怎麼分工?
SOUL.md 寫了「回答簡潔」,Model 為什麼真的看得到?先假設小航已經存在,它的 Workspace 是:
~/.openclaw/workspace-flight
裡面有:
| 順序 | 標準檔案 | 放進上下文的目的 |
|---|---|---|
| 1 | AGENTS.md |
工作指示,例如接到改票需求後先收集哪些資料 |
| 2 | SOUL.md |
行為原則與語氣 |
| 3 | IDENTITY.md |
名稱、形象等身分資料 |
| 4 | USER.md |
使用者資訊與偏好 |
| 5 | BOOTSTRAP.md |
首次設定引導,依設定完成狀態過濾 |
| 6 | MEMORY.md |
符合注入條件的長期摘要 |
例如:
SOUL.md
你叫小航。
回答乘客問題時保持簡潔。
不要一次丟出大量規則。
磁碟上的 Markdown,怎麼變成 Model 的輸入?
Model 本身不會掃 Workspace。
OpenClaw 要先經過一條明確的載入流程。
OpenClaw 執行 flight 時,會先解析它的 Workspace,接著呼叫:
src/agents/workspace.ts
loadWorkspaceBootstrapFiles()
它讀一份固定清單:
src/agents/workspace-bootstrap-policy.ts
AGENTS.md
SOUL.md
IDENTITY.md
USER.md
BOOTSTRAP.md
MEMORY.md
所以:
workspace-flight/
├── AGENTS.md ← 會自動讀
├── SOUL.md ← 會自動讀
├── USER.md ← 會自動讀
└── flight-sop.md ← 不會因為是 Markdown 就自動讀
loadWorkspaceBootstrapFiles() 讀完後,拿到的大概是:
{
name: "SOUL.md",
path: ".../SOUL.md",
content: "...",
missing: false
}
這時還只是「檔案資料」,還沒有進 Model。
下一步會進:
src/agents/bootstrap-files.ts
resolveBootstrapFilesForRun()
resolveBootstrapContextForRun()
處理:
這是哪種 Session?
哪些文件這次可以載入?
Workspace bootstrap 是否完成?
有沒有個人的 USER.md?
Context 是否超過大小限制?
最後buildBootstrapContextForFiles(),會套用 Context Budget,把文件轉成contextFiles
contextFiles 最後才進 System Prompt接著buildAgentSystemPrompt()會把 Runtime 本身的指示、Tools、Skills,以及 Workspace 的 contextFiles 組在一起。
Workspace 文件真正被放進 Project Context 的邏輯在:
src/agents/system-prompt-context-files.ts
最後,buildAgentSystemPrompt() 會把整理後的 contextFiles 交給 buildProjectContextSection(),產生 # Project Context 區塊,並直接加入完整的 System Prompt。因此 AGENTS.md、SOUL.md 等 Workspace 文件不是由 Model 自己讀取,而是由 OpenClaw Runtime 先載入,再組進 System Prompt。
# Project Context
## /workspace/AGENTS.md
收到改票需求後,
先確認訂位、票規與新航班。
## /workspace/SOUL.md
你叫小航。
回答乘客問題時保持簡潔。
這個Repo會在後續 Turn 重新檢查 Bootstrap 文件,而不是 Agent 建立時只讀一次,所以修改soul.md,會被重新讀取。
現在小航已經能服務乘客。
接著我對它說:
幫我建立一個票規助理,專門檢查退改限制。
這裡其實包含兩個不同工作:
Model:
理解使用者想建立一個新 Agent
以及:
Host:
真的修改設定、建立 Workspace
OpenClaw 把這兩件事分開。
官方文件明確支援:
已配置的 Agent 可以透過
openclawTool 請 OpenClaw 建立另一個 Agent。
因此大致流程是:
User
│
│ 幫我建立票規助理
▼
小航 Model
│
│ 理解意圖
▼
openclaw Tool
│
▼
OpenClaw System Agent
自然語言仍先交給 Model 理解。
Model 再選擇:
create_agent
這個 typed operation。
例如 Model 最後提出的 Tool Call 可以簡化成:
{
"action": "create_agent",
"agentId": "fare_rules",
"name": "票規助理",
"purpose": "檢查退票與改票限制"
}
system-agent-tool.ts 會把它轉成:
kind = create-agent
但到這裡 Agent 還沒真的建立。
因為建立 Agent 是 Persistent Operation,會修改:
OpenClaw Config
Workspace
Agent State
所以還必須經過 Host 的:
Permission Policy
Authorization
必要時 Human Approval
createAgent()通過授權後,才進入:
src/agents/agent-create.ts
createAgent()
如果先忽略大量安全檢查,它最重要的工作可以濃縮成兩件。
第一個:
applyAgentConfig()
把新 Agent 加入設定。
原本:
agents
└── flight
變成:
agents
├── flight
└── fare_rules
第二個:
ensureAgentWorkspace()
建立或準備:
~/.openclaw/workspace-fare-rules
以及必要的 Bootstrap 文件。
這裡是 OpenClaw Multi-Agent 很容易混淆的一點。
完成:
createAgent()
只代表:
fare_rules
這個持久 Agent 已經存在。
它現在有:
自己的 Agent Config
自己的 Workspace
自己的狀態空間
但還沒有在處理 Alice 的票規。
真正讓它開始做一件事情,是後面的:
sessions_spawn
所以兩者可以簡單分成:
createAgent()
→ 建立「這個 Agent 是誰」
sessions_spawn
→ 建立「這次請它做什麼」
這個區分也是接下來理解委派的關鍵。
現在系統中有:
flight
→ 小航
fare_rules
→ 票規助理
Alice 從 Slack 問:
BR123 可以改到明天下午嗎?
這次直接跟著資料走。
Gateway 收到 Slack 訊息後,會先把它 Route 到:
agentId = flight
並找到 Alice 對應的:
sessionKey #ex. agent:flight:slack:direct:alice
這個 sessionKey 可以直接理解成:
OpenClaw 用來定位一段邏輯對話的地址。
所以 Alice 與 Bob 都使用小航,不代表他們一定共用同一段 Conversation。
實際是否共享,仍取決於 Session Scope 設定。
目前版本的 Agent runtime state 預設放在:
~/.openclaw/agents/<agentId>/agent/openclaw-agent.sqlite
因此小航有:
~/.openclaw/agents/flight/agent/openclaw-agent.sqlite
票規助理則有:
~/.openclaw/agents/fare_rules/agent/openclaw-agent.sqlite
Alice 原本的聊天歷史屬於 flight。
票規助理之後建立的 Child Session,則屬於 fare_rules。
兩邊不是共用同一份 Transcript。
例如小航透過訂位 Tool 查到:
PNR = ABC123
Fare Basis = Y26
Ticket Status = OPEN
Requested Date = tomorrow
現在它不自己處理所有票規,而是呼叫:
sessions_spawn
例如:
{
"agentId": "fare_rules",
"task": "檢查此訂位是否可改到明天下午。Fare Basis=Y26,Ticket Status=OPEN。請回傳限制與費用,不要執行改票。",
"context": "isolated",
"completionTarget": "parent"
}
入口在:
src/agents/tools/sessions-spawn-tool.ts
最後會進:
spawnSubagentDirect()
並建立一個新的 Child Session。
這裡是最值得注意的地方。
OpenClaw 的:
context = "fork"
目前只支援:
Requester Agent
=
Target Agent
如果:
flight
→ fare_rules
是不同 Agent,就必須使用:
context = "isolated"
因此票規助理不會因為和小航在同一個 Gateway,就自動得到:
Alice 全部聊天紀錄
小航所有 Tool Result
小航的 Persona
真正需要的資訊,必須由小航放進:
task
所以這次 B 真正收到的是:
檢查改票限制
Fare Basis = Y26
Ticket Status = OPEN
需求 = 明天下午
不要執行改票
而不是整段 Alice Conversation。
這其實是一個很乾淨的隔離方式:
A 的 Context
≠
B 的 Context
A 要明確決定哪些資料可以交給 B。
建立 Child Session 時:
createInitialSubagentSession()
除了產生新的 Session Identity,還會記錄:
spawnedBy
parentSessionKey
completionOwnerSessionKey
概念上:
fare_rules child session
spawnedBy
= Alice 的 flight session
parentSessionKey
= Alice 的 flight session
completionOwnerSessionKey
= Alice 的 flight session
這代表系統不需要讓 B 自己記得:
「我做完之後要通知小航。」
Runtime 一開始就已經知道這次工作的 Parent 是誰。
所以完整流程是:
Alice
↓
flight Session
↓
小航查訂位
↓
sessions_spawn
↓
建立 fare_rules Child Session
↓
B 執行票規檢查
↓
完成結果
↓
completionOwnerSessionKey
↓
flight Session
↓
小航整理結果
↓
Alice
例如 B 最後得到:
Y26 可以改期
手續費 TWD 1,500
另補 Fare Difference
仍需確認新航班座位
這個結果回到 Parent Session 後,小航才把它整理成:
可以改到明天下午,但目前票規會收取
TWD 1,500 改票費,另外仍需計算票價差額。
我還需要確認明天下午航班的座位後,
才能提供最終金額。
所以 Multi-Agent 的本質不是:
兩個 Model 自己互相聊天
而是:
A 呼叫 sessions_spawn
↓
Runtime 建立 B 的 Session
↓
保存 Parent / Child 關係
↓
B 執行
↓
Runtime 將結果交回 A
走完前面的 Case,其實就不需要另外背很多定義。
直接看小航需要什麼。
例如改票 Skill 可以寫:
收到改票需求
↓
先取得訂位
↓
確認 Ticket Status
↓
查票規
↓
確認新航班
↓
試算費用
↓
回報乘客
Skill 解決的是:
Agent 應該怎麼做這類工作?
它比較像操作方法。
但 Skill 本身不會真的查訂位,也不會真的修改 Booking。
例如:
get_booking()
search_flights()
quote_change()
update_booking()
sessions_spawn()
Model 可以根據 Skill 決定:
下一步要呼叫 get_booking()
但真正取得資料的是 Tool。
同樣地:
sessions_spawn
也不是一句 Prompt。
它是真的 Runtime Tool,會建立 Child Session。
所以:
Skill
→ 教 Model 怎麼工作
Tool
→ 真的執行工作
假設航空公司的規則是:
未取得乘客確認,不得真的改票。
如果只在 Skill 寫:
請先取得乘客確認,再執行改票。
這仍然只是 Model Instruction。
如果「未取得乘客確認不得改票」是不可違反的業務規則,不需要修改 OpenClaw 核心程式;應在自行串接的 update_booking Tool 或後端 API 加入檢查。OpenClaw 的 Tool Policy 可以限制 Agent 是否能使用該 Tool,若還需要每次呼叫前再次攔截,也可以透過 Plugin 的 before_tool_call Hook 加上額外 Gate。
最後才是 Runtime。
小航執行一次工作時,需要準備:
Workspace Context
Session History
Skills
Tools
Model
然後送進 Agent Loop:
Model
↓
Tool
↓
Tool Result
↓
Model
因此可以把這次航空 Case 收斂成:
| 需求 | 放在哪裡 |
|---|---|
| 「你叫小航、回答簡潔」 | SOUL.md / IDENTITY.md |
| 航空客服的一般工作規則 | AGENTS.md |
| 改票 SOP | Skill |
| 查訂位、查航班、改票 | Tool |
| 將票規工作交給 B | sessions_spawn |
| Alice 與 B 各自的工作紀錄 | Session / SQLite |
| 未確認不得改票 | Tool / Backend Gate |
| 把 Context、Session、Tool、Model 組起來 | Runtime |
這張表其實就是 OpenClaw Agent 架構最重要的部分。