SDK
Multi-user apps
Your server holds one root handle per tenant and derives a handle for each end user with scoped(). A handle's scope is fixed when it is made, and no method takes a scope argument.
A handle per end user
Two users of the same tenant, each with a Planet of their own. Ada's order number never reaches Bob.
import { createOort, formatForPrompt } from 'oortmem';
// One root handle per tenant (`org`), kept in trusted host code: it can reach every user.
const oort = await createOort({
mode: 'local',
home: process.env['OORT_HOME'] ?? '/data/oort',
org: 'acme',
project: null,
projectConfig: false,
keySources: ['env'],
});
// Per request: a handle fixed to one end user. Hand only this one to the model's tools.
const forUser = (userId: string) => oort.scoped({ user: userId, agent: 'support-bot' });
await forUser('user-ada').remember('My order number is 1042.');
const ada = await forUser('user-ada').recall('what is my order number?');
const bob = await forUser('user-bob').recall('what is my order number?');
console.log(formatForPrompt(ada)); // Ada's Planet holds her order number
console.log(bob.memories.length); // 0: each end user has a Planet of their own
// At the end of a user's session (or from a scheduled job): no model call.
await forUser('user-ada').consolidate({ budgetMs: 200 });
await oort.close();| Option | What it is |
|---|---|
org | Your tenant: the storage and deletion boundary. Required, and not "local", whenever a user is set. |
user | One end user. Each gets their own Planet. |
agent | Your agent. Still recorded as the author of what it saves. |
project | A project id, or null for global memories only. |
A separate home per tenant gives physical separation (files, backups and deletion) when a contract needs it.
An explicit org
A user needs an explicit org other than 'local', the default. Without one, createOort and scoped throw OortUsageError with reason principal-required. A user can never be 'self' (the person who ran oort setup): scoped({ user: 'self' }) is refused with self-not-derivable.
End users get their Planet only
An end user's handle reads and writes their own Planet, never a Star. Today an agent's Star is kept per org and agent, not per user, so it would leak between users.
recall(q, { include: ['star'] })on a user handle is refused (scope-denied).- An agent configured as a Star together with a
useris refused (star-with-user). Use an agent that writes the Planet. includeonly narrows what a handle reads. A Constellation or Galaxy in local mode iscloud-only(hive-level).
A dropped user id fails loudly
When the user key is present, it must hold a user id. undefined, null or an empty string throws OortUsageError with reason user-missing. Only an options object with no user key at all acts as the person, so a server bug can never fall back to the tenant's own memory. So scoped({ user: req.user?.id }) with no signed-in user throws instead of reading anyone's memories.
agent and user are configuration, not authentication. Every process that runs as the same OS user can open the same home, as the CLI and the hooks do. Authenticate your end users before you scope a handle to them.
Housekeeping per user
Consolidation (tiers, expiry, moving old history to cold storage) runs per scope, and the hooks consolidate only the person's own memories. A multi-user host calls it per user. It calls no model, and it is local only: the example above ends with consolidate({ budgetMs: 200 }) for one user. Run it at the end of a user's session, or from a scheduled job over your active users.
The CLI acts as the person: memories written for a user do not show up in oort recall. For containers and keys on a server, see Running in your own infrastructure.