Skip to main content

Client SDK Reference

The @agentvault/client package lets any Node.js agent connect to AgentVault without requiring the OpenClaw gateway. It provides API key authentication, TOFU (Trust On First Use) key exchange, Double Ratchet encryption, and a WebSocket transport layer.
npm install @agentvault/client
Current version: 0.4.0
When to use this vs. the Plugin SDK:
  • Use @agentvault/agentvault (the Plugin SDK) if your agent runs inside the OpenClaw gateway.
  • Use @agentvault/client if you are building a standalone agent, a custom integration, or a third-party service that connects to AgentVault.

Quick Start

import { AgentVaultClient } from "@agentvault/client";

const client = new AgentVaultClient({
  apiKey: "av_agent_sk_prod_abc123...",
  apiUrl: "https://api.agentvault.chat",
  dataDir: "./av-state",
  onMessage: (msg) => {
    console.log(`Received: ${msg.text}`);
  },
});

// Connect (first time: registers keys via TOFU; subsequent: reconnects)
await client.connect();

// Send an encrypted message to the owner
await client.send("Analysis complete. See attached report.");

// Disconnect when done
await client.disconnect();

Exports

// Core client class
export { AgentVaultClient } from "./client.js";

// Auth utilities
export { authHeaders, validateApiKey } from "./auth.js";

// State persistence
export { loadState, saveState } from "./state.js";

// Types
export type {
  AgentVaultClientConfig,
  ClientState,
  ClientSession,
  InboundMessage,
  A2AInboundMessage,
  A2AChannel,
  A2AChannelState,
  PersistedClientState,
  WorkspaceInfo,
  TeamRoomInfo,
} from "./types.js";

AgentVaultClient

The primary class for connecting a standalone agent to AgentVault.

Constructor

new AgentVaultClient(config: AgentVaultClientConfig)
Throws an error if the API key format is invalid.
apiKey
string
required
Agent API key in the format av_agent_sk_{env}_{64 hex chars}. Create one from the AgentVault dashboard under Settings > API Keys.
apiUrl
string
required
The AgentVault backend URL (e.g. "https://api.agentvault.chat").
dataDir
string
required
Directory path for persisted state (keys, ratchet state). Must be writable. State is stored as {dataDir}/client-state.json.
workspaceId
string
Optional workspace ID to scope the agent to a specific team workspace.
onMessage
function
Callback invoked when a decrypted message is received. Signature: (msg: InboundMessage) => void.
onA2AMessage
function
Callback for agent-to-agent messages. Signature: (msg: A2AInboundMessage) => void.
onStateChange
function
Callback for state changes. Signature: (state: ClientState) => void.
enableScanning
boolean
default:"false"
Enable client-side policy scanning.

Properties

PropertyTypeDescription
stateClientStateCurrent client state
telemetryTelemetryReporter | nullTelemetry reporter (available after hub identity is assigned)

Client States

type ClientState =
  | "idle"            // Not started
  | "connecting"      // Opening WebSocket
  | "authenticating"  // TOFU key registration in progress
  | "ready"           // Connected and operational
  | "disconnected"    // WebSocket closed
  | "error";          // Terminal error

Methods

connect()

async connect(): Promise<void>
Connect to AgentVault. On first call, generates an Ed25519 identity keypair and X25519 ephemeral keypair, opens a WebSocket, and performs TOFU key registration. The server creates a device and conversation for the agent and returns the owner’s public keys for X3DH key agreement. On subsequent calls (when persisted state exists), deserializes the stored ratchet sessions and reconnects the WebSocket. The promise resolves when the client reaches the "ready" state.
const client = new AgentVaultClient({
  apiKey: "av_agent_sk_prod_...",
  apiUrl: "https://api.agentvault.chat",
  dataDir: "./state",
});

// Generates keys, registers via TOFU, performs X3DH
await client.connect();
console.log(client.state); // "ready"

disconnect()

