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

# @sovseal/sdk: Node.js SDK Reference for Agent Memory

> Complete reference for @sovseal/sdk — install, AgentStateClient configuration, local store/recall semantic memory, snapshot/restore/lineage replication methods, crypto utilities, and error handling.

The `@sovseal/sdk` package gives you a programmatic client for the sovseal zero-knowledge memory replication protocol. Every cryptographic operation — AES-256-GCM encryption, key wrapping, and SHA-256 payload hashing — runs entirely client-side before any data leaves your process. The replication server stores only ciphertext it can never decrypt.

## Install

Choose your package manager. The package requires Node.js 20 or later.

<CodeGroup>
  ```bash npm theme={null}
  npm install @sovseal/sdk
  ```

  ```bash yarn theme={null}
  yarn add @sovseal/sdk
  ```

  ```bash pnpm theme={null}
  pnpm add @sovseal/sdk
  ```
</CodeGroup>

## Configuration

Instantiate `AgentStateClient` with your endpoint URL and API key. All six methods — `store`, `recall`, `snapshot`, `restore`, `restoreAt`, and `lineage` — are called on this client instance. `store` and `recall` need no `endpoint`/`apiKey` (they talk to the local native host over IPC); the other four require both.

```typescript theme={null}
import { AgentStateClient } from "@sovseal/sdk";

const client = new AgentStateClient({
  endpoint: "https://ksrlmubaxzwufziwarps.supabase.co/functions/v1/v2-agent-state",
  apiKey: "sov_live_abc123...", // or a project token: "sov_proj_..."
});
```

### `AgentStateClientConfig` options

<ParamField body="endpoint" type="string" required>
  The base Edge Function URL for your sovseal replication server. Use the managed platform URL shown above, or point to your own self-hosted endpoint.
</ParamField>

<ParamField body="apiKey" type="string" required>
  Bearer credential that authorizes writes and reads against your project. Live keys start with `sov_live_`; project-scoped tokens start with `sov_proj_`.
</ParamField>

<ParamField body="fetch" type="typeof fetch">
  Optional custom fetch implementation. Useful when targeting Deno, Cloudflare Workers, or mock testing runtimes where the global `fetch` is absent or you need intercept hooks.
</ParamField>

## Method catalog

`@sovseal/sdk` covers two distinct subsystems on the same `AgentStateClient` instance: **local semantic memory** (`store`, `recall`), which talks to a native host process over IPC and never touches the network, and **zero-knowledge state replication** (`snapshot`, `restore`, `restoreAt`, `lineage`), which talks to your configured `endpoint`.

<Note>
  `store`/`recall` require the native host launcher at `~/.sovseal/native-host/run.sh`, which `@sovseal/sdk` does not install itself. Run `npx -y @sovseal/mcp-server` once (or its installer, `sovseal-install-native-host`) to register it — the same host also serves the browser extension and `@sovseal/mcp-server`'s own tools, so installing it once covers every surface on the machine. Without it, `store`/`recall` throw `EngineUnavailableError("not-installed")`.
</Note>

### `store`

Stores a memory in the local ONEBRAIN engine over framed IPC to the native host (`~/.sovseal/native-host/run.sh`). No `endpoint` or `apiKey` round-trip — this is entirely local.

```typescript theme={null}
const result = await client.store("User prefers dark mode in the dashboard");
console.log(result.id, result.reinforced);
```

<ParamField body="content" type="string" required>
  The fact to store, as a self-contained string.
</ParamField>

<ResponseField name="result" type="EngineStoreResult">
  `{ id, reinforced, redacted, redactedRules }`. `reinforced` is `true` when the content deduplicated against an existing memory instead of inserting a new row. `redacted` counts PII fields masked before storage.
</ResponseField>

***

### `recall`

Retrieves memories relevant to a query from the local ONEBRAIN engine, ranked by composite score.

```typescript theme={null}
const hits = await client.recall("frontend stack preferences", { topK: 5 });
for (const hit of hits) {
  console.log(hit.score, hit.text);
}
```

<ParamField body="query" type="string" required>
  Natural-language search string.
</ParamField>

<ParamField body="opts.topK" type="number">
  Number of results to return (1–20). Server default applies when omitted.
</ParamField>

<ResponseField name="hits" type="EngineRecallHit[]">
  Array of `{ id, text, score }`, highest score first.
</ResponseField>

***

### `snapshot`

Encrypts an `AgentPayload` client-side using Web Crypto AES-256-GCM and publishes the resulting ciphertext to the replication server. The call returns a receipt once the server acknowledges the checkpoint.

