> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sovseal.com/llms.txt
> Use this file to discover all available pages before exploring further.

# How sovseal Keeps Recall Local and the Server Blind

> A detailed walkthrough of sovseal's deployment shape, write and read lifecycles, encryption boundary, key custody, and replication behavior.

<style>
  {`
      main, article, .prose {
        margin-left: 2.5cm !important;
        margin-right: 2.5cm !important;
      }
      `}
</style>

sovseal is built around a single architectural commitment: plaintext never leaves the device, and reads never touch the network. Every guarantee the platform makes — 0-RTT recall, server-blind replication, crash-safe lineage — is a direct consequence of that commitment, not a feature layered on top. This page walks through the mechanics so you know exactly what is and isn't happening when your agent calls `store_memory` or `recall_memory`.

***

## Deployment shape

At runtime, sovseal occupies two distinct zones: an on-device agent process that holds all plaintext and keys, and a remote replication endpoint that holds nothing but ciphertext.

```text theme={null}
┌─────────────────────────── Agent Process ───────────────────────────┐
│                                                                     │
│   ┌──────────────┐   store    ┌────────────────────────────┐        │
│   │  Your agent  │ ─────────► │  sovseal SDK / MCP server  │        │
│   │  (LLM tool   │            │  ───────────────────────── │        │
│   │   calls)     │ ◄───────── │  • LanceDB (vectors)       │        │
│   └──────────────┘   recall   │  • Transformers.js (384-d) │        │
│                               │  • AES-256-GCM (per record)│        │
│                               └────────────┬───────────────┘        │
│                                            │ write-behind           │
│                                            │ (ciphertext only)      │
└────────────────────────────────────────────┼────────────────────────┘
                                             ▼
                              ┌────────────────────────────┐
                              │  Replication endpoint      │
                              │  (Platform or self-hosted) │
                              │  ───────────────────────── │
                              │  Sees: ciphertext + SHA-256│
                              │  path hashes. Nothing else.│
                              └────────────────────────────┘
```

The agent process and the replication endpoint are separated by an encryption boundary that only the on-device key can cross. There is no mode in which the server receives plaintext, and there is no code path in which `recall_memory` makes an outbound HTTP request.

***

## `store_memory` write lifecycle

When your agent calls `store_memory`, seven things happen in sequence before the call returns — and one more happens asynchronously afterward.

<Steps>
  <Step title="Payload canonicalization">
    The SDK receives the payload object, orders all keys alphabetically, and serializes the result into a stable JSON byte array. This step ensures that semantically identical payloads produce identical cryptographic hashes regardless of how the object was originally constructed.
  </Step>

  <Step title="Local embedding">
    The canonicalized payload is passed to the local `Transformers.js` model, producing a 384-dimensional floating-point dense vector. This step runs entirely on-device using ONNX Runtime. No text is sent to a remote embedding API.
  </Step>

  <Step title="Encryption boundary">
    The SDK encrypts the serialized JSON bytes using **AES-256-GCM** with a 96-bit random IV generated on-device. This yields ciphertext and a 128-bit authentication tag. The plaintext and the encryption key never leave the local trust boundary.
  </Step>

  <Step title="Lineage attachment">
    The SDK queries the local index for the current HEAD snapshot ID, then computes the new snapshot ID as:

    ```text theme={null}
    snapshot_id = sha256(canonicalize(payload) ‖ parent_snapshot_id)
    ```

    This creates a hash-linked chain of records. Any modification to a past record breaks the chain forward from that point.
  </Step>

  <Step title="Local commit">
    The vector, path hash (`sha256(path)`), ciphertext, nonce, auth tag, parent pointer, and snapshot ID are committed atomically to the on-device **LanceDB** index. LanceDB calls `fsync` to ensure disk durability before the commit returns.
  </Step>

  <Step title="Return to the agent">
    The `store_memory` call resolves in **under 5ms**. Your agent continues without blocking on any network round-trip.
  </Step>

  <Step title="Write-behind replication (async)">
    A background worker picks up the local record, batches it with any pending records, and sends the **ciphertext and metadata only** to the replication endpoint. If the network is unavailable, the worker retries with exponential backoff. The agent is not blocked at any point during this process.
  </Step>
</Steps>

***

## `recall_memory` read lifecycle

When your agent calls `recall_memory`, the entire operation completes locally. There is no network path for reads.

