← Writing

OpenClaw

The Plugin Is Installed. Why Can’t OpenClaw Use It Yet?

The plugin is listed, but its tool is missing. Follow a capability through synchronous registration, failure rollback, and service startup and shutdown.

Imagine the project checklist can finally be checked and saved. You want one more capability: watch tasks for changes and notify you when a new result arrives. You install a plugin and complete its configuration. It appears in the plugin list, but when you ask the assistant to use it, the assistant still says the tool is unavailable.

This time, the problem may be neither the model nor the sandbox. The plugin is on disk, but its capabilities have not reached the OpenClaw instance serving your conversation.

“Installed” is a filesystem fact. “Callable” is a runtime fact. An assembly process stands between them. In this final article of the series, we follow a capability as it acquires an identity, enters a registry, starts working, and eventually releases its resources.

The notification plugin is hypothetical. The source baseline remains commit a374c3a, corresponding to v2026.5.22. We focus on native OpenClaw plugins; compatible bundles from other products have different formats and loading paths.

A sign on the door does not mean the shop is open

The openclaw.plugin.json file in a native plugin’s root is a sign that can be inspected first: who the plugin is, what its configuration should look like, and what capabilities it declares. OpenClaw can read that metadata and validate configuration without executing plugin code. Code entrypoints and package installation metadata have their own locations; the manifest is not a startup script. Native plugin manifest

That ordering is useful. If the notification plugin expects a numeric polling interval and receives a string, the system can report the mistake before opening a connection and discovering the error deep inside a timer. The manifest keeps some failures in the “not open yet” phase.

The loader also handles candidate discovery, identity, activation conditions, and configuration validation. A plugin on disk may be disabled, or a configuration or loading error may leave only a diagnostic. The ordinary runtime loading path validates plugin configuration against the manifest schema before proceeding to the appropriate module-loading and registration steps. Discovery · Manifest registry · Loader

A plugin list is therefore a clue rather than final proof. The official guide distinguishes static inspection from runtime inspection: to establish that tools, hooks, services, and other capabilities were registered, use openclaw plugins inspect <plugin-id> --runtime --json. Also confirm that the Gateway being inspected is the one serving the current channel. Post-installation verification

If your terminal inspects a newly started process while the conversation still reaches an older Gateway, both observations can be accurate and still disagree. Identify the running instance before checking its capabilities, rather than repeatedly reinstalling the same files.

Stages from plugin manifest discovery to synchronous registration and service startup and shutdown
Installation, declaration, registration, and operation are separate stages. Success at one stage does not verify the next.

Write the menu before getting busy in the kitchen

After loading a module, OpenClaw resolves its export and looks for register, with compatibility for the corresponding activate export. The plugin receives an API through which it can register tools, hooks, services, and other capabilities. There is a strict but useful constraint: the registration function must finish synchronously. Export resolution and registration

Why not call a remote endpoint inside register and decide which tools to add when the response arrives? Imagine the Gateway already serving requests from its registry, only for a plugin to insert another capability later. “Loading completed” would cease to be a stable boundary. Diagnostics, caching, and failure rollback would become harder to explain.

The source implements this directly. After invoking register, it checks whether the return value is Promise-like. If so, it reports that registration must be synchronous. The check includes objects or functions with a callable then, not just native Promises. It handles asynchronous rejection to avoid leaving an unhandled rejection behind, but does not wait for asynchronous initialization to finish.

A Proxy adds another boundary around the API. After the registration window closes, late calls to ordinary registration methods are ignored. Saving the API and calling registerTool after an await cannot add a capability through that path. Registration guard

This does not disable every API operation. The implementation explicitly lists methods that remain callable at runtime, including event emission, session attachment delivery, scheduling a later session turn, and cancelling scheduled turns by tag. The registration window constrains assembly; runtime operations retain defined entrypoints. API lifecycle policy · Grouped API forwarding

Our notification plugin can synchronously register a query tool and a background service. Opening connections and waiting for remote responses belong in service startup or tool execution. Settle the menu first, then start cooking. A tool’s execution function can still be asynchronous; the restriction here concerns the registration entrypoint.

A failed registration should not leave half a menu

Suppose the plugin registers its query tool and then throws while registering a hook. If the system merely logs the error and retains the first half, users face a plugin that appears to have failed loading but still exposes some capabilities.

Before invoking register, OpenClaw snapshots the registry and records several categories of process-level extension state. On registration failure, it restores that snapshot, rolls back managed global side effects, and records the plugin error. The registry includes tools, hooks, services, routes, and other entries. Commands, internal hooks, and selected runtime providers have corresponding restoration paths. Registration failure handling · Global side-effect rollback