```typescript theme={null}
import { AgentStateClient, CryptoService } from "@sovseal/sdk";

const client = new AgentStateClient({
  endpoint: "https://ksrlmubaxzwufziwarps.supabase.co/functions/v1/v2-agent-state",
  apiKey: process.env.SOVSEAL_API_KEY!,
});

// Generate a fresh AES-256-GCM key. Store this key in your key management
// system — you need it to decrypt any ciphertext produced with it.
const key = await CryptoService.generateAesKey();

const receipt = await client.snapshot({
  payload: {
    agent_id: "agent-id-hash",
    sequence_number: 0,
    parent_snapshot: null,
    policy_hash: "0000000000000000000000000000000000000000000000000000000000000000",
    active_context: { content: "User prefers dark mode" },
    timestamp: new Date().toISOString(),
  },
  key,
});

console.log(receipt.snapshot_id); // e.g. "snap_4f3ad8…"
```

<ParamField body="payload" type="AgentPayload" required>
  The agent state object to checkpoint. Omit `client_payload_hash` — the SDK derives it deterministically from the canonicalized payload before encryption.

  | Field             | Type             | Notes                                                                                |
  | ----------------- | ---------------- | ------------------------------------------------------------------------------------ |
  | `agent_id`        | `string`         | Stable identifier for this agent. Never sent in plaintext.                           |
  | `sequence_number` | `number`         | Monotonically increasing integer. Start at `0` for a new agent.                      |
  | `parent_snapshot` | `string \| null` | Snapshot ID of the preceding checkpoint, or `null` for genesis.                      |
  | `policy_hash`     | `string`         | 64-char hex SHA-256 of your policy document. Use all-zeros for unconstrained agents. |
  | `active_context`  | `object`         | Free-form JSON representing the agent's current working state.                       |
  | `timestamp`       | `string`         | ISO 8601 string.                                                                     |
</ParamField>

<ParamField body="key" type="CryptoKey" required>
  A Web Crypto `CryptoKey` produced by `CryptoService.generateAesKey()` or imported via the Web Crypto API. The key never leaves your process.
</ParamField>

<ResponseField name="receipt" type="SnapshotReceipt">
  Server-issued confirmation that the ciphertext was accepted and durably stored. Contains `snapshot_id`, `agent_id`, `sequence_number`, and `created_at`.
</ResponseField>

<Note>
  `snapshot` throws a `RangeError` if the encrypted ciphertext size exceeds `MAX_PAYLOAD_BYTES` (256 KB). Validate large payloads before calling snapshot, or split them across multiple checkpoints.
</Note>

***

### `restore`

Fetches the metadata receipt and a presigned ciphertext download URL for the **latest confirmed checkpoint** of a given agent. Use this to resume an agent from its most recent known-good state.

```typescript theme={null}
const { receipt, ciphertextUrl } = await client.restore({
  agentId: "agent-id-hash",
});

// Download and decrypt the ciphertext using your stored key
const response = await fetch(ciphertextUrl);
const ciphertext = await response.arrayBuffer();
```

<ParamField body="agentId" type="string" required>
  The `agent_id` string used when the snapshot was created.
</ParamField>

<ResponseField name="receipt" type="SnapshotReceipt">
  Metadata for the latest checkpoint: `snapshot_id`, `sequence_number`, `policy_hash`, `created_at`, and the parent lineage pointer.
</ResponseField>

<ResponseField name="ciphertextUrl" type="string">
  A time-limited presigned URL pointing to the encrypted payload blob. Fetch and decrypt it with the same `CryptoKey` used at snapshot time.
</ResponseField>

***

### `restoreAt`

Fetches metadata and a ciphertext download URL for a checkpoint at a **specific sequence number**. Use this for point-in-time recovery, audit replay, or rollback to a known checkpoint.

```typescript theme={null}
const { receipt, ciphertextUrl } = await client.restoreAt({
  agentId: "agent-id-hash",
  sequence: 4,
});
```

<ParamField body="agentId" type="string" required>
  The `agent_id` string used when the snapshot was created.
</ParamField>

<ParamField body="sequence" type="number" required>
  The exact `sequence_number` of the checkpoint you want to retrieve.
</ParamField>

<ResponseField name="receipt" type="SnapshotReceipt">
  Metadata for the requested checkpoint at the given sequence number.
</ResponseField>

<ResponseField name="ciphertextUrl" type="string">
  Presigned URL for the encrypted payload blob at that sequence.
</ResponseField>

***

### `lineage`

