Skip to main content

Plugin SDK Overview

The plugin SDK is the typed contract between plugins and core. This page is the reference for what to import and what you can register.
Looking for a how-to guide?

Import convention

Always import from a specific subpath:
Each subpath is a small, self-contained module. This keeps startup fast and prevents circular dependency issues. For channel-specific entry/build helpers, prefer velaclaw/plugin-sdk/channel-core; keep velaclaw/plugin-sdk/core for the broader umbrella surface and shared helpers such as buildChannelConfigSchema. Do not add or depend on provider-named convenience seams such as velaclaw/plugin-sdk/slack, velaclaw/plugin-sdk/discord, velaclaw/plugin-sdk/signal, velaclaw/plugin-sdk/whatsapp, or channel-branded helper seams. Bundled plugins should compose generic SDK subpaths inside their own api.ts or runtime-api.ts barrels, and core should either use those plugin-local barrels or add a narrow generic SDK contract when the need is truly cross-channel. The generated export map still contains a small set of bundled-plugin helper seams such as plugin-sdk/feishu, plugin-sdk/feishu-setup, plugin-sdk/zalo, plugin-sdk/zalo-setup, and plugin-sdk/matrix*. Those subpaths exist for bundled-plugin maintenance and compatibility only; they are intentionally omitted from the common table below and are not the recommended import path for new third-party plugins.

Subpath reference

The most commonly used subpaths, grouped by purpose. The generated full list of 200+ subpaths lives in scripts/lib/plugin-sdk-entrypoints.json. Reserved bundled-plugin helper subpaths still appear in that generated list. Treat those as implementation detail/compatibility surfaces unless a doc page explicitly promotes one as public.

Plugin entry

Registration API

The register(api) callback receives an VelaclawPluginApi object with these methods:

Capability registration

Tools and commands

Infrastructure

Reserved core admin namespaces (config.*, exec.approvals.*, wizard.*, update.*) always stay operator.admin, even if a plugin tries to assign a narrower gateway method scope. Prefer plugin-specific prefixes for plugin-owned methods.

CLI registration metadata

api.registerCli(registrar, opts?) accepts two kinds of top-level metadata:
  • commands: explicit command roots owned by the registrar
  • descriptors: parse-time command descriptors used for root CLI help, routing, and lazy plugin CLI registration
If you want a plugin command to stay lazy-loaded in the normal root CLI path, provide descriptors that cover every top-level command root exposed by that registrar.
Use commands by itself only when you do not need lazy root CLI registration. That eager compatibility path remains supported, but it does not install descriptor-backed placeholders for parse-time lazy loading.

CLI backend registration

api.registerCliBackend(...) lets a plugin own the default config for a local AI CLI backend such as codex-cli.
  • The backend id becomes the provider prefix in model refs like codex-cli/gpt-5.
  • The backend config uses the same shape as agents.defaults.cliBackends.<id>.
  • User config still wins. Velaclaw merges agents.defaults.cliBackends.<id> over the plugin default before running the CLI.
  • Use normalizeConfig when a backend needs compatibility rewrites after merge (for example normalizing old flag shapes).

Exclusive slots

Memory embedding adapters

  • registerMemoryCapability is the preferred exclusive memory-plugin API.
  • registerMemoryCapability may also expose publicArtifacts.listArtifacts(...) so companion plugins can consume exported memory artifacts through velaclaw/plugin-sdk/memory-host-core instead of reaching into a specific memory plugin’s private layout.
  • registerMemoryPromptSection, registerMemoryFlushPlan, and registerMemoryRuntime are legacy-compatible exclusive memory-plugin APIs.
  • registerMemoryEmbeddingProvider lets the active memory plugin register one or more embedding adapter ids (for example openai, gemini, or a custom plugin-defined id).
  • User config such as agents.defaults.memorySearch.provider and agents.defaults.memorySearch.fallback resolves against those registered adapter ids.

Events and lifecycle

Hook decision semantics

  • before_tool_call: returning { block: true } is terminal. Once any handler sets it, lower-priority handlers are skipped.
  • before_tool_call: returning { block: false } is treated as no decision (same as omitting block), not as an override.
  • before_install: returning { block: true } is terminal. Once any handler sets it, lower-priority handlers are skipped.
  • before_install: returning { block: false } is treated as no decision (same as omitting block), not as an override.
  • reply_dispatch: returning { handled: true, ... } is terminal. Once any handler claims dispatch, lower-priority handlers and the default model dispatch path are skipped.
  • message_sending: returning { cancel: true } is terminal. Once any handler sets it, lower-priority handlers are skipped.
  • message_sending: returning { cancel: false } is treated as no decision (same as omitting cancel), not as an override.

API object fields

Internal module convention

Within your plugin, use local barrel files for internal imports:
Never import your own plugin through velaclaw/plugin-sdk/<your-plugin> from production code. Route internal imports through ./api.ts or ./runtime-api.ts. The SDK path is the external contract only.
Facade-loaded bundled plugin public surfaces (api.ts, runtime-api.ts, index.ts, setup-entry.ts, and similar public entry files) now prefer the active runtime config snapshot when Velaclaw is already running. If no runtime snapshot exists yet, they fall back to the resolved config file on disk. Provider plugins can also expose a narrow plugin-local contract barrel when a helper is intentionally provider-specific and does not belong in a generic SDK subpath yet. Current bundled example: the Anthropic provider keeps its Claude stream helpers in its own public api.ts / contract-api.ts seam instead of promoting Anthropic beta-header and service_tier logic into a generic plugin-sdk/* contract. Other current bundled examples:
  • @velaclaw/openai-provider: api.ts exports provider builders, default-model helpers, and realtime provider builders
  • @velaclaw/openrouter-provider: api.ts exports the provider builder plus onboarding/config helpers
Extension production code should also avoid velaclaw/plugin-sdk/<other-plugin> imports. If a helper is truly shared, promote it to a neutral SDK subpath such as velaclaw/plugin-sdk/speech, .../provider-model-shared, or another capability-oriented surface instead of coupling two plugins together.