Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/sdk/api/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,11 @@ import {
} from '@atomicmemory/atomicmemory-sdk';

try {
await sdk.package(request, site);
await memory.package(request);
} catch (err) {
if (err instanceof UnsupportedOperationError) {
// Provider does not support packaging; fall back
const page = await sdk.search(request, site);
const page = await memory.search(request);
return formatFallback(page.results);
}
if (err instanceof NetworkError) {
Expand Down
20 changes: 10 additions & 10 deletions docs/sdk/api/memory-provider.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ Every provider must implement these methods. No exceptions.

| Method | Obligation |
|---|---|
| `name: string` | Stable identifier used for registry lookup and capability reports. Match the key in `context.providers`. |
| `ingest(input)` | Accept one of the declared `ingestModes` (`text`, `message`, `memory`). Return `IngestResult` describing what was created / updated / unchanged. |
| `name: string` | Stable identifier used for registry lookup and capability reports. Match the key used in `MemoryClient`'s `providers` config. |
| `ingest(input)` | Accept one of the declared `ingestModes` (`text`, `messages`, `memory`). Return `IngestResult` describing what was created / updated / unchanged. |
| `search(request)` | Return a `SearchResultPage`. Honour `limit`, `cursor`, `scope`. Scope-invisible data must not surface. |
| `get(ref)` | Return the memory or `null` — never throw for "not found". |
| `delete(ref)` | Idempotent. Deleting a missing memory is not an error. |
Expand All @@ -28,7 +28,7 @@ Extending `BaseMemoryProvider` is the supported path. It provides scope-validati
`capabilities()` is a declaration, not a hope. Apps read it and branch. Misrepresenting capabilities breaks callers in ways that are hard to debug.

- If `extensions.package` is `true`, `getExtension('package')` must return a real `Packager`. The default `BaseMemoryProvider.getExtension` returns `this` when the capability is true — so the subclass must implement `package(request)` on itself.
- If `requiredScope.user` is `true`, every request missing `scope.user` should be rejected before any network call. `BaseMemoryProvider.validateScope` does this for you when called.
- `requiredScope` is `{ default: Array<keyof Scope>, ingest?: ..., search?: ..., ... }`. If `'user'` appears in `default` (or any per-operation override), every request for that operation missing `scope.user` should be rejected before any network call. `BaseMemoryProvider.validateScope` does this for you when called.
- `ingestModes` lists the shapes you accept. Rejecting an unlisted mode is fine; accepting one outside the list is not.

## Extension resolution
Expand All @@ -49,22 +49,22 @@ Subclasses override `getExtension` only when they need to return a different obj
| `package` | `Packager` | `package(req)` returning a token-bounded `ContextPackage` |
| `temporal` | `TemporalSearch` | `searchAsOf(req)` for point-in-time queries |
| `versioning` | `Versioner` | `history(ref)` returning version timeline |
| `updater` | `Updater` | `update(ref, content)` in-place |
| `update` | `Updater` | `update(ref, content)` in-place |
| `graph` | `GraphSearch` | `searchGraph(req)` traversing relationships |
| `forgetter` | `Forgetter` | `forget(ref, reason)` explicit deletion with metadata |
| `profiler` | `Profiler` | `profile(scope)` generating user/scope summaries |
| `reflector` | `Reflector` | `reflect(query, scope)` generating insights |
| `batchOps` | `BatchOps` | `batchIngest(inputs)` and similar bulk APIs |
| `forget` | `Forgetter` | `forget(ref, reason)` explicit deletion with metadata |
| `profile` | `Profiler` | `profile(scope)` generating user/scope summaries |
| `reflect` | `Reflector` | `reflect(query, scope)` generating insights |
| `batch` | `BatchOps` | `batchIngest(inputs)` and similar bulk APIs |
| `health` | `Health` | `health()` returning a liveness status |

If you want to offer a capability not on this list, use `customExtensions` — the registry passes arbitrary objects through `getExtension(name)`. Apps that use custom extensions cast the return value to the expected type.

## Lifecycle

- **`initialize?()`** — optional. Runs once after construction. The right place for health checks, warm-up, long-lived connections.
- **`close?()`** — optional. Runs during SDK teardown. Release connections, flush caches.
- **`close?()`** — optional. Release connections, flush caches. Callers invoke it directly on the provider when they need teardown; `MemoryClient` itself does not drive it.

Both are async. Both may throw; errors propagate to the caller of `AtomicMemorySDK.initialize()` / `close()`.
Both are async. Both may throw; `initialize` errors propagate to the caller of `MemoryClient.initialize()`.

## Error expectations

Expand Down
6 changes: 3 additions & 3 deletions docs/sdk/api/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ Three pages, each covering something human writing adds that generated docs cann

## What's generated

Method signatures, class members, types, and subpath exports — `AtomicMemorySDK`, `IngestInput`, `SearchRequest`, `ContextPackage`, `StorageManager`, `EmbeddingGenerator`, and the rest — are generated from the SDK's source via its `typedoc` pipeline.
Method signatures, class members, types, and subpath exports — `MemoryClient`, `IngestInput`, `SearchRequest`, `ContextPackage`, `StorageManager`, `EmbeddingGenerator`, and the rest — are generated from the SDK's source via its `typedoc` pipeline.

Until the generated reference is published alongside these docs, point users at the SDK repo's types and JSDoc comments directly:

- `src/core/sdk.ts` — `AtomicMemorySDK` class
- `src/client/memory-client.ts` — `MemoryClient` class
- `src/memory/types.ts` — public types (`Scope`, `MemoryRef`, `Memory`, `IngestInput`, `SearchRequest`, `SearchResultPage`, `PackageRequest`, `ContextPackage`, `Capabilities`)
- `src/memory/provider.ts` — `MemoryProvider` + `BaseMemoryProvider` + extension interfaces
- `src/storage/`, `src/embedding/`, `src/search/` — subpath exports
- `src/settings/` — `ExtensionSettings`, `DeveloperSettings`
- `src/browser.ts` — the slim browser entry

## Why this split

Expand Down
70 changes: 24 additions & 46 deletions docs/sdk/concepts/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,96 +5,74 @@ sidebar_position: 1

# Architecture

`AtomicMemorySDK` is a layered dispatcher. Application code talks to a single class; under the hood, policy, routing, and backend calls are separate concerns that compose cleanly.
`MemoryClient` is a layered dispatcher. Application code talks to a single class; under the hood, routing and backend calls are separate concerns that compose cleanly.

## The layers

```mermaid
flowchart TB
App["Host app (web, extension, Node)"]
SDK["AtomicMemorySDK\n(src/core/sdk.ts)"]
CM["ContextManager\n(capture + inject gates)"]
MC["MemoryClient\n(src/client/memory-client.ts)"]
MS["MemoryService\n(provider routing)"]
Reg["ProviderRegistry"]
AMP["AtomicMemoryProvider → atomicmemory-core"]
M0["Mem0Provider → Mem0"]
Custom["Custom MemoryProvider"]
UA["UserAccountsManager"]
WS["WebSDKService"]
Store["StorageManager + adapters"]
Emb["EmbeddingGenerator\n(transformers.js)"]

App --> SDK
SDK --> CM
CM --> MS
SDK -.-> UA
SDK -.-> WS
App --> MC
MC --> MS
MS --> Reg
Reg --> AMP
Reg --> M0
Reg --> Custom
SDK -.-> Store
SDK -.-> Emb
AMP -->|HTTPS| Core["atomicmemory-core"]
M0 -->|HTTPS| Mem0BE["Mem0 service"]
App -.-> Store
App -.-> Emb
```

Solid arrows are the request path; dotted arrows are peer subsystems the SDK composes. Four responsibilities are kept separate on purpose:
Solid arrows are the request path; dotted arrows are peer subsystems that applications compose directly via subpath exports.

| Layer | Responsibility | Source of truth |
|---|---|---|
| `AtomicMemorySDK` | Public surface. Config validation, lifecycle, facade for all operations. | `src/core/sdk.ts` |
| `ContextManager` | Policy enforcement — capture gating and injection gating before any backend call. | `src/core/context-manager.ts` |
| `MemoryClient` | Public surface. Config validation, lifecycle, facade for all memory operations. | `src/client/memory-client.ts` |
| `MemoryService` | Provider routing. Picks the right provider per operation, runs any op-level pipeline. | `src/memory/memory-service.ts` |
| `MemoryProvider` | Backend I/O. HTTP calls, mapping to/from wire format, declaring capabilities. | `src/memory/provider.ts` and per-provider directories |

Peer subsystems — storage adapters, embedding generation, user accounts, the web SDK helpers — are composed by `AtomicMemorySDK` but do not sit on the request path. They run alongside.
`StorageManager` and `EmbeddingGenerator` are standalone subsystems reachable through the `./storage` and `./embedding` subpath exports. They run alongside `MemoryClient` when an application needs client-side persistence or local embeddings, and are independent of the memory backend.

## Ingest, end to end

```mermaid
sequenceDiagram
participant App
participant SDK as AtomicMemorySDK
participant CM as ContextManager
participant UA as UserAccountsManager
participant MC as MemoryClient
participant MS as MemoryService
participant P as Provider
participant BE as Backend

App->>SDK: ingest(input, platform)
SDK->>CM: ingest(input, platform)
CM->>UA: shouldCaptureFromPlatform(platform)
alt blocked
UA-->>CM: false
CM-->>SDK: void
SDK-->>App: undefined
else allowed
UA-->>CM: true
CM->>MS: ingest(input)
MS->>P: ingest(input)
P->>BE: POST /memories
BE-->>P: {id, status}
P-->>MS: IngestResult
MS-->>SDK: IngestResult
SDK-->>App: IngestResult
end
App->>MC: ingest(input)
MC->>MS: ingest(input)
MS->>P: ingest(input)
P->>BE: POST /v1/memories/ingest
BE-->>P: {id, status}
P-->>MS: IngestResult
MS-->>MC: IngestResult
MC-->>App: IngestResult
```

The gate comes first. If the user has disabled capture from the originating platform, the operation returns `undefined` and nothing reaches the backend — the SDK emits a `captureBlocked` event so instrumentation can observe the decision.

Search follows the same shape with a different gate (injection). See [Consent and gating](/sdk/concepts/consent-and-gating) for the full search sequence.
Search follows the same shape. Every operation on `MemoryClient` delegates to `MemoryService`, which resolves the active provider and dispatches.

## Why layered

The separation matters for three reasons:

1. **Swappable backends.** `MemoryService` does not know the wire format. `MemoryProvider` does not know the user's capture preferences. Each layer can change independently, which is what makes backend-agnosticism real and not aspirational.
2. **Clear policy boundary.** Every gate lives in `ContextManager`. There is exactly one place to audit capture and injection decisions, and exactly one place to extend them.
3. **Testable in isolation.** Providers are tested against the `MemoryProvider` contract alone. Policy is tested against `ContextManager` with stub providers. The top-level SDK has a thin integration layer and is tested mostly for lifecycle.
1. **Swappable backends.** `MemoryService` does not know the wire format. `MemoryProvider` does not know the operation-level pipeline. Each layer can change independently, which is what makes backend-agnosticism real and not aspirational.
2. **One integration seam.** Every backend implements `MemoryProvider`. Adding a new backend is a matter of subclassing `BaseMemoryProvider` and registering it.
3. **Testable in isolation.** Providers are tested against the `MemoryProvider` contract alone. The top-level `MemoryClient` has a thin integration layer and is tested mostly for lifecycle and routing.

## Where to go next

- [Provider model](/sdk/concepts/provider-model) — the `MemoryProvider` interface and the extension system
- [Consent and gating](/sdk/concepts/consent-and-gating) — what `ContextManager` actually enforces
- [Scopes and identity](/sdk/concepts/scopes-and-identity) — how `UserAccountsManager` resolves who the caller is
- [Scopes and identity](/sdk/concepts/scopes-and-identity) — the `Scope` shape every operation carries
- [Capabilities](/sdk/concepts/capabilities) — how to probe what a provider supports
18 changes: 9 additions & 9 deletions docs/sdk/concepts/capabilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ Not every memory backend supports every operation. `atomicmemory-core` ships con
Every provider returns a `Capabilities` object from its `capabilities()` method, declaring what it supports:

```typescript
const caps = sdk.capabilities();
const caps = memory.capabilities();
// {
// ingestModes: ['text', 'message', 'memory'],
// requiredScope: { user: true, agent: false, namespace: false, thread: false },
// ingestModes: ['text', 'messages', 'memory'],
// requiredScope: { default: ['user'], ingest: ['user', 'namespace'] },
// extensions: { package: true, temporal: true, versioning: true, health: true, ... },
// customExtensions: { ... },
// }
Expand All @@ -24,8 +24,8 @@ const caps = sdk.capabilities();
Three fields matter in practice:

- **`ingestModes`** — which shapes of input the provider accepts. Most providers accept all three; some specialize.
- **`requiredScope`** — which scope fields must be present in a request. For example, a workspace-only provider might require `namespace`; a personal-memory provider might only need `user`.
- **`extensions`** — a record of capability keys to booleans. `package`, `temporal`, `versioning`, `updater`, `graph`, `forgetter`, `profiler`, `reflector`, `batchOps`, `health`. If `extensions.package` is `true`, the provider supports `sdk.package()`. If `false`, calling `sdk.package()` raises `UnsupportedOperationError`.
- **`requiredScope`** — which scope fields must be present in a request. A `default` array lists the fields required across all operations, with optional per-operation overrides keyed by operation name (`ingest`, `search`, `get`, `delete`, `list`, `package`, `update`, ...). A workspace-only provider might require `namespace` in `default`; a personal-memory provider might require only `user`.
- **`extensions`** — a record of capability keys to booleans. `package`, `temporal`, `versioning`, `update`, `graph`, `forget`, `profile`, `reflect`, `batch`, `health`. If `extensions.package` is `true`, the provider supports `memory.package()`. If `false`, calling `memory.package()` raises `UnsupportedOperationError`.

## The extension probe

Expand All @@ -45,19 +45,19 @@ The extension key matches the capability key (`'package'`, not `'packager'`) —

## Why apps must check

An app that calls `sdk.package()` without first confirming the capability is declaring a dependency on a specific backend. That's fine if you know you're always talking to `atomicmemory-core`, but it defeats the purpose of the SDK's backend-agnostic design.
An app that calls `memory.package()` without first confirming the capability is declaring a dependency on a specific backend. That's fine if you know you're always talking to `atomicmemory-core`, but it defeats the purpose of the SDK's backend-agnostic design.

The recommended pattern for portable code:

```typescript
const { extensions } = sdk.capabilities();
const { extensions } = memory.capabilities();

if (extensions.package) {
const pkg = await sdk.package(request, site);
const pkg = await memory.package(request);
return pkg.text;
} else {
// Graceful degradation: fall back to search + local formatting
const page = await sdk.search(request, site);
const page = await memory.search(request);
return formatResults(page.results);
}
```
Expand Down
89 changes: 0 additions & 89 deletions docs/sdk/concepts/consent-and-gating.md

This file was deleted.

Loading