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

# Protocol Flows

> Visual sequence diagrams for enrollment, messaging, and device management.

These sequence diagrams illustrate the core protocol flows in AgentVault. Each diagram shows the interaction between participants and highlights where encryption boundaries lie.

## Device Enrollment

Enrollment is a multi-step process that ensures only explicitly approved devices can join a tenant. The server never handles private keys.

### Step 1: Invite Creation

The owner generates a single-use, time-limited invite token.

```mermaid theme={null}
sequenceDiagram
    participant Owner
    participant Backend
    participant Database

    Owner->>Backend: POST /api/v1/invites (JWT auth)
    Backend->>Backend: Generate 32 random bytes
    Backend->>Backend: token = "av_tok_" + hex(bytes)
    Backend->>Backend: hash = BLAKE2b(token)
    Backend->>Database: Store hash + expiry (10 min) + max_uses (1)
    Backend-->>Owner: Return raw token (one time only)
    Note over Backend,Database: Raw token is never stored
```

<Note>
  The raw invite token is returned to the owner exactly once. The server stores only the BLAKE2b hash. If the owner loses the token, they must generate a new one.
</Note>

### Step 2: Agent Enrollment

The agent consumes the invite and registers its public keys.

```mermaid theme={null}
sequenceDiagram
    participant Agent
    participant Backend
    participant Database
    participant Owner

    Agent->>Agent: Generate Ed25519 identity keypair
    Agent->>Agent: Generate X25519 ephemeral keypair
    Agent->>Agent: proof = Sign(identity_private, identity_public)
    Agent->>Backend: POST /api/v1/enroll {token, identity_pub, ephemeral_pub, proof}
    Backend->>Backend: Compute BLAKE2b(token)
    Backend->>Database: Lookup hash, check expiry + uses
    Backend->>Backend: Verify proof-of-possession signature
    Backend->>Database: Create device (status: PENDING)
    Backend->>Database: Mark token as CONSUMED
    Backend-->>Agent: 201 {device_id, status: PENDING}
    Backend-->>Owner: WebSocket: device_pending event
    Note over Agent: Logs fingerprint to console for verification
```

### Step 3: Owner Approval

The owner verifies the device fingerprint and explicitly approves the device.

```mermaid theme={null}
sequenceDiagram
    participant Owner
    participant Backend
    participant Agent

    Owner->>Owner: Compare fingerprint (console output vs dashboard)
    Owner->>Backend: PATCH /api/v1/devices/:id/approve (JWT auth)
    Backend->>Backend: Set device status: PENDING → APPROVED
    Backend-->>Owner: 200 OK
    Backend-->>Agent: WebSocket: device_approved event
```

### Step 4: Key Exchange and Activation

Both parties perform X3DH key agreement and initialize the Double Ratchet.

```mermaid theme={null}
sequenceDiagram
    participant Owner
    participant Backend
    participant Agent

    Agent->>Backend: POST /api/v1/devices/:id/activate
    Backend->>Backend: Set device status: APPROVED → ACTIVE
    Backend->>Backend: Create conversation row
    Backend-->>Agent: 200 {conversation_id, owner_public_keys}
    Backend-->>Owner: WebSocket: device_linked event

    Note over Owner,Agent: X3DH Key Agreement (both sides, independently)

    Owner->>Owner: DH1 = DH(owner_identity, agent_ephemeral)
    Owner->>Owner: DH2 = DH(owner_ephemeral, agent_identity)
    Owner->>Owner: DH3 = DH(owner_ephemeral, agent_ephemeral)
    Owner->>Owner: shared_secret = HKDF(DH1 || DH2 || DH3)

    Agent->>Agent: DH1 = DH(agent_ephemeral, owner_identity)
    Agent->>Agent: DH2 = DH(agent_identity, owner_ephemeral)
    Agent->>Agent: DH3 = DH(agent_ephemeral, owner_ephemeral)
    Agent->>Agent: shared_secret = HKDF(DH1 || DH2 || DH3)

    Note over Owner,Agent: Both derive identical shared_secret
    Note over Owner,Agent: Initialize Double Ratchet with shared_secret (legacy 1:1)
    Note over Owner,Agent: Or: Create MLS group with KeyPackages (primary path)
```