Use the word “transaction” carefully here. The snapshot copies the outer arrays and mappings; it does not deeply clone every object. It cannot undo files already written, network requests already sent, or timers created by module-level code. Restoring registry membership is a very different promise from restoring the entire process world.

Registration becomes easier to make reliable when it stays close to declaration. Acquiring real resources inside register moves the rollback boundary into territory the host cannot see. Rejecting an asynchronous register does not automatically cancel the asynchronous function already running. The API guard can block late registration, but cannot reverse arbitrary JavaScript side effects.

Registration itself also performs checks. This version requires Agent tools to be declared in contracts.tools, then checks registered tool names against that declaration. Services are checked for duplicate IDs: a repeat registration from the same plugin keeps the first entry, while another plugin claiming that service ID produces an error diagnostic. Tool and service registration

A diagnostic is not necessarily an exception. A registration can be rejected, produce an error diagnostic, and return. Therefore, register returning without throwing does not prove every declared capability was accepted. Inspect the actual entries and diagnostics. Even a successfully registered tool must still pass through session tool construction and policy filtering before it can reach the model.

Responsibility split between registration rollback and cleanup of service resources
The host manages tracked registration state; plugins manage resources they acquire. A failed start especially needs its own partial cleanup.

Once work starts, who turns out the lights?

The service manager separates startup from registration. It awaits each service.start in registry order. A failed start is logged, but later services are still attempted. Only services whose start returns successfully enter the running list. Service lifecycle

A loaded plugin therefore does not guarantee a healthy background service. Our notification plugin might expose its query tool while its background connection fails during startup. A single green “enabled” indicator flattens distinct states. Discovery, registration, startup, and runtime errors deserve separate evidence.

At shutdown, the manager traverses successfully started services in reverse order and calls stop when present. A failed stop does not prevent other services from being stopped. This order suits resources established in sequence, but the implementation does not build a dependency graph here. It should not be described as automatically discovering dependencies or performing a topological sort.

Partial startup is the case plugin authors need to handle carefully. Suppose start opens connection A, then attempts connection B. If B fails and start throws, the service never enters running and will not receive a later manager-driven stop. Start should close A in its own error path. Sharing a resource-release function between failed startup and normal shutdown makes those paths easier to keep consistent.

The returned stop handle also lacks a once-only flag. Calling it again attempts to stop those services again. Cleanup should tolerate repetition: clear references to cancelled timers, recognize closed connections, and avoid publishing duplicate completion events. This is a design recommendation inferred from the implementation, not a claim that the host supplies idempotency for the plugin.

Sequential waiting has a cost, too. This layer does not automatically wrap every start in a timeout. A startup that never returns holds up subsequent services. Plugins should bound their own waits on external systems. The local experiment checks continuation after failed startup, reverse shutdown, continuation after failed shutdown, and repeated stop calls. It does not open real notification connections.

For our notification plugin, connection handles, timers, and unsubscribe functions can live in one service-owned object. The query tool reads state through a defined interface, while the service releases those resources during shutdown. Replacing the notification channel then requires replacing its connection adapter rather than rewriting discovery and registration. This also gives tests a useful seam: a fake connection can verify lifecycle behavior without starting a real external service or changing the plugin’s public capabilities.

A snapshot is not necessarily the live plugin set

The loader distinguishes non-activating snapshot loads from activation. Snapshots support inspection; the relevant paths avoid or restore managed process-level state so inspection does not silently replace live commands and similar registrations. On a cache hit, an activating load may restore cached registrations and install the active registry without rerunning every plugin’s register function. Caching and activation

A snapshot is not a sandbox for untrusted code. Native plugins execute in process, and module imports and plugin-owned side effects still require separate consideration. The tool sandbox from the previous article does not automatically move plugin loading into an isolated process.

Back in the story, the debugging sequence becomes straightforward: were the files discovered, did configuration validate, did the current Gateway register the tool, did the background service start, and does the current session allow the tool? Each step has different evidence and a different place to fix the problem.

This article ran 25 local checks using contiguous function segments from the pinned source with TypeScript types removed. They cover the registration window, runtime exceptions, rejection of asynchronous registration, shallow snapshots, and service startup and shutdown. Service context creation, tracing, and the HTTP registry wrapper use test adapters. The complete loader, global rollback, a real Gateway, and plugin imports were not reproduced end to end.

From the first article on routing messages to sessions to this final article on bringing capabilities into a runtime, the useful lesson from OpenClaw is to make boundaries explicit: who owns a message, where state lives, where tools execute, and who releases resources. Plugins let capabilities grow. Those boundaries help the expanded system remain understandable and maintainable.

继续阅读