SDK

Running in your own infrastructure

What oortmem 0.6.0 supports on your own servers: serverless functions in cloud mode, and one long-running service with the store on a block volume. Everything else on this page is labelled as planned.

Oort in your infrastructureYour service runs the SDK. A serverless function uses cloud mode and keeps memories in Oort Cloud. A long-running container uses local mode and keeps memories in SQLite on a block volume, with one replica per store.Your servicefunction or containeroortmemcloud or local modeMemoryOort Cloud, or SQLiteon a volume

What runs where

ShapeIn 0.6.0
Serverless: Lambda, Cloud Functions, Cloud RunCloud mode. A local store in serverless is not supported: /tmp is ephemeral and per instance.
A long-running Node.js service: ECS on EC2, GKE, EKSLocal mode, one replica per store, with SQLite on a block volume.
Several replicas on one storeNot supported: SQLite needs one host. Use cloud mode when one replica is not enough.
Oort as a memory service for any languagePlannedPlanned for 0.7.

Serverless

A handler at module scope, and a one-shot job that closes its own handle with await using:

TypeScript
import { createOort, formatForPrompt } from 'oortmem';

// Module scope: warm invocations reuse the handle and its connection. Cloud mode writes nothing
// to disk and loads no native module.
const oort = await createOort({
  mode: 'cloud',
  keySources: ['explicit', 'env'],
  project: 'support-site',
  projectConfig: false,
});

export async function handler(event: { question: string; requestId: string }): Promise<string> {
  const memory = formatForPrompt(await oort.recall(event.question, { speed: 'fast' }));
  const answer = await answerWith(memory, event.question);
  // Await before returning: the runtime freezes after the response. `turnRef` makes a retried
  // invocation save the turn once.
  await oort.observe({ user: event.question, assistant: answer, turnRef: event.requestId });
  return answer;
}

// A one-shot job (a cron function, a queue consumer) closes its own handle when it ends.
export async function importFacts(facts: string[]): Promise<number> {
  await using job = await createOort({
    mode: 'cloud',
    keySources: ['explicit', 'env'],
    project: 'support-site',
    projectConfig: false,
  });
  const { results } = await job.remember(facts);
  return results.length;
}

/** Your model call goes here. */
async function answerWith(memory: string, question: string): Promise<string> {
  return `(an answer to "${question}", written with ${memory.length} characters of memory)`;
}
  • No native module, no disk writes. Cloud mode never imports @oortmem/store-sqlite or better-sqlite3, so the native build never has to match the function's architecture. It reads OORT_HOME only if files exist there, and never creates a directory, a store, a log or a cache.
  • Mark the store external when you bundle, so the bundler does not pull it in.
  • Keys from code or the environment: keySources: ['explicit', 'env'], with OORT_API_KEY set from your secret manager. There is no Keychain in a function.
  • A checkout as the working directory (a monorepo deploy): pass projectConfig: false, so the handle skips the repository's oort.config.json.
  • Create the handle at module scope. Its sockets do not keep the process alive, so a function that never calls close() still ends.
  • Await observe before you return. The runtime freezes after the response.
esbuild
esbuild handler.ts --bundle --platform=node --format=esm --external:@oortmem/store-sqlite --external:better-sqlite3 --external:sqlite-vec

Scripts and jobs: await using

Outside a function's module scope, await using closes the handle when the block ends, as importFacts does above. TypeScript 5.2 or newer compiles it. Elsewhere, call close() in a finally block.

Containers with a persistent volume

A long-running service embeds the SDK in local mode, with OORT_HOME on a block volume: EBS (an EKS StatefulSet, or ECS on EC2) or a GCE persistent disk (a GKE StatefulSet). Pass everything explicitly:

TypeScript
const oort = await createOort({
  mode: 'local',
  home: process.env['OORT_HOME'] ?? '/data/oort',
  org: 'acme',
  project: null,
  projectConfig: false,
  keySources: ['env'],
});
  • One writer host per store. SQLite's WAL needs shared memory on one host. Run one replica (replicas: 1, ReadWriteOnce), and roll out with Recreate or maxSurge: 0, so two pods never open the store at once. Several processes on that one host are fine.
  • No network filesystems. EFS, Filestore and other NFS, SMB or FUSE mounts (GCS FUSE included) break WAL's locking. 0.6.0 does not detect them, so check the mount yourself.
  • Migrations. A new oortmem migrates an older store when it opens it (migrate: true, the default). With migrate: false it refuses (migration-required): run oort doctor against the volume as a one-off job first.
  • Backups. Take volume snapshots. oort export acts as the person who runs it, so it does not include memories written for end users.
  • Housekeeping. Consolidate each end user's memory from your own code (see Multi-user apps).

Keys from the environment or a secret manager

A server sets keySources explicitly. With ['env'], inject VOYAGE_API_KEY, TYPESAFE_API_KEY and ANTHROPIC_API_KEY (or OORT_API_KEY for the Oort Pro relay) from AWS Secrets Manager or GCP Secret Manager. Or read them from the secret manager yourself, pass them as keys (voyage, typesafe, anthropic, oort), and set keySources: ['explicit'].

  • keySources: ['explicit'] turns off every other source, the Oort Pro relay included unless you pass keys.oort.
  • Passing keys with a keySources that leaves out 'explicit' is a usage error, never a silent fallback.
  • Keys given in code stay in memory. console.log(oort) and JSON.stringify(oort) print only the mode and the scope, and an error never carries a key.
  • With no model keys, recall falls back to keyword search (warning lexical-only) and a write throws OortNeedsKeyError. Local models (oort models pull) embed and rerank with no egress, but saving a memory still needs the decide and writer keys.