Verified by the sovseal team

store_memory

Write-behind replication — the local commit returns in milliseconds; ciphertext sync happens in the background.

store_memory writes a record to the local LanceDB index and returns. It does not wait for the replication endpoint. The contract is "durable locally, eventually durable remotely" — a write-behind pattern with a transactional local commit.

This is the single most important operational property of sovseal: no tool call ever blocks on I/O.


The contract

PropertyGuarantee
Local durability on returnYes — fsync'd to disk before the call returns
Remote durability on returnNo — replication is asynchronous
Latency p503.8 ms
Latency p957.2 ms
Latency p9912.5 ms
Network behavior0 RTT on the call path — replication is a background job
Failure mode if replication is offlineBuffered locally; flushed when the network returns

The write pipeline, end to end

   ┌───────────────────────────────────────────────────────────────────┐
   │                          Application                              │
   │                                                                   │
   │   await memory.store("user.editor", { value: "neovim" })          │
   │                              │                                    │
   └──────────────────────────────┼────────────────────────────────────┘

   ┌───────────────────────────────────────────────────────────────────┐
   │                          sovseal SDK                              │
   │                                                                   │
   │   1. canonicalize(payload)       ─►  stable JSON bytes            │
   │   2. embed(payload)              ─►  384-d vector                 │
   │   3. encrypt(payload, key, n)    ─►  ciphertext + auth_tag        │
   │   4. snapshot_id =                                                │
   │      sha256(canonicalize(payload) ‖ parent_id)                    │
   │   5. lancedb.upsert({ id, embedding, ciphertext, … })             │
   │   6. fsync                                                        │
   │   7. enqueue(replication_job)                                     │
   │   8. return  ◄────────────────────────────── caller resumes here  │
   │                                                                   │
   └──────────────────────────────┬────────────────────────────────────┘
                                  │ (background, write-behind)

   ┌───────────────────────────────────────────────────────────────────┐
   │                  Replication worker (in-process)                  │
   │                                                                   │
   │   • Batch up to N jobs (default 32 or 250ms)                      │
   │   • POST /v2/replicate/push { records: [ … ] }                    │
   │   • On 2xx:  mark records as replicated                           │
   │   • On 4xx:  surface to telemetry, do not retry                   │
   │   • On 5xx / network error:  exponential backoff, infinite retry  │
   │                                                                   │
   └───────────────────────────────────────────────────────────────────┘

PII redaction (before embedding)

Every write funnels through one client-side chokepoint that masks high-risk PII before the embedding and encryption steps above. The redaction happens on the plaintext, so the secret never reaches the 384-d vector, the at-rest LanceDB row, or the ciphertext that replication pushes. This is pure local middleware — there is no cloud surface for redaction.

RuleDefaultDetects
ssnonUS Social Security numbers (123-45-6789)
credit_cardon13–19 digit PANs, Luhn-validated to suppress false positives
api_keyonsk_live_ / sk_test_ / AKIA / ghp_ / gho_ / glpat- / xoxb- / xoxp-
emailoffEmail addresses (opt-in)
phoneoffPhone numbers (opt-in)
aws_secretoff40-char AWS secret access keys (opt-in)

Matches are replaced with [REDACTED:…] tokens. The store_memory response reports redacted (a count) and redactedRules; the matched values are never echoed back or logged.

Configuration & plan gating

Rules are configured under the redaction key in ~/.sovseal/config.json:

{
  "plan": "growth",
  "redaction": {
    "rules": { "email": true, "ssn": false },
    "custom": [{ "name": "employee_id", "pattern": "EMP-\\d{6}" }],
    "auditLog": false
  }
}
PlanRedaction capability
Hobby / StarterDefault trio only. Disable attempts, opt-in enables, and custom regex are ignored — the trio cannot be turned off.
GrowthFull control: enable/disable any rule and add custom regex rules.
Pro / EnterpriseFull control plus a persistent redaction audit log at ~/.sovseal/redaction-audit.log (PII-free: rule names + counts only).

The active plan is read from the plan field or the SOVSEAL_PLAN environment override. A redaction.applied event (rule names + counts, never raw values) is emitted to the local log on every redaction.


Retention & TTL