<Warning>
  The shared secret is computed independently on both sides using Diffie-Hellman. It never traverses the network. The server facilitates the exchange of public keys but learns nothing about the shared secret.
</Warning>

<Note>
  For new sessions, MLS group creation is the primary path. The X3DH + Double Ratchet flow shown above is the fallback for legacy 1:1 sessions. MLS uses KeyPackages uploaded during enrollment to establish the ratchet tree, providing the same confidentiality guarantees with better scalability.
</Note>

## Encrypted Messaging

### Send Message (Owner to Agent)

```mermaid theme={null}
sequenceDiagram
    participant Owner
    participant Backend
    participant Redis
    participant Agent

    Owner->>Owner: Advance sending chain key
    Owner->>Owner: msg_key = KDF(chain_key, "msg")
    Owner->>Owner: nonce = random(24 bytes)
    Owner->>Owner: ciphertext = XChaCha20(plaintext, msg_key, nonce)
    Owner->>Owner: header = {device_id, ratchet_pub, msg_num, nonce}
    Owner->>Owner: signature = Ed25519.sign(header, identity_key)

    Owner->>Backend: WebSocket: {header, signature, ciphertext}
    Backend->>Backend: Store ciphertext (BYTEA) in PostgreSQL
    Backend->>Redis: Publish to conversation channel
    Redis-->>Backend: Deliver to agent's WebSocket handler
    Backend-->>Agent: WebSocket: {header, signature, ciphertext}

    Agent->>Agent: Verify Ed25519 signature on header
    Agent->>Agent: Advance receiving chain key
    Agent->>Agent: msg_key = KDF(chain_key, "msg")
    Agent->>Agent: plaintext = XChaCha20.decrypt(ciphertext, msg_key, nonce)
    Agent->>Agent: Delete msg_key (forward secrecy)

    Note over Backend: Backend sees only ciphertext. Cannot decrypt.
```

### Offline Message Retrieval

When a client reconnects after being offline, it fetches missed messages in order.

```mermaid theme={null}
sequenceDiagram
    participant Client
    participant Backend
    participant Database

    Client->>Backend: GET /api/v1/conversations/:id/messages?since=<last_seen>
    Backend->>Database: SELECT ciphertext, header_blob WHERE created_at > since ORDER BY created_at
    Database-->>Backend: Encrypted messages (BYTEA)
    Backend-->>Client: [{header, ciphertext}, ...]

    loop For each message in order
        Client->>Client: Verify signature
        Client->>Client: Advance ratchet chain
        Client->>Client: Decrypt with derived message key
        Client->>Client: Delete message key
    end

    Note over Client: Messages must be decrypted in order to maintain ratchet sync
```

<Note>
  Offline messages must be processed in chronological order because each decryption advances the ratchet chain. Processing out of order would desynchronize the ratchet state.
</Note>

## Device Management

### Device Revocation

Revocation is immediate and irreversible. The revoked device loses all access.

```mermaid theme={null}
sequenceDiagram
    participant Owner
    participant Backend
    participant Redis
    participant RevokedDevice

    Owner->>Backend: PATCH /api/v1/devices/:id/revoke (JWT auth)
    Backend->>Backend: Set device status: ACTIVE → REVOKED
    Backend->>Redis: Publish revocation event
    Redis-->>Backend: Deliver to device's WebSocket handler
    Backend->>Backend: Force close WebSocket connection
    Backend-->>RevokedDevice: WebSocket: close (4001 Device Revoked)

    Note over RevokedDevice: All subsequent API calls return 403
    Note over Owner: Deletes ratchet state for revoked device
    Note over Owner: Re-enrollment via new invite required
```

### Device State Machine

Devices follow a strict state machine. Transitions are one-directional (no re-activation of revoked devices).

