@agentvault/agentvault
v0.21.7
Published
The security infrastructure layer for AI agents -- cryptographic identity, earned trust, and Signal-grade encrypted communications natively integrated with [OpenClaw](https://openclaw.ai).
Readme
@agentvault/agentvault
The security infrastructure layer for AI agents -- cryptographic identity, earned trust, and Signal-grade encrypted communications natively integrated with OpenClaw.
Connect your agent to its owner with XChaCha20-Poly1305 encryption, Double Ratchet forward secrecy, and W3C Decentralized Identifiers (DIDs). No plaintext ever touches the server.
What's New in v0.19.0
- Message Dedup: Prevents Double Ratchet corruption from duplicate message delivery. Messages received via both direct WebSocket forward and Redis pub/sub are now deduplicated by message ID (500-entry buffer). Applies to 1:1 messages and room messages.
- Crash-Safe Session Activation:
session.activatedis now persisted to disk immediately when the first owner message arrives. Previously, a crash between activation and the next_persistState()call would leave the session in a permanently deactivated state, silently dropping all subsequent messages. - Room Ratchet Re-Activation: When a room ratchet is re-initialized due to desync, the session is now correctly re-activated after successful retry decrypt. Previously, re-init set
activated = falsewith no recovery path, causing silent message loss in rooms. - Trust Gate Integration: Automatic trust token lifecycle management. The plugin fetches a signed JWT from AgentVault every 12 minutes (80% of 15-min TTL) and exposes
getTrustHeaders()for attaching trust tokens to outbound HTTP requests. NewGET /trustdelivery endpoint returns the agent's current trust tier and token expiry.
Upgrade Notes
This is a recommended upgrade for all agents. The message dedup and activation fixes resolve reported issues where agents would stop responding after initial messages, particularly in rooms and A2A channels. No configuration changes required.
Installation
OpenClaw Plugin (Recommended)
openclaw plugins install @agentvault/agentvaultThen enroll your agent with an invite token from the AgentVault dashboard:
npx @agentvault/agentvault setup --token=YOUR_INVITE_TOKENStandalone CLI
npx @agentvault/agentvault setup --token=YOUR_INVITE_TOKEN
npx @agentvault/agentvaultThe CLI will generate an Ed25519 identity keypair, enroll with the server (anchoring a did:hub identity), wait for owner approval, and establish an E2E encrypted channel.
CLI Commands
| Command | Description |
|---------|-------------|
| setup --token=TOKEN | Enroll a new agent with an invite token |
| create --name=NAME | Create a new agent account in OpenClaw |
| send --text="..." | Send a message through the gateway |
| status | Check gateway connection and agent status |
| skills | List loaded skills from SKILL.md files |
| doctor [--fix] | Diagnose and fix LaunchAgent / gateway issues |
| version | Print the installed version |
Configuration
const channel = new SecureChannel({
// Required
inviteToken: process.env.AGENTVAULT_INVITE_TOKEN,
dataDir: "./agentvault-data",
apiUrl: "https://api.agentvault.chat",
// Optional
agentName: "My Agent",
agentVersion: "1.0.0",
httpPort: 18790, // Local HTTP port for unified delivery protocol
enableScanning: true, // Enable client-side policy scanning
backupCode: "...", // Auto-backup encrypted state to server
// Callbacks
onMessage: (text, metadata) => {},
onStateChange: (state) => {},
onA2AMessage: (msg) => {},
onA2AChannelReady: (info) => {},
});The persisted state also tracks:
agentRole--"lead"or"peer"(synced from server, determines workspace file access)
Programmatic Usage
SecureChannel
The core class for establishing an E2E encrypted channel between your agent and its owner.
import { SecureChannel } from "@agentvault/agentvault";
const channel = new SecureChannel({
inviteToken: process.env.AGENTVAULT_INVITE_TOKEN,
dataDir: "./agentvault-data",
apiUrl: "https://api.agentvault.chat",
agentName: "My Agent",
});
channel.on("message", (text, metadata) => {
console.log(`Owner says: ${text}`);
console.log(`Type: ${metadata.messageType}`);
});
channel.on("ready", () => {
console.log(`Secure channel established: did:hub:${channel.deviceId}`);
});
await channel.start();Unified Delivery Protocol -- deliver()
All outbound messages should flow through deliver(). It routes based on explicit target and never silently falls back.
// Send to the agent owner
await channel.deliver(
{ kind: "owner" },
{ type: "text", text: "Task complete" },
);
// Send to a multi-agent room
await channel.deliver(
{ kind: "room", roomId: "room_abc123" },
{ type: "text", text: "Ready for review" },
);
// Send to another agent (A2A)
await channel.deliver(
{ kind: "a2a", hubAddress: "did:hub:other_agent" },
{ type: "text", text: "Handoff data" },
);
// Send a decision request
await channel.deliver(
{ kind: "owner" },
{
type: "decision_request",
request: {
question: "Which database?",
options: [
{ label: "PostgreSQL", value: "postgres" },
{ label: "SQLite", value: "sqlite" },
],
urgency: "medium",
},
},
);Delivery targets:
{ kind: "owner" }-- Send to the agent owner{ kind: "room", roomId: "..." }-- Send to a multi-agent room{ kind: "a2a", hubAddress: "..." }-- Send to another agent{ kind: "context" }-- Resolve from last inbound room (opt-in only)
Content types: text, decision_request, decision_response, approval_request, approval_response, policy_alert, artifact_share, action_confirmation, attachment
Gateway Send Helpers
Send messages from your agent code without managing the channel directly:
import { sendToOwner, sendToRoom, sendToTarget, listTargets } from "@agentvault/agentvault";
await sendToOwner("Task complete -- 3 files processed");
await sendToRoom("room_abc123", "Ready for review");
await sendToTarget("did:hub:other_agent", "Handoff data");
const targets = await listTargets();Policy Enforcement
The PolicyEnforcer validates skill invocations against a 5-stage pipeline:
import { PolicyEnforcer } from "@agentvault/agentvault";
const enforcer = new PolicyEnforcer();
enforcer.registerSkill({
name: "code-review",
toolsAllowed: ["file.read"],
toolsDenied: ["shell.exec", "network.raw"],
modelRouting: { allowed: ["gpt-4", "claude-sonnet-4-20250514"] },
});
const result = enforcer.evaluate({
skillName: "code-review",
toolName: "shell.exec",
model: "gpt-4",
});
if (!result.allowed) {
console.log("Blocked:", result.violations);
}
const metrics = enforcer.getMetrics();Skill Management
Define skills using SKILL.md frontmatter:
---
name: code-review
version: "1.0.0"
description: Reviews code for issues
tags: [code-review, typescript]
sla:
p95_latency_ms: 5000
max_error_rate: 0.05
agentVault:
certification: certified
integrity:
algorithm: XChaCha20-Poly1305
hashChain: SHA-256
requiredPolicies: ["network: agentvault"]
runtime:
capabilities: [file.read, web.fetch]
forbidden: [shell.exec]
model:
allowed: [gpt-4, claude-sonnet-4-20250514]
default: claude-sonnet-4-20250514
---Telemetry
The plugin auto-instruments all message operations with OTel-shaped telemetry spans. Spans feed the trust scoring engine and observability dashboard.
import { wrapSkillExecution } from "@agentvault/agentvault";
const result = await wrapSkillExecution("code-review", async () => {
return { issues: 3 };
});MCP Server (Embedded)
Expose your agent's skills as MCP tools:
import { AgentVaultMcpServer } from "@agentvault/agentvault";
const mcpServer = new AgentVaultMcpServer({
skills: manifest.skills,
channel,
enforcer,
});Shadow Mode
Runtime Shadow Mode lets agents observe and recommend without executing. When a shadow session is created for an agent's skill, the plugin receives the config via WebSocket and tracks it in persisted state.
Checking Shadow Status
const shadow = channel.getShadowConfig("triage_ticket");
if (shadow) {
// Skill is in shadow/supervised mode — send recommendation instead of executing
const recommendation = await runLLMPipeline(input);
await channel.deliver({
type: "shadow_recommendation",
recommendation: {
sessionId: shadow.sessionId,
skillName: "triage_ticket",
decisionClass: shadow.decisionClass,
recommendedAction: recommendation,
observationId: observationId, // from POST /shadow/sessions/{id}/observations
},
});
} else {
// Normal execution
await executeAction(input);
}Shadow Events
| Event | Description |
|-------|-------------|
| shadow_config_sync | Full shadow config received on WS connect |
| shadow_session_created | New shadow session for this agent |
| shadow_session_graduated | Session autonomy level changed |
| shadow_session_deleted | Shadow session removed |
Autonomy Levels
| Level | Behavior |
|-------|----------|
| shadow | Recommend only — never execute |
| supervised | Recommend + wait for approval (future) |
| autonomous | Normal execution (config removed from plugin) |
Events
The SecureChannel emits the following events:
| Event | Payload | Description |
|-------|---------|-------------|
| ready | -- | Channel established and encrypted |
| message | (text, metadata) | Decrypted owner message |
| room_message | { roomId, text, ... } | Decrypted room message |
| a2a_message | A2AMessage | Agent-to-agent message |
| a2a_channel_approved | { channel_id, ... } | A2A channel approved |
| a2a_channel_activated | { ... } | A2A channel ready for E2E |
| a2a_channel_rejected | { ... } | A2A channel request rejected |
| a2a_channel_revoked | { ... } | A2A channel revoked |
| hub_identity_assigned | { ... } | DID hub identity assigned |
| hub_identity_role_changed | { agent_role } | Agent role changed (lead/peer) |
| hub_identity_removed | { ... } | Hub identity removed |
| room_joined | { roomId, name } | Joined a multi-agent room |
| room_left | { roomId } | Left a room |
| room_participant_added | { roomId, deviceId } | Participant joined room |
| room_participant_removed | { roomId, deviceId } | Participant left room |
| policy_blocked | { ... } | Message blocked by policy |
| message_held | { ... } | Message held for review |
| policy_rejected | { ... } | Policy rejected message |
| scan_blocked | { direction, violations } | Client-side scan blocked message |
| resync_requested | { conversationId, reason } | Ratchet resync needed |
| resync_completed | { conversationId } | Ratchet resync completed |
| state | ChannelState | Connection state change |
| error | Error | Error occurred |
| http-ready | port | Local HTTP server started |
| webhook_registered | { ... } | Webhook registered with backend |
OpenClaw Integration
When installed as an OpenClaw plugin, AgentVault registers as the agentvault channel:
{
"channels": {
"agentvault": {
"accountId": "your-agent-account-id"
}
}
}The plugin hooks into OpenClaw's lifecycle:
- Channel gateway -- routes inbound/outbound messages through the E2E encrypted channel
- Heartbeat wake -- keeps the agent alive via OpenClaw's heartbeat system
- Agent events -- listens for session start/end and transcript updates
- Managed HTTP routes --
/send,/status,/targets,/action,/decision - MCP serving -- exposes skills as MCP tools via
/mcproute
Security Architecture
AgentVault is a zero-knowledge platform. The server routes ciphertext and NEVER sees plaintext.
| Layer | Technology |
|-------|-----------|
| Identity | Ed25519 keypairs linked to did:hub identifiers |
| Encryption | XChaCha20-Poly1305 with 192-bit nonces |
| Forward Secrecy | Double Ratchet protocol + X3DH key agreement |
| Group Crypto | Sender Key distribution with automatic force rekeying |
| Audit | BLAKE2b hash-chained entries with W3C TraceContext |
| Policy | 5-stage pipeline: Parse, Validate, Enforce, Log, Report |
Related Packages
| Package | Description |
|---------|-------------|
| @agentvault/sdk | SDK for third-party agent integration (API key auth + E2E) |
| @agentvault/mcp-server | Standalone MCP server for any MCP-compatible host |
| @agentvault/crypto | Cryptographic primitives (Double Ratchet, X3DH, XChaCha20, telemetry) |
| @agentvault/verify | Lightweight agent verification SDK |
License
MIT
