Skip to main content
Every plugin exports a default entry object. The SDK provides a helper for each entry shape: defineToolPlugin, definePluginEntry, defineChannelPluginEntry, defineSetupPluginEntry. All plugin APIs are experimental, including these entry helpers. Pin and test the OpenClaw host versions your plugin supports.
Looking for a walkthrough? See Tool Plugins, Channel Plugins, or Provider Plugins for step-by-step guides.

Where each section moved

Every section of the single-page version now lives on this page or on one of the eight child pages below. The anchors from the single-page version still resolve here.

Plugin shapes

OpenClaw classifies loaded plugins by their registration behavior: Use openclaw plugins inspect <id> to see a plugin’s shape.

MCP subprocess runtime

Import: mcpStdioRuntime from openclaw/plugin-sdk/agent-harness-runtime using dynamic import() when opening a connection. Its frozen object lazily loads one factory:
Use createMcpStdioClient(params) for a caller-owned MCP proxy subprocess fronting a stateful driver. OpenClaw owns the subprocess and its descendants, newline framing and JSON-RPC validation, initialization, request admission, deadlines, and shutdown. The client starts connecting when the factory returns. Keep this runtime out of plugin registration and paths that do not open MCP connections. Supply command, optional args, and an exact env. The child inherits no other environment variables. Set clientInfo (name and version), the required protocolVersion, startupTimeoutMs, maxPendingRequests, and maxFrameBytes. The server must return exactly the requested protocol version. OpenClaw retains a fixed 32 KiB stderr tail for unexpected-exit diagnostics. The decoder bounds pending bytes plus each incoming chunk before buffering, preserves fragmented UTF-8, skips empty lines, and requires safe integer response IDs. The caller supplies errors.unavailable(message, cause?) and errors.protocol(message, cause?), each returning an Error. The first classifies process, lifecycle, admission, deadline, and cancellation failures. The second classifies malformed frames, non-timeout JSON-RPC errors, and handshake contract violations. Plugin-specific tool-result normalization stays with the caller. The returned client exposes three methods:
  • isAvailable() synchronously reports whether initialization completed and the connection remains usable.
  • request(method, params, { timeoutMs, signal? }) waits for startup and returns the object result. An already-aborted signal or a full pending-request limit rejects only that call. After admission, cancellation or timeout retires the entire connection and rejects pending requests with the retained fatal error. The client suppresses SDK cancellation notifications because it terminates the process instead. A non-timeout JSON-RPC error response rejects only its matching request through errors.protocol.
  • stop() closes admission, retires pending requests, and awaits startup settlement and owned-process cleanup. It rejects through errors.unavailable with proxy cleanup could not be confirmed if cleanup is uncertain. It never stops a separately started service reached through the proxy’s socket.
After successful stop(), the optional read-only cleanupResult records forced relay retirement: reason: "forced-relay-exit", signalRequested, the observed relay exit code and signal, durationMs, and escalationAfterMs. It retains signalError when signal delivery reported failure but exit was subsequently confirmed. It is absent for ordinary cleanup. Closed control/output/lineage pipes and a matching closing receipt admit escalation; pending force requests are reconsidered as closure and group-exit facts arrive. A live anchor is killed and reaped through its relay. Confirmed anchor-group absence permits direct native termination of an unresponsive relay. Actual relay exit and server-group disappearance must then be confirmed within the original hard deadline. Uncertain cleanup retains missing closure facts and timing or signal-delivery details in the error’s cause chain. Malformed frames, incompatible initialization, write failures, and unexpected process exit also retire the whole connection. The first fatal error is retained. Create a new client to reconnect. Timeout classification follows the SDK error code, so a timeout-coded server error also retires the connection.

Workspace access

