docs: add agent guide and generic integration runbooks

This commit is contained in:
2026-04-24 19:57:29 +08:00
parent d35fe70c21
commit 1cf8bf491d
3 changed files with 1883 additions and 0 deletions

View File

@@ -0,0 +1,591 @@
# Continuity PluginGeneric / Manual Integration Runbook
> 目標讀者:**沒有 `force-recall` hook 的宿主 / orchestrator / planner 實作者**
>
> 這份文件專門說明:當你的 host 沒有 `hooks/force-recall/handler.ts` 時,如何仍然安裝 continuity plugin、提供 payload、執行 manual/generic preflight並驗證 continuity gate block 真的有生效。
> 英文版:`GENERIC_INTEGRATION.md`
---
## 0. 這份 runbook 解決什麼問題
如果你的宿主屬於以下任一情境,請優先看這份:
- 你沒有 `force-recall` hook。
- 你有自己的 planner / preflight / orchestrator。
- 你想手動把 approved-plan 狀態餵給 continuity plugin。
- 你需要一份「最少要給哪些欄位」的 payload 規格。
- 你需要知道如何驗證 continuity block 有真的擋住錯誤結案,而不是只是在文件裡說可以。
一句話總結:
**沒有 `force-recall` 也能裝 continuity plugin但你必須自己提供 generic continuity input並把回傳的 block 接進 agent prompt。**
---
## 1. 最小安裝目標
安裝完成後,至少要達到以下結果:
- 你的 host 能 import `plugins/continuity/src/index.mjs`
- 你的 host 能呼叫:
- `runGenericPreflightContinuityAdapter(...)`,或
- `runManualContinuityPreflight(...)`
- 你的 host 能把回傳的 `out.block` prepend 到 agent 會看到的 prompt/body
- 當任務完成、但還有下一步且沒有真實 dispatch receipt 時continuity gate 會擋住「直接結案」
- `cd plugins/continuity && npm test` 可通過
---
## 2. 安裝位置與檔案
建議放在:
```text
<workspace>/plugins/continuity
```
generic/manual 路徑至少需要這些檔案:
```text
plugins/continuity/
package.json
src/index.mjs
src/adapters/generic-preflight.mjs
src/config/defaults.mjs
src/config/schema.mjs
src/continuity/engine.mjs
src/continuity/evaluator.mjs
src/continuity/receipt-validator.mjs
src/continuity/receipt-store.mjs
src/continuity/types.md
examples/openclaw.continuity.example.json
examples/approved-plan-receipt.example.json
README.zh-TW.md
README.md
GENERIC_INTEGRATION.zh-TW.md
```
可先用這個命令確認:
```bash
cd <workspace>
find plugins/continuity -maxdepth 3 -type f | sort
```
---
## 3. generic/manual 路徑會用到哪些 API
`src/index.mjs` 已經 re-export 這幾個你最需要的 API
- `defaultConfig`
- `normalizeContinuityConfig()`
- `validateContinuityConfig()`
- `buildGenericContinuityInput()`
- `createGenericPreflightContinuityAdapter()`
- `runGenericPreflightContinuityAdapter()`
- `runManualContinuityPreflight()`
- `validateReceipt()`
- `isValidReceipt()`
- `writeReceipt()`
如果你只想最短路徑完成整合,通常會直接用:
- `runGenericPreflightContinuityAdapter(...)`
- `runManualContinuityPreflight(...)`
- `writeReceipt(...)`
---
## 4. continuity input 最小 contract
generic adapter 吃的是 **host-agnostic continuity input**
`src/continuity/types.md`,實務上最少要準備:
- `planId`: string
- `currentTask`: string
- `taskState`: string | null
- `nextDerivedAction`: object | null
- `replyClosureState`: string | null
- `dispatchReceipt`: object | null
- `nextTaskKnown`: boolean
- `sameApprovedPlan`: boolean
- `taskBoundaryStop`: boolean
- `highRiskStop`: boolean
可選欄位:
- `nextTaskId`: string | null
- `nextTaskKey`: string | null
- `derivedAction`: object | null
- `metadata`: object
### 最小可運作 payload 範例
```js
const source = {
planId: 'approved-plan-1',
currentTask: 'task-3',
taskState: 'complete',
nextTaskKnown: true,
sameApprovedPlan: true,
taskBoundaryStop: true,
highRiskStop: false,
nextTaskId: 'task-4',
nextDerivedAction: {
type: 'message_subagent',
task: 'continue with task-4',
},
replyClosureState: 'completed',
dispatchReceipt: null,
metadata: {
host: 'custom-orchestrator',
},
};
```
### 什麼情況下這個 payload 會觸發 gate
常見高風險情境:
- `taskState = 'complete'`
- `nextTaskKnown = true`
- `sameApprovedPlan = true`
- `nextDerivedAction != null`
- `dispatchReceipt = null`
- `replyClosureState = 'completed'`
這類情況通常代表:**目前 task 做完了、下一步也知道了,但你還沒有真實派發 receipt卻準備把整件事收掉。**
這正是 continuity gate 要攔的核心情境。
---
## 5. generic adapter 接法
### 最小接法
```js
import plugin from './plugins/continuity/src/index.mjs';
const out = plugin.runGenericPreflightContinuityAdapter({
config: plugin.defaultConfig,
source,
});
if (out?.block) {
agentPromptBody = `${out.block}\n${agentPromptBody}`;
}
```
### 回傳值你至少要看哪些欄位
`out` 常用欄位:
- `out.input`
- `out.result`
- `out.evaluation`
- `out.block`
- `out.meta.adapterName`
- `out.meta.hostAgnostic`
### 什麼時候不該忽略 `out.block`
只要 `out.block` 非空,就表示 plugin 產生了應注入 prompt 的 continuity gate block。
**不要只把 `evaluation` 印 log就算整合完成。**
真正有完成整合的最低標準,是:
- 有拿到 `out.block`
- 有把它接到 agent prompt/body
- agent 後續真的會看到該 block
---
## 6. manual runner 接法
如果你沒有完整 generic source builder或者只想先做一次單點 preflight 驗證,可用 manual runner
```js
import plugin from './plugins/continuity/src/index.mjs';
const out = plugin.runManualContinuityPreflight({
config: plugin.defaultConfig,
planId: 'approved-plan-1',
currentTask: 'task-3',
taskState: 'complete',
nextDerivedAction: {
type: 'message_subagent',
task: 'continue with task-4',
},
replyClosureState: 'completed',
nextTaskKnown: true,
sameApprovedPlan: true,
taskBoundaryStop: true,
dispatchReceipt: null,
});
if (out?.block) {
agentPromptBody = `${out.block}\n${agentPromptBody}`;
}
```
### manual runner 適合什麼情況
- 你還沒有完整 planner但想先驗證 gate 行為
- 你要做 integration smoke test
- 你在 migration 階段,先用手動欄位對接舊宿主
### manual runner 不代表可以跳過正式整合
manual runner 比較像 **preflight harness**,不是完整 host integration 的替代品。
等你確認行為正確後,仍建議回到 generic adapter把 host 真實資料映射進 source。
---
## 7. config 怎麼準備
建議先從:
```text
plugins/continuity/examples/openclaw.continuity.example.json
```
開始,內容大致如下:
```json
{
"enabled": true,
"planMatchers": ["approved-plan"],
"legalTerminalStates": [
"waiting_user",
"blocked",
"pending_verification"
],
"receiptDir": "state/approved-plan-continuity",
"requireRealDispatchReceipt": true,
"allowReplyClosureWithoutDispatch": false,
"debug": false,
"adapter": {
"forceRecall": {
"enabled": true,
"injectBlockLabel": "APPROVED_PLAN_CONTINUITY_GATE"
},
"genericPreflight": {
"enabled": true,
"injectBlockLabel": "APPROVED_PLAN_CONTINUITY_GATE"
}
}
}
```
### generic/manual 路徑先檢查這些欄位
- [ ] `enabled = true`
- [ ] `planMatchers` 符合你的 approved plan 名稱
- [ ] `legalTerminalStates` 符合你的 closure policy
- [ ] `receiptDir` 可寫
- [ ] `requireRealDispatchReceipt = true`(通常建議保持)
- [ ] `allowReplyClosureWithoutDispatch = false`(通常建議保持)
- [ ] `adapter.genericPreflight.enabled = true`
- [ ] `adapter.genericPreflight.injectBlockLabel` 有你想要的 label
---
## 8. receipt 怎麼提供
如果你的宿主真的有派發下一步,不要只在 metadata 或 log 裡寫「已派發」,請提供真實 `dispatchReceipt`
### 最小 receipt shape
依目前 validator至少要有
- `planId`
- `currentTask`
- `nextDerivedAction`
- `dispatchedAt`
- `dispatchRunId`
- `childSessionKey`
- `replyClosureState`
對照範例:
```text
plugins/continuity/examples/approved-plan-receipt.example.json
```
範例內容:
```json
{
"planId": "example-plan",
"currentTask": "task-01",
"nextDerivedAction": {
"kind": "delegate",
"target": "subagent",
"task": "placeholder"
},
"dispatchedAt": "2026-04-24T16:43:00+08:00",
"dispatchRunId": "example-run",
"childSessionKey": "session-placeholder",
"replyClosureState": "pending_verification"
}
```
### 寫 receipt 前先驗證
```js
import plugin from './plugins/continuity/src/index.mjs';
const receiptValidation = plugin.validateReceipt(receipt);
if (!receiptValidation.ok) {
throw new Error(`Invalid continuity receipt: ${receiptValidation.errors.join('; ')}`);
}
```
### 寫 receipt 範例
```js
await plugin.writeReceipt({
receiptDir: 'state/approved-plan-continuity',
receipt,
});
```
### receiptDir 先建立
```bash
cd <workspace>
mkdir -p state/approved-plan-continuity
```
---
## 9. manual/generic preflight 驗證流程
這一段是本文件最重要的實作 checklist。
### Case A應該被 gate 擋下
測這種輸入:
- task 已完成
- 下一步已知
- 同一 approved plan
- 有 nextDerivedAction
- 沒有 dispatchReceipt
- closure 想直接完成
例如:
```js
const out = plugin.runGenericPreflightContinuityAdapter({
config: plugin.defaultConfig,
source: {
planId: 'approved-plan-1',
currentTask: 'task-3',
taskState: 'complete',
nextTaskKnown: true,
sameApprovedPlan: true,
taskBoundaryStop: true,
nextTaskId: 'task-4',
nextDerivedAction: { type: 'message_subagent', task: 'continue' },
replyClosureState: 'completed',
dispatchReceipt: null,
},
});
```
你應該預期:
- `out.block` 非空
- `out.evaluation` 顯示這不是可安全直接結案的狀態
- agent prompt 若接上此 block應被提醒不可把整件事直接收掉
### Case B有合法 terminal state不應誤擋
例如:
- `replyClosureState = 'waiting_user'`
-`blocked`
-`pending_verification`
你應該預期:
- 不會把合法等待/阻塞/待驗證情境誤當成錯誤結案
### Case C有真實 receipt應與 dry-run 區分
`dispatchReceipt` 換成合法 receipt object再重跑一次。
你要確認:
- plugin 有辨識 receipt
- 行為和 `dispatchReceipt = null` 時不同
- 不是只因為 `nextDerivedAction` 存在,就一律當成已派發
---
## 10. 如何驗證 continuity block 真的有被接進 prompt
這一步很常被忽略,但它才是整合成功與否的分水嶺。
### 最低驗證標準
你至少要證明以下三件事:
1. plugin 有回傳 `out.block`
2. host 有把 `out.block` prepend 到 agent prompt/body
3. 最終 agent 真正看到的 prompt 中,存在:
```text
[APPROVED_PLAN_CONTINUITY_GATE]
```
### 建議做法
- 在 preflight / orchestrator 測試輸出中印出最終 prompt 前段
- 或在 debug mode 下 dump prompt body
- 或加一個 integration smoke test直接 assert block label 存在
### 不能只驗證什麼
以下都 **不夠**
- 只看到 `out.evaluation`
- 只看到 console log 說有 run adapter
- 只看到 code 裡有 prepend 那一行,但沒有實際驗證 prompt 結果
---
## 11. 建議 smoke test 順序
### Step 1plugin 本體測試
```bash
cd <workspace>/plugins/continuity
npm test
```
### Step 2快速 smoke
```bash
cd <workspace>/plugins/continuity
node test/continuity.smoke.test.mjs
```
### Step 3你的 generic/manual host integration test
如果你有自家 orchestrator請至少補一個 smoke case
- 無 receipt + 想 completed → 應被 gate
- 合法 terminal state → 不應誤擋
- 有合法 receipt → 行為應與無 receipt 不同
### Step 4人工檢查 prompt block
確認輸出真的含有:
```text
[APPROVED_PLAN_CONTINUITY_GATE]
```
---
## 12. 宿主實作者最常踩的坑
### 坑 1只有 planner output沒有實際 prompt injection
這會讓 continuity 只停在「有計算」,沒有真正影響 agent 行為。
### 坑 2把 dry-run next action 當成已 dispatch
`nextDerivedAction != null` 不等於 `dispatchReceipt != null`
### 坑 3payload 缺少 `planId` 或 `currentTask`
`buildGenericContinuityInput()` 若拿不到這些關鍵欄位,可能直接回不出有效 input。
### 坑 4把合法 terminal state 設定錯
例如你宿主其實接受 `waiting_user`,但 config 沒放進 `legalTerminalStates`,就可能造成誤判。
### 坑 5receipt 寫檔了,但內容不合法
`validateReceipt()`,不要跳過。
### 坑 6只測 happy path
至少要測:
- 應 fail 的情況
- 應 pass / 不誤擋的情況
- 有 receipt 與無 receipt 的差異
---
## 13. 一頁版 generic/manual checklist
### 安裝
- [ ] 複製 `plugins/continuity` 到 workspace
- [ ] 確認 `src/index.mjs` 可 import
- [ ] 建立 `state/approved-plan-continuity/`
### config
- [ ] 套用 example config
- [ ] 開啟 `adapter.genericPreflight.enabled`
- [ ] 確認 `receiptDir` 可寫
### host 接法
- [ ] 準備 generic continuity payload
- [ ] 呼叫 `runGenericPreflightContinuityAdapter(...)``runManualContinuityPreflight(...)`
- [ ]`out.block` prepend 到 agent prompt/body
### receipt
- [ ] 真有派發時才產生 receipt
- [ ]`validateReceipt()`
- [ ]`writeReceipt()`
### 驗證
- [ ] `cd plugins/continuity && npm test`
- [ ] 驗證無 receipt 時會擋錯誤結案
- [ ] 驗證合法 terminal state 不誤擋
- [ ] 驗證最終 prompt 含 continuity block
---
## 14. 什麼叫 generic/manual 整合完成
只有在以下條件都成立時,才算真的完成:
- 不是只完成 plugin 安裝,而是 host 真的有呼叫 adapter
- 不是只拿到 evaluation而是 block 真的有注入 agent prompt
- 不是只做 dry-run而是實際 dispatch 時可提供真實 receipt
- 不是只測 happy path而是有驗證 gate fail/pass 的差異
- 文件足夠讓陌生宿主實作者知道自己要準備哪些欄位、怎麼驗證
如果這五點都做到,才算接近「沒有 `force-recall` 的宿主也能照文件安裝」。
---
## 15. 參考文件
- `plugins/continuity/README.zh-TW.md`
- `plugins/continuity/README.md`
- `plugins/continuity/AGENT_GUIDE.zh-TW.md`
- `plugins/continuity/AGENT_GUIDE.md`
- `plugins/continuity/src/continuity/types.md`
- `plugins/continuity/examples/openclaw.continuity.example.json`
- `plugins/continuity/examples/approved-plan-receipt.example.json`
如果你的宿主未來會新增專屬 adapter也建議保留這份 generic/manual runbook作為最低整合基準。