14 KiB
Continuity Plugin:Generic / Manual Integration Runbook
目標讀者:沒有
force-recallhook 的宿主 / orchestrator / planner 實作者這份文件專門說明:當你的 host 沒有
hooks/force-recall/handler.ts時,如何仍然安裝 continuity plugin、提供 payload、執行 manual/generic preflight,並驗證 continuity gate block 真的有生效。
英文版:
GENERIC_INTEGRATION.md
0. 這份 runbook 解決什麼問題
如果你的宿主屬於以下任一情境,請優先看這份:
- 你沒有
force-recallhook。 - 你有自己的 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.blockprepend 到 agent 會看到的 prompt/body - 當任務完成、但還有下一步且沒有真實 dispatch receipt 時,continuity gate 會擋住「直接結案」
cd plugins/continuity && npm test可通過
2. 安裝位置與檔案
建議放在:
<workspace>/plugins/continuity
generic/manual 路徑至少需要這些檔案:
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
可先用這個命令確認:
cd <workspace>
find plugins/continuity -maxdepth 3 -type f | sort
3. generic/manual 路徑會用到哪些 API
src/index.mjs 已經 re-export 這幾個你最需要的 API:
defaultConfignormalizeContinuityConfig()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: stringcurrentTask: stringtaskState: string | nullnextDerivedAction: object | nullreplyClosureState: string | nulldispatchReceipt: object | nullnextTaskKnown: booleansameApprovedPlan: booleantaskBoundaryStop: booleanhighRiskStop: boolean
可選欄位:
nextTaskId: string | nullnextTaskKey: string | nullderivedAction: object | nullmetadata: object
最小可運作 payload 範例
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 = truesameApprovedPlan = truenextDerivedAction != nulldispatchReceipt = nullreplyClosureState = 'completed'
這類情況通常代表:目前 task 做完了、下一步也知道了,但你還沒有真實派發 receipt,卻準備把整件事收掉。
這正是 continuity gate 要攔的核心情境。
5. generic adapter 接法
最小接法
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.inputout.resultout.evaluationout.blockout.meta.adapterNameout.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:
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 怎麼準備
建議先從:
plugins/continuity/examples/openclaw.continuity.example.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 = trueplanMatchers符合你的 approved plan 名稱legalTerminalStates符合你的 closure policyreceiptDir可寫requireRealDispatchReceipt = true(通常建議保持)allowReplyClosureWithoutDispatch = false(通常建議保持)adapter.genericPreflight.enabled = trueadapter.genericPreflight.injectBlockLabel有你想要的 label
8. receipt 怎麼提供
如果你的宿主真的有派發下一步,不要只在 metadata 或 log 裡寫「已派發」,請提供真實 dispatchReceipt。
最小 receipt shape
依目前 validator,至少要有:
planIdcurrentTasknextDerivedActiondispatchedAtdispatchRunIdchildSessionKeyreplyClosureState
對照範例:
plugins/continuity/examples/approved-plan-receipt.example.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 前先驗證
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 範例
await plugin.writeReceipt({
receiptDir: 'state/approved-plan-continuity',
receipt,
});
receiptDir 先建立
cd <workspace>
mkdir -p state/approved-plan-continuity
9. manual/generic preflight 驗證流程
這一段是本文件最重要的實作 checklist。
Case A:應該被 gate 擋下
測這種輸入:
- task 已完成
- 下一步已知
- 同一 approved plan
- 有 nextDerivedAction
- 沒有 dispatchReceipt
- closure 想直接完成
例如:
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
這一步很常被忽略,但它才是整合成功與否的分水嶺。
最低驗證標準
你至少要證明以下三件事:
- plugin 有回傳
out.block - host 有把
out.blockprepend 到 agent prompt/body - 最終 agent 真正看到的 prompt 中,存在:
[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 1:plugin 本體測試
cd <workspace>/plugins/continuity
npm test
Step 2:快速 smoke
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
確認輸出真的含有:
[APPROVED_PLAN_CONTINUITY_GATE]
12. 宿主實作者最常踩的坑
坑 1:只有 planner output,沒有實際 prompt injection
這會讓 continuity 只停在「有計算」,沒有真正影響 agent 行為。
坑 2:把 dry-run next action 當成已 dispatch
nextDerivedAction != null 不等於 dispatchReceipt != null。
坑 3:payload 缺少 planId 或 currentTask
buildGenericContinuityInput() 若拿不到這些關鍵欄位,可能直接回不出有效 input。
坑 4:把合法 terminal state 設定錯
例如你宿主其實接受 waiting_user,但 config 沒放進 legalTerminalStates,就可能造成誤判。
坑 5:receipt 寫檔了,但內容不合法
先 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.blockprepend 到 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.mdplugins/continuity/README.mdplugins/continuity/AGENT_GUIDE.zh-TW.mdplugins/continuity/AGENT_GUIDE.mdplugins/continuity/src/continuity/types.mdplugins/continuity/examples/openclaw.continuity.example.jsonplugins/continuity/examples/approved-plan-receipt.example.json
如果你的宿主未來會新增專屬 adapter,也建議保留這份 generic/manual runbook,作為最低整合基準。