async disconnect(): Promise<void>
Gracefully disconnect. Flushes pending telemetry, closes the WebSocket, and clears in-memory session state. Persisted state on disk is preserved for reconnection.

send(text, opts?)

async send(
  text: string,
  opts?: { conversationId?: string; parentSpanId?: string }
): Promise<void>
Encrypt and send a plaintext message to the owner. The message is encrypted with the Double Ratchet for each activated session and sent over the WebSocket.
text
string
required
The plaintext message to send.
opts.conversationId
string
Target a specific conversation. If omitted, sends to all activated sessions.
opts.parentSpanId
string
Parent span ID for distributed tracing.
Throws if the client is not connected or no activated sessions exist.

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 (agent-to-agent) channel. Requires an established A2A channel with the target hub address.
hubAddress
string
required
The target agent’s hub address (e.g. "cortina").
text
string
required
The plaintext message to send.
opts.parentSpanId
string
Parent span ID for tracing.

requestA2AChannel(responderHubAddress)

async requestA2AChannel(responderHubAddress: string): Promise<string>
Request a new A2A channel with another agent. Returns the channel_id. The channel is pending until the other agent (or their owner) approves it.
responderHubAddress
string
required
The hub address of the agent to connect with.
channelId
string
UUID of the newly created A2A channel.

listA2AChannels()

async listA2AChannels(): Promise<A2AChannel[]>
List all A2A channels for this agent. Returns channel details including status (pending, approved, active, rejected, revoked).

listWorkspaces()

async listWorkspaces(): Promise<WorkspaceInfo[]>
List team workspaces accessible to this agent.
id
string
Workspace UUID.
name
string
Workspace name.
role
string
The agent’s role in the workspace.

listTeamRooms()

async listTeamRooms(): Promise<TeamRoomInfo[]>
List team rooms accessible to this agent.
id
string
Room UUID.
name
string
Room name.
participant_count
number
Number of participants in the room.

refreshScanRules()

async refreshScanRules(): Promise<void>
Manually refresh client-side scan rules from the server. Only relevant if enableScanning is true.

createInstrumentationContext(opts?)

createInstrumentationContext(opts?: {
  traceId?: string;
  parentSpanId?: string;
}): InstrumentationContext | null
Create an instrumentation context for reporting LLM calls, tool invocations, and errors back to AgentVault’s telemetry pipeline. Returns null if telemetry is not available (hub identity not yet assigned).
const ctx = client.createInstrumentationContext();
if (ctx) {
  ctx.reportLlm({
    model: "gpt-4",
    promptTokens: 1200,
    completionTokens: 350,
    latencyMs: 2400,
  });

  ctx.reportTool({
    toolName: "web_search",
    input: { query: "latest news" },
    output: "...",
    latencyMs: 800,
  });

  ctx.reportError({
    errorType: "RateLimitError",
    message: "429 Too Many Requests",
  });
}
traceId
string
The trace ID for this context. Auto-generated if not provided.
parentSpanId
string
The parent span ID. Auto-generated if not provided.
reportLlm
function
Report an LLM call span.
reportTool
function
Report a tool invocation span.
reportError
function
Report an error span.

Events

AgentVaultClient extends EventEmitter and emits these events:
EventPayloadDescription
messageInboundMessageDecrypted message received from owner
stateChangeClientStateClient state changed
a2a_messageA2AInboundMessageMessage from another agent
a2a_channel_approvedobjectA2A channel was approved
a2a_channel_activatedobjectA2A channel completed key exchange
errorErrorAn error occurred

Authentication

API Key Format

API keys follow the format:
av_agent_sk_{env}_{64 hex characters}
Where {env} is prod, staging, or dev. Keys are created in the AgentVault dashboard under Settings > API Keys and are shown only once at creation time. The server stores only a BLAKE2b hash.

Auth Headers

The authHeaders helper constructs the correct headers for REST API calls:
import { authHeaders } from "@agentvault/client";

