Plugin SDK reference
Plugin SDK capability registration
The capability registrars on OpenClawPluginApi, and the runtime contracts a
worker or embedding provider must satisfy. Part of the
Plugin SDK overview.
Capability registration
| Method | What it registers |
|---|---|
api.registerProvider(...) |
Text inference (LLM) |
api.registerWorkerProvider(...) |
Cloud-worker lifecycle leases |
api.registerModelCatalogProvider(...) |
Model catalog rows for text and media generation |
api.registerAgentHarness(...) |
Experimental native agent executor (Codex, Copilot) |
api.registerCliBackend(...) |
Local CLI inference backend |
api.registerChannel(...) |
Messaging channel |
api.registerEmbeddingProvider(...) |
Reusable vector embedding provider |
api.registerSpeechProvider(...) |
Text-to-speech / STT synthesis |
api.registerRealtimeTranscriptionProvider(...) |
Streaming realtime transcription |
api.registerRealtimeVoiceProvider(...) |
Duplex realtime voice sessions |
api.registerMediaUnderstandingProvider(...) |
Image/audio/video analysis |
api.registerTranscriptSourceProvider(...) |
Live or imported meeting transcript source |
api.registerImageGenerationProvider(...) |
Image generation |
api.registerMusicGenerationProvider(...) |
Music generation |
api.registerVideoGenerationProvider(...) |
Video generation |
api.registerWebFetchProvider(...) |
Web fetch / scrape provider |
api.registerWebSearchProvider(...) |
Web search |
api.registerCompactionProvider(...) |
Pluggable transcript-compaction backend |
Transcript source providers that share an account namespace with an inbound
channel declare an accountOwnership descriptor with that channel id and a
canonical account resolver. OpenClaw then
ignores model-selected account ids for same-channel capture, binds the trusted
inbound account, and records it as the session owner for later lifecycle
actions. The resolver also selects an omitted account before OpenClaw starts or
persists live capture. It validates an already-bound trusted account without
redirecting it and returns an actionable typed error when no unique capable
account exists. Configured auto-start must supply a nonempty source account or
resolve one with this descriptor. OpenClaw rejects ambiguous or unresolved ownership before it
persists the start or invokes the provider. Provider aliases are lookup names
only and must not be used for this declaration.
Worker providers must also declare their id in contracts.workerProviders.
Providers may implement maintain({ profiles, signal, assertCurrent }) for bounded cleanup that must continue with no active leases. The Gateway invokes it for enabled, configured providers from its existing periodic worker sweep, separately from allocation and reconciliation waits. profiles contains cloned settings for the provider's current configured profiles. Call assertCurrent() immediately before external effects and after awaited work before durable mutations; authority ends when the invocation settles, its configuration or registration changes, or the Gateway stops. Honor signal and settle only after owned commands stop. A provider's plugin service must also cancel and drain maintenance during generation replacement. The hook must not allocate running capacity or treat maintenance as user demand; retention and cleanup policy remain provider-owned.
Core persists durable intent before provision(profile, operationId, options?). Providers validate settings and any optional options.machineClass, options.os, and options.executionMode before external allocation and throw WorkerProviderError for permanent profile rejection. provision must adopt the same lease for the same operation id and selected execution mode; a retry cannot silently change modes. If provider-owned setup fails after allocation and cleanup is indeterminate, throw WorkerProviderError.cleanupIndeterminate(leaseId, provisionError, cleanupError) so core persists the known lease and reconciles teardown instead of replaying provision. Providers may expose process-stable picker metadata with asynchronous listMachineOptions(profile); omit the hook when the profile has no meaningful machine choice. Machine options contain id, label, optional os, optional positive-integer cpu and memoryGb, and optional default. An option without os applies to every advertised operating system. The optional asynchronous listOperatingSystems(profile) hook returns provider-owned { id, label, default? } choices. Plugin authors can derive the item type from Awaited<ReturnType<NonNullable<WorkerProvider["listOperatingSystems"]>>>[number] or declare a local structural type; there is no public WorkerOperatingSystem export. Core treats OS ids as opaque strings; plugins own their meaning and validation. environments.list exposes up to 64 machine options and up to eight operating systems per profile, omitting the operating-system list when there is only one choice. Session-placement providers declare one or both current supportedExecutionModes values in deterministic canonical order: ["worker-turn"], ["remote-exec"], or ["worker-turn", "remote-exec"]. Empty lists, duplicate values, unknown modes, and noncanonical order are rejected. worker-turn requires a node lease; remote-exec accepts either a node lease or an existing SSH lease. Omission advertises no placement modes while preserving direct environment lifecycle calls. Direct environment creation without a session supplies no execution mode, so providers retain their intentional default setup; the bundled Crabbox provider defaults to worker-turn. Providers whose provisioning can legitimately exceed core's five-minute default may return a positive millisecond budget from resolveProvisionTimeoutMs(profile); include acquisition, provider-owned setup, and cleanup in that bound. resolveDestroyTimeoutMs(profile) declares the equivalent budget for teardown, including snapshot capture or other provider-owned work before confirmed release. Core uses that budget for both requested teardown and bootstrap-failure cleanup; an explicit service timeout override takes precedence. Budgets must be positive safe integers within the platform timer limit.
An optional options.signal cancels the current provisioning attempt. Forward it to acquisition, project preparation, setup, readiness, and enrollment waits, and settle active commands before rejecting; a caller-visible timeout or abort is not proof that a provider child exited or a lease was released. Compose it with project, runtime-preparation, and enrollment signals instead of replacing it with a narrower grant's signal. Keep cleanup on its independent, uncancelled lifecycle authority. Core records destroy intent for the exact operation and resolves its allocation before canonical teardown; providers that cannot cancel promptly remain owned until their real operation settles. Gateway shutdown is distinct: enrollment closure alone retains a fixed allocation for restart adoption, while an aborted provisioning signal means explicit cancellation.
Providers can implement prepareProvision(profile, operationId, options?), returning an allocation function () => Promise<WorkerLease>. Preparation may validate local settings and read configuration, but must not allocate, renew, modify resources, enroll nodes, or call project preparation. Core keeps a fresh environment in requested during preparation and records provisioning immediately before invoking the returned function. Prepared facts remain in that invocation; do not reread configuration inside the allocation function. A fresh preparation failure needs no provider teardown. A replay preparation failure remains uncertain because a previous invocation may already have allocated. Cancellation or timeout discards the prepared function. Providers without this hook retain the existing provision contract; providers implementing it should delegate their direct provision entry point to the same preparation and allocation flow.
Every worker provider must implement resolveAllocation(profile, operationId), returning { leaseId: string; sharedHost: boolean } for the exact operation. Core passes the frozen settings snapshot, even after the named profile changes or is removed. The handle identifies the cleanup target; it does not prove a machine was created or a transport is ready. Resolution must not allocate, start, renew, run setup, read setup secrets, enroll nodes, or wait for availability. Throw if the identity cannot be resolved safely. When destruction is requested before a provision result is recorded, core persists this handle with the existing teardown state and calls destroy, without replaying provision. destroy still must prove release or authoritative absence. Both calls remain serialized behind any earlier provider operation until it actually settles, including after a caller-visible timeout.
Providers that enroll cloud nodes set requiresNodeEnrollment: true and call options.beginNodeEnrollment() after allocating the machine. The returned WorkerNodeEnrollment supplies displayName, openclawVersion, an optional enrollment-lifetime signal, waitForDeviceId(), and either mode: "connect" with setupCode and setupId, or mode: "resume" with the bound deviceId. Its required nodeBootstrap contains the Gateway-prepared runtime archive's url, secret bearer token, sha256, bytes, openclawVersion, enabledPluginIds, and optional tlsFingerprint. Download that exact archive, verify its size and digest, install its target-platform dependencies, and enable the listed plugins in the node's isolated state before connecting. Do not substitute a global or registry package based only on a matching version. Keep download and enrollment credentials out of command arguments, logs, npm, and the launched node's environment; cancel work when signal aborts. Download authority belongs to the live enrollment attempt, not the URL or digest alone. After connection, return the device identity from waitForDeviceId() in the node lease. See Bundle installation for source builds, artifact reuse, and proxy requirements. Bootstrap installation does not authorize node commands or replace invocation policy.
Providers that capture reusable project images can declare supportsProjectPreparation(profile, machineClass?, os?). The optional placement overrides are strings; the machine-class argument retains its existing position so older providers remain compatible. Use those overrides when deciding whether preparation is supported. For eligible Git placements, core persists a project identity and pinned base commit before allocation, then supplies options.project. Call its prepare({ runScript, upload }) adapter on the allocated machine; core owns the bounded Git pack, clean checkout verification, and cache layout, while the provider owns command and file transport. Honor project.signal and call project.assertCurrent() around awaited provider work. Retained callbacks reject after the provision attempt closes. Project keys are scoped to the Gateway and repository, including linked worktrees; session edits and Git credentials are excluded from the prepared base.
Core supplies options.nodeRuntimeIdentity as a grant-free prepared fact: the node archive SHA-256, execution mode, and worker archive SHA-256 when project preparation retains that archive. Compare this identity when deciding whether captured runtime content is current; a version string is insufficient. Core checks the later runtime and enrollment descriptors against these prepared bytes and closes their grants if the identity changes.
Before capturing, call options.prepareNodeRuntime() to obtain artifact access without creating a node identity or enrollment code. The result includes nodeBootstrap, workerBundle, and the operation's cancellation signal. The worker archive descriptor supplies url, secret token, sha256, bytes, optional tlsFingerprint, and the core-owned packageRelativePath within the installed node package. Download and verify both archives, install the runtime, and publish the compressed worker archive at that exact contained location before capture. Keep one published worker archive per runtime package, exclude credentials and receipts, and never add the standalone payload to the slim runtime archive. The normal authenticated installer validates the prepared bytes and creates a fresh installation after enrollment; the raw archive grants no admission authority. Finish capture before calling beginNodeEnrollment(). Beginning enrollment, cancellation, replacement, or closure revokes both preparation grants. A native capture with an uncertain outcome must settle or be explicitly recovered before enrollment can introduce credentials into its source machine. Persist the original cold/checkpoint allocation decision before contacting the provider, retain checkpoint references until confirmed release, and never switch images when replaying the same operation.
Core persists the validated profile settings with the lease and supplies that snapshot to destroy({ leaseId, profile }), which must be idempotent, and inspect({ leaseId, profile }), which returns active, dormant, destroyed, or unknown. This lets providers route lifecycle calls after a gateway restart or named-profile removal. SSH endpoints use a SecretRef for keyRef, never inline key material, and include a hostKey from trusted provisioning output as exactly algorithm base64, without a hostname or comment. Core pins hostKey and never trusts a key from the first connection. Providers may also return up to 10 ordered, unique fallbackPorts (integer ports from 1 through 65535, excluding the primary port); core validates and persists those advertised candidates for idempotent probes, content-addressed transfers, receipt/lock-guarded artifact installation, convergent managed-worktree mirroring, and tunnel reconnects. Ambiguous unguarded stateful commands fail closed and are not replayed across candidates. A lease may set sharedHost: true when the SSH account also owns unrelated processes; core then avoids host-wide process freezing during workspace reconciliation. Omitted or false means a dedicated worker host. Active inspection repeats this fact so core can reconcile provider-owned isolation for leases persisted before the field existed; tunnel startup waits for that first authoritative inspection. A provider that mints a dynamic keyRef can implement resolveSshIdentity({ leaseId, profile, keyRef }); when present, that resolver is authoritative, while providers without it use the configured generic secret resolver.
WorkerLease.desktop is optional and has the shape { protocol: "rfb"; port: number; passwordFilePath?: string; apps?: WorkerDesktopApp[] }; passwordFilePath, when present, must be absolute. Providers report this warm-time capability from provision; it cannot be retrofitted onto a live lease. The owning SSH or node carrier reads the password on the worker when needed and never persists it in the Gateway store. WorkerDesktopApp is a closed union: { id: "browser"; executablePath: string; cdpPort: number } or { id: "terminal"; executablePath: string }. App ids must be unique, executable paths must be absolute, browser CDP ports must be integers from 1 through 65535, and the list accepts at most eight entries. Core rejects unknown ids and fields.
Providers with renewable leases can also implement renew(leaseId).
inspect must throw on transient or indeterminate failures; return unknown only for authoritative absence. Core fences the environment and invokes canonical teardown; shared or unknown host isolation still requires acknowledgment that the exact worker stopped. A shared host must not be stopped or unpaired merely to release its logical lease.
Embedding providers registered with api.registerEmbeddingProvider(...) must
also be listed in contracts.embeddingProviders in the plugin manifest. This
is the generic embedding surface for reusable vector generation. Memory search
consumes this generic provider surface. The older memory-specific registrar and
manifest contract were removed after their August 2026 migration window.
Memory-specific providers that still expose a runtime batchEmbed(...) stay on
the existing per-file batching contract unless their runtime explicitly sets
sourceWideBatchEmbed: true. That opt-in lets the memory host submit chunks from
multiple dirty memory files and enabled sources in one batchEmbed(...) call up
to the host batch limits. Batch adapters that upload JSONL request files must
split provider jobs before their upload-size cap as well as their request-count
cap. The provider must return one embedding per input chunk in the same order as
batch.chunks; omit the flag when the provider expects file-local batches or
cannot preserve input ordering across a larger source-wide job.