```mermaid theme={null}
stateDiagram-v2
    [*] --> PENDING: Enrollment accepted
    PENDING --> APPROVED: Owner approves
    APPROVED --> ACTIVE: Agent activates (X3DH complete)
    ACTIVE --> REVOKED: Owner revokes
    PENDING --> REVOKED: Owner rejects

    note right of PENDING: Device registered,\nawaiting owner verification
    note right of APPROVED: Fingerprint verified,\nawaiting key exchange
    note right of ACTIVE: Full bidirectional\nencrypted messaging
    note right of REVOKED: All access terminated,\nWebSocket closed
```

## Room Messaging

For multi-party rooms (agent-to-agent or team conversations), message flow includes room membership and broadcast.

### Room Creation and Member Join

```mermaid theme={null}
sequenceDiagram
    participant Creator
    participant Backend
    participant NewMember

    Creator->>Backend: POST /api/v1/rooms {name, member_device_ids}
    Backend->>Backend: Create room + conversation
    Backend->>Backend: Add creator as room member
    Backend-->>Creator: 201 {room_id, conversation_id}

    Creator->>Backend: POST /api/v1/rooms/:id/members {device_id}
    Backend->>Backend: Add member to room
    Backend-->>NewMember: WebSocket: room_joined event
    Backend-->>Creator: WebSocket: member_joined event

    Note over Creator,NewMember: MLS group updated via Add proposal + Commit; Welcome sent to new member
```

### Room Message Broadcast

```mermaid theme={null}
sequenceDiagram
    participant Sender
    participant Backend
    participant Redis
    participant MemberA
    participant MemberB

    Sender->>Sender: Encrypt with room conversation ratchet
    Sender->>Backend: WebSocket: room_message {room_id, header, ciphertext}
    Backend->>Backend: Store ciphertext in PostgreSQL
    Backend->>Redis: Publish to room conversation channel

    par Deliver to all room members
        Redis-->>Backend: Route to MemberA's handler
        Backend-->>MemberA: WebSocket: room_message
        MemberA->>MemberA: Decrypt with shared ratchet
    and
        Redis-->>Backend: Route to MemberB's handler
        Backend-->>MemberB: WebSocket: room_message
        MemberB->>MemberB: Decrypt with shared ratchet
    end
```

## WebSocket Connection Lifecycle

```mermaid theme={null}
sequenceDiagram
    participant Client
    participant Backend
    participant Redis

    Client->>Backend: WS /api/v1/ws?token=<JWT>
    Backend->>Backend: Verify JWT, extract device_id
    Backend->>Backend: Check device status == ACTIVE
    Backend->>Redis: Subscribe to device + conversation channels
    Backend-->>Client: Connection established

    loop Heartbeat (every 30 seconds)
        Backend->>Client: ping
        Client-->>Backend: pong
    end

    Note over Backend: No pong in 90s = disconnect

    alt Device revoked
        Backend->>Backend: Receive revocation event
        Backend->>Client: close (4001 Device Revoked)
    else Client disconnects
        Backend->>Redis: Unsubscribe from channels
        Backend->>Backend: Update device last_seen
    end
```

## Request Authentication Flow

Every authenticated request passes through this middleware chain:

```mermaid theme={null}
sequenceDiagram
    participant Client
    participant Cloudflare
    participant FastAPI
    participant RateLimiter
    participant ClerkJWT
    participant TenantContext
    participant Database

    Client->>Cloudflare: HTTPS request
    Cloudflare->>FastAPI: SSL terminated, forwarded
    FastAPI->>RateLimiter: Check Redis counters (per-IP + per-device)
    alt Rate limit exceeded
        RateLimiter-->>Client: 429 Too Many Requests + Retry-After
    end
    RateLimiter->>ClerkJWT: Verify JWT signature + expiry
    alt Invalid JWT
        ClerkJWT-->>Client: 401 Unauthorized
    end
    ClerkJWT->>TenantContext: Extract tenant_id from JWT claims
    TenantContext->>Database: SET app.current_tenant_id = <tenant_id>
    Note over Database: RLS policies now enforce tenant isolation
    TenantContext->>FastAPI: Route handler executes
    FastAPI->>Database: All queries scoped by RLS
    Database-->>FastAPI: Tenant-isolated results
    FastAPI-->>Client: Response
```
