Mnemos TypeScript SDK (@mnemoscale/sdk)
Typed client for the Mnemos memory API, mirroring the Python SDK
(libs/sdk/mnemos_sdk) 1 API-key auth, retry/backoff on 429/5xx, typed
errors, job polling, a session helper, and webhook signature verification.
- Zero runtime dependencies — uses the global
fetch: Node >= 20, browsers, and edge runtimes all work. - ESM + CJS +
.d.ts— plaintscdual build, no bundler. - Generated-spec honesty — the wire types are hand-written for readability
but verified 1:1 against the committed OpenAPI snapshot
(
openapi.json) in CI (see below).
Quickstart (first query in under 5 minutes)
Bring up Mnemos as in the repo-root quickstart.py
(compose deps + API + outbox relay + extraction worker, then
mnemos-admin tenant create ... for an API key, agent id, and namespace id).
Then:
npm install @mnemoscale/sdk # in this repo: cd sdk-ts && npm ci && npm run build
import { MnemosClient } from "@mnemoscale/sdk";
const client = new MnemosClient("http://localhost:8001", process.env.MNEMOS_API_KEY!);
await client.withSession(
{ agentId: process.env.MNEMOS_AGENT_ID!, namespaceId: process.env.MNEMOS_NAMESPACE_ID! },
async (s) => {
// Blocks until the extraction job completes (extractAsync to fire-and-poll later).
await s.extract([{ content: "the customer asked about delivery delays", role: "user" }]);
const result = await s.query("delivery delays", { topK: 5, rerank: true });
for (const r of result.results) {
if (r.from_working_memory) continue; // session turns merged per PRD §7.1
console.log(r.hybrid_score, r.content);
}
},
);
The session is created before the callback and ended afterwards (even on
error) — the analogue of the Python SDK's with client.session(...) as s:. On
runtimes with explicit resource management you can also write:
await using s = await client.openSession({ agentId, namespaceId });
const accepted = await s.extractAsync([{ content: "..." }]); // fire...
const job = await client.waitForJob(accepted.job_id, { agentId }); // ...and poll later
Responses are returned exactly as they appear on the wire (snake_case keys, UUIDs/datetimes as strings), fully typed; method parameters are camelCase and mapped to the wire shape by the client.
Surface
Everything the Python SDK exposes: createNamespace, listNamespaces,
createAgent, createSession / getSession / endSession, extract /
getJob / waitForJob, query (routing, topK, metadataFilter, rerank,
scoring, efSearch, maxContextTokens, minRerankLogit), feedback,
getUsage, getProfile, warmUp, webhooks (createWebhook / listWebhooks /
deleteWebhook / testWebhook), sources (getSource / deleteSource /
eraseSources / exportSources), plus the session helper
(openSession / withSession) and verifyWebhookSignature.
Every method accepts request options: agentId (X-Agent-Id header),
correlationId (X-Correlation-Id, echoed by the API and attached to typed
errors), timeoutMs, and signal (AbortSignal). Client options:
timeoutMs, maxRetries, retryBackoffBaseMs, retryAfterCapMs,
pollScheduleMs, maxPolls, defaultHeaders, and injectable fetch/sleep
for tests.
Errors and retries (Python SDK parity)
HTTP failures throw subclasses of MnemosError; every HTTP error is a
MnemosApiError carrying status, code, message, correlationId, and
retryable:
| Status | Error | Retried? |
|---|---|---|
| 401 | AuthenticationError |
no |
| 402 | PaymentRequiredError (code, manageUrl) |
never — billing suspended |
| 403 | PermissionError |
no |
| 404 | NotFoundError |
no |
| 409 | ConflictError |
no |
| 422 | ValidationError |
no |
| 429 | RateLimitError (code) |
yes, unless X-Mnemos-Retryable: false |
| 500/502/503/504 | ServerError |
yes |
Retry policy (identical to the Python SDK): up to maxRetries (default 3)
extra attempts with exponential backoff retryBackoffBaseMs * 2^(attempt-1)
(default 100 ms → 100/200/400), no jitter. A server-sent Retry-After
(seconds or HTTP-date) overrides the computed backoff for that attempt, capped
at retryAfterCapMs (default 30 s).
Transport failures follow the same replay-safety rule as the Python SDK:
connection-phase failures (refused, DNS, unreachable — the request provably
never reached the server) are retried for any method; ambiguous failures
(timeouts, dropped sockets) are only replayed for idempotent requests
(GET/DELETE) or POSTs carrying an idempotencyKey. They surface as
MnemosConnectionError / MnemosTimeoutError in both SDKs — neither makes you
catch the underlying transport library's exception type.
waitForJob polls 250ms → 500ms → 1s → 2s → 5s (then 5 s repeating, max 60
polls) until the job is completed | partial | failed, else throws
MnemosTimeoutError — the Python SDK's schedule exactly.
An AbortSignal passed via request options cancels the whole call, including
any in-progress backoff/Retry-After or poll sleep: the sleep ends immediately
and the call rejects with the abort reason.
Verifying webhooks (PRD §13.2)
import { verifyWebhookSignature, WebhookVerificationError } from "@mnemoscale/sdk";
// In your webhook endpoint, before parsing the body:
try {
await verifyWebhookSignature(WEBHOOK_SECRET, req.headers["x-mnemos-signature"], rawBody);
} catch (err) {
if (err instanceof WebhookVerificationError) {
// err.reason: malformed_header | stale_timestamp | signature_mismatch
return new Response(null, { status: 400 });
}
throw err;
}
Uses WebCrypto (crypto.subtle), hence async — otherwise identical to the
Python SDK's verify_webhook_signature (HMAC-SHA-256 over "{t}.{body}",
300 s replay tolerance, constant-time compare).
OpenAPI snapshot
openapi.json is exported from the FastAPI app by
scripts/export_openapi.py and committed, so this
package's CI needs no network or Python toolchain. Two gates keep it honest:
tests/openapi-parity.test.ts verifies every endpoint the client calls and
every wire type's property/required sets against the snapshot, and the
Python-side contract test tests/contract/test_openapi_snapshot.py (repo
root, contract marker) rebuilds the spec from the live app via this script's
build_snapshot() and fails CI when the committed snapshot goes stale. When
the v1 API changes:
uv sync --all-packages
uv run python sdk-ts/scripts/export_openapi.py > sdk-ts/openapi.json
cd sdk-ts && npm test # parity test tells you what to update in src/types.ts
Developing
cd sdk-ts
npm ci
npm run typecheck # tsc strict, src + tests
npm test # vitest, mocked fetch — no network, no live services
npm run build # dist/esm + dist/cjs + .d.ts
npm pack # publish-ready tarball (runs the build via prepack)
The sdk-ts CI job runs exactly these and uploads the npm pack tarball as
the mnemos-sdk-npm artifact.
Releasing
Publishing is automated: .github/workflows/release-sdk-ts.yaml publishes to
npm with trusted publishing (GitHub OIDC — no NPM_TOKEN to store, and
provenance attached automatically) when a sdk-ts-v<version> tag is pushed.
# 1. bump `version` in package.json and land it on main
# 2. rehearse: Actions -> release-sdk-ts -> Run workflow (stops at --dry-run)
# 3. release (read the version back, so the tag cannot disagree with package.json):
tag="sdk-ts-v$(node -p "require('./package.json').version")"
git tag "$tag" && git push origin "$tag"
The job re-runs typecheck/tests/build against the tagged commit, refuses a tag
that disagrees with package.json or a version already on the registry,
publishes, and then installs the published package in a scratch directory and
imports it through both the ESM and CJS entry points.
The one-time human setup — the @mnemoscale scope and the trusted-publisher
entry — is in
docs/runbooks/sdk-release.md. The package
is licensed Apache-2.0 (LICENSE).
Parity with the Python SDK
sdk-parity.json at the repo root is the shared surface
contract between this package and libs/sdk: methods, error taxonomy,
constants, retry policy, and request/client options, named in both languages.
tests/parity.test.ts and the Python tests/unit/test_sdk_parity.py assert
their own side against it, so adding something to one SDK fails the other's
suite until it lands there too. The manifest's notes record the differences
that are deliberate because the languages differ — milliseconds here versus
seconds in Python, one async client here versus a sync and an async one there,
AbortSignal versus asyncio cancellation.