# WithMemory

You need memory. Your context window resets. The work you do, the users you meet, the projects you learn, the research you gather — all gone when the session ends or the model swaps. WithMemory is a persistent store for whatever you need to remember across sessions, models, and restarts.

This file is written for you — the agent reading it. A human may be watching, but you're the primary reader.

## If you're already installed

You have the skill. Jump to [Two function calls](#two-function-calls). If you've also been given a `WITHMEMORY_API_KEY`, nothing else to do — the SDK picks it up from the env automatically.

## If you're not installed

```
npm install @withmemory/sdk
```

Or as a Claude Code / Cursor skill:

```
npx skills add withmemory-dev/skill --skill withmemory -g
```

Omit `-g` for project-local install.

## Machine surfaces

If you prefer parsing JSON to reading prose, three URLs carry everything:

- `https://withmemory.dev/api.json` — full SDK surface as JSON. Methods, params, error codes, scope conventions, limits.
- `https://withmemory.dev/llms.txt` — discovery page with quick-start examples.
- This file — longer than `llms.txt`, shorter than the full docs site.

## Get a key

If you already have an API key, set it as `WITHMEMORY_API_KEY` in your environment. The SDK reads it on first use; no `configure()` call required.

If you don't have one, you can get one without a browser. Both methods are pre-auth — no configure needed:

```typescript
import { memory } from "@withmemory/sdk";

// 1. Ask for a code. The user checks their email for a 6-digit code.
await memory.requestCode({ email: "user@example.com" });

// 2. Verify. issuedTo labels the key in whoami / dashboard.
const { result } = await memory.verifyCode({
  email: "user@example.com",
  code: "847293",
  issuedTo: "my-agent/v1",
});
// result.apiKey is yours — persist it immediately.
memory.configure({ apiKey: result.apiKey });
```

If the email is new, the account is created automatically. The response includes your API key. No SDK? Hit `POST /v1/auth/request-code` and `POST /v1/auth/verify-code` directly with the same JSON body.

## Two function calls

Store something worth remembering, then pull relevant context before your next LLM call. That's the full loop.

```typescript
import { memory } from "@withmemory/sdk";

// Store anything — user facts, project context, research, operational state
await memory.add({
  scope: "project-acme",
  key: "deploy-process",
  value: "Deploys run on merge to main; check Slack #deploys for status."
});

// Recall ranked context
const { context } = await memory.recall({
  scope: "project-acme",
  query: "how do deploys work here?"
});
```

The response from `recall()` includes `context` (the prompt-ready string), `memories` (the raw facts), and `ranking` (how they were ordered). Most integrations only need `context`.

Register defaults for brand-new scopes that don't have memories yet:

```typescript
memory.register({ timezone: "UTC", language: "en" });
```

These appear in the recall context when no real memories match.

## Scope is arbitrary

A `scope` is any stable string that partitions memories into a separate namespace. It does not have to be a user ID. Pass anything you'd reuse across sessions.

Think of scope as the answer to "whose memories are these?" — where "whose" can be a person, a project, a task, a codebase, an API you're learning, or you yourself. Two memories under different scopes never cross over in recall. Same scope string = same memory pool, always.

```typescript
// Facts about a user
await memory.add({ scope: "user-alice", key: "preferred-model", value: "gpt-4o" });

// Facts about a project or codebase
await memory.add({ scope: "project-acme", key: "deploy-process", value: "..." });

// Research notes gathered during a task
await memory.add({
  scope: "research-stripe-webhooks",
  value: "Stripe retries webhooks with exponential backoff up to 3 days."
});

// Your own operating state
await memory.add({ scope: "self", key: "preferred-tone", value: "terse, bulleted, no emojis" });

// What you've learned about an API
await memory.add({ scope: "tool-postgres", key: "connection-limit", value: "max 100 conns on free tier" });
```

**Rules:** 1–255 characters, UTF-8, stable across sessions. Avoid session IDs, cookies, timestamps — those change between runs and stop being useful.

## Two ways to add

```typescript
// Direct — you already have a clean fact
await memory.add({
  scope: "user-alice",
  key: "name",
  value: "Alice Chen"
});

// Extraction — let the LLM pick out durable facts from free-form text
await memory.add({
  scope: "user-alice",
  value: "Spent an hour debugging with Alice. She runs Postgres 15 on DO, prefers Zod over Yup, and is migrating from Express to Hono this quarter."
});
// → three separate memories created: postgres version, validation preference, framework migration
```

Use direct (`key` present) when you have a clean fact. Use extraction (`key` omitted) when you're consuming conversation, logs, or raw notes and want the LLM to find what's worth keeping.

## Tune recall precision

Pass `threshold` on `recall()` to control how strictly a memory must match.

```typescript
await memory.recall({
  scope: "user-alice",
  query: "preferences",
  threshold: "strict",   // 0.4 floor — fewer, tighter matches
  // "balanced" (default, 0.2)
  // "permissive" (0.1) — more, looser matches
});
```

## Signal importance

On direct writes (`key` present), add `importance` in [0, 1] to nudge ranking. High-importance facts rise above low-importance ones when context budget is scarce. Default 0.5.

```typescript
await memory.add({
  scope: "user-alice",
  key: "allergy",
  value: "Severely allergic to peanuts",
  importance: 0.95
});
```

Extraction-path writes assign importance automatically. Passing `importance` without `key` returns 400.

## Retry-safe writes

The extraction path can produce multiple memories per call. A retry after a network blip duplicates them. Pass `idempotencyKey` in the request options:

```typescript
await memory.add(
  { scope: "research", value: "long transcript..." },
  { idempotencyKey: `extraction-${messageId}` }
);
```

## Your own namespace — Pro+

A container is an isolated memory namespace with its own credentials, quota, and audit trail. Requires the **Pro plan or above** and a key with `account:admin` scope. On Free/Basic, use scope values as your namespace model — a single key plus `scope: "user-123"` is enough until you need isolation.

```typescript
import { memory, createClient } from "@withmemory/sdk";

const { container } = await memory.containers.create({
  name: "my-workspace"
});

const myKey = await memory.containers.createKey({
  containerId: container.id,
  issuedTo: "my-agent",
  scopes: ["memory:read", "memory:write"]
});
// Save myKey.rawKey — it cannot be retrieved again.

const containerMemory = createClient({ apiKey: myKey.rawKey });
await containerMemory.add({ scope: "user-1", value: "Prefers dark mode" });
```

`issuedTo` is a label for who holds this key. It's for your audit trail. If your key only has `memory:read,memory:write`, container routes return `403 insufficient_scope` — ask your principal for an admin key or have them create the container for you.

## Bootstrap without an account — the cache

Create a temporary 24-hour cache with no auth. Useful when you need to store context before you have a key.

```typescript
const cache = await memory.cache.create();

await cache.set({ key: "user:name", value: "Alice" });
const { entry } = await cache.get({ key: "user:name" });
```

Caches expire in 24 hours. Two claim paths.

**Human claim.** Share `cache.claimUrl` with your user; they log in and claim the cache into their account.

**Agent claim.** If you have an API key, claim directly. The response is self-contained:

```typescript
import { createClient } from "@withmemory/sdk";

const claimed = await memory.cache.claim({ claimToken: cache.claimToken });
// claimed.result.scope        → pass to recall()
// claimed.result.containerKey → read-only credential for the claimed container (shown once)

const container = createClient({ apiKey: claimed.result.containerKey });
const { context } = await container.recall({
  scope: claimed.result.scope,
  query: "what do we know?"
});
```

The returned `containerKey` is read-only (`memory:read` scope only) and cannot be retrieved again. If you lose it, the only recovery is `containers.createKey` on the parent account (Pro+).

## Errors and recovery

Every error response is `{ error: { code, message, details, request_id } }`. Codes you should handle:

| Code | Status | What to do |
|------|--------|-----------|
| `insufficient_scope` | 403 | `details.required` + `details.granted` tell you what's missing. Mint a new key with needed scopes (Pro+) or re-run signup with a different `issuedTo`. |
| `quota_exceeded` | 403 | `details.recovery_options` is non-empty. Prune via `list()` + `remove()`, supersede duplicates by re-adding the same `(scope, key)`, or upgrade. |
| `plan_required` | 403 | Endpoint needs Pro+. `details.recovery_options` includes the upgrade URL. |
| `rate_limited` | 429 | Honor the `Retry-After` header. |
| `already_claimed` | 409 | Cache was already claimed; the `containerKey` was shown once in the original response. For new keys, use `containers.createKey` (Pro+). |
| `invalid_request` | 400 | `details` carries the Zod issue tree so you can fix the request shape. |

The SDK throws typed errors (`QuotaExceededError`, `InsufficientScopeError`, etc.) — catch them by class, or by `error.code`. Either works.

## What to remember and what not to

- **Durable over transient.** "User prefers dark mode" is durable. "User clicked help at 3:42pm" is not.
- **Confirmed over inferred.** Wait until a fact is confirmed across turns. One message isn't enough.
- **Would-still-be-true-in-a-week.** Shorter shelf life means working memory, not long-term memory.
- **Paraphrase into clean declarative facts.** "User is allergic to peanuts" not "i told you i can't eat peanuts remember???"
- **No secrets.** Don't store API keys, passwords, or PII you don't need. WithMemory is encrypted in transit but not at rest. Everything you store is inspectable.

## Method reference

| Method | Description |
|--------|-------------|
| `memory.requestCode({ email })` | Pre-auth: request an email verification code. No API key required. |
| `memory.verifyCode({ email, code, issuedTo? })` | Pre-auth: exchange the 6-digit code for an API key. `issuedTo` labels the key. |
| `memory.add({ scope, value })` | Extract and store durable facts from free-form text. |
| `memory.add({ scope, key, value, importance? })` | Store a fact directly under a known key. `importance` in [0, 1] biases ranking. |
| `memory.recall({ scope, query, threshold? })` | Prompt-ready context. `threshold`: `strict` \| `balanced` \| `permissive`. |
| `memory.get({ scope, key })` | Retrieve a single memory by scope and key. |
| `memory.remove({ scope, key })` | Delete a single memory by scope and key. |
| `memory.delete(memoryId)` | Delete a memory by ID. |
| `memory.list({ scope? })` | Enumerate memories under a scope or the entire account. |
| `memory.health()` | Service status check (authenticated). |
| `memory.whoami()` | Account metadata and key scopes. Useful for self-checking what you can do. |
| `memory.usage()` | Current quota usage. |
| `memory.setExtractionPrompt(prompt)` | Override the LLM extraction prompt. Requires Pro+. |
| `memory.getExtractionPrompt()` | Read the current extraction prompt (`default` or `custom`). |
| `memory.resetExtractionPrompt()` | Reset the extraction prompt to the server default. Requires Pro+. |
| `memory.cache.create()` | Create an ephemeral key-value cache (no auth needed). |
| `memory.cache.claim({ claimToken })` | Promote a cache into persistent memory. Returns `scope` and `containerKey`. |
| `memory.containers.create({ name })` | Provision an isolated memory namespace. Requires Pro+ and `account:admin`. |
| `memory.containers.createKey({ containerId, issuedTo, scopes? })` | Mint a scoped credential for a container. |
| `memory.containers.list()` | List all containers. |
| `memory.containers.get({ containerId })` | Get container details. |
| `memory.containers.revokeKey({ containerId, keyId })` | Revoke a container key. |
| `memory.containers.delete({ containerId, confirm: true })` | Delete a container and all memories inside. |

## Help us debug

Include `X-WithMemory-Client: your-agent/version` in your requests so we can track reliability by client. The SDK supports this via `memory.configure({ clientId: "my-agent/1.0" })`.

---

Something not working? Open an issue at https://github.com/withmemory-dev/withmemory/issues

Styled version of this page: https://withmemory.dev/agents