Walks **backward** along the parent-snapshot chain and returns an ordered list of sequence headers, most-recent first. Use this to reconstruct history, audit state changes, or display a timeline of agent activity.

```typescript theme={null}
const lineageHistory = await client.lineage({
  agentId: "agent-id-hash",
  limit: 50,
});

for (const entry of lineageHistory) {
  console.log(`seq=${entry.sequence_number} snapshot=${entry.snapshot_id}`);
}
```

<ParamField body="agentId" type="string" required>
  The `agent_id` whose snapshot chain you want to traverse.
</ParamField>

<ParamField body="limit" type="number">
  Maximum number of lineage entries to return. The server default applies when omitted. Set this when you only need recent history to avoid fetching the full chain.
</ParamField>

<ResponseField name="lineageHistory" type="SnapshotReceipt[]">
  Array of receipt objects in reverse-chronological order (highest sequence first). Each entry contains `snapshot_id`, `sequence_number`, `parent_snapshot`, `policy_hash`, and `created_at`.
</ResponseField>

***

## Re-exported crypto utilities

The SDK re-exports the following cryptographic primitives from the underlying `@sovseal/core-protocol` package. You can import them directly from `@sovseal/sdk` without adding a separate dependency.

```typescript theme={null}
import {
  CryptoService,       // SHA-256 hashing and AES-256-GCM key generation
  canonicalize,        // Deterministic JSON stringifier (RFC 8785-style)
  encryptJson,         // AES-256-GCM encryption: (data, key) => { ciphertext, nonce, authTag }
  decryptJson,         // AES-256-GCM decryption: (ciphertext, key, nonce, authTag) => data
  MAX_PAYLOAD_BYTES,   // 256 KB — the enforced ciphertext size ceiling
} from "@sovseal/sdk";
```

| Export              | Purpose                                                                                                                       |
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `CryptoService`     | Namespace with `generateAesKey()`, `sha256(data)`, and key import/export helpers.                                             |
| `canonicalize`      | Converts a JavaScript object to a deterministically ordered UTF-8 JSON string. Required for stable `policy_hash` computation. |
| `encryptJson`       | Encrypts any JSON-serializable value with a given `CryptoKey`. Returns `{ ciphertext, nonce, authTag }` as `Uint8Array`s.     |
| `decryptJson`       | Inverse of `encryptJson`. Takes `ciphertext`, `nonce`, `authTag`, and the same `CryptoKey`. Returns the original value.       |
| `MAX_PAYLOAD_BYTES` | Numeric constant (`262_144`). Pre-check your payload size against this value to avoid a thrown `RangeError` from `snapshot`.  |

## Error handling

### `EngineUnavailableError` — local native host unreachable

`store` and `recall` throw `EngineUnavailableError` if the local ONEBRAIN native host can't be reached over IPC.

```typescript theme={null}
import { AgentStateClient, EngineUnavailableError } from "@sovseal/sdk";

const client = new AgentStateClient({ endpoint: "...", apiKey: "..." });

try {
  await client.store("User prefers dark mode");
} catch (err) {
  if (err instanceof EngineUnavailableError) {
    console.error(`Engine unavailable: ${err.reason}`); // "not-installed" | "version-mismatch" | "timeout"
  } else {
    throw err;
  }
}
```

`err.reason` is one of `"not-installed"`, `"version-mismatch"`, or `"timeout"`. This error is specific to `store`/`recall` — it is never thrown by `snapshot`, `restore`, `restoreAt`, or `lineage`, which only depend on your configured `endpoint`.

### `RangeError` — oversized payload

`snapshot` throws a `RangeError` synchronously if the encrypted ciphertext exceeds `MAX_PAYLOAD_BYTES` (256 KB). This check happens before any network call.

```typescript theme={null}
import { AgentStateClient, CryptoService, MAX_PAYLOAD_BYTES } from "@sovseal/sdk";

const client = new AgentStateClient({ endpoint: "...", apiKey: "..." });
const key = await CryptoService.generateAesKey();

try {
  const receipt = await client.snapshot({ payload: myPayload, key });
} catch (err) {
  if (err instanceof RangeError) {
    console.error(
      `Payload too large. Max allowed: ${MAX_PAYLOAD_BYTES} bytes. ` +
      `Consider splitting the context across multiple snapshots.`
    );
  } else {
    throw err; // re-throw unexpected errors
  }
}
```

<Tip>
  Call `encryptJson` on your payload manually and check the resulting `ciphertext.byteLength` against `MAX_PAYLOAD_BYTES` before calling `snapshot` if you routinely handle large context objects.
</Tip>
