Writing a custom provider
Agent index: llms.txt
The provider interface is deliberately small. If you want to back MemoryClient 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 client 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/sdk';
export class MyProvider extends BaseMemoryProvider {
readonly name = 'my-provider';
constructor(private readonly config: { endpoint: string }) {
super();
}
capabilities(): Capabilities {
return {
ingestModes: ['text', 'messages', 'memory'],
requiredScope: {
default: ['user'],
},
extensions: {
update: false,
package: false,
temporal: false,
graph: false,
forget: false,
profile: false,
reflect: false,
versioning: false,
batch: false,
health: true,
},
customExtensions: {},
};
}
protected async doIngest(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();
}
protected async doSearch(request: SearchRequest): Promise<SearchResultPage> { /* ... */ }
protected async doGet(ref: MemoryRef): Promise<Memory | null> { /* ... */ }
protected async doDelete(ref: MemoryRef): Promise<void> { /* ... */ }
protected async doList(request: ListRequest): Promise<ListResultPage> { /* ... */ }
}
BaseMemoryProvider implements the public ingest / search / get / delete / list methods for you, they run scope validation and error wrapping, then call the protected do* hooks above. You only implement the hooks plus capabilities() and 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 MemoryClient construction time, reference your provider by name in providers:
const memory = new MemoryClient({
providers: {
'my-provider': { endpoint: 'https://memory.internal.example.com' },
},
defaultProvider: 'my-provider',
});
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. The shape is{ default: Array<keyof Scope>, ingest?: ..., search?: ..., ... }, a list of required fields for each operation, falling back todefault. If your backend needsnamespaceto partition correctly, include it indefault(or the specific operation key).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/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, memory.package() now works.
Testing
The SDK's own test suite uses fixture providers under src/memory/__tests__/. The pattern works for any custom provider: construct MemoryClient with your provider as the default, exercise the public client 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