docs: add agent guide and generic integration runbooks
This commit is contained in:
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.
|
||||
Reference in New Issue
Block a user