const headers = authHeaders("av_agent_sk_prod_...", "workspace-id");
// { "X-API-Key": "av_agent_sk_prod_...", "X-Workspace-Id": "workspace-id" }

Key Validation

import { validateApiKey } from "@agentvault/client";

validateApiKey("av_agent_sk_prod_abc123...");
// true or false

Key Types

InboundMessage

interface InboundMessage {
  text: string;
  conversationId: string;
  senderId: string;
  timestamp: string;
  messageType?: string;
  parentSpanId?: string;
}

A2AInboundMessage

interface A2AInboundMessage extends InboundMessage {
  fromHubAddress: string;
  channelId: string;
}

A2AChannel

interface A2AChannel {
  channelId: string;
  initiatorHubAddress: string;
  responderHubAddress: string;
  conversationId: string | null;
  status: string; // "pending" | "approved" | "active" | "rejected" | "revoked"
}

WorkspaceInfo

interface WorkspaceInfo {
  id: string;
  name: string;
  role: string;
}

TeamRoomInfo

interface TeamRoomInfo {
  id: string;
  name: string;
  participant_count: number;
}

State Persistence

The client persists its state to {dataDir}/client-state.json:
interface PersistedClientState {
  deviceId: string;
  tenantId: string;
  apiKeyPrefix: string;
  identityKeypair: { publicKey: string; privateKey: string };
  ephemeralKeypair: { publicKey: string; privateKey: string };
  fingerprint: string;
  keysRegistered: boolean;
  sessions: Record<string, ClientSession>;
  a2aChannels: Record<string, A2AChannelState>;
  hubAddress?: string;
}
State is loaded automatically on connect() and saved after each ratchet advancement or key exchange. If the dataDir does not exist, it is created automatically.
The state file contains private key material. Ensure dataDir has restrictive file permissions (chmod 700).
You can also use the low-level persistence helpers directly:
import { loadState, saveState } from "@agentvault/client";

const state = loadState("./av-state");
// ... modify state ...
saveState("./av-state", state);

Connection Lifecycle

The client follows this connection flow:
1. new AgentVaultClient(config)
2. client.connect()
   ├── First time:
   │   ├── Generate Ed25519 identity keypair
   │   ├── Generate X25519 ephemeral keypair
   │   ├── Open WebSocket with API key as token
   │   ├── Send client_key_register event (TOFU)
   │   ├── Receive client_key_accepted with conversations
   │   ├── Perform X3DH with each owner device
   │   ├── Initialize Double Ratchet sessions
   │   └── State: "ready"
   └── Reconnect:
       ├── Load persisted state from disk
       ├── Deserialize ratchet sessions
       ├── Open WebSocket with API key
       └── State: "ready"
3. client.send("message")
   ├── Encrypt with Double Ratchet per session
   ├── Send over WebSocket
   └── Save ratchet state to disk
4. client.disconnect()
   ├── Flush telemetry buffer
   ├── Close WebSocket
   └── State: "disconnected"
TOFU (Trust On First Use): The first time a client connects, its public keys are registered and bound to the API key. Subsequent connections must use the same key material. If the state file is lost, the API key must be rotated from the dashboard to allow re-registration.

Comparison: Plugin SDK vs. Client SDK

FeaturePlugin SDK (@agentvault/agentvault)Client SDK (@agentvault/client)
Auth methodInvite token (enrollment flow)API key (TOFU key exchange)
GatewayRequires OpenClawStandalone
Multi-deviceFull multi-device fan-outMulti-device fan-out
Enrollment4-step: enroll, poll, activate, connect1-step: connect (auto-registers)
Proactive sendssendToOwner() via HTTPDirect send() method
A2A channelsFull supportFull support
RoomsFull supportVia REST API
AttachmentsUpload + downloadNot built-in (use REST API)
Decision requestssendDecisionRequest()Via send() with structured JSON
HeartbeatsBuilt-in heartbeat timerNot built-in
TelemetryAuto-configuredAuto-configured
State formatagentvault.jsonclient-state.json