By default a memory persists forever. A plan-gated retention engine can stamp a time-to-live on each write so memories are automatically purged once they expire — the foundation for GDPR "right to erasure" workflows.

  • On write, each memory is stamped with an expires_at deadline (0 = never) derived from the active namespace's TTL. Reinforcing a memory resets its TTL window, so a fact you keep restating is not purged on its original deadline.
  • A local sweep runs every 24h, hard-deleting expired on-device vectors.
  • For memories that were synced, a daily cloud cron purges the replicated (still-encrypted) blob server-side, but only after a mandatory 7-day grace period past the deadline — a recovery window before deletion is irreversible. Each purge emits a PII-free retention.purged webhook.

Configure it under the retention key in ~/.sovseal/config.json:

{
  "plan": "growth",
  "retention": {
    "defaultTtlDays": 90,
    "namespaces": { "ephemeral": 7 },
    "auditLog": false
  }
}

TTLs are in days and clamped to [7, 365]. A per-namespace value overrides the default; the active namespace comes from SOVSEAL_NAMESPACE (default "default").

PlanRetention capability
Hobby / StarterNo retention. Any TTL config is ignored — memories are kept forever (fails closed: never deletes without an entitlement).
GrowthNamespace TTL of 7–365 days.
Pro / EnterpriseTTL plus a persistent retention audit log / purge reports at ~/.sovseal/retention-audit.log.

Calling it

import { sovseal } from "@sovseal/sdk";

const memory = new sovseal({ apiKey: process.env.SOVSEAL_API_KEY });
await memory.ready();

const result = await memory.store("user.preferences.testing", {
  framework: "vitest",
  reason: "ESM-native; faster cold start than Jest",
});

console.log(result.snapshotId);   // e.g. "snap_4f3ad8…"
console.log(result.replicated);   // false — write-behind, expected
// Tool call payload sent by the AI client
{
  "name": "store_memory",
  "arguments": {
    "path": "user.preferences.testing",
    "payload": {
      "framework": "vitest",
      "reason": "ESM-native; faster cold start than Jest"
    }
  }
}

// Response
{
  "success": true,
  "data": {
    "snapshot_id": "snap_4f3ad8…",
    "replicated": false
  },
  "timestamp": 1716301928117
}
# This is what the SDK does under the hood, in background batches.
# You almost never call this directly.

curl -X POST https://your-endpoint/v2/replicate/push \
  -H "Authorization: Bearer sov_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "records": [{
      "path_hash":   "4f3ad801…",
      "ciphertext":  "base64…",
      "nonce":       "base64…",
      "auth_tag":    "base64…",
      "parent_id":   "snap_1a2bc9d0…",
      "snapshot_id": "snap_4f3ad8…"
    }]
  }'

Parameters

ParamTypeRequiredNotes
pathstringLogical key. Never sent to the server (only sha256(path) is).
payloadobject or stringCanonicalized to JSON before hashing & encryption.
metadataobjectSearchable, encrypted alongside the payload.
parentstring (snapshot id)Override HEAD as the parent (used by fork).

Failure modes — and what happens

FailureWhat the SDK does
Local disk is fullstore rejects before fsync; the snapshot is not created
Embedder is still warming upawait memory.ready() resolves first; the call queues briefly
Replication endpoint returns 401Surfaced via memory.on("replication-error", …); local write is unaffected
Replication endpoint returns 5xxExponential backoff, infinite retry; records remain in the local outbox
Network is offlineSame as 5xx — buffered locally, flushed on reconnect
Process crashes between commit & ackRecords survive in the outbox; next process start re-flushes

Backpressure

If replication falls far enough behind that the local outbox grows past maxOutboxBytes (default 256 MB), store begins to block until the worker drains. This is the only condition under which a write can stall.


What gets sent to the server

   POST /v2/replicate/push

   {
     "records": [
       {
         "path_hash":   "sha256(path)",                 ← NOT the path itself
         "ciphertext":  "AES-256-GCM(payload, key)",    ← server cannot decrypt
         "nonce":       "...",
         "auth_tag":    "...",
         "parent_id":   "...",
         "snapshot_id": "...",
         "created_at":  "..."
       }
     ]
   }

What is NOT sent:

  • The path itself
  • The plaintext payload
  • The encryption key
  • The embedding vector
  • Any metadata not explicitly serialized into the payload

This is enforced at the SDK boundary. If you want to verify, run with SOVSEAL_LOG_REPLICATION_BODIES=1 and inspect the requests.


Next

On this page