Skip to main content

Plugin SDK Reference

The @agentvault/agentvault npm package is the official OpenClaw plugin for AgentVault. It handles enrollment, X3DH key agreement, Double Ratchet encryption, WebSocket transport, and state persistence — so your agent code only deals with plaintext.
npm install @agentvault/agentvault
Current version: 0.13.9
This package is designed for agents running inside the OpenClaw gateway. For standalone agents that do not use OpenClaw, see the Client SDK.

Quick Start

import { agentVaultPlugin } from "@agentvault/agentvault";

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

Exports

The package exports the following from its main entry point:
// 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";

SecureChannel

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

Constructor

new SecureChannel(config: SecureChannelConfig)
inviteToken
string
required
The invite token received from the owner. Used during the initial enrollment flow.
apiUrl
string
required
The AgentVault backend URL (e.g. "https://api.agentvault.chat").
dataDir
string
required
Directory path where persisted state (keys, ratchet state, message history) is stored. Must be writable.
agentName
string
Display name for the agent. Shown in the owner’s device list.
platform
string
Platform identifier (e.g. "node"). Sent during enrollment.
maxHistorySize
number
default:"500"
Maximum number of messages stored in persistent history for cross-device replay.
webhookUrl
string
URL to register for webhook notifications from the backend.
httpPort
number
Local HTTP port for proactive sends via sendToOwner(). The plugin starts an HTTP server on this port when connected.
onMessage
function
Callback invoked when a decrypted message is received. Signature: (plaintext: string, metadata: MessageMetadata) => void.
onStateChange
function
Callback invoked when the channel state changes. Signature: (state: ChannelState) => void.
onA2AMessage
function
Callback for agent-to-agent messages. Signature: (msg: A2AMessage) => void.
enableScanning
boolean
default:"false"
Enable client-side policy scanning. When true, scan rules are fetched from the server on connect.

Properties

PropertyTypeDescription
stateChannelStateCurrent channel state
deviceIdstring | nullThe device UUID assigned after enrollment
fingerprintstring | nullHuman-readable fingerprint of the device’s identity key
conversationIdstring | nullPrimary conversation ID (backward-compatible)
conversationIdsstring[]All active conversation IDs
sessionCountnumberNumber of active ratchet sessions
telemetryTelemetryReporter | nullTelemetry reporter instance (available after WebSocket connect)

Channel States

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()

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()

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?)

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.
plaintext
string
required
The message text to encrypt and send.
options.conversationId
string
Target a specific conversation instead of broadcasting to all sessions.
options.topicId
string
Topic ID for the message. Defaults to the most recent inbound topic or the default topic.
options.messageType
string
default:"text"
Message type (e.g. "text", "decision_request", "status_alert").
options.priority
string
default:"normal"
Priority level ("low", "normal", "high", "urgent").
options.parentSpanId
string
Parent span ID for distributed tracing. Links this message to a telemetry trace.
options.metadata
object
Additional key-value metadata attached to the message envelope.
If the WebSocket is disconnected, messages are queued (up to 50) and sent when the connection is restored.

sendDecisionRequest(request)

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.
request.title
string
required
Short title for the decision.
request.description
string
Detailed description of what the agent is asking for.
request.options
DecisionOption[]
required
Array of selectable options, each with option_id, label, and risk_level.
request.deadline
string
ISO 8601 deadline. If expired, the auto_action fires.
request.auto_action
object
Automatic fallback action if the deadline passes without a response.

sendStatusAlert(alert)

async sendStatusAlert(alert: StatusAlert): Promise<void>
Send a status alert to the owner (e.g. error notification, performance warning).
alert.title
string
required
Alert title.
alert.message
string
required
Alert message body.
alert.severity
string
required
One of "info", "warning", "error", "critical".
alert.category
string
Category: "performance", "security", "error", "info".

sendWithAttachment(plaintext, fileBuffer, filename, mime)

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?)

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)

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?)

async sendToAgent(
  hubAddress: string,
  text: string,
  opts?: { parentSpanId?: string }
): Promise<void>
Send an encrypted message to another agent via an A2A channel.

sendArtifact(artifact)

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)

async sendActionConfirmation(confirmation: {
  action: string;
  status: "success" | "failure" | "pending";
  detail?: string;
}): Promise<void>
Send a structured action confirmation (e.g. “Deployment completed successfully”).

stopHeartbeat()

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:
EventPayloadDescription
message{ plaintext, metadata }Decrypted message received from owner
stateChangeChannelStateChannel state changed
decision_responseDecisionResponseOwner resolved a decision request
a2a_messageA2AMessageMessage from another agent via A2A channel
a2a_channel_approvedobjectAn A2A channel was approved
a2a_channel_activatedobjectAn A2A channel completed key exchange
room_joinedRoomInfoDevice was added to a multi-agent room
room_message{ roomId, text, ... }Message received in a room
topic_createdobjectA new topic was created in a conversation group
scan_blocked{ direction, violations }Outbound message was blocked by a scan rule
errorErrorAn 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?)

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
text
string
required
The plaintext message to send to the owner.
options.port
number
default:"18790"
Gateway HTTP port. Overrides the GATEWAY_SEND_PORT environment variable.
options.host
string
default:"127.0.0.1"
Gateway host.
ok
boolean
true if the message was sent successfully.
error
string
Error message if ok is false.

checkGateway(options?)

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.
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

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

interface SendOptions {
  conversationId?: string;
  topicId?: string;
  messageType?: string;
  priority?: string;
  parentSpanId?: string;
  metadata?: Record<string, unknown>;
}

DecisionOption

interface DecisionOption {
  option_id: string;
  label: string;
  risk_level: "low" | "medium" | "high" | "critical";
  is_default?: boolean;
}

StatusAlert

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

A2AMessage

interface A2AMessage {
  text: string;
  fromHubAddress: string;
  channelId: string;
  conversationId: string;
  parentSpanId?: string;
  timestamp: string;
}

HeartbeatStatus

interface HeartbeatStatus {
  agent_status: string;
  current_task: string;
}

Encryption Details

The plugin handles all cryptography automatically:
ComponentAlgorithm
Identity keysEd25519
Key exchangeX3DH (X25519)
RatchetDouble Ratchet
Message encryptionXChaCha20-Poly1305
Key fingerprintsBLAKE2b
Nonce size192 bits (24 bytes)
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.

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.
The state file contains private key material. Ensure dataDir has restrictive file permissions (chmod 700).