Use openclaw/plugin-sdk/agent-workspace-runtime to declare, register, and acquire AgentWorkspaceAccess without loading the agent execution runtime. Declare a configured remote workspace during registration so callers cannot fall back to local files before its service starts. Register its bridge when ready and release it when the service stops. Callers keep their existing document authorization. createWorkspaceBootstrapFilePolicy({ workspaceDir, config }) lets adapters restrict this bridge to native bootstrap documents and the configured bootstrap-extra-files patterns. Check canList for directory metadata, canRead for file bytes, and canWrite for the four owner-editable documents. Directory access does not grant reads of other files. The underlying bridge still enforces filesystem containment and returns the read’s canonical source. Workspace access that has not started or has stopped throws WorkspaceAccessUnavailableError. Use isWorkspaceAccessUnavailableError(error) to recognize this condition through wrapped errors or separate SDK instances. The error code is WORKSPACE_ACCESS_UNAVAILABLE; do not match message text. The optional memoryFiles provider keeps workspace Memory files on the host while the native index, embedding providers and original sessions stay on Gateway. It supplies discovery, file inspection, reads and change notifications. Both indexing and memory_get use it; index publication rechecks the host file. The canonical source returned with a read supplies provenance, without resolving a stale Gateway copy. Stopping the workspace binding revokes retained file access and subscriptions. The Memory file worker supports --files <workspace> for native file operations without opening a host index or receiving embedding credentials. A provider can invoke it through its existing subprocess transport. createWorkspaceMemoryFileClient maps Gateway/host paths and preserves native errors for this worker. Supply request for one JSON exchange and subscribe for the --watch-files JSON-line stream, plus the binding’s abort signal. Neither callback depends on Codex; providers own transport and authorization. memoryFiles.maintenance routes existing dreaming, promotion, corpus and forget file operations to the host. Compound writes reuse native atomic publication and conflict handling; maintenance decisions, locks and SQLite state stay on Gateway. A remote binding without maintenance support fails instead of using Gateway files. The file worker implements these operations and native change notifications. The paired-node file-transfer adapter connects these operations through the existing service-owned node channel and node file policy. Task-time Skill preparation uses remote discovery. Channel-native menus use Gateway-owned Skills without waiting for the Harness; remote menu support is tracked in Enterprise #241. The optional skillResources provider handles Skill reads separately from Agent document access. Its readInstructions reads the selected instruction file for Code Mode; readSkillFiles supplies a bundle for worker delivery. Gateway-owned bundled, plugin, Library, Workshop and user-level sources keep their Gateway paths. Workspace-owned sources use the remote provider. Gateway preserves source precedence and uses existing resource delivery for workers. Discovery assigns file ownership; a provider cannot request Gateway-local reads by returning a source label or fileHost value. Stopping the binding revokes retained host readers. The Skills worker also runs install and ClawHub operations. Install/remove use an authenticated adapter’s duplex channel so Gateway policy and mutation checks run before the native filesystem operation. The adapter admits source roots and uploads; the worker uses its host account’s permissions. For a remote workspace, dependency installation uses installSkillDependencies. Gateway selects the recipe and runs install policy; the host runs the existing installer through the worker’s installDependencies operation. Requests contain the Skill key, recipe, installation preferences and timeout. Recipe choices use the host’s OS and binaries. Missing host support fails without installing on Gateway. File-inspecting Gateway policies receive a temporary tree from the existing Skill resource reader; Gateway-owned sources remain local. Resource bundle limits apply. readWorkspaceSkillResources lazily reuses the bounded native bundle reader. File-transfer adapters can check each file’s requested and verified canonical paths before returning a bundle; admitting the Skill directory alone does not admit every child. Hosts can provide watchSkills(request, onChange, signal) to notify the existing snapshot cache when admitted Skill sources change. Keep the subscription alive until aborted, and send change after the initial scan and later edits. Send unavailable if file watching stops: preparation then refreshes on each call, without reopening the subscription. Hosts without watchSkills use that same fallback. skills.load.watch: false disables the subscription and this fallback. Gateway watches Workshop locally under the same snapshot invalidation lifecycle. The paired-node file-transfer adapter also connects Skill discovery, resource reads, watching and dependency installation through workspace.skills. Its native worker launcher uses resolveWorkspaceWorkerArgv("memory" | "skills") from agent-workspace-runtime, then appends the operation arguments. Use the same OpenClaw version on Gateway and node. This adapter does not implement remote Skill source install/update/remove or ClawHub lifecycle operations; those remain tracked in Enterprise #242.

Tool failure diagnostics

Agent harnesses can import readToolOperatorHint(error) from openclaw/plugin-sdk/agent-harness-runtime to read optional operator advice attached to a tool failure. Include it only in the operator log. Keep it out of model responses, tool-result callbacks, and serialized transcripts, and preserve the original error message. An unannotated or immutable error needs no substitute hint; the reader returns undefined when no advice is available.

ACP harness turns

Pass optional currentInboundContext to resolveAgentHarnessBeforePromptBuildResult from openclaw/plugin-sdk/agent-harness-runtime. It combines the prompt with its inbound context and channel-provided joiner before prompt hooks run. Frame ordinary chat with prose section labels so a leading file path cannot become a native slash command. Keep one admitted user turn while assigning each provider attempt its own request and reply identity. Host requestApproval normalizes the title and description within the shared display bounds and preserves full action evidence in detail. Its response acknowledges the request with an ID. Call waitForApproval with that ID to obtain the decision, then recheck the turn’s signal and authority before allowing the native operation. Use the plugin approval timeout independently of the agent-run timeout. Authenticated Control UI reviewers can inspect detail, while channel messages retain the bounded description. Oversized detail is rejected by the existing request schema.