MainSDK
Search⌘K
Interfaces

AI and agents

Agent memory

memory

Let app agents remember the people they serve, inside the tenant and the app, with untrusted recall, versions and forgetting you can prove.

Building for 0.4ships as a experimental APIPhase 1 · Core, public betaSpec memoryFree in your app, Apache-2.0

Status Status is where the work is: Planned (in a milestone, not started), Building (tickets in progress), Preview (usable, may change), then the stability it ships at.

Stability The promise an interface ships with.

  • Stable Follows semantic versioning; breaking changes need a new major, 12 months of overlap and a codemod.
  • Beta Usable in production; may change before it becomes stable.
  • Experimental May change or go away; needs an explicit opt-in in main.yaml.
Stability levels
Updated 2026-10-28

memory lets your app's agents remember the people they serve, scoped to the tenant and app, with untrusted recall and a forget you can prove. It is free and runs in your app.

Every memory is about one person (or the whole tenant), labelled personal by default and sealed with that person's key. Recalled text goes to a model only as data inside one escaped <memory> block. forget deletes for real, takes everything derived with it and returns a signed receipt. Spec memory is experimental, so the shape may change. Import it from mainsdk/experimental/memory, not from mainsdk.

At a glance

memory:
  driver: db                       # db | local | mem0 | zep | letta | pinecone | mcp
  embed: { model: openai/text-embedding-3-small, feature: memory-embed }
  extract: { model: anthropic/claude-haiku-4-5, feature: memory-learn }
  ttl: { event: 90d }
  from-tools: quarantine           # quarantine | allow | refuse
  children: off                    # off | guardian
agents:
  support-bot:
    memory: { read: own, write: true, forget: approve, recalls: 500/day, writes: 100/day }

Calls

  • memory.remember(input, { about, kind, ttl, label, source, key, session })
  • memory.recall(query, { about, kinds, k, asOf, halfLife, hops, session })
  • memory.context({ about, query, budget })
  • memory.revise(id, next, { reason })
  • memory.forget(target, { reason, dryRun })
  • memory.pin(id)
  • memory.unpin(id)
  • memory.history(id)
  • memory.learn(messages, { about, kinds, maxCost })
  • memory.relate({ from, predicate, to })
  • memory.consolidate({ about, reflect })
  • memory.export({ about, kinds })
  • memory.import(lines, { about, session })
  • memory.mcp({ compat })
  • memory.anthropicTool()

Guarantees

Whatever the driver.

  • Memories never cross tenants or apps; the tenant comes from tenant.current()
  • Recalled text reaches the model only as escaped data, never as system text
  • The injection check runs on every write and every recall
  • Tool, MCP, web and import writes stay in their session until a person confirms them
  • Revisions make new versions; nothing is overwritten
  • Forget is a hard delete that cascades and returns a signed receipt
  • Labelled personal by default, sealed with the person's key, kept only as long as retention allows
  • Every write, recall and forget audited with ids only, never the text
  • remember, recall and forget never call a generative model

Drivers

7 drivers. Select one to see its line in main.yaml.

