Skip to main content
Every memory operation in AtomicMemory — ingest, search, get, delete, package, and list — requires a scope object. Scope is the routing key that determines which partition of the memory store is read from and written to. Two requests with different scopes are completely isolated from each other: a search in scope { user: 'alice' } will never return memories stored under { user: 'bob' }, even on the same Core instance. This isolation is enforced by the server, not just the SDK.

The Four Scope Fields

user
string
The end-user identifier. This is the most commonly set field and should be a stable, unique ID for the person interacting with your application — for example, a database primary key or auth provider subject claim. Do not use display names or email addresses, which can change.
agent
string
The agent or assistant identifier. Use this to separate memories by which agent variant stored them — for example, 'customer-support', 'coding-assistant', or 'research-agent'. Useful when multiple agents share memory for the same user but should maintain independent memory sets.
namespace
string
An application or project namespace. Use this to scope memories to a specific product, tenant, or environment within a larger system — for example, 'my-app', 'acme-corp', or 'staging'.
thread
string
A specific conversation thread or session identifier. Thread scope is the most granular: memories stored with a thread ID are only retrieved when searching that exact thread. Use this for session-scoped memory that should not bleed across conversations.
At least one of these fields must be non-empty. The SDK will throw a validation error if you pass an empty scope object.

TypeScript Type

interface Scope {
  user?: string;
  agent?: string;
  namespace?: string;
  thread?: string;
  // At least one field must be non-empty
}

Common Scope Patterns

Different applications call for different combinations of scope fields. The examples below cover the most frequently used patterns:
// User-scoped: memories belong to a specific user across all sessions and agents.
// The most common pattern for single-product applications.
const userScope = { user: 'user-abc123' };

// User + namespace: memories scoped to a user within a specific app or tenant.
// Useful in multi-product platforms where the same user appears in multiple contexts.
const appScope = { user: 'user-abc123', namespace: 'customer-support' };

// User + agent: separate memory stores for different agent personalities
// serving the same user.
const agentScope = { user: 'user-abc123', agent: 'coding-assistant' };

// User + thread: session-pinned memory. Only retrieved during that specific
// conversation — does not accumulate across sessions.
const threadScope = { user: 'user-abc123', thread: 'thread-xyz' };

// Full scope: pinned to a specific agent, user, namespace, and thread.
// Maximum isolation — useful in complex multi-tenant multi-agent systems.
const fullScope = {
  user: 'user-abc123',
  agent: 'assistant-v2',
  namespace: 'my-app',
  thread: 'thread-xyz',
};

Multi-Tenant Safety

Scope is the only memory boundary in AtomicMemory. There is no additional row-level access control layer — if two tenants share the same user value, their memories will be mixed. You must ensure that user identifiers are globally unique across your entire tenant population.
Never reuse user IDs across tenants. If your application serves multiple organisations and two organisations happen to have a user with ID user-123, you must disambiguate — for example, by prefixing with a tenant ID: tenant-acme:user-123 — or by using the namespace field to partition tenant memory: { user: 'user-123', namespace: 'tenant-acme' }.

Scope in Framework Adapters

When AtomicMemory is integrated through a framework adapter (LangChain, Vercel AI SDK, etc.), scope is typically fixed at factory time when the adapter is configured. This means the agent or tool cannot change its own scope at runtime — it is bound to the scope passed during initialisation. This design is intentional. Fixing scope at factory time means:
  • An agent cannot accidentally or maliciously read another user’s memories by passing a different user value.
  • Scope is auditable — you can trace which scope was in effect for a given agent instance from your application code, without needing to inspect every individual memory call.
  • Your authorisation logic lives in one place (the factory / initialisation layer), not scattered across every memory operation.
// Scope is established once, at initialisation time.
// The agent that receives this client can only read and write
// memories for 'user-abc123' in the 'customer-support' namespace.
const memory = new MemoryClient({
  providers: {
    atomicmemory: {
      apiUrl: process.env.ATOMICMEMORY_URL!,
      apiKey: process.env.ATOMICMEMORY_KEY!,
    },
  },
});
await memory.initialize();

// All subsequent calls use the same scope — defined by the calling layer,
// not by the agent itself.
const defaultScope = { user: currentUserId, namespace: 'customer-support' };

await memory.ingest({ mode: 'messages', messages, scope: defaultScope });
const results = await memory.search({ query, scope: defaultScope });
If your application serves requests from multiple users, create one MemoryClient instance per request (or per session) with the appropriate scope. Do not share a single client across users by swapping scope values — instantiate a fresh client each time to keep scope provenance clear.