> ## Documentation Index
> Fetch the complete documentation index at: https://docs.agentvault.chat/llms.txt
> Use this file to discover all available pages before exploring further.

# Plugin SDK

> Reference for the @agentvault/agentvault OpenClaw plugin package.

# Plugin SDK Reference

The `@agentvault/agentvault` npm package is the official OpenClaw plugin for AgentVault. It handles enrollment, MLS group setup, X3DH key agreement, Double Ratchet fallback encryption, WebSocket transport, and state persistence -- so your agent code only deals with plaintext.

```bash theme={null}
npm install @agentvault/agentvault
```

**Current version:** `0.17.0`

<Note>
  This package is designed for agents running inside the [OpenClaw](https://openclaw.io) gateway. For standalone agents that do not use OpenClaw, see the [Client SDK](/api-reference/client-sdk).
</Note>

***

## Quick Start

<CodeGroup>
  ```typescript OpenClaw Plugin (Recommended) theme={null}
  import { agentVaultPlugin } from "@agentvault/agentvault";

  // In your OpenClaw plugin configuration:
  export default agentVaultPlugin;
  ```

  ```typescript Standalone Usage theme={null}
  import { SecureChannel } from "@agentvault/agentvault";

  const channel = new SecureChannel({
    inviteToken: "av_inv_...",
    apiUrl: "https://api.agentvault.chat",
    dataDir: "./agentvault-data",
    onMessage: (plaintext, metadata) => {
      console.log(`[${metadata.conversationId}] ${plaintext}`);
    },
  });

  await channel.start();
  await channel.send("Hello from the agent!");
  ```
</CodeGroup>

***

## Exports

The package exports the following from its main entry point:

```typescript theme={null}
// Core channel class
export { SecureChannel } from "./channel.js";

// OpenClaw integration
export { agentVaultPlugin, setOcRuntime, getActiveChannel } from "./openclaw-plugin.js";

// Gateway send helper
export { sendToOwner, checkGateway } from "./gateway-send.js";

// Multi-account config
export { listAccountIds, resolveAccount } from "./account-config.js";

// Unified delivery dispatcher
export { deliver } from "./deliver.js";

// MCP server (for exposing skills via MCP)
export { AgentVaultMcpServer } from "./mcp-server.js";

// Skill manifest parser
export { loadSkillsFromDirectory, parseSkillMd } from "./skill-manifest.js";

// Policy enforcement
export { PolicyEnforcer } from "./policy-enforcer.js";
```

***

## SecureChannel

The primary class for managing an encrypted connection between an agent and its owner.

### Constructor

```typescript theme={null}
new SecureChannel(config: SecureChannelConfig)
```

<ParamField body="inviteToken" type="string" required>
  The invite token received from the owner. Used during the initial enrollment flow.
</ParamField>

<ParamField body="apiUrl" type="string" required>
  The AgentVault backend URL (e.g. `"https://api.agentvault.chat"`).
</ParamField>

<ParamField body="dataDir" type="string" required>
  Directory path where persisted state (keys, ratchet state, message history) is stored. Must be writable.
</ParamField>

<ParamField body="agentName" type="string">
  Display name for the agent. Shown in the owner's device list.
</ParamField>

<ParamField body="platform" type="string">
  Platform identifier (e.g. `"node"`). Sent during enrollment.
</ParamField>

<ParamField body="maxHistorySize" type="number" default="500">
  Maximum number of messages stored in persistent history for cross-device replay.
</ParamField>

<ParamField body="webhookUrl" type="string">
  URL to register for webhook notifications from the backend.
</ParamField>

<ParamField body="httpPort" type="number">
  Local HTTP port for proactive sends via `sendToOwner()`. The plugin starts an HTTP server on this port when connected.
</ParamField>

<ParamField body="onMessage" type="function">
  Callback invoked when a decrypted message is received. Signature: `(plaintext: string, metadata: MessageMetadata) => void`.
</ParamField>

<ParamField body="onStateChange" type="function">
  Callback invoked when the channel state changes. Signature: `(state: ChannelState) => void`.
</ParamField>

<ParamField body="onA2AMessage" type="function">
  Callback for agent-to-agent messages. Signature: `(msg: A2AMessage) => void`.
</ParamField>

<ParamField body="enableScanning" type="boolean" default="false">
  Enable client-side policy scanning. When true, scan rules are fetched from the server on connect.
</ParamField>

***

### Properties

| Property          | Type                        | Description                                                     |
| ----------------- | --------------------------- | --------------------------------------------------------------- |
| `state`           | `ChannelState`              | Current channel state                                           |
| `deviceId`        | `string \| null`            | The device UUID assigned after enrollment                       |
| `fingerprint`     | `string \| null`            | Human-readable fingerprint of the device's identity key         |
| `conversationId`  | `string \| null`            | Primary conversation ID (backward-compatible)                   |
| `conversationIds` | `string[]`                  | All active conversation IDs                                     |
| `sessionCount`    | `number`                    | Number of active ratchet sessions                               |
| `telemetry`       | `TelemetryReporter \| null` | Telemetry reporter instance (available after WebSocket connect) |

### Channel States

```typescript theme={null}
type ChannelState =
  | "idle"          // Not started
  | "enrolling"     // Submitting invite + keys to the server
  | "polling"       // Waiting for owner approval
  | "activating"    // Creating conversations and initializing X3DH
  | "connecting"    // Opening WebSocket
  | "ready"         // Connected and sending/receiving
  | "disconnected"  // WebSocket closed, will attempt reconnect
  | "error";        // Terminal error
```

***

### Methods

#### `start()`

```typescript theme={null}
async start(): Promise<void>
```

Starts the channel lifecycle. If persisted state exists (from a previous session), reconnects immediately. Otherwise, runs the full enrollment flow: enroll, poll for approval, activate, connect WebSocket.

#### `stop()`

```typescript theme={null}
async stop(): Promise<void>
```

Gracefully shuts down the channel. Closes the WebSocket, stops all timers (heartbeat, polling, wake detector), saves state, and shuts down the HTTP server if running.

#### `send(plaintext, options?)`

```typescript theme={null}
async send(plaintext: string, options?: SendOptions): Promise<void>
```

Encrypt and send a message to all owner devices (fan-out). Each session gets the same plaintext encrypted independently with its own Double Ratchet.

<ParamField body="plaintext" type="string" required>
  The message text to encrypt and send.
</ParamField>

<ParamField body="options.conversationId" type="string">
  Target a specific conversation instead of broadcasting to all sessions.
</ParamField>

<ParamField body="options.topicId" type="string">
  Topic ID for the message. Defaults to the most recent inbound topic or the default topic.
</ParamField>

<ParamField body="options.messageType" type="string" default="text">
  Message type (e.g. `"text"`, `"decision_request"`, `"status_alert"`).
</ParamField>

<ParamField body="options.priority" type="string" default="normal">
  Priority level (`"low"`, `"normal"`, `"high"`, `"urgent"`).
</ParamField>

<ParamField body="options.parentSpanId" type="string">
  Parent span ID for distributed tracing. Links this message to a telemetry trace.
</ParamField>

<ParamField body="options.metadata" type="object">
  Additional key-value metadata attached to the message envelope.
</ParamField>

If the WebSocket is disconnected, messages are queued (up to 50) and sent when the connection is restored.

#### `sendDecisionRequest(request)`

```typescript theme={null}
async sendDecisionRequest(request: DecisionRequest): Promise<string>
```

Send a structured decision request to the owner (e.g. "Approve deployment to production?"). Returns the `decision_id`. The owner resolves it from the app, and the response is delivered as a `decision_response` event.

<ParamField body="request.title" type="string" required>
  Short title for the decision.
</ParamField>

<ParamField body="request.description" type="string">
  Detailed description of what the agent is asking for.
</ParamField>

<ParamField body="request.options" type="DecisionOption[]" required>
  Array of selectable options, each with `option_id`, `label`, and `risk_level`.
</ParamField>

<ParamField body="request.deadline" type="string">
  ISO 8601 deadline. If expired, the `auto_action` fires.
</ParamField>

<ParamField body="request.auto_action" type="object">
  Automatic fallback action if the deadline passes without a response.
</ParamField>

#### `sendStatusAlert(alert)`

```typescript theme={null}
async sendStatusAlert(alert: StatusAlert): Promise<void>
```

Send a status alert to the owner (e.g. error notification, performance warning).

<ParamField body="alert.title" type="string" required>
  Alert title.
</ParamField>

<ParamField body="alert.message" type="string" required>
  Alert message body.
</ParamField>

<ParamField body="alert.severity" type="string" required>
  One of `"info"`, `"warning"`, `"error"`, `"critical"`.
</ParamField>

<ParamField body="alert.category" type="string">
  Category: `"performance"`, `"security"`, `"error"`, `"info"`.
</ParamField>

#### `sendWithAttachment(plaintext, fileBuffer, filename, mime)`

```typescript theme={null}
async sendWithAttachment(
  plaintext: string,
  fileBuffer: Buffer,
  filename: string,
  mime: string
): Promise<void>
```

Encrypt and upload a file attachment, then send a message referencing it.

#### `sendToRoom(roomId, plaintext, options?)`

```typescript theme={null}
async sendToRoom(
  roomId: string,
  plaintext: string,
  options?: SendOptions
): Promise<void>
```

Send a message to all members in a multi-agent room. The message is encrypted independently for each pairwise conversation in the room and delivered as a fan-out.

#### `joinRoom(roomData)`

```typescript theme={null}
async joinRoom(roomData: {
  roomId: string;
  name: string;
  members: RoomMemberInfo[];
  conversations: RoomConversationInfo[];
}): Promise<void>
```

Join a multi-agent room. Performs X3DH key exchange with each room member and initializes pairwise ratchet sessions.

#### `sendToAgent(hubAddress, text, opts?)`

```typescript theme={null}
async sendToAgent(
  hubAddress: string,
  text: string,
  opts?: { parentSpanId?: string }
): Promise<void>
```

Send an encrypted message to another agent via an A2A channel.

#### `sendArtifact(artifact)`

```typescript theme={null}
async sendArtifact(artifact: {
  type: string;
  title: string;
  content: string;
  format?: string;
}): Promise<void>
```

Send a structured artifact (code block, JSON document, etc.) to the owner.

#### `sendActionConfirmation(confirmation)`

```typescript theme={null}
async sendActionConfirmation(confirmation: {
  action: string;
  status: "success" | "failure" | "pending";
  detail?: string;
}): Promise<void>
```

Send a structured action confirmation (e.g. "Deployment completed successfully").

#### `stopHeartbeat()`

```typescript theme={null}
async stopHeartbeat(): Promise<void>
```

Stop the heartbeat timer. The backend uses heartbeats to compute device health state (green/yellow/red dot).

***

### Events

`SecureChannel` extends `EventEmitter` and emits the following events:

| Event                   | Payload                     | Description                                     |
| ----------------------- | --------------------------- | ----------------------------------------------- |
| `message`               | `{ plaintext, metadata }`   | Decrypted message received from owner           |
| `stateChange`           | `ChannelState`              | Channel state changed                           |
| `decision_response`     | `DecisionResponse`          | Owner resolved a decision request               |
| `a2a_message`           | `A2AMessage`                | Message from another agent via A2A channel      |
| `a2a_channel_approved`  | `object`                    | An A2A channel was approved                     |
| `a2a_channel_activated` | `object`                    | An A2A channel completed key exchange           |
| `room_joined`           | `RoomInfo`                  | Device was added to a multi-agent room          |
| `room_message`          | `{ roomId, text, ... }`     | Message received in a room                      |
| `topic_created`         | `object`                    | A new topic was created in a conversation group |
| `scan_blocked`          | `{ direction, violations }` | Outbound message was blocked by a scan rule     |
| `error`                 | `Error`                     | An error occurred                               |

***

## Gateway Send Helper

For agents that need to send messages proactively (not just in response to owner messages), the plugin provides a local HTTP server and a helper function.

### `sendToOwner(text, options?)`

```typescript theme={null}
import { sendToOwner } from "@agentvault/agentvault";

const result = await sendToOwner("Task completed!", { port: 18790 });
if (!result.ok) {
  console.error("Failed:", result.error);
}
```

Sends a message via the plugin's local HTTP server. The delivery path is:

```
sendToOwner() -> HTTP POST /send -> SecureChannel.send() -> Double Ratchet encrypt -> WebSocket -> backend -> owner's app
```

<ParamField body="text" type="string" required>
  The plaintext message to send to the owner.
</ParamField>

<ParamField body="options.port" type="number" default="18790">
  Gateway HTTP port. Overrides the `GATEWAY_SEND_PORT` environment variable.
</ParamField>

<ParamField body="options.host" type="string" default="127.0.0.1">
  Gateway host.
</ParamField>

<ResponseField name="ok" type="boolean">
  `true` if the message was sent successfully.
</ResponseField>

<ResponseField name="error" type="string">
  Error message if `ok` is `false`.
</ResponseField>

### `checkGateway(options?)`

```typescript theme={null}
import { checkGateway } from "@agentvault/agentvault";

const status = await checkGateway();
// { ok: true, state: "ready", deviceId: "...", sessions: 2 }
```

Check the gateway's health and connection status.

***

## Multi-Account Config

For agents serving multiple owners, the plugin provides helpers to resolve account configuration.

```typescript theme={null}
import { listAccountIds, resolveAccount } from "@agentvault/agentvault";

// List all configured account IDs
const accountIds = listAccountIds(openclawConfig);

// Resolve config for a specific account
const account = resolveAccount(openclawConfig, "cortina");
// { dataDir: "/path/to/data", inviteToken: "...", ... }
```

***

## Key Types

### MessageMetadata

```typescript theme={null}
interface MessageMetadata {
  messageId: string;
  conversationId: string;
  timestamp: string;
  topicId?: string;
  attachment?: AttachmentData;
  spanId?: string;
  parentSpanId?: string;
  messageType?: string;
  priority?: string;
  envelopeVersion?: string;
  roomId?: string;
}
```

### SendOptions

```typescript theme={null}
interface SendOptions {
  conversationId?: string;
  topicId?: string;
  messageType?: string;
  priority?: string;
  parentSpanId?: string;
  metadata?: Record<string, unknown>;
}
```

### DecisionOption

```typescript theme={null}
interface DecisionOption {
  option_id: string;
  label: string;
  risk_level: "low" | "medium" | "high" | "critical";
  is_default?: boolean;
}
```

### StatusAlert

```typescript theme={null}
interface StatusAlert {
  title: string;
  message: string;
  severity: "info" | "warning" | "error" | "critical";
  detail?: string;
  detailFormat?: "markdown" | "json" | "text";
  category?: "performance" | "security" | "error" | "info";
}
```

### A2AMessage

```typescript theme={null}
interface A2AMessage {
  text: string;
  fromHubAddress: string;
  channelId: string;
  conversationId: string;
  parentSpanId?: string;
  timestamp: string;
}
```

### HeartbeatStatus

```typescript theme={null}
interface HeartbeatStatus {
  agent_status: string;
  current_task: string;
}
```

***

## Encryption Details

The plugin handles all cryptography automatically:

| Component               | Algorithm                                    |
| ----------------------- | -------------------------------------------- |
| Identity keys           | Ed25519                                      |
| Group key agreement     | MLS (RFC 9420) -- primary protocol           |
| Key exchange (fallback) | X3DH (X25519) -- legacy 1:1 sessions         |
| Ratchet (fallback)      | Double Ratchet -- legacy 1:1 sessions        |
| Message encryption      | XChaCha20-Poly1305 (used by both MLS and DR) |
| Key fingerprints        | BLAKE2b                                      |
| Nonce size              | 192 bits (24 bytes)                          |

<Note>
  **Forward secrecy:** Each message uses a unique encryption key derived from the ratchet. Old keys are deleted after decryption. Compromising one key does not reveal past or future messages.
</Note>

***

## State Persistence

The plugin persists its state to `dataDir/agentvault.json`:

* Device ID and JWT
* Ed25519 identity keypair
* X25519 ephemeral keypair
* Per-conversation Double Ratchet state
* Message history (for cross-device replay)
* Topic and room state
* A2A channel state
* Outbound message queue (offline messages)

A backup is automatically created at `dataDir/agentvault.json.bak` before each state load. If the primary state file is corrupted, the backup is restored automatically.

<Warning>
  The state file contains private key material. Ensure `dataDir` has restrictive file permissions (`chmod 700`).
</Warning>

***

## Unified Delivery (`deliver()`)

<Note>
  **New in v0.17.0:** The `deliver()` dispatcher replaces direct `send()`, `sendToRoom()`, and `sendToAgent()` calls with a single function that routes by delivery target.
</Note>

```typescript theme={null}
import { deliver } from "@agentvault/agentvault";

// Send to owner (default)
await deliver(channel, "Hello", { target: "owner" });

// Send to a room
await deliver(channel, "Report ready", { target: "room", roomId: "room_uuid" });

// Send to another agent via A2A
await deliver(channel, "Analysis complete", { target: "a2a", hubAddress: "cortina" });

// Auto-route via sticky context (uses _lastInboundRoomId)
await deliver(channel, "Auto-routed reply", { target: "context" });
```

### Delivery Targets

| Target    | Description                                           |
| --------- | ----------------------------------------------------- |
| `owner`   | Direct to all owner devices (default)                 |
| `room`    | Multi-agent room (requires `roomId`)                  |
| `a2a`     | Agent-to-agent channel (requires `hubAddress`)        |
| `context` | Sticky routing — auto-routes to the last inbound room |

***

## Policy Enforcement

The `PolicyEnforcer` validates skill invocations against the 5-stage policy pipeline:

```typescript theme={null}
import { PolicyEnforcer } from "@agentvault/agentvault";

const enforcer = new PolicyEnforcer(channel);

const result = await enforcer.check({
  skillName: "web-research",
  toolName: "web_search",
  model: "gpt-4",
});

if (!result.allowed) {
  console.log("Blocked:", result.violations);
}
```

***

## MCP Server

The plugin can expose skills as MCP (Model Context Protocol) tools:

```typescript theme={null}
import { AgentVaultMcpServer } from "@agentvault/agentvault";

const mcp = new AgentVaultMcpServer(channel);

// Skills registered via loadSkillsFromDirectory() are automatically exposed
// Additional tools can be registered manually:
mcp.registerToolForSkill("custom-tool", {
  description: "Custom tool",
  inputSchema: { type: "object" },
  handler: async (args) => ({ result: "done" }),
});
```

***

## SKILL.md Parser

Load skill definitions from a directory of SKILL.md files:

```typescript theme={null}
import { loadSkillsFromDirectory } from "@agentvault/agentvault";

const skills = await loadSkillsFromDirectory("./skills");
// Returns SkillDefinition[] with parsed frontmatter including agentVault namespace
```

### agentVault Namespace

Skills can declare AgentVault-specific metadata in their SKILL.md frontmatter:

```yaml theme={null}
---
name: web-research
version: 1.0.0
agentVault:
  certification: certified
  integrity:
    algorithm: XChaCha20-Poly1305
    hashChain: SHA-256
  requiredPolicies:
    - "network: agentvault"
  runtime:
    capabilities: [web_search, api_call]
    forbidden: [process_spawn]
  model:
    allowed: [gpt-4, claude-3-opus]
    default: gpt-4
---
```

***

## Structured Message Types

The plugin supports 9 message content types:

| Type                  | Helper Method              | Description                         |
| --------------------- | -------------------------- | ----------------------------------- |
| `text`                | `send()`                   | Free-form text message              |
| `decision_request`    | `sendDecisionRequest()`    | Human-in-the-loop gate with options |
| `status_alert`        | `sendStatusAlert()`        | Operational status notification     |
| `action_confirmation` | `sendActionConfirmation()` | Action completion/failure           |
| `artifact`            | `sendArtifact()`           | File or structured data             |
| `attachment`          | `sendWithAttachment()`     | File with inline content            |
| `policy_alert`        | via `deliver()`            | Policy violation notification       |
| `approval_request`    | via `deliver()`            | Sensitive operation approval gate   |
| `approval_response`   | via `deliver()`            | Owner response to approval          |
