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.
What runs where
| Shape | In 0.6.0 |
|---|---|
| Serverless: Lambda, Cloud Functions, Cloud Run | Cloud 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, EKS | Local mode, one replica per store, with SQLite on a block volume. |
| Several replicas on one store | Not supported: SQLite needs one host. Use cloud mode when one replica is not enough. |
| Oort as a memory service for any language | PlannedPlanned for 0.7. |
Serverless
A handler at module scope, and a one-shot job that closes its own handle with await using:
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-sqliteorbetter-sqlite3, so the native build never has to match the function's architecture. It readsOORT_HOMEonly 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'], withOORT_API_KEYset 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'soort.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 handler.ts --bundle --platform=node --format=esm --external:@oortmem/store-sqlite --external:better-sqlite3 --external:sqlite-vecScripts 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:
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 withRecreateormaxSurge: 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). Withmigrate: falseit refuses (migration-required): runoort doctoragainst the volume as a one-off job first. - Backups. Take volume snapshots.
oort exportacts 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 passkeys.oort.- Passing
keyswith akeySourcesthat leaves out'explicit'is a usage error, never a silent fallback. - Keys given in code stay in memory.
console.log(oort)andJSON.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 throwsOortNeedsKeyError. Local models (oort models pull) embed and rerank with no egress, but saving a memory still needs the decide and writer keys.