<Steps>
  <Step title="Query embedding">
    The query string is embedded locally using the same `Transformers.js` model. If the query matches a recent search, the embedding is served from a 256-entry LRU cache, bypassing the model entirely.
  </Step>

  <Step title="Vector lookup">
    The SDK runs a vector similarity search (L2 distance) across the local LanceDB index. This operation is **0 RTT** and works completely offline — there is no fallback to a remote query.
  </Step>

  <Step title="Decryption and auth-tag verification">
    The SDK retrieves the matching record from LanceDB, uses the local device key to decrypt the ciphertext, and validates the AES-GCM 128-bit authentication tag. A tag mismatch means the ciphertext was tampered with and fails closed — the record is not returned.
  </Step>

  <Step title="VSR anchor validation">
    The SDK re-derives the snapshot ID by hashing the decrypted payload together with the stored parent snapshot ID and compares it against the snapshot ID in the index. A mismatch means either the payload was substituted or the lineage was broken; in either case the read fails closed.
  </Step>

  <Step title="Return to the agent">
    Plaintext is returned to your agent in **\~6ms p50**. The agent never sees a record that failed either the auth-tag check or the VSR anchor check.
  </Step>
</Steps>

***

## Encryption boundary and key custody

Plaintext and cryptographic keys exist only inside your agent process's memory space. The diagram below shows exactly where the trust boundary falls.

```text theme={null}
┌────────────────────────────────────────────────────────┐
│                   LOCAL TRUST BOUNDARY                 │
│                                                        │
│  [Plaintext Data] ──► [AES-256-GCM] ──► [Device Key]  │
│                                │                       │
└────────────────────────────────┼───────────────────────┘
                                 ▼
                     =========================
                     Public Network Boundary
                     =========================
                                 │
                                 ▼
┌────────────────────────────────────────────────────────┐
│                 UNTRUSTED REMOTE SPACE                 │
│                                                        │
│                [ Opaque Ciphertext ]                   │
│                [ SHA-256 Path Hash ]                   │
│                                                        │
└────────────────────────────────────────────────────────┘
```

### Key custody details

Your master key is held in the **OS keychain** — macOS Keychain, Windows Credential Manager, or Linux libsecret — not in a plaintext config file.

* Generated with a CSPRNG on first run; never written to disk in the clear.
* Purpose-bound subkeys are derived on demand via HKDF-SHA256: `k_rest` for local at-rest encryption and `k_sync` for replication.
* `~/.sovseal/config.json` (mode `0600`) holds identity and routing only — `project_id`, `api_key`, `endpoint` — and contains no key material.
* **Headless fallback:** Set `SOVSEAL_KEY_FALLBACK=file` to store the master key at `~/.sovseal/` (mode `0600`). Without this flag, a missing keychain fails closed rather than falling back silently.
* The server verifies your key by hashing it alone (`sha256(key)`) and comparing against the stored `key_hash` column — it never sees the raw key.

<Warning>
  If you lose the OS keychain master key (or the fallback key file), your stored memories cannot be decrypted. sovseal has no password recovery or key escrow flow by design. See [Key Management and Custody](/self-hosted/components) for backup guidance before going to production.
</Warning>

***

## Local embedding pipeline

sovseal generates embeddings on-device to ensure that no plaintext is sent to a remote embedding API.

| Property              | Value                                               |
| --------------------- | --------------------------------------------------- |
| **Engine**            | ONNX Runtime via `Transformers.js`, CPU-bound       |
| **Model**             | `Xenova/all-MiniLM-L6-v2` (384-dim, SHA-256-pinned) |
| **Model size**        | \~22 MB download                                    |
| **Model cache**       | `~/.sovseal/models/`                                |
| **Cold start**        | \~1.2s (first connect only)                         |
| **Query cache**       | 256-entry LRU; repeated queries bypass the model    |
| **Vector dimensions** | 384-dimensional float32                             |

The cold start happens once — when the MCP server or SDK first loads the ONNX model. After warmup, query latency is sub-25ms p99.

***

## Write-behind replication and partition recovery

Replication runs on a non-blocking background queue. The agent always gets control back before any network I/O occurs.

* **Batching:** The sync worker polls every `SOVSEAL_SYNC_INTERVAL_MS` (default 2000ms), draining up to 5,000 pending rows per cycle into AES-256-GCM blocks capped at 64 KB ciphertext each.
* **Offline buffering:** Transient failures leave rows `pending`; the next poll cycle retries automatically. Records stay queued in the local LanceDB store — `store_memory` calls are never blocked by replication.
* **Split-brain halt:** If the worker detects another writer already holds a different block at the same `sequence_number`, it halts sync entirely for that agent rather than guessing which version is correct — see [Replication & Sync](/platform/features/replication-sync) for the full conflict-resolution behavior.
* **Crash safety:** In-flight replication jobs are tracked in the on-device outbox table. If the agent process crashes, unacknowledged sync events are reloaded on restart and replicated successfully.

<Tip>
  If you're running sovseal in an air-gapped environment intentionally, simply never construct an `AgentStateClient` — `store`/`recall` run entirely on-device via the local native host and have no dependency on a replication endpoint existing at all.
</Tip>
