docs: add agent guide and generic integration runbooks
This commit is contained in:
701
plugins/continuity/AGENT_GUIDE.md
Normal file
701
plugins/continuity/AGENT_GUIDE.md
Normal 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.
|
||||||
591
plugins/continuity/GENERIC_INTEGRATION.md
Normal file
591
plugins/continuity/GENERIC_INTEGRATION.md
Normal 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.
|
||||||
591
plugins/continuity/GENERIC_INTEGRATION.zh-TW.md
Normal file
591
plugins/continuity/GENERIC_INTEGRATION.zh-TW.md
Normal file
@@ -0,0 +1,591 @@
|
|||||||
|
# Continuity Plugin:Generic / 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 1:plugin 本體測試
|
||||||
|
|
||||||
|
```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`。
|
||||||
|
|
||||||
|
### 坑 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.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,作為最低整合基準。
|
||||||
Reference in New Issue
Block a user