On this page
On this page
Plugin internals
Plugin SDK migration
OpenClaw replaced a broad backwards-compatibility layer with a modern plugin architecture built from small, focused imports. If your plugin predates that change, this guide gets it onto the current contracts.
For synchronous SQLite APIs, use the complete async migration guide. The final compatibility day is 2026-12-31; removal ships in the first January 2027 stable release, without a Plugin SDK major version. The tables cover every affected member and its awaited or prepared replacement.
Memory flush model selection
Remove model from plans returned by flushPlanResolver or
providerFlushPlanResolver. MemoryFlushPlan, MemoryFlushFilePlanDraft, and
MemoryFlushToolsPlan no longer expose a model override. Existing JavaScript
plans that still return model have that field ignored. All memory flushes
use the active session model and its normal fallback chain.
This intentional removal gives the session ownership of model selection.
To keep memory extraction local, select a local session model and local
fallbacks. Doctor removes the corresponding retired
agents.defaults.compaction.memoryFlush.model setting with a warning; see
Config migrations.
Prompt hook history is read-only
before_prompt_build and agent_turn_prepare now share frozen transcript payload
leaves instead of making mutable deep copies of the entire history. Plugins that
mutated nested message content in these hooks now get a TypeError in strict
mode; return the hook result instead. For example, replace a history edit intended
to add instructions with an explicit contribution:
Both hooks accept prependContext and appendContext. Ordinary
before_prompt_build also accepts systemPrompt, prependSystemContext,
appendSystemContext, and toolsAllow; authorized enrichment only consumes the
context contributions. None of these fields rewrites stored history.
Message arrays and envelopes remain fresh per dispatch, so array edits and whole-field assignments remain isolated from core history and later dispatches. Handlers in the same dispatch still share those edits in priority order. Do not rely on nested values being mutable, even when a particular harness supplies non-owned values that are cloned. If a plugin needs a mutable working copy for its own computation, explicitly clone that data inside the plugin. Return only the supported contribution fields. See Prompt and session hooks.
MCP result compatibility window
OpenClaw's session and node MCP clients use the split MCP SDK v2 packages while continuing to negotiate legacy MCP. The published plugin boundary retains its MCP SDK v1 result shapes for one stable-release window: the first stable release containing this migration. Replacing those shapes with v2 types is scheduled for the following stable release; no calendar date is assigned yet.
The affected contracts are SessionMcpRuntime.callTool, listTools,
listResourceTemplates, and getPrompt from
openclaw/plugin-sdk/agent-harness-runtime, its
prepareHarnessNativeMcpAppPreview.toolResult input, and the runtime accepted by
openclaw/plugin-sdk/codex-mcp-projection. Keep the existing v1 types when
implementing these adapters during the compatibility window. Ordinary plugins
that register tools or MCP connection resolvers do not need a new dependency.
Host results cross this boundary losslessly. An incompatible result throws an
error with code: "MCP_SDK_RESULT_UNREPRESENTABLE" and the MCP method name rather
than dropping fields. This includes non-object structuredContent (arrays,
scalars, or null), output schemas without type: "object", non-object
properties or non-string-array required in an output schema, scalar or null
values in a tool's input/output schema properties, and nested fields that the
v1 result schemas would discard.
Metadata, resource content, prompt messages, and pagination cursors are preserved
when representable.
When the next stable release adopts v2 result types, import result and client
types from @modelcontextprotocol/client, schema validators from
@modelcontextprotocol/core, and the AJV adapter from
@modelcontextprotocol/client/validators/ajv. Treat structuredContent as
unknown and narrow it before reading properties; preserve JSON Schema values
without assuming every property schema is an object. For example:
The compatibility window does not enable modern protocol serving or change MCP server configuration.
What changed
Several wide-open import surfaces used to let plugins reach almost anything from a single entry point:
openclaw/plugin-sdkandopenclaw/plugin-sdk/compat- re-exported dozens of helpers while the focused SDK was being built. Both roots are now removed. Import a documented subpath instead.openclaw/plugin-sdk/infra-runtime- a broad barrel mixing system events, heartbeat state, delivery queues, fetch/proxy helpers, file helpers, approval types, and unrelated utilities.openclaw/plugin-sdk/config-runtime- a removed broad config barrel, including its deprecated directloadConfigandwriteConfigFileexports.openclaw/plugin-sdk/channel-lifecycle,channel-message, andchannel-reply-pipeline- removed channel compatibility facades. Use the focused outbound and inbound contracts, checking each named export.openclaw/extension-api- a removed bridge that gave plugins direct access to host-side helpers like the embedded agent runner.api.registerEmbeddedExtensionFactory(...)- a removed embedded-runner-only hook that observed embedded-runner events such astool_result. Use agent tool-result middleware instead (see Migrate embedded tool-result extensions to middleware).
The root SDK, compat barrel, extension bridge, embedded extension factory, and five channel/config/infrastructure compatibility facades have been removed. The latter retirement received explicit SDK-owner approval on September 30, 2026; see the removal timeline.
OpenClaw's default compatibility policy preserves documented plugin behavior through a compatibility adapter, diagnostics, docs, and a deprecation window. That applies to SDK imports, manifest fields, setup APIs, hooks, and runtime registration behavior. The three session-controller changes below are explicit exceptions with immediate enforcement and no compatibility aliases or window.
ChatCommandDefinition.category retains the "docks" value accepted by the
2026.8.1 SDK. Command lists display these legacy definitions under Tools.
The category does not enable channel docking or restore retired docking commands.
New definitions should use "tools".
Bundled conversation-binding adapters can use resolveThreadBindingTargetKind from
openclaw/plugin-sdk/thread-bindings-session-runtime to select their stored
"subagent", "session", or "acp" kind. The helper preserves explicit subagents
and recognizes ACP session keys without loading storage. Other session targets,
including main/global aliases, use the canonical asynchronous ACP metadata reader;
unreadable stores reject classification rather than defaulting to ordinary sessions.
Pass the current config, optional agent owner, channel display name, and a required
assertCurrent callback covering both manager and caller authority. Adapters must
still recheck authority at their native-create and binding-commit boundaries.
Bundled ACP integrations should await readAcpSessionEntryAsync from the local
openclaw/plugin-sdk/acp-runtime facade for session metadata reads. This facade
remains a JavaScript compatibility export; its declarations are excluded from
the typed public SDK. File-backed reads run on the SQLite workers and preserve the session lifecycle throughout the
metadata join. File-backed results are detached snapshots, including when
clone: false is supplied. The returned storeReadFailed flag still distinguishes an
unreadable session store from missing metadata; startup cleanup must keep
bindings when that flag is set. Unbound legacy ACP metadata can still accompany
that flag; it does not certify the session row. Source retirement and worker failures reject
the read and must not be treated as an absent session.
The synchronous readAcpSessionEntry and
getAcpSessionManager().resolveSession() contracts shipped in v2026.9.4
remain available for durable sessions and are deprecated for runtime use.
Incognito targets reject synchronous access with IncognitoSessionSyncAccessError;
use readAcpSessionEntryAsync and
getAcpSessionManager().resolveSessionAsync() instead. The asynchronous path
acquires the existing memory session actor and joins global ACP metadata through
the shared-state worker. It does not create a missing incognito session.
Discord and Telegram startup cleanup use prepareAcpSessionEntryRead for
incognito targets. Its prepared reader retains the selected memory owner and
carries current caller authority through conditional binding deletion. Closing
the session ends that authority; cleanup does not reopen the retired native
incognito database.
IncognitoSessionEndedError and IncognitoSessionSyncAccessError, exposed through
the local openclaw/plugin-sdk/acp-runtime facade, must propagate through cleanup and status
probes. Use rethrowIncognitoSessionError(error) before treating other failures
as an absent session or an uncertain probe; it also recognizes nested refusals.
The deprecated synchronous contracts for durable sessions remain available
through 2026-12-31 and are removed in the first January 2027 stable release.
Session-keyed harness registration
The session-controller cutover changes standalone session-keyed
setActiveEmbeddedRun(...) registration immediately, with no compatibility
window. Register native runs from the host-provided, admitted harness attempt.
Calling the function without that admission throws
AgentHarnessPreflightError, with code
SESSION_CONTROLLER_ADMISSION_REQUIRED and migration guidance.
A hosted call inherits the host's captured operation; passing a session key,
agent ID, or a plugin-created operation does not obtain admission. Host-owned
detached work can carry a session key for policy, but cannot own that session's
turn, queue, or Stop. See Harness attempt runtime
for the supported registration and cleanup path. Registration accepts only the
five public identity arguments and returns a cleanup function bound to that
exact run. The optional replyOperation refers to the same admitted controller
and retains its documented tool-authority methods; it does not authorize
standalone registration. Host admission state and native attachment records
stay private to the host.
Gateway session control
GatewayRequestHandlerOptions.context no longer exposes the mutable
chatAbortControllers or chatQueuedTurns maps. The session controller owns
admission, queued input, and Stop; plugin handlers cannot register or cancel work
by changing shared maps. Reading either removed property fails with code
PLUGIN_CAPABILITY_REMOVED and the exact capability name.
Keep api.registerGatewayMethod(...) for plugin methods. To compose existing
session operations from an authenticated handler, declare
contracts: { gatewayMethodDispatch: ["authenticated-request"] } in the plugin
manifest and import dispatchGatewayMethod from
openclaw/plugin-sdk/gateway-method-runtime. Use chat.send to submit input,
chat.abort with { sessionKey, runId? } to cancel a chat run, or
sessions.abort with { key, runId?, clearQueued? } for session cancellation.
These calls retain the current caller's authority and the target method's
authorization; the declaration does not grant additional scopes. Use
agent.wait to await a run, and sessions.list for activity observations.
Bundled or trusted official plugins can also use api.runtime.gateway.request,
session facts, and subscriptions; those runtime capabilities retain their
existing trust restriction.
See Gateway runtime
for the supported read and control paths.
Agent turn start observation
AgentTurnStartOwner.observe() now returns startDeadlineAtMs instead of
expiresAtMs. The old property has no alias; reading it fails with code
PLUGIN_CAPABILITY_REMOVED. Update consumers of the onStartOwner callback
exposed through context.createAgentTurnFacade to inspect the new field.
A finite startDeadlineAtMs is the controller's deadline for execution to
start. null means a live controller-owned wait has no finite start deadline;
an absent field means no controller deadline is available yet. An undefined
observation means the captured owner is no longer current. Stop watching for
start once executionStarted is true. This field does not extend execution
authority or describe the run's execution timeout. Other API fields named
expiresAtMs retain their existing contracts.
Session and command preparation
Use getSessionEntryAsync, readSessionUpdatedAtAsync,
getConversationSessionAsync, readAmbientTranscriptWatermarkAsync,
loadTranscriptEvents, and readTranscriptStatsAsync from
openclaw/plugin-sdk/session-store-runtime for ordinary session reads. The
runtime equivalent is api.runtime.agent.session.getSessionEntryAsync. These
operations read through the existing session worker and observe committed
in-process writes. Missing entries remain undefined; read failures propagate.
loadTranscriptEventsSync and readTranscriptStatsSync are deprecated; their
awaited replacements preserve ordered events and transcript statistics while
keeping durable reads off the Gateway thread.
Await UserTurnTranscriptRecorder.completeProcessingAsync(outcome) to settle an
accepted input through its session owner. The deprecated completeProcessing
method preserves synchronous completion and returns after the terminal outcome
commits, with a migration warning, through 2026-12-31. Recorders
without an accepted input keep their existing no-op behavior.
Use readSessionTranscriptCatalogTitleAsync from
openclaw/plugin-sdk/session-transcript-runtime for catalog titles. The
synchronous readSessionTranscriptCatalogTitle remains deprecated through
2026-12-31.
Use resolveStoredModelOverrideAsync and resolveCommandArgMenuAsync from
openclaw/plugin-sdk/command-auth-native when preparing native commands.
resolveCommandArgMenuAsync is also exported by
openclaw/plugin-sdk/native-command-registry. An inherited-model loader may
return a promise; direct overrides still need no parent read. Menu preparation
reads the current session's verbose setting before returning its choices.
The corresponding synchronous APIs are deprecated for ordinary runtime use. Released signatures remain supported through 2026-12-31. New legacy calls emit a bounded deprecation warning. Bundled callers use the awaited operations; final tool, disclosure, and mutation authority checks retain their current owner and timing.
For a complete cross-session store view, await
loadCombinedSessionStoreForGatewayAsync from
openclaw/plugin-sdk/session-transcript-hit. It preserves full session entries
and excludes incognito sessions while reading durable stores through the existing
database worker. The synchronous loadCombinedSessionStoreForGateway retains its
return value and emits a bounded deprecation warning; it remains supported through
2026-12-31.
Native session lineage
Use resolvePreparedNativeSessionBinding or
retainNativeSessionGenerationAuthority from
openclaw/plugin-sdk/agent-harness-session-runtime to prepare lineage in the
session worker. Both return an owned capture: call release() in finally
after the last effect and any terminal cleanup. Repeated authority checks use
the session owner's published facts and immediately reject revoked lineage.
Use authority.withLiveness(assertCurrent) when cleanup needs its own lifecycle
check. This borrows the same source; it does not extend the capture's lifetime.
Use createNativeSessionBindingScope when there is no stored session lineage.
The released createNativeSessionBindingAuthority,
combineNativeSessionBindingAuthority, prepareNativeSessionGenerationAuthority,
and resolveNativeSessionBindingWithAuthority contracts remain available but
deprecated. Their synchronous readers are compatibility paths; bundled harnesses
use the prepared API.
Exec approval reads
Await loadExecApprovalsReadOnlyAsync or readExecApprovalsSnapshotAsync from
openclaw/plugin-sdk/exec-approvals-runtime. Both use the existing shared-state
read worker. The synchronous loadExecApprovals and readExecApprovalsSnapshot
exports retain their signatures through 2026-12-31, emit a bounded
deprecation warning, and are scheduled for removal in the first January 2027 stable release. Bundled
runtime callers use the awaited reads. The execution owner still checks current
approval policy immediately before command execution using committed in-process
policy publications; that final check does not query SQLite.
The SDK's readExecApprovalsSnapshotAsync returns a private mutable snapshot for
edit-then-save callers. The deprecated synchronous readers likewise retain private
mutable results through their compatibility window. loadExecApprovalsReadOnlyAsync
shares frozen policy on cached reads: treat its file and nested grants as read-only,
and use structuredClone(file) before preparing an edit. Internal execution reads
still reuse frozen owner snapshots. Retaining any snapshot does not retain execution
authority: pending updates and revocations still fail closed at the final execution check.
Durable-send destination history
Remove mirror from calls to sendDurableMessageBatch and
withDurableMessageSendContext in openclaw/plugin-sdk/channel-outbound.
Confirmed sends now record their delivered content automatically in the resolved
destination conversation. The caller's own conversation is not recorded twice.
session.key now names the conversation whose turn produced the send. A send to
that same conversation is skipped because the producing turn already recorded
it. For proactive or scheduled sends that have no producing turn, pass the
destination as session.policyKey (for example with
buildOutboundSessionContext({ policySessionKey })) instead of session.key;
otherwise the send is treated as the producer's own reply and adds no history row.
The mirror parameter and DeliveryMirror type remain source-compatible with
the 2026.10.1 SDK until the next Plugin SDK major, when removal requires explicit
breaking-release approval. Legacy calls emit one deprecation warning per plugin
and process. The old field does not select a different history destination or
override delivered text or media; a mismatched mirror.sessionKey creates no
extra conversation row.
Session reset freshness
Channel runtime consumers should await
runtime.channel.session.resolveEntryResetFreshnessAsync(...) when deciding
whether a session needs reset. File-backed entries and transcript lifecycle
timestamps are read from the same worker snapshot, with the original database
owner retained through validation and cleanup. The synchronous
resolveEntryResetFreshness(...) method shipped in v2026.9.8 remains available
for existing JavaScript consumers and is deprecated for runtime use; removal
ships in the first January 2027 stable release after the 2026-12-31 compatibility cutoff. Both methods retain the
existing reset policy and process-held incognito behavior.
Why
- Slow startup - importing one helper loaded dozens of unrelated modules.
- Circular dependencies - broad re-exports made import cycles easy to create.
- Unclear API surface - no way to tell stable exports from internal ones.
The typed public SDK is organized into focused subpaths with documented contracts. Not every SDK build entrypoint is a public plugin API.
Legacy provider convenience interfaces for bundled channels are gone too -
channel-branded helper shortcuts were private mono-repo conveniences, not
stable plugin contracts. Use narrow generic SDK subpaths instead. Inside the
bundled plugin workspace, keep provider-owned helpers in that plugin's own
api.ts or runtime-api.ts:
- Anthropic keeps Claude-specific stream helpers in its own
api.ts/contract-api.tsinterface. - OpenAI keeps provider builders, default-model helpers, and realtime provider
builders in its own
api.ts. - OpenRouter keeps provider builder and onboarding/config helpers in its own
api.ts.
Where each topic lives
Every section of the single-page version lives on one of the six pages below. The anchors from the single-page version still resolve here.
Migration steps
How to migrate a plugin — the ordered migration steps.
- Await personal model-account operations
- Await session transcript persistence
- Await extension session changes
- Await provider replay metadata
- How to migrate
- Migrate runtime config load/write helpers
- Migrate embedded tool-result extensions to middleware
- Migrate approval-native handlers to capability facts
- Audit Windows wrapper fallback behavior
- Find deprecated imports
- Replace with focused imports
- Replace broad
infra-runtimeimports - Migrate channel route helpers
- Build and test
Import paths
Import path reference — which typed-public subpath replaces each legacy import.
Removed surfaces and replacements
Removed surfaces and replacements — what was removed, and the replacement for each legacy API.
- Heartbeat executor and wake helpers
- Removed compatibility surfaces
- Process-global API-provider publication
- Deactivate hook alias
- Private testing barrel
- Migration reference
command-authhelp builders ->command-status- Mention gating helpers ->
resolveInboundMentionDecision - Channel runtime shim and channel actions helpers
- Web search provider
tool()helper ->createTool()on the plugin - Plaintext channel envelopes ->
BodyForAgent subagent_spawninghook -> core thread binding- Provider discovery types -> provider catalog types
- Thinking policy hooks ->
resolveThinkingProfile - External auth providers ->
contracts.externalAuthProviders - Provider env-var lookup ->
setup.providers[].envVars - Memory plugin registration ->
registerMemoryCapability - Memory embedding provider API
- Raw channel send results ->
OutboundDeliveryResult - Subagent session messages types renamed
- Removed session and transcript file APIs
- Agent harness attempt params -> V2 host-capability contract
- Embedded extension factories -> agent tool-result middleware
OpenClawSchemaTypealias ->OpenClawConfig
Talk and voice
Talk and realtime voice migration — the unified Talk session API and its method map.
Compatibility records
Compatibility policy and records — what is retained, why, and on what condition it can be removed.
- Compatibility policy
- Retained helper contracts
- Harness attempt result migration
- Model-provider result compatibility
- Memory read missing results
- Config record migrations
- Plugin state migration declarations
- AuthStorage SQLite migration
- Published channel setup compatibility
- Channel setup input field compatibility
- Verifying readers
- Media legacy projection
Timeline
Removal timeline — when deprecated surfaces become eligible for removal.
Related
- Getting Started - build your first plugin
- SDK Overview - full subpath import reference
- Channel Plugins - building channel plugins
- Provider Plugins - building provider plugins
- Plugin Internals - architecture deep dive
- Plugin Manifest - manifest schema reference
- Plugin hooks - typed and custom hook surfaces
For the removed Tasks and TaskFlow surfaces, see Tasks and TaskFlow API removal.
The agent-runtime snapshot publisher now provides
replaceRuntimeAuthProfileStoreSnapshotsAsync. Await it when publishing a default
shared-owner snapshot; it resolves cold ownership through the database worker.
The synchronous replaceRuntimeAuthProfileStoreSnapshots remains deprecated until
a later stable release and retains native cold-owner admission for compatibility.
Entries with explicit database paths or agent directories remain in-memory publications.