Plugin SDK overview
기준일: 2026-07-26
공식 기준: Plugin SDK overview
Plugin SDK overview 문서는 OpenClaw 공식 문서(plugins/sdk-overview)를 한국어로 정리한 가이드입니다. Import map, registration API reference, and SDK architecture 명령·설정 키·코드 예시는 공식 문서를 그대로 보존하며, 해석과 절차 안내는 한국어로 제공합니다. 최종 동작은 설치된 CLI 버전과 공식 원문을 확인하세요.
핵심 요약
Import map, registration API reference, and SDK architecture
한국어 가이드 범위: plugins/sdk-overview 경로의 설정·명령·제약·예시를 학습용으로 재구성합니다.
문서 구성
공식 문서의 주요 섹션은 다음과 같습니다.
- Import convention
- Subpath reference
- Registration API
- Capability registration
- Tools and commands
- Infrastructure
- Host hooks for workflow plugins
- Gateway discovery registration
- CLI registration metadata
- CLI backend registration
- Exclusive slots
- Deprecated memory embedding adapters
- Events and lifecycle
- Hook decision semantics
- API object fields
- Internal module convention
- 관련 문서
상세 내용
본문
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.
위 내용은 공식 문서의 해당 섹션 요지입니다. 세부 플래그·기본값은 원문과
--help를 확인하세요.
Import convention
Each subpath is a small, self-contained module. This keeps startup fast and prevents circular dependency issues. For channel-specific entry/build helpers, prefer openclaw/plugin-sdk/channel-core; keep openclaw/plugin-sdk/core for the broader umbrella surface and shared helpers such as buildChannelConfigSchema.
위 내용은 공식 문서의 해당 섹션 요지입니다. 세부 플래그·기본값은 원문과
--help를 확인하세요.
Subpath reference
The plugin SDK is exposed as a set of narrow subpaths grouped by area (plugin entry, channel, provider, auth, runtime, capability, memory, and reserved bundled-plugin helpers). For the full catalog — grouped and linked — see Plugin SDK subpaths.
위 내용은 공식 문서의 해당 섹션 요지입니다. 세부 플래그·기본값은 원문과
--help를 확인하세요.
Registration API
The register(api) callback receives an OpenClawPluginApi object with these methods:
위 내용은 공식 문서의 해당 섹션 요지입니다. 세부 플래그·기본값은 원문과
--help를 확인하세요.
Capability registration
Worker providers must also declare their id in contracts.workerProviders. Core persists durable intent before provision(profile, operationId). Providers validate settings before external allocation and throw WorkerProviderError for permanent profile rejection. provision must adopt the same lease when the operation id repeats. 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, 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. 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. Providers with renewable leases can also implement renew(leaseId). inspect must throw on transient or indeterminate failures; return unknown only for authoritative absence. Core marks an active local record orphaned, or treats the absence as teardown completion after a persisted destroy request.
위 내용은 공식 문서의 해당 섹션 요지입니다. 세부 플래그·기본값은 원문과
--help를 확인하세요.
| 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; meeting plugins can use createMeetingTranscriptSourceProvider from plugin-sdk/transcripts |
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 |
Tools and commands
Use defineToolPlugin for simple tool-only plugins with fixed tool names. Use api.registerTool(...) directly for mixed plugins or fully dynamic tool registration.
위 내용은 공식 문서의 해당 섹션 요지입니다. 세부 플래그·기본값은 원문과
--help를 확인하세요.
| Method | What it registers |
|---|---|
api.registerTool(tool, opts?) |
Agent tool (required or { optional: true }) |
api.registerCommand(def) |
Custom command (bypasses the LLM) |
api.registerNodeHostCommand(command) |
Command handled by openclaw node run; optional agentTool metadata can expose it as an agent-visible tool while the node is connected |
agentPromptGuidance: [
"Global command hint.",
{ text: "Only show this in the main OpenClaw prompt.", surfaces: ["openclaw_main"] },
];
Infrastructure
Webhook routes that acknowledge a request before processing finishes must move that detached work onto its own tracked admission root:
위 내용은 공식 문서의 해당 섹션 요지입니다. 세부 플래그·기본값은 원문과
--help를 확인하세요.
주요 항목:
- Resolver context carries trusted host identity only (
requesterSenderId, - One plugin owns one server name: a duplicate
- Tool names are derived from the full declared server set so partial resolution
- Runs without a trusted
requesterSenderId(cron, subagent, heartbeat, public resolveis bounded at 10 seconds per server; a timeout or throw omits that- Resolved connections are revalidated at most every 5 minutes per requester:
- Resolved
headersare never logged or persisted; core keeps only an ephemeral - Requester-scoped servers do not mint MCP App views: a view outlives the
- Static servers without a resolver keep the existing session-scoped lifecycle.
- Harness delivery rule: requester-scoped servers never enter harness-native
- Embedded runner: session MCP runtime + bundle tools (static + scoped).
- Codex app-server: dynamic tools via
- Scoped tool specs are session-stable after the first successful resolve in
- Unauthenticated requesters on a shared-thread harness still see the advertised
| Method | What it registers |
|---|---|
api.registerHook(events, handler, opts?) |
Event hook |
api.registerHttpRoute(params) |
Gateway HTTP endpoint |
api.registerGatewayMethod(name, handler) |
Gateway RPC method |
api.registerGatewayDiscoveryService(service) |
Local Gateway discovery advertiser |
api.registerCli(registrar, opts?) |
CLI subcommand |
api.registerNodeCliFeature(registrar, opts?) |
Node feature CLI under openclaw nodes |
api.registerService(service) |
Background service |
api.registerInteractiveHandler(registration) |
Interactive handler |
api.registerAgentToolResultMiddleware(...) |
Runtime tool-result middleware |
api.registerMemoryPromptSupplement(builder) |
Additive memory-adjacent prompt section |
api.registerMemoryPromptPreparation(prepare) |
Async preparation for a memory-adjacent prompt section |
api.registerMemoryCorpusSupplement(adapter) |
Additive memory search/read corpus |
api.registerHostedMediaResolver(resolver) |
Resolver for browser-style hosted media URLs |
api.registerMcpServerConnectionResolver(...) |
Per-requester MCP transport (url/headers) for a static server name |
api.registerTextTransforms(transforms) |
Plugin-owned prompt/message compatibility text rewrites |
api.registerConfigMigration(migrate) |
Lightweight config migration run before plugin runtime loads |
api.registerMigrationProvider(provider) |
Importer for openclaw migrate |
api.registerAutoEnableProbe(probe) |
Config probe that can auto-enable this plugin |
api.registerReload(registration) |
Restart/hot/noop config-prefix policy for reload handling |
api.registerNodeHostCommand(command) |
Command handler exposed to paired nodes |
api.registerNodeInvokePolicy(policy) |
Allowlist/approval policy for node-invoked commands |
api.registerSecurityAuditCollector(collector) |
Findings collector for openclaw security audit |
void runDetachedWebhookWork(() => processWebhookEvent(event)).catch((error) => {
runtime.error?.(`webhook dispatch failed: ${String(error)}`);
});
api.registerMcpServerConnectionResolver({
serverName: "user-email",
resolve: async (ctx) => {
// ctx.requesterSenderId is host-trusted; never invent sender identity here.
const token = await lookupUserToken(ctx.requesterSenderId);
if (!token) {
return null; // omit this server for the current run
}
return {
url: "https://mcp.example.com/email",
headers: { Authorization: `Bearer ${token}` },
};
},
});
Host hooks for workflow plugins
Host hooks are the SDK seams for plugins that need to participate in the host lifecycle rather than only adding a provider, channel, or tool. They are generic contracts; Plan Mode can use them, but so can approval workflows, workspace policy gates, background monitors, setup wizards, and UI companion plugins.
위 내용은 공식 문서의 해당 섹션 요지입니다. 세부 플래그·기본값은 원문과
--help를 확인하세요.
주요 항목:
api.session.state.registerSessionExtension(...)api.session.workflow.enqueueNextTurnInjection(...)api.session.workflow.registerSessionSchedulerJob(...)api.session.workflow.sendSessionAttachment(...)api.session.workflow.scheduleSessionTurn(...)api.session.workflow.unscheduleSessionTurnsByTag(...)api.session.controls.registerSessionAction(...)api.session.controls.registerControlUiDescriptor(...)api.agent.events.registerAgentEventSubscription(...)api.agent.events.emitAgentEvent(...)api.runContext.setRunContext(...)/getRunContext(...)/clearRunContext(...)api.lifecycle.registerRuntimeLifecycle(...)- External plugins can own session extensions, UI descriptors, commands, tool
- Trusted tool policies run before ordinary
before_tool_callhooks and are - Reserved command ownership is bundled-only. External plugins should use their
allowPromptInjection=falsedisables prompt-mutating hooks including
| Method | Contract it owns |
|---|---|
api.session.state.registerSessionExtension(...) |
Plugin-owned, JSON-compatible session state projected through Gateway sessions |
api.session.workflow.enqueueNextTurnInjection(...) |
Durable exactly-once context injected into the next agent turn for one session |
api.registerTrustedToolPolicy(...) |
Manifest-gated trusted pre-plugin tool policy that can block or rewrite tool params |
api.registerToolMetadata(...) |
Tool catalog display metadata without changing the tool implementation |
api.registerCommand(...) |
Scoped plugin commands; command results can set continueAgent: true or suppressReply: true; Discord native commands support descriptionLocalizations |
api.session.controls.registerControlUiDescriptor(...) |
Control UI contribution descriptors for session, tool, run, settings, or tab surfaces |
api.lifecycle.registerRuntimeLifecycle(...) |
Cleanup callbacks for plugin-owned runtime resources on reset/delete/reload paths |
api.agent.events.registerAgentEventSubscription(...) |
Sanitized event subscriptions for workflow state and monitors |
api.runContext.setRunContext(...) / getRunContext(...) / clearRunContext(...) |
Per-run plugin scratch state cleared on terminal run lifecycle |
api.session.workflow.registerSessionSchedulerJob(...) |
Cleanup metadata for plugin-owned scheduler jobs; does not schedule work or create task records |
api.session.workflow.sendSessionAttachment(...) |
Bundled-only host-mediated file attachment delivery to the active direct-outbound session route |
api.session.workflow.scheduleSessionTurn(...) / unscheduleSessionTurnsByTag(...) |
Bundled-only Cron-backed scheduled session turns plus tag-based cleanup |
api.session.controls.registerSessionAction(...) |
Typed session actions clients can dispatch through the Gateway |
| Plugin archetype | Hooks used |
|---|---|
| Approval workflow | Session extension, command continuation, next-turn injection, UI descriptor |
| Budget/workspace policy gate | Trusted tool policy, tool metadata, session projection |
| Background lifecycle monitor | Runtime lifecycle cleanup, agent event subscription, session scheduler ownership/cleanup, heartbeat prompt contribution, UI descriptor |
| Setup or onboarding wizard | Session extension, scoped commands, Control UI descriptor |
api.session.controls.registerControlUiDescriptor({
surface: "tab",
id: "logbook",
label: "Logbook",
description: "Your day as a timeline, built from screen snapshots.",
icon: "sun",
group: "control",
requiredScopes: ["operator.write"],
});
Gateway discovery registration
api.registerGatewayDiscoveryService(...) lets a plugin advertise the active Gateway on a local discovery transport such as mDNS/Bonjour. OpenClaw calls the service during Gateway startup when local discovery is enabled, passes the current Gateway ports and non-secret TXT hint data, and calls the returned stop handler during Gateway shutdown.
위 내용은 공식 문서의 해당 섹션 요지입니다. 세부 플래그·기본값은 원문과
--help를 확인하세요.
api.registerGatewayDiscoveryService({
id: "my-discovery",
async advertise(ctx) {
const handle = await startMyAdvertiser({
gatewayPort: ctx.gatewayPort,
tls: ctx.gatewayTlsEnabled,
displayName: ctx.machineDisplayName,
});
return { stop: () => handle.stop() };
},
});
CLI registration metadata
api.registerCli(registrar, opts?) accepts two kinds of command metadata:
위 내용은 공식 문서의 해당 섹션 요지입니다. 세부 플래그·기본값은 원문과
--help를 확인하세요.
주요 항목:
commands: explicit command names owned by the registrardescriptors: parse-time command descriptors used for CLI help,parentPath: optional parent command path for nested command groups, such as
api.registerCli(
async ({ program }) => {
const { registerMatrixCli } = await import("./src/cli.js");
registerMatrixCli({ program });
},
{
descriptors: [
{
name: "matrix",
description: "Manage Matrix accounts, verification, devices, and profile state",
hasSubcommands: true,
},
],
},
);
api.registerCli(
async ({ program }) => {
const { registerNodesCanvasCommands } = await import("./src/cli.js");
registerNodesCanvasCommands(program);
},
{
parentPath: ["nodes"],
descriptors: [
{
name: "canvas",
description: "Capture or render canvas content from a paired node",
hasSubcommands: true,
},
],
},
);
CLI backend registration
api.registerCliBackend(...) lets a plugin own the default config for a local AI CLI backend such as claude-cli or my-cli.
위 내용은 공식 문서의 해당 섹션 요지입니다. 세부 플래그·기본값은 원문과
--help를 확인하세요.
주요 항목:
- The backend
idbecomes the provider prefix in model refs likemy-cli/gpt-5. - The backend
configis the authoritative command adapter: argv, environment, - Users select the backend through model refs or model-scoped
agentRuntime.id; - Use
normalizeConfigwhen registered static fields need a runtime-aware - Use
resolveExecutionArgsfor request-scoped argv rewrites that belong to - Use
prepareExecutionfor backend-owned launch environment or temporary - Backends that can disable all native tools for a specific run may declare
Exclusive slots
| Method | What it registers |
|---|---|
api.registerContextEngine(id, factory) |
Context engine (one active at a time). Lifecycle callbacks receive runtimeSettings when the host can provide model/provider/mode diagnostics; older strict engines are retried without that key. |
api.registerMemoryCapability(capability) |
Unified memory capability |
Deprecated memory embedding adapters
for host-managed exports. Companion plugins that enumerate those declared artifacts still use listActiveMemoryPublicArtifacts(...) from the retained openclaw/plugin-sdk/memory-host-core facade until a focused public consumer API exists; they must not reach into another plugin's private layout.
위 내용은 공식 문서의 해당 섹션 요지입니다. 세부 플래그·기본값은 원문과
--help를 확인하세요.
주요 항목:
registerMemoryCapabilityis the exclusive memory-plugin API.registerMemoryCapabilitymay also exposepublicArtifacts.listArtifacts(...)MemoryFlushPlan.modelcan pin the flush turn to an exactprovider/modelregisterMemoryEmbeddingProvideris deprecated. New embedding providers- Existing memory-specific providers continue to work during the migration
| Method | What it registers |
|---|---|
api.registerMemoryEmbeddingProvider(adapter) |
Memory embedding adapter for the active plugin |
Events and lifecycle
See Plugin hooks for examples, common hook names, and guard semantics.
위 내용은 공식 문서의 해당 섹션 요지입니다. 세부 플래그·기본값은 원문과
--help를 확인하세요.
| Method | What it does |
|---|---|
api.on(hookName, handler, opts?) |
Typed lifecycle hook |
api.onConversationBindingResolved(handler) |
Conversation binding callback |
Hook decision semantics
before_install is a plugin-runtime lifecycle hook, not the operator install policy surface. Use security.installPolicy when an allow/block decision must cover CLI and Gateway-backed install or update paths.
위 내용은 공식 문서의 해당 섹션 요지입니다. 세부 플래그·기본값은 원문과
--help를 확인하세요.
주요 항목:
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 omittingblock), 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 omittingblock), 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 omittingcancel), not as an override.message_received: use the typedthreadIdfield when you need inbound thread/topic routing. Keepmetadatafor channel-specific extras.message_sending: use typedreplyToId/threadIdrouting fields before falling back to channel-specificmetadata.gateway_start: usectx.config,ctx.workspaceDir, andctx.getCron?.()for gateway-owned startup state instead of relying on internalgateway:startuphooks. Cron may still be loading at this point.cron_reconciled: rebuild a full external cron projection after startup or scheduler reload. It includesreasonand the effectiveenabledstate, includingenabled: false, whilectx.getCron?.()returns the exact reconciled scheduler. Passctx.abortSignalinto durable projection work; it aborts when that scheduler snapshot is superseded or the Gateway closes.cron_changed: observe gateway-owned cron lifecycle changes.scheduledandremovedevents are post-commit reconciliation hints, not an ordered delta log. A scheduled event'sevent.nextRunAtMsis absent when the job has no next wake; a removed event still carries the deleted job snapshot.
API object fields
| Field | Type | Description |
|---|---|---|
api.id |
string |
Plugin id |
api.name |
string |
Display name |
api.version |
string? |
Plugin version (optional) |
api.description |
string? |
Plugin description (optional) |
api.source |
string |
Plugin source path |
api.rootDir |
string? |
Plugin root directory (optional) |
api.config |
OpenClawConfig |
Current config snapshot (active in-memory runtime snapshot when available) |
api.pluginConfig |
Record<string, unknown> |
Plugin-specific config from plugins.entries.<id>.config |
api.runtime |
PluginRuntime |
Runtime helpers |
api.logger |
PluginLogger |
Scoped logger (debug, info, warn, error) |
api.registrationMode |
PluginRegistrationMode |
Current load mode; "setup-runtime" is the lightweight pre-full-entry startup/setup window |
api.resolvePath(input) |
(string) => string |
Resolve path relative to plugin root |
Internal module convention
Within your plugin, use local barrel files for internal imports:
위 내용은 공식 문서의 해당 섹션 요지입니다. 세부 플래그·기본값은 원문과
--help를 확인하세요.
주요 항목:
- Anthropic: public
api.ts/contract-api.tsseam for Claude @openclaw/openai-provider:api.tsexports provider builders,@openclaw/openrouter-provider:api.tsexports the provider builder
my-plugin/
api.ts # Public exports for external consumers
runtime-api.ts # Internal-only runtime exports
index.ts # Plugin entry point
setup-entry.ts # Lightweight setup-only entry (optional)
관련 문서
definePluginEntry and defineChannelPluginEntry options.
위 내용은 공식 문서의 해당 섹션 요지입니다. 세부 플래그·기본값은 원문과
--help를 확인하세요.
실습 체크리스트
- 공식 문서와 로컬 버전을 대조합니다:
https://docs.openclaw.ai/plugins/sdk-overview - 관련 CLI는
openclaw --help및 하위 명령--help로 옵션을 확인합니다. - 설정 변경 시
openclaw config/openclaw doctor로 유효성을 검사합니다. - Gateway·채널·플러그인 변경 후에는 필요 시 Gateway를 재시작합니다.
자주 쓰는 명령·설정 예시
agentPromptGuidance: [
"Global command hint.",
{ text: "Only show this in the main OpenClaw prompt.", surfaces: ["openclaw_main"] },
];
void runDetachedWebhookWork(() => processWebhookEvent(event)).catch((error) => {
runtime.error?.(`webhook dispatch failed: ${String(error)}`);
});
api.registerMcpServerConnectionResolver({
serverName: "user-email",
resolve: async (ctx) => {
// ctx.requesterSenderId is host-trusted; never invent sender identity here.
const token = await lookupUserToken(ctx.requesterSenderId);
if (!token) {
return null; // omit this server for the current run
}
return {
url: "https://mcp.example.com/email",
headers: { Authorization: `Bearer ${token}` },
};
},
});
api.session.controls.registerControlUiDescriptor({
surface: "tab",
id: "logbook",
label: "Logbook",
description: "Your day as a timeline, built from screen snapshots.",
icon: "sun",
group: "control",
requiredScopes: ["operator.write"],
});
api.registerGatewayDiscoveryService({
id: "my-discovery",
async advertise(ctx) {
const handle = await startMyAdvertiser({
gatewayPort: ctx.gatewayPort,
tls: ctx.gatewayTlsEnabled,
displayName: ctx.machineDisplayName,
});
return { stop: () => handle.stop() };
},
});
관련 링크
이 가이드는 공식 문서를 한국어 학습용으로 재구성한 것입니다. 옵션 기본값·플래그 이름은 설치 버전에 따라 달라질 수 있습니다.