Documentation Index
Fetch the complete documentation index at: https://docs.openclaw.ai/llms.txt
Use this file to discover all available pages before exploring further.
This page is the detailed API reference design for the public
OpenClaw App SDK. It is intentionally separate from
the Plugin SDK.
@openclaw/sdk is the external app/client package for talking to the
Gateway. openclaw/plugin-sdk/* is the in-process plugin authoring contract.
Do not import Plugin SDK subpaths from apps that only need to run agents.
The public app SDK should be built in two layers:
- A low-level generated Gateway client.
- A high-level ergonomic wrapper with
OpenClaw, Agent, Session, Run,
Task, Artifact, Approval, and Environment objects.
Namespace design
The low-level namespaces should closely follow Gateway resources:
oc.agents.list();
oc.agents.get("main");
oc.agents.create(...);
oc.agents.update(...);
oc.sessions.list();
oc.sessions.create(...);
oc.sessions.resolve(...);
oc.sessions.send(...);
oc.sessions.messages(...);
oc.sessions.fork(...);
oc.sessions.compact(...);
oc.sessions.abort(...);
oc.runs.create(...);
oc.runs.get(runId);
oc.runs.events(runId, { after });
oc.runs.wait(runId);
oc.runs.cancel(runId);
oc.tasks.list(); // future API: current SDK throws unsupported
oc.tasks.get(taskId); // future API: current SDK throws unsupported
oc.tasks.cancel(taskId); // future API: current SDK throws unsupported
oc.tasks.events(taskId, { after }); // future API
oc.models.list();
oc.models.status(); // Gateway models.authStatus
oc.tools.list();
oc.tools.invoke(...); // future API: current SDK throws unsupported
oc.artifacts.list({ runId }); // future API: current SDK throws unsupported
oc.artifacts.get(artifactId); // future API: current SDK throws unsupported
oc.artifacts.download(artifactId); // future API: current SDK throws unsupported
oc.approvals.list();
oc.approvals.respond(approvalId, ...);
oc.environments.list(); // future API: current SDK throws unsupported
oc.environments.create(...); // future API: current SDK throws unsupported
oc.environments.status(environmentId); // future API: current SDK throws unsupported
oc.environments.delete(environmentId); // future API: current SDK throws unsupported
High-level wrappers should return objects that make common flows pleasant:
const run = await agent.run(inputOrParams);
await run.cancel();
await run.wait();
for await (const event of run.events()) {
// normalized event stream
}
const artifacts = await run.artifacts.list();
const session = await run.session();
Event contract
The public SDK should expose versioned, replayable, normalized events.
type OpenClawEvent = {
version: 1;
id: string;
ts: number;
type: OpenClawEventType;
runId?: string;
sessionId?: string;
sessionKey?: string;
taskId?: string;
agentId?: string;
data: unknown;
raw?: unknown;
};
id is a replay cursor. Consumers should be able to reconnect with
events({ after: id }) and receive missed events when retention allows.
Recommended normalized event families:
| Event | Meaning |
|---|
run.created | Run accepted. |
run.queued | Run is waiting for a session lane, runtime, or environment. |
run.started | Runtime started execution. |
run.completed | Run finished successfully. |
run.failed | Run ended with an error. |
run.cancelled | Run was cancelled. |
run.timed_out | Run exceeded its timeout. |
assistant.delta | Assistant text delta. |
assistant.message | Complete assistant message or replacement. |
thinking.delta | Reasoning or plan delta, when policy allows exposure. |
tool.call.started | Tool call began. |
tool.call.delta | Tool call streamed progress or partial output. |
tool.call.completed | Tool call returned successfully. |
tool.call.failed | Tool call failed. |
approval.requested | A run or tool needs approval. |
approval.resolved | Approval was granted, denied, expired, or cancelled. |
question.requested | Runtime asks the user or host app for input. |
question.answered | Host app supplied an answer. |
artifact.created | New artifact available. |
artifact.updated | Existing artifact changed. |
session.created | Session created. |
session.updated | Session metadata changed. |
session.compacted | Session compaction happened. |
task.updated | Background task state changed. |
git.branch | Runtime observed or changed branch state. |
git.diff | Runtime produced or changed a diff. |
git.pr | Runtime opened, updated, or linked a pull request. |
Runtime-native payloads should be available through raw, but apps should not
have to parse raw for normal UI.
Result contract
Run.wait() should return a stable result envelope:
type RunResult = {
runId: string;
status: "accepted" | "completed" | "failed" | "cancelled" | "timed_out";
sessionId?: string;
sessionKey?: string;
taskId?: string;
startedAt?: string | number;
endedAt?: string | number;
output?: {
text?: string;
messages?: SDKMessage[];
};
usage?: {
inputTokens?: number;
outputTokens?: number;
totalTokens?: number;
costUsd?: number;
};
artifacts?: ArtifactSummary[];
error?: SDKError;
};
The result should be boring and stable. Timestamp values preserve the Gateway
shape, so current lifecycle-backed runs usually report epoch millisecond
numbers while adapters may still surface ISO strings. Rich UI, tool traces, and
runtime-native details belong in events and artifacts.
accepted is a non-terminal wait result: it means the Gateway wait deadline
expired before the run produced a lifecycle end/error. It must not be treated as
timed_out; timed_out is reserved for a run that exceeded its own runtime
timeout.
Approvals and questions
Approvals must be first-class because coding agents constantly cross safety
boundaries.
run.onApproval(async (request) => {
if (request.kind === "tool" && request.toolName === "exec") {
return request.approveOnce({ reason: "CI command allowed by policy" });
}
return request.askUser();
});
Approval events should carry:
- approval id
- run id and session id
- request kind
- requested action summary
- tool name or environment action
- risk level
- available decisions
- expiration
- whether the decision can be reused
Questions are separate from approvals. A question asks the user or host app for
information. An approval asks for permission to perform an action.
Apps need to understand the tool surface without importing plugin internals.
const tools = await run.toolSpace();
for (const tool of tools.list()) {
console.log(tool.name, tool.source, tool.requiresApproval);
}
The SDK should expose:
- normalized tool metadata
- source: OpenClaw, MCP, plugin, channel, runtime, or app
- schema summary
- approval policy
- runtime compatibility
- whether a tool is hidden, readonly, write capable, or host capable
Tool invocation through the SDK should be explicit and scoped. Most apps should
run agents, not call arbitrary tools directly.
Artifact model
Artifacts should cover more than files.
type ArtifactSummary = {
id: string;
runId?: string;
sessionId?: string;
type:
| "file"
| "patch"
| "diff"
| "log"
| "media"
| "screenshot"
| "trajectory"
| "pull_request"
| "workspace";
title?: string;
mimeType?: string;
sizeBytes?: number;
createdAt: string;
expiresAt?: string;
};
Common examples:
- file edits and generated files
- patch bundles
- VCS diffs
- screenshots and media outputs
- logs and trace bundles
- pull request links
- runtime trajectories
- managed environment workspace snapshots
Artifact access should support redaction, retention, and download URLs without
assuming every artifact is a normal local file.
Security model
The app SDK must be explicit about authority.
Recommended token scopes:
| Scope | Allows |
|---|
agent.read | List and inspect agents. |
agent.run | Start runs. |
session.read | Read session metadata and messages. |
session.write | Create, send to, fork, compact, and abort sessions. |
task.read | Read background task state. |
task.write | Cancel or modify task notification policy. |
approval.respond | Approve or deny requests. |
tools.invoke | Invoke exposed tools directly. |
artifacts.read | List and download artifacts. |
environment.write | Create or destroy managed environments. |
admin | Administrative operations. |
Defaults:
- no secret forwarding by default
- no unrestricted environment variable pass-through
- secret references instead of secret values
- explicit sandbox and network policy
- explicit remote environment retention
- approvals for host execution unless policy proves otherwise
- raw runtime events redacted before they leave Gateway unless the caller has a
stronger diagnostic scope
Managed environment provider
Managed agents should be implemented as environment providers.
type EnvironmentProvider = {
id: string;
capabilities: {
checkout?: boolean;
sandbox?: boolean;
networkPolicy?: boolean;
secrets?: boolean;
artifacts?: boolean;
logs?: boolean;
pullRequests?: boolean;
longRunning?: boolean;
};
};
The first implementation does not need to be a hosted SaaS. It can target
existing node hosts, ephemeral workspaces, CI-style runners, or Testbox-style
environments. The important contract is:
- prepare workspace
- bind safe environment and secrets
- start run
- stream events
- collect artifacts
- clean up or retain by policy
Once this is stable, a hosted cloud service can implement the same provider
contract.
Package structure
Recommended packages:
| Package | Purpose |
|---|
@openclaw/sdk | Public high-level SDK and generated low-level Gateway client. |
@openclaw/sdk-react | Optional React hooks for dashboards and app builders. |
@openclaw/sdk-testing | Test helpers and fake Gateway server for app integrations. |
The repo already has openclaw/plugin-sdk/* for plugins. Keep that namespace
separate to avoid confusing plugin authors with app developers.
Generated client strategy
The low-level client should be generated from versioned Gateway protocol
schemas, then wrapped by handwritten ergonomic classes.
Layering:
- Gateway schema source of truth.
- Generated low-level TypeScript client.
- Runtime validators for external inputs and event payloads.
- High-level
OpenClaw, Agent, Session, Run, Task, and Artifact
wrappers.
- Cookbook examples and integration tests.
Benefits:
- protocol drift is visible
- tests can compare generated methods with Gateway exports
- App SDK stays independent from Plugin SDK internals
- low-level consumers still have full protocol access
- high-level consumers get the small product API