Writing a custom provider
The provider interface is deliberately small. If you want to back the SDK with something other than atomicmemory-core or Mem0 — an internal memory service, a Pinecone-fronted store, a SQLite-over-disk prototype — you write a MemoryProvider and register it. Application code keeps calling the same SDK methods.
Minimum implementation
Extend BaseMemoryProvider — it provides the scope-validation scaffolding and a default getExtension that checks your declared capabilities.
import {
BaseMemoryProvider,
type Capabilities,
type IngestInput,
type IngestResult,
type SearchRequest,
type SearchResultPage,
type ListRequest,
type ListResultPage,
type MemoryRef,
type Memory,
} from '@atomicmemory/atomicmemory-sdk';
export class MyProvider extends BaseMemoryProvider {
readonly name = 'my-provider';
constructor(private readonly config: { endpoint: string }) {
super();
}
capabilities(): Capabilities {
return {
ingestModes: ['text', 'message', 'memory'],
requiredScope: {
user: true,
agent: false,
namespace: false,
thread: false,
},
extensions: {
package: false,
temporal: false,
versioning: false,
updater: false,
graph: false,
forgetter: false,
profiler: false,
reflector: false,
batchOps: false,
health: true,
},
customExtensions: {},
};
}
async ingest(input: IngestInput): Promise<IngestResult> {
const res = await fetch(`${this.config.endpoint}/ingest`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(input),
});
if (!res.ok) throw new Error(`ingest failed: ${res.status}`);
return res.json();
}
async search(request: SearchRequest): Promise<SearchResultPage> { /* ... */ }
async get(ref: MemoryRef): Promise<Memory | null> { /* ... */ }
async delete(ref: MemoryRef): Promise<void> { /* ... */ }
async list(request: ListRequest): Promise<ListResultPage> { /* ... */ }
}
That's it. The six core methods plus capabilities() and the provider name. Everything else is opt-in.
Register the provider
Providers are created by factories in the registry. The simplest path is to construct your provider and pass it in via a factory-style registration. See src/memory/providers/registry.ts for the shipped registry and the pattern for adding to it.
At SDK construction time, reference your provider by name in context.providers:
const sdk = new AtomicMemorySDK({
userAccounts: { /* ... */ },
context: {
providers: {
default: 'my-provider',
'my-provider': { endpoint: 'https://memory.internal.example.com' },
},
},
});
Declaring capabilities accurately
Two rules:
- Don't lie. If
capabilities().extensions.packageistrue, yourgetExtension('package')must return a realPackager. The defaultBaseMemoryProvider.getExtensionreturnsthiswhen the capability is true and relies on the subclass implementing the extension methods on itself. - Declare
requiredScopehonestly. If your backend needsnamespaceto partition correctly, setrequiredScope.namespace: true.BaseMemoryProvider.validateScopewill reject requests missing required fields before they reach your network code.
Implementing an extension
If you want to support context packaging:
import type { Packager, PackageRequest, ContextPackage } from '@atomicmemory/atomicmemory-sdk';
export class MyProvider extends BaseMemoryProvider implements Packager {
// ... core methods, capabilities ...
async package(request: PackageRequest): Promise<ContextPackage> {
const page = await this.search(request);
const text = formatInjection(page.results, request.tokenBudget ?? 2000);
return { text, results: page.results, tokens: approxTokens(text) };
}
}
Set capabilities().extensions.package = true. Done — sdk.package() now works.
Testing
The SDK's own test suite uses a fixture provider (src/memory/__tests__/memory-service.test.ts). The pattern works for any custom provider: construct the SDK with your provider as default, exercise the public SDK methods, assert on observable behaviour rather than internal state.
Next
- Provider model — the full extension menu you can opt into
- Capabilities — the contract apps rely on when they call your provider