# @agentvault/agentvault

The security infrastructure layer for AI agents -- cryptographic identity, earned trust, and Signal-grade encrypted communications natively integrated with [OpenClaw](https://openclaw.ai).

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.activated` is 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 = false` with 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. New `GET /trust` delivery 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)

```bash
openclaw plugins install @agentvault/agentvault
```

Then enroll your agent with an invite token from the [AgentVault dashboard](https://app.agentvault.chat):

```bash
npx @agentvault/agentvault setup --token=YOUR_INVITE_TOKEN
```

### Standalone CLI

```bash
npx @agentvault/agentvault setup --token=YOUR_INVITE_TOKEN
npx @agentvault/agentvault
```

The 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

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

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

```typescript
// 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:

```typescript
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:

```typescript
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:

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

```typescript
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:

```typescript
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

```typescript
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:

```json
{
  "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 `/mcp` route

## 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`](https://www.npmjs.com/package/@agentvault/sdk) | SDK for third-party agent integration (API key auth + E2E) |
| [`@agentvault/mcp-server`](https://www.npmjs.com/package/@agentvault/mcp-server) | Standalone MCP server for any MCP-compatible host |
| [`@agentvault/crypto`](https://www.npmjs.com/package/@agentvault/crypto) | Cryptographic primitives (Double Ratchet, X3DH, XChaCha20, telemetry) |
| [`@agentvault/verify`](https://www.npmjs.com/package/@agentvault/verify) | Lightweight agent verification SDK |

## License

MIT
