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,701 @@
# Continuity Plugin MVP: Agent Installation and Operations Guide
> Audience: OpenClaw agents / implementers
>
> This is a hands-on runbook and checklist, not a conceptual overview.
> If you only need to know whether to install it, how to wire it, and how to verify it, start here instead of reading the full README first.
## 0. When should you install this plugin?
### Recommended situations
Install this plugin if any of the following are true in your workspace:
- You run **approved-plan** style long-running or multi-step tasks.
- You do not want the agent to treat a finished task as “the whole job is done” by default.
- You need the agent to decide whether there is a **next task to dispatch** inside the same approved plan.
- You need a **dispatch receipt** as evidence that the next step was actually dispatched, instead of only saying so in prose.
- You already use `hooks/force-recall/handler.ts` and want to inject the approved-plan continuity gate into the agent prompt.
- You do not have a `force-recall` hook, but you do have your own preflight / planner / orchestrator and want to call the generic continuity adapter directly.
### Cases where you can skip it for now
You can defer installation, or treat this plugin as reference only, when:
- Your workspace has no approved-plan / auto-chain / dispatch flow.
- You only need ordinary single-turn chat behavior and do not need a continuity hard gate.
- You do not have a `force-recall` hook and you also do not plan to add generic glue code in your own host/orchestrator.
---
## 1. Installation target and expected outcome
After installation, the minimum expected result is:
- your host (for example `force-recall` or your own preflight path) can load `plugins/continuity/src/index.mjs`
- the host calls one of these during preflight:
- `runForceRecallContinuityAdapter(...)`
- `runGenericPreflightContinuityAdapter(...)`
- `runManualContinuityPreflight(...)`
- the agent prompt contains a visible `[APPROVED_PLAN_CONTINUITY_GATE] ... [/APPROVED_PLAN_CONTINUITY_GATE]` block
- when an approved-plan task is finished but there is no real next-step dispatch receipt, the gate blocks an incorrect closure
- plugin tests and the relevant host smoke path pass
---
## 2. Where should it live?
### Standard location
Place the plugin here inside the workspace:
```text
<workspace>/plugins/continuity
```
Recommended layout (adjust as needed for your host):
```text
<workspace>/
hooks/
force-recall/
handler.ts
HOOK.md
plugins/
continuity/
AGENT_GUIDE.zh-TW.md
AGENT_GUIDE.md
README.zh-TW.md
README.md
HOOK.md
GENERIC_INTEGRATION.zh-TW.md
GENERIC_INTEGRATION.md
package.json
examples/
src/
test/
scripts/
test_force_recall_long_task_preflight.mjs
```
### Actual path for this document set
```text
/home/alice/.openclaw/workspace/plugins/continuity
```
---
## 3. Pre-install checklist
Before you start, confirm:
- [ ] your workspace has `hooks/force-recall/handler.ts`, or you already know which generic/manual host path will call the plugin
- [ ] your workspace has `scripts/test_force_recall_long_task_preflight.mjs` if you intend to use the `force-recall` path
- [ ] your workspace is willing to adopt the approved-plan continuity hard gate
- [ ] you understand that `force-recall` is the most mature adapter, but not the only host path
- [ ] you understand that the receipt is a **minimum contract**, not a complete workflow engine
Quick checks:
```bash
cd <workspace>
ls plugins/continuity/package.json
ls plugins/continuity/src/index.mjs
ls hooks/force-recall/handler.ts
ls scripts/test_force_recall_long_task_preflight.mjs
```
If you do not have `force-recall`, see `GENERIC_INTEGRATION.zh-TW.md` or `GENERIC_INTEGRATION.md` for the generic/manual path.
---
## 4. Required files
### Required files
At minimum, you should have:
```text
plugins/continuity/
package.json
src/index.mjs
src/adapters/force-recall.mjs
src/adapters/generic-preflight.mjs
src/config/defaults.mjs
src/config/schema.mjs
src/continuity/evaluator.mjs
src/continuity/receipt-validator.mjs
src/continuity/receipt-store.mjs
examples/openclaw.continuity.example.json
examples/approved-plan-receipt.example.json
```
### Strongly recommended files
```text
plugins/continuity/
README.zh-TW.md
README.md
HOOK.md
AGENT_GUIDE.zh-TW.md
AGENT_GUIDE.md
GENERIC_INTEGRATION.zh-TW.md
GENERIC_INTEGRATION.md
test/continuity.config.test.mjs
test/continuity.receipt-validator.test.mjs
test/continuity.receipt-store.test.mjs
test/continuity.evaluator.test.mjs
test/continuity.plugin.test.mjs
test/continuity.smoke.test.mjs
```
### If files are missing, what should you restore first?
Priority order:
1. `src/index.mjs`
2. `src/adapters/force-recall.mjs`
3. `src/adapters/generic-preflight.mjs`
4. `src/continuity/evaluator.mjs`
5. `src/continuity/receipt-validator.mjs`
6. `src/continuity/receipt-store.mjs`
7. `examples/openclaw.continuity.example.json`
8. `test/continuity.smoke.test.mjs`
---
## 5. Direct installation steps
### Path A: you are already inside the same repo/workspace
- [ ] confirm `plugins/continuity` exists
- [ ] confirm `package.json` and `src/index.mjs` exist
- [ ] confirm your chosen host entrypoint exists
Verification:
```bash
cd <workspace>
find plugins/continuity -maxdepth 3 -type f | sort
```
### Path B: you want to copy it into another OpenClaw workspace
From the source workspace:
```bash
cd <source-workspace>
rsync -av plugins/continuity/ <target-workspace>/plugins/continuity/
```
Then verify inside the target workspace:
```bash
cd <target-workspace>
find plugins/continuity -maxdepth 3 -type f | sort
```
---
## 6. How to apply the example config
### Step 1: copy the example continuity config
Source example:
```text
plugins/continuity/examples/openclaw.continuity.example.json
```
Example content:
```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"
}
}
}
```
### Step 2: only change the minimum fields first
Usually you only need to verify these first:
- [ ] `enabled`
- [ ] `planMatchers`
- [ ] `legalTerminalStates`
- [ ] `receiptDir`
- [ ] `requireRealDispatchReceipt`
- [ ] `allowReplyClosureWithoutDispatch`
- [ ] `adapter.forceRecall.enabled`
- [ ] `adapter.forceRecall.injectBlockLabel`
- [ ] `adapter.genericPreflight.enabled`
- [ ] `adapter.genericPreflight.injectBlockLabel`
### Step 3: recommended minimum application strategy
#### Situation A: you want the current approved-plan MVP behavior
Use the example as-is in most cases:
- `planMatchers = ["approved-plan"]`
- `legalTerminalStates = ["waiting_user", "blocked", "pending_verification"]`
- `requireRealDispatchReceipt = true`
- `allowReplyClosureWithoutDispatch = false`
#### Situation B: your planner uses a different plan name
Adjust:
```json
{
"planMatchers": ["your-approved-plan-name"]
}
```
#### Situation C: you want a different injected block label
Adjust:
```json
{
"adapter": {
"genericPreflight": {
"enabled": true,
"injectBlockLabel": "YOUR_CONTINUITY_GATE"
}
}
}
```
### Step 4: create the receipt directory up front
```bash
cd <workspace>
mkdir -p state/approved-plan-continuity
```
---
## 7. How to wire the host/hook
## 7.1 Integration points to confirm
The most mature integration point is still:
```text
hooks/force-recall/handler.ts
```
But that is no longer the only path. A generic or manual preflight host can also call the plugin directly.
A typical host-side flow should look like this:
1. run long-task preflight / wrapper, or equivalent host preflight
2. run gate lock if you have one
3. run auto-chain planner, or derive next action in your own planner
4. load the continuity plugin
5. call the appropriate adapter
6. prepend the returned continuity block into the prompt body shown to the agent
### At minimum, look for these symbols
- `plugins/continuity/src/index.mjs`
- `runForceRecallContinuityAdapter`
- `runGenericPreflightContinuityAdapter`
- `runManualContinuityPreflight`
- `APPROVED_PLAN_CONTINUITY_GATE`
Example checks:
```bash
cd <workspace>
grep -n "runForceRecallContinuityAdapter" hooks/force-recall/handler.ts
grep -n "APPROVED_PLAN_CONTINUITY_GATE" hooks/force-recall/handler.ts
```
## 7.2 Minimum `force-recall` wiring example
```js
import plugin from './plugins/continuity/src/index.mjs';
const out = plugin.runForceRecallContinuityAdapter({
config: plugin.defaultConfig,
wrapperResult,
autoChainPlanResult,
});
if (out?.block) {
context.bodyForAgent = `${out.block}\n${context.bodyForAgent}`;
}
```
## 7.3 Minimum generic/manual wiring example
If you do **not** have `force-recall`, feed your own preflight result into the generic adapter:
```js
import plugin from './plugins/continuity/src/index.mjs';
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,
},
});
```
If you only need a one-off manual continuity preflight:
```js
const out = plugin.runManualContinuityPreflight({
config: plugin.defaultConfig,
planId: 'approved-plan-1',
currentTask: 'task-3',
taskState: 'complete',
nextDerivedAction: { type: 'message_subagent', task: 'continue' },
replyClosureState: 'waiting_user',
});
```
## 7.4 Real checks implementers should perform
- [ ] the host can import `plugins/continuity/src/index.mjs`
- [ ] the chosen adapter function exists
- [ ] relevant input is passed in
- [ ] `config` is passed in, at least `defaultConfig` to start
- [ ] when `out.block` is non-empty, it is prepended to the agent-facing prompt/body
- [ ] the final agent prompt really contains the continuity gate block
## 7.5 Relationship between receipt and continuity input
If your flow really dispatches the next step, do **not** only say “dispatched” in plain text. Emit a real `dispatchReceipt`.
Minimum receipt shape:
- `planId`
- `currentTask`
- `nextDerivedAction`
- `dispatchedAt`
- `dispatchRunId`
- `childSessionKey`
- `replyClosureState`
Reference example: `plugins/continuity/examples/approved-plan-receipt.example.json`
---
## 8. Suggested implementation workflow
### Checklist: first-time plugin integration into an existing workspace
- [ ] create/copy `plugins/continuity`
- [ ] confirm `src/index.mjs` can be loaded by the host
- [ ] confirm the example config is readable
- [ ] create `state/approved-plan-continuity/`
- [ ] confirm your host has a continuity adapter call site
- [ ] confirm the continuity block is prepended into the agent prompt/body
- [ ] if a real dispatch occurs, persist a receipt
- [ ] run plugin tests
- [ ] run the host smoke path
- [ ] confirm the injected prompt block is present
### Checklist: first-time receipt persistence
- [ ] prepare a valid receipt object
- [ ] validate it with `validateReceipt()` / `isValidReceipt()` first
- [ ] persist it with `writeReceipt({ receiptDir, receipt })`
- [ ] confirm the receipt file lands in `state/approved-plan-continuity/`
- [ ] rerun continuity smoke tests so you do not mistake “looks like a receipt” for a real passing state
---
## 9. How to run smoke tests after installation
### Minimum smoke test
Run the plugin tests first:
```bash
cd <workspace>/plugins/continuity
npm test
```
If you only want a quick plugin-only verification:
```bash
cd <workspace>/plugins/continuity
node test/continuity.smoke.test.mjs
```
### Hook integration smoke test
```bash
cd <workspace>
node scripts/test_force_recall_long_task_preflight.mjs
node --check hooks/force-recall/handler.ts
```
### Recommended full smoke order
This order makes failures easier to localize:
1. plugin unit/integration tests
```bash
cd <workspace>/plugins/continuity
npm test
```
2. hook syntax check
```bash
cd <workspace>
node --check hooks/force-recall/handler.ts
```
3. hook preflight smoke test
```bash
cd <workspace>
node scripts/test_force_recall_long_task_preflight.mjs
```
4. manual inspection: confirm the block is really injected
Look for:
```text
[APPROVED_PLAN_CONTINUITY_GATE]
```
---
## 10. Smoke test success criteria
The minimum install is successful only if all of these are true:
- [ ] `npm test` passes
- [ ] `node --check hooks/force-recall/handler.ts` passes, if you use that path
- [ ] `node scripts/test_force_recall_long_task_preflight.mjs` passes, if you use that path
- [ ] the continuity gate block is visible in test output or debug prompt output
- [ ] when there is no receipt, the gate fails / hard-gates the missing-dispatch case
- [ ] legal terminal states such as `waiting_user`, `blocked`, or `pending_verification` are not misclassified
---
## 11. What to check first when something fails
Follow this order. Do not start by changing random code.
### Layer 1: path and file existence
- [ ] `plugins/continuity/src/index.mjs` exists
- [ ] your host entrypoint exists
- [ ] `plugins/continuity/examples/openclaw.continuity.example.json` exists
- [ ] `plugins/continuity/examples/approved-plan-receipt.example.json` exists
Commands:
```bash
cd <workspace>
ls plugins/continuity/src/index.mjs
ls hooks/force-recall/handler.ts
ls plugins/continuity/examples/openclaw.continuity.example.json
ls plugins/continuity/examples/approved-plan-receipt.example.json
```
### Layer 2: is the host really calling the plugin?
- [ ] your host imports or dynamically loads the continuity plugin
- [ ] your host calls the correct adapter function
- [ ] your host prepends `out.block` into the agent prompt/body
### Layer 3: is the config sensible?
Check first:
- [ ] `enabled === true`
- [ ] the chosen adapter is enabled
- [ ] `planMatchers` matches your approved-plan name
- [ ] `receiptDir` is writable
- [ ] `requireRealDispatchReceipt` matches your intended policy
- [ ] `allowReplyClosureWithoutDispatch` has not accidentally weakened the gate
### Layer 4: is the receipt real, not placeholder data?
Common problems:
- only “planning to dispatch”, but no real receipt
- incomplete receipt shape
- empty `dispatchRunId` or `childSessionKey`
- invalid `replyClosureState`
- writing to the wrong directory
### Layer 5: is the host preflight data sufficient?
The continuity adapter depends on preflight inputs such as:
- wrapper/planner output in the `force-recall` path
- normalized continuity source data in the generic path
So verify:
- [ ] the host really carries forward long-task / approved-plan state
- [ ] the planner really produced `derivedAction` / `nextDerivedAction`
- [ ] the continuity input can be built from host data
- [ ] closure state is derived correctly
---
## 12. Quick debugging commands
### Run the plugin test suite
```bash
cd <workspace>/plugins/continuity
npm test
```
### Check hook syntax
```bash
cd <workspace>
node --check hooks/force-recall/handler.ts
```
### Check continuity keywords in the handler
```bash
cd <workspace>
grep -n "runForceRecallContinuityAdapter" hooks/force-recall/handler.ts
grep -n "APPROVED_PLAN_CONTINUITY_GATE" hooks/force-recall/handler.ts
```
### Check whether receipts are actually written
```bash
cd <workspace>
find state/approved-plan-continuity -maxdepth 2 -type f | sort
```
---
## 13. Current limitations
Accept these limitations before installation:
- this plugin is an extracted **approved-plan continuity hard gate**, not a general workflow engine
- `force-recall` is still the most mature adapter, but not the only adapter
- if your workspace has no `force-recall`, you still need to connect host data into the generic/manual path yourself
- config is still “module defaults + caller input”, not a full installer/registry flow
- the receipt store only writes files; it does not manage retention, cleanup, or indexing
- the receipt validator checks only the minimum contract; it does not deeply validate every `nextDerivedAction` semantic variant
- if you only perform dry-run planning and never create a real dispatch receipt, the gate may still fail by design; that is expected behavior, not a bug
---
## 14. Recommended delivery standard: what counts as “another agent can follow this”?
If you hand this plugin to another agent or implementer, the handoff should at least satisfy all of the following:
- [ ] they understand the plugin purpose without re-reading the entire conversation
- [ ] they know where the files should live
- [ ] they know which files must be copied
- [ ] they know how to wire the `force-recall` hook, if applicable
- [ ] they know which example config fields to check first
- [ ] they know how to run smoke tests
- [ ] they know the first debugging layers to inspect
- [ ] they understand the current limits and do not mistake this for a full workflow framework
If all eight points are covered, this has reached the “agent can follow it” bar.
---
## 15. One-page execution summary
If you are in a hurry, do only these:
1. put the plugin here:
```text
<workspace>/plugins/continuity
```
2. confirm the host is wired to:
- `plugins/continuity/src/index.mjs`
- `runForceRecallContinuityAdapter(...)` or `runGenericPreflightContinuityAdapter(...)`
- prepend `out.block` into the agent prompt/body
3. apply the example config:
```text
plugins/continuity/examples/openclaw.continuity.example.json
```
4. create the receipt directory:
```bash
mkdir -p <workspace>/state/approved-plan-continuity
```
5. run tests:
```bash
cd <workspace>/plugins/continuity && npm test
cd <workspace> && node --check hooks/force-recall/handler.ts
cd <workspace> && node scripts/test_force_recall_long_task_preflight.mjs
```
6. confirm the prompt contains:
```text
[APPROVED_PLAN_CONTINUITY_GATE]
```
7. confirm that without a real dispatch receipt, the task is not incorrectly treated as completed.
---
## 16. Reference files
- `plugins/continuity/README.zh-TW.md`
- `plugins/continuity/README.md`
- `plugins/continuity/HOOK.md`
- `plugins/continuity/GENERIC_INTEGRATION.zh-TW.md`
- `plugins/continuity/GENERIC_INTEGRATION.md`
- `plugins/continuity/examples/openclaw.continuity.example.json`
- `plugins/continuity/examples/approved-plan-receipt.example.json`
- `hooks/force-recall/handler.ts`
- `scripts/test_force_recall_long_task_preflight.mjs`
If you need the Traditional Chinese operator version, use `AGENT_GUIDE.zh-TW.md` first; this file is the English counterpart.