main.yamlYour database (Postgres with pgvector (default)
memory:
  driver: db

Change this one line to swap providers; your calls stay the same.Your database integration

Codes

CodeWhere it appearsMeaning
MAIN-MEM-0020Checkmainsdk check, before deployA driver that only soft-deletes holds memories that may be personal
MAIN-MEM-0021Checkmainsdk check, before deployA region the app serves has no endpoint in the memory driver
MAIN-MEM-0022Checkmainsdk check, before deployRecalled memory is passed as the system text of an ai call
MAIN-MEM-0023Checkmainsdk check, before deployA driver is named memory; the in-process driver is local (ADR-0039)
MAIN-MEM-0024Checkmainsdk check, before deploychildren: guardian with an extract model on a provider
MAIN-MEM-0025Checkmainsdk check, before deployThe local memory driver in production
MAIN-MEM-0001RuntimeError responses and logsNo subject: about is missing and the call has no on_behalf_of or session user (400)
MAIN-MEM-0002RuntimeError responses and logsNo memory with that id in the current tenant, app and scope (404, same body either way)
MAIN-MEM-0003RuntimeError responses and logsThe injection check flagged the text of a write; nothing was stored (422)
MAIN-MEM-0004RuntimeError responses and logsA recalled memory was flagged by the injection check and left out of the context (logged)
MAIN-MEM-0005RuntimeError responses and logsThe agent has no memory grant for this verb, or forget: deny (403)
MAIN-MEM-0006RuntimeError responses and logsThe agent is over its memory recalls or writes per day (429)
MAIN-MEM-0007RuntimeError responses and logsForget refused: a legal hold applies to the person (409)
MAIN-MEM-0008RuntimeError responses and logsA children's rule refused it: children are off, no guardian consent, or a provider model would extract (403)
MAIN-MEM-0009RuntimeError responses and logsInvalid input: unknown kind, bad duration, pan label, k over 100, hops over 2 or a bad predicate (400)
MAIN-MEM-0010RuntimeError responses and logsRevise of a version that is no longer current (409)
MAIN-MEM-0011RuntimeError responses and logsImport refused: a line is malformed, or personal or non-procedure memory would cross a tenant or app (422)
MAIN-MEM-0012RuntimeError responses and logsThe driver doesn't declare the capability the call needs, such as asOf, hops or relate (501)
MAIN-MEM-0013RuntimeError responses and logsThe memory store is unreachable (503, retryable); context degrades to an empty block
MAIN-MEM-0014RuntimeError responses and logsAn Anthropic memory tool path is outside /memories/ or tries to traverse (400)
MAIN-MEM-0015RuntimeError responses and logsA driver hasn't confirmed the erase yet; no receipt is issued and the erase keeps retrying (503)
MAIN-MEM-0016RuntimeError responses and logsA write from a tool, MCP, the web or an import was refused by from-tools: refuse (403)

Features

Nothing here has shipped yet. Each feature lands in its phase; the spec ticket builds it.

FeaturePhaseMilestoneSpec ticket
Remember and recall by tenantPhase 10.4memory#remember-recall
Model context with pinned memories in one escaped data blockPhase 10.4memory#context-pin
Versions with revisePhase 10.4memory#revise-history
Hard forget with cascade and a signed receiptPhase 10.4memory#forget-receipt
Injection check and quarantine for tool and import writesPhase 10.4memory#guards
Agent grantsPhase 10.4memory#governance
Personal labelPhase 10.4memory#privacy-keys
Audit of every writePhase 10.4memory#audit
db driver with row-level security and pgvectorPhase 10.4memory#db-driver
Local driver with BM25Phase 10.4memory#local-driver
Memory conformance suitePhase 10.4memory#conformance
MCP tools for rememberPhase 10.4memory#mcp-tools
Anthropic memory tool compatibilityPhase 10.4memory#anthropic-tool
Children off by defaultPhase 10.4memory#children
Learn memories from a conversationPhase 10.5memory-advanced#learn
Consolidate and reflect as a workflowPhase 10.5memory-advanced#consolidate
Relations and recall hopsPhase 10.5memory-advanced#relate
Lossless JSONL export and importPhase 10.5memory-advanced#export-import
Knowledge-graph MCP server compatibilityPhase 10.5memory-advanced#kg-compat
Mem0Phase 10.5memory-advanced#provider-drivers
Pinecone and MCP driversPhase 10.5memory-advanced#provider-drivers-vector-mcp

Quick start

main.yaml
memory:
  driver: db                       # db | local | mem0 | zep | letta | pinecone | mcp
  embed: { model: openai/text-embedding-3-small, feature: memory-embed }
  ttl: { event: 90d }
agents:
  support-bot:
    memory: { read: own, write: true, forget: approve, recalls: 500/day, writes: 100/day }
lib/support-reply.ts
import { ai } from "mainsdk";
import { memory } from "mainsdk/experimental/memory";

export async function reply(user: { id: string }, message: string) {
  const ctx = await memory.context({ query: message, budget: 800 });
  return ai.generate({
    model: "anthropic/claude-sonnet-4-5",
    messages: [
      { role: "user", content: ctx.text },   // memories as data, never system text
      { role: "user", content: message },
    ],
    feature: "support-chat",
    agent: "support-bot",
    user,
  });
}

You never pass a tenant: it comes from tenant.current(). about defaults to the person the agent acts for.

Configuration

KeyWhat it does
memory.driverWhere memories live. db (default) uses your database with row-level security; local keeps them under .mainsdk/memory/ for development
memory.embedThe model and feature for embeddings, made in a background job through ai
memory.extractThe model and feature learn and consolidate use (0.5)
memory.ttl.<kind>How long a kind lives. The label's retention under privacy.retention caps it
memory.from-toolsquarantine (default), allow or refuse for writes from tools, MCP, the web and imports
memory.childrenoff (default) or guardian
agents.<name>.memory.readown, all or none
agents.<name>.memory.writetrue lets the agent write
agents.<name>.memory.forgetapprove (default) waits for a person; deny refuses
agents.<name>.memory.recalls, writesDaily limits per agent

What you get

  • remember, recall and context on one scoped store, with versions and asOf reads.
  • forget with a signed receipt, and erasure through privacy.
  • Untrusted-by-default recall, the injection check and an audit trail without text.
  • MCP tools and the Claude memory tool, so agents use it without glue code.

Examples

Remember and recall

lib/preferences.ts
import { memory } from "mainsdk/experimental/memory";
import { s } from "mainsdk/schema";

await memory.remember("Prefers WhatsApp over email", { kind: "preference" });
await memory.remember(
  { schema: s.object({ city: s.string() }), value: { city: "Pune" } },
  { key: "home-city" },                      // a second remember with this key makes a new version
);

const found = await memory.recall("how to contact", { kinds: ["preference"], k: 5 });

Revise, read the past and see the history

lib/moved.ts
import { memory } from "mainsdk/experimental/memory";

declare const id: string;   // a memory id from remember or recall

const next = await memory.revise(id, "Lives in Mumbai", { reason: "told support on 27 Oct" });
const then = await memory.recall("where does she live", { asOf: "2026-10-01T00:00:00Z" });
const versions = await memory.history(id);   // oldest first; nothing is overwritten

Pin what the agent must always see

lib/pin.ts
import { memory } from "mainsdk/experimental/memory";

declare const allergyId: string;
declare const oldNoteId: string;

await memory.pin(allergyId);     // pinned memories come first in memory.context()
await memory.unpin(oldNoteId);

Forget, with a receipt

app/api/memory/forget/route.ts
import { auth } from "mainsdk";
import { memory } from "mainsdk/experimental/memory";

export async function POST(req: Request) {
  const session = await auth.require(req);
  const preview = await memory.forget({ about: session.user }, { reason: "user request", dryRun: true });
  const receipt = await memory.forget({ about: session.user }, { reason: "user request" });
  if ("pending" in receipt) return Response.json({ approvalId: receipt.approvalId }, { status: 202 });   // an agent's forget waits for a person
  return Response.json({ deleted: receipt.ids.length + receipt.cascaded.length, receipt, preview });
}

The receipt lists every id, everything derived from them and each driver's confirmation, and is signed by your project key. Forgetting everything about a person also destroys their memory key, so backups can't be read.

Confirm a quarantined memory

A memory written from a tool, MCP, the web or an import is returned only in its own session until a person confirms it.

app/api/memory/confirm/route.ts
import { auth } from "mainsdk";
import { memory } from "mainsdk/experimental/memory";

export async function POST(req: Request) {
  await auth.require(req);
  const { id } = (await req.json()) as { id: string };
  return Response.json(await memory.revise(id, { trust: "confirmed" }, { reason: "person confirmed" }));
}

Give agents memory over MCP

app/mcp/route.ts
import { mcpServer } from "mainsdk";
import { memory } from "mainsdk/experimental/memory";

export const POST = mcpServer({ actions: [...memory.mcp()] });   // remember (write), recall (read), forget (destructive)

Use Claude's memory tool

lib/claude-notes.ts
import { ai } from "mainsdk";
import { memory } from "mainsdk/experimental/memory";

declare const user: string;

const out = await ai.run({
  model: "anthropic/claude-sonnet-4-5",
  goal: "Plan my week from my notes",
  tools: [memory.anthropicTool()],   // memory_20250818 commands on note memories under /memories/
  feature: "planner",
  agent: "planner-bot",
  user,
});

Learn from a conversation (0.5)

jobs/after-chat.ts
import { memory } from "mainsdk/experimental/memory";

declare const messages: { role: "user" | "assistant"; content: string }[];
declare const userId: string;

const { changes } = await memory.learn(messages, { maxCost: "$0.05" });   // ADD, REVISE, INVALIDATE or NOOP
const { runId } = await memory.consolidate({ about: userId, reflect: true });   // a workflow, never in the request

Reference for 0.5 calls

These calls are specified in spec memory-advanced.

CallWhat it does
memory.relate({ from, predicate, to })Links two memories with a lowercase snake_case predicate such as works_at, so recall can follow hops of 0, 1 or 2. A driver without the graph capability fails with MAIN-MEM-0012.
memory.export({ about?, kinds? })Streams the scope's memories as JSONL (mainsdk-memory/1), with every version, pin and relation.
memory.import(lines, { about?, session? })Reads that JSONL back. Imported memories start untrusted and quarantined to the import session. Into another app, only procedure memories labelled none import (MAIN-MEM-0011).
memory.mcp({ compat? })Returns the MCP actions: remember, recall and forget. With compat: "kg" (0.5) it serves the knowledge-graph server's tool names instead.

Guarantees

  • Memories never cross tenants or apps. An agent with read: own sees only what it wrote.
  • Recalled text is untrusted data, never system text; check reports MAIN-MEM-0022 when it is passed as system.
  • The injection check runs on every write (MAIN-MEM-0003) and every recall (MAIN-MEM-0004).
  • remember, recall and forget never call a generative model.
  • Every write, recall and forget is audited with ids only, never the text.
  • privacy.export() and privacy.erase() include memory.

Errors and checks

CodeWhen
MAIN-MEM-0001No about, and no person in context
MAIN-MEM-0002No memory with that id in this tenant, app and scope
MAIN-MEM-0005The agent has no memory grant for this verb
MAIN-MEM-0013The store is down; memory.context() returns an empty block with degraded: true instead

The check codes MAIN-MEM-0020 to MAIN-MEM-0025 and the rest are in check codes.

Swapping drivers

main.yaml
memory:
  driver: mem0   # was db

Your calls stay the same. A provider that keeps readable text stores it as the provider's own, so the SDK does not seal it; a driver without graph fails relate and hops with MAIN-MEM-0012. See Swap a driver.

Testing

memory runs on the local driver in tests, and the fake clock expires a memory without waiting. runMemoryConformance checks a driver and fakeProvider stands in for a provider, both from mainsdk/testing. See the testing kit.

Next

Was this page helpful?

↑↓ moveEnter openEsc close

Not decided yet