15 KiB
Continuity Plugin: Generic / Manual Integration Runbook
Audience: host / orchestrator / planner implementers without a
force-recallhookThis 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-recallhook - 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(...), orrunManualContinuityPreflight(...)
- your host prepends
out.blockinto 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 testpasses
2. Install location and files
Recommended location:
<workspace>/plugins/continuity
For generic/manual integration, you should at least have:
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:
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:
defaultConfignormalizeContinuityConfig()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: stringcurrentTask: stringtaskState: string | nullnextDerivedAction: object | nullreplyClosureState: string | nulldispatchReceipt: object | nullnextTaskKnown: booleansameApprovedPlan: booleantaskBoundaryStop: booleanhighRiskStop: boolean
Optional fields:
nextTaskId: string | nullnextTaskKey: string | nullderivedAction: object | nullmetadata: object
Minimum workable payload example
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 = truesameApprovedPlan = truenextDerivedAction != nulldispatchReceipt = nullreplyClosureState = '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
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.inputout.resultout.evaluationout.blockout.meta.adapterNameout.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.blockexists- 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:
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:
plugins/continuity/examples/openclaw.continuity.example.json
The example looks like this:
{
"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 = trueplanMatchersmatches your approved-plan naminglegalTerminalStatesmatches your closure policyreceiptDiris writablerequireRealDispatchReceipt = truein most casesallowReplyClosureWithoutDispatch = falsein most casesadapter.genericPreflight.enabled = trueadapter.genericPreflight.injectBlockLabelis 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:
planIdcurrentTasknextDerivedActiondispatchedAtdispatchRunIdchildSessionKeyreplyClosureState
Reference file:
plugins/continuity/examples/approved-plan-receipt.example.json
Example content:
{
"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
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
await plugin.writeReceipt({
receiptDir: 'state/approved-plan-continuity',
receipt,
});
Create the receipt directory first
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:
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.blockis non-emptyout.evaluationshows 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 = nullcase - merely having
nextDerivedActiondoes 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:
- the plugin returned
out.block - the host prepended
out.blockinto the agent prompt/body - the final prompt actually contains:
[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
cd <workspace>/plugins/continuity
npm test
Step 2: quick plugin smoke
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:
[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/continuityinto the workspace - confirm
src/index.mjscan be imported - create
state/approved-plan-continuity/
Config
- apply the example config
- enable
adapter.genericPreflight.enabled - confirm
receiptDiris writable
Host wiring
- prepare a generic continuity payload
- call
runGenericPreflightContinuityAdapter(...)orrunManualContinuityPreflight(...) - prepend
out.blockinto 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.mdplugins/continuity/README.mdplugins/continuity/AGENT_GUIDE.zh-TW.mdplugins/continuity/AGENT_GUIDE.mdplugins/continuity/src/continuity/types.mdplugins/continuity/examples/openclaw.continuity.example.jsonplugins/continuity/examples/approved-plan-receipt.example.json
If your host later adds a dedicated adapter, keep this generic/manual runbook anyway as the minimum integration baseline.