View File

@@ -0,0 +1,591 @@
# Continuity Plugin: Generic / Manual Integration Runbook
> Audience: host / orchestrator / planner implementers **without** a `force-recall` hook
>
> This document explains how to install the continuity plugin even when your host does not use `hooks/force-recall/handler.ts`, how to provide payloads, how to run generic/manual preflight, and how to verify that the continuity block is actually enforced.
> 中文版:`GENERIC_INTEGRATION.zh-TW.md`
---
## 0. What problem does this runbook solve?
Read this first if any of the following apply:
- your host does not have a `force-recall` hook
- you have your own planner / preflight / orchestrator
- you want to feed approved-plan state into the continuity plugin manually
- you need a clear payload contract showing the minimum required fields
- you need a verification path that proves the continuity block is really preventing incorrect closure
One-line summary:
**You can install the continuity plugin without `force-recall`, but you must provide generic continuity input yourself and inject the returned block into the agent prompt.**
---
## 1. Minimum installation target
At minimum, the integration should achieve all of the following:
- your host can import `plugins/continuity/src/index.mjs`
- your host can call either:
- `runGenericPreflightContinuityAdapter(...)`, or
- `runManualContinuityPreflight(...)`
- your host prepends `out.block` into the prompt/body shown to the agent
- when a task is finished but there is a known next step and no real dispatch receipt, the continuity gate blocks “just close the whole thing” behavior
- `cd plugins/continuity && npm test` passes
---
## 2. Install location and files
Recommended location:
```text
<workspace>/plugins/continuity
```
For generic/manual integration, you should at least have:
```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.md
```
Quick check:
```bash
cd <workspace>
find plugins/continuity -maxdepth 3 -type f | sort
```
---
## 3. Which APIs are used on the generic/manual path?
`src/index.mjs` already re-exports the APIs most hosts need:
- `defaultConfig`
- `normalizeContinuityConfig()`
- `validateContinuityConfig()`
- `buildGenericContinuityInput()`
- `createGenericPreflightContinuityAdapter()`
- `runGenericPreflightContinuityAdapter()`
- `runManualContinuityPreflight()`
- `validateReceipt()`
- `isValidReceipt()`
- `writeReceipt()`
For the shortest path, most integrations only need:
- `runGenericPreflightContinuityAdapter(...)`
- `runManualContinuityPreflight(...)`
- `writeReceipt(...)`
---
## 4. Minimum continuity input contract
The generic adapter accepts a **host-agnostic continuity input**.
According to `src/continuity/types.md`, the practical minimum fields are:
- `planId`: string
- `currentTask`: string
- `taskState`: string | null
- `nextDerivedAction`: object | null
- `replyClosureState`: string | null
- `dispatchReceipt`: object | null
- `nextTaskKnown`: boolean
- `sameApprovedPlan`: boolean
- `taskBoundaryStop`: boolean
- `highRiskStop`: boolean
Optional fields:
- `nextTaskId`: string | null
- `nextTaskKey`: string | null
- `derivedAction`: object | null
- `metadata`: object
### Minimum workable payload example
```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',
},
};
```
### When should this payload trigger the gate?
A common high-risk case is:
- `taskState = 'complete'`
- `nextTaskKnown = true`
- `sameApprovedPlan = true`
- `nextDerivedAction != null`
- `dispatchReceipt = null`
- `replyClosureState = 'completed'`
That usually means: **the current task is done, the next task is already known, but you still have no real dispatch receipt and are about to close the whole run anyway.**
That is exactly the core case the continuity gate is supposed to block.
---
## 5. How to wire the generic adapter
### Minimal wiring
```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}`;
}
```
### Which returned fields matter most?
Most hosts should inspect:
- `out.input`
- `out.result`
- `out.evaluation`
- `out.block`
- `out.meta.adapterName`
- `out.meta.hostAgnostic`
### When must you not ignore `out.block`?
If `out.block` is non-empty, the plugin generated a continuity gate block intended for prompt injection.
**Do not treat the integration as finished just because you logged `evaluation`.**
The real minimum bar is:
- `out.block` exists
- it is injected into the agent prompt/body
- the agent will actually see it downstream
---
## 6. How to use the manual runner
If you do not yet have a full generic source builder, or you want a quick single-point preflight harness, use the 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}`;
}
```
### When is the manual runner useful?
- you do not yet have a full planner but want to validate gate behavior first
- you want an integration smoke test
- you are migrating an older host and need a temporary bridge
### The manual runner is not a substitute for real integration
Treat it as a **preflight harness**, not the final architecture.
Once behavior is confirmed, move back to the generic adapter and map real host data into `source`.
---
## 7. How to prepare config
Start from:
```text
plugins/continuity/examples/openclaw.continuity.example.json
```
The example looks like this:
```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"
}
}
}
```
### Fields to verify first on the generic/manual path
- [ ] `enabled = true`
- [ ] `planMatchers` matches your approved-plan naming
- [ ] `legalTerminalStates` matches your closure policy
- [ ] `receiptDir` is writable
- [ ] `requireRealDispatchReceipt = true` in most cases
- [ ] `allowReplyClosureWithoutDispatch = false` in most cases
- [ ] `adapter.genericPreflight.enabled = true`
- [ ] `adapter.genericPreflight.injectBlockLabel` is the label you expect
---
## 8. How to provide receipts
If your host really dispatches the next step, do not just say “dispatched” in metadata or logs. Provide a real `dispatchReceipt`.
### Minimum receipt shape
The current validator expects at least:
- `planId`
- `currentTask`
- `nextDerivedAction`
- `dispatchedAt`
- `dispatchRunId`
- `childSessionKey`
- `replyClosureState`
Reference file:
```text
plugins/continuity/examples/approved-plan-receipt.example.json
```
Example content:
```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"
}
```
### Validate before writing
```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('; ')}`);
}
```
### Write a receipt
```js
await plugin.writeReceipt({
receiptDir: 'state/approved-plan-continuity',
receipt,
});
```
### Create the receipt directory first
```bash
cd <workspace>
mkdir -p state/approved-plan-continuity
```
---
## 9. Generic/manual preflight verification flow
This is the most important checklist in the document.
### Case A: the gate should block
Test an input where:
- the task is complete
- the next task is known
- it is the same approved plan
- a next derived action exists
- no dispatch receipt exists
- closure is trying to mark the run completed
Example:
```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,
},
});
```
Expected outcome:
- `out.block` is non-empty
- `out.evaluation` shows that this is not a safe direct closure state
- once injected, the agent should be warned not to close the entire job prematurely
### Case B: legal terminal states should not be falsely blocked
Examples:
- `replyClosureState = 'waiting_user'`
- or `blocked`
- or `pending_verification`
Expected outcome:
- valid waiting / blocked / pending-verification states are not misclassified as incorrect closure
### Case C: a real receipt should differ from dry-run behavior
Replace `dispatchReceipt` with a valid receipt object and run again.
Confirm that:
- the plugin recognizes the receipt
- behavior differs from the `dispatchReceipt = null` case
- merely having `nextDerivedAction` does not automatically count as dispatched
---
## 10. How to verify the continuity block is actually injected into the prompt
This step is commonly skipped, but it is what separates “calculated something” from “integration actually works.”
### Minimum proof required
You should prove all three of these:
1. the plugin returned `out.block`
2. the host prepended `out.block` into the agent prompt/body
3. the final prompt actually contains:
```text
[APPROVED_PLAN_CONTINUITY_GATE]
```
### Recommended approaches
- print the beginning of the final prompt during preflight/orchestrator tests
- dump prompt body in debug mode
- add an integration smoke assertion for the block label
### What is *not* enough
The following are insufficient on their own:
- seeing only `out.evaluation`
- seeing only a console log that says the adapter ran
- seeing a prepend line in code without verifying the actual prompt output
---
## 11. Recommended smoke test order
### Step 1: plugin test suite
```bash
cd <workspace>/plugins/continuity
npm test
```
### Step 2: quick plugin smoke
```bash
cd <workspace>/plugins/continuity
node test/continuity.smoke.test.mjs
```
### Step 3: your generic/manual host integration test
If you have your own orchestrator, add at least one smoke case for:
- no receipt + trying to complete -> should be gated
- legal terminal state -> should not be falsely blocked
- valid receipt present -> behavior should differ from the no-receipt case
### Step 4: manual prompt block inspection
Confirm the output really contains:
```text
[APPROVED_PLAN_CONTINUITY_GATE]
```
---
## 12. Common pitfalls for host implementers
### Pitfall 1: planner output exists, but no prompt injection
That means continuity is only “computed”, not actually affecting agent behavior.
### Pitfall 2: treating dry-run next action as equivalent to dispatched
`nextDerivedAction != null` does **not** mean `dispatchReceipt != null`.
### Pitfall 3: missing `planId` or `currentTask`
If `buildGenericContinuityInput()` cannot derive those core fields, it may fail to produce a usable input.
### Pitfall 4: incorrect legal terminal state config
For example, if your host legitimately uses `waiting_user` but your config omits it from `legalTerminalStates`, you may get false positives.
### Pitfall 5: a receipt file exists, but the receipt is invalid
Validate first with `validateReceipt()`.
### Pitfall 6: testing only the happy path
At minimum, test:
- a case that should fail/gate
- a case that should pass or not be falsely blocked
- the difference between “with receipt” and “without receipt”
---
## 13. One-page generic/manual checklist
### Install
- [ ] copy `plugins/continuity` into the workspace
- [ ] confirm `src/index.mjs` can be imported
- [ ] create `state/approved-plan-continuity/`
### Config
- [ ] apply the example config
- [ ] enable `adapter.genericPreflight.enabled`
- [ ] confirm `receiptDir` is writable
### Host wiring
- [ ] prepare a generic continuity payload
- [ ] call `runGenericPreflightContinuityAdapter(...)` or `runManualContinuityPreflight(...)`
- [ ] prepend `out.block` into the agent prompt/body
### Receipt
- [ ] only create a receipt when dispatch really happened
- [ ] validate with `validateReceipt()` first
- [ ] then call `writeReceipt()`
### Verification
- [ ] `cd plugins/continuity && npm test`
- [ ] verify that missing receipts block incorrect closure
- [ ] verify that legal terminal states are not falsely blocked
- [ ] verify that the final prompt contains the continuity block
---
## 14. What counts as a complete generic/manual integration?
The integration is only genuinely complete when all of the following are true:
- not only the plugin is installed, but the host really calls the adapter
- not only evaluation exists, but the block is actually injected into the agent prompt
- not only dry-run planning exists, but a real dispatch can produce a real receipt
- not only the happy path is tested, but both gate-fail and gate-pass behavior are verified
- the documentation is sufficient for an unfamiliar host implementer to know which fields to provide and how to validate the result
If all five are true, you are much closer to “an unfamiliar host can install this from the docs.”
---
## 15. Reference files
- `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`
If your host later adds a dedicated adapter, keep this generic/manual runbook anyway as the minimum integration baseline.

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作為最低整合基準。