Files
approved-plan-continuity-ha…/plugins/continuity/GENERIC_INTEGRATION.md

15 KiB

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:

<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:

  • 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

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

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:

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 = 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:

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.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

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:
[APPROVED_PLAN_CONTINUITY_GATE]
  • 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

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.

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.