SurrealDB storage adapter for Mastra AI. Covers conversation memory, workflow snapshots, scoring, observability, and native vector search.
- Conversation memory (threads, messages, working memory)
- Observational Memory —
@mastra/memory's observer/reflector compression and memory extractors, backed by SurrealDB - Workflow suspend/resume with atomic snapshot storage
- Scores and observability spans
- HNSW vector indexes for RAG without a separate vector database
- SurrealDB v3
- Bun >= 1.0 or Node >= 22
@mastra/core>= 1.31.0
The 1.0 line is currently in beta and is published under the beta dist-tag:
bun add @surrealdb/mastra-ai@betasurreal start --user root --pass root memoryimport { Mastra } from '@mastra/core/mastra';
import { Agent } from '@mastra/core/agent';
import { anthropic } from '@ai-sdk/anthropic';
import { SurrealDBStore } from '@surrealdb/mastra-ai';
const store = new SurrealDBStore({
id: 'my-store',
url: 'ws://localhost:8000',
username: 'root',
password: 'root',
namespace: 'mastra',
database: 'my_app',
});
const agent = new Agent({
name: 'assistant',
instructions: 'You are a helpful assistant.',
model: anthropic('claude-sonnet-4-6'),
});
const mastra = new Mastra({
agents: { assistant: agent },
storage: store,
});
await store.init();
const response = await mastra.getAgent('assistant').generate('Hello!', {
resourceId: 'user-001',
threadId: 'thread-001',
});
console.log(response.text);
await store.close();SurrealDBStore accepts three config shapes.
Username + password:
new SurrealDBStore({
id: 'my-store',
url: 'ws://localhost:8000',
username: 'root',
password: 'root',
namespace: 'mastra', // optional, defaults to 'mastra'
database: 'my_app', // optional, defaults to 'mastra'
});Token auth:
new SurrealDBStore({
id: 'my-store',
url: 'wss://cloud.surrealdb.com',
token: 'your-jwt-token',
namespace: 'mastra',
database: 'my_app',
});Pre-connected instance:
import { Surreal } from 'surrealdb';
const db = new Surreal();
await db.connect('ws://localhost:8000', { /* ... */ });
new SurrealDBStore({ id: 'my-store', db });import { Mastra } from '@mastra/core/mastra';
import { createWorkflow, createStep } from '@mastra/core/workflows';
import { SurrealDBStore } from '@surrealdb/mastra-ai';
import { z } from 'zod';
const store = new SurrealDBStore({ id: 'store', url: 'ws://localhost:8000', username: 'root', password: 'root' });
const mastra = new Mastra({ storage: store });
const approveStep = createStep({
id: 'approve',
inputSchema: z.object({ value: z.number() }),
resumeSchema: z.object({ approved: z.boolean() }),
outputSchema: z.object({ approved: z.boolean() }),
execute: async ({ inputData, resumeData, suspend }) => {
if (!resumeData) {
await suspend({});
}
return { approved: resumeData!.approved };
},
});
const workflow = createWorkflow({
id: 'approval',
mastra,
inputSchema: z.object({ value: z.number() }),
outputSchema: z.object({ approved: z.boolean() }),
steps: [approveStep],
}).then(approveStep).commit();
await store.init();
const run = workflow.createRun();
await run.start({ inputData: { value: 42 } });
await run.resume({
step: approveStep,
resumeData: { approved: true },
});
await store.close();This package supports three memory configurations. They solve different problems: Observational Memory manages the context window (compressing the active thread so long sessions don't blow the token budget), while Agent Memory is a context layer (durable cross-session facts, semantic recall, profile, and documents, served from the cloud).
| Setup | Use when | Context-window compression | Long-term cross-session memory |
|---|---|---|---|
Memory (@mastra/memory) + SurrealDBStore |
You want Mastra's native memory stack on your own SurrealDB — threads, working memory, Observational Memory, extractors | ✅ Observational Memory | Per-resource working memory; OM resource scope (experimental) |
AgentMemory |
You want hosted fact extraction, semantic recall, and profile with zero memory infrastructure to run | ❌ (verbatim history) | ✅ Agent Memory |
Memory + SurrealDBStore + agentMemoryExtractedSink |
You want both: OM keeps the active thread lean, and extracted facts land in your Agent Memory context layer | ✅ Observational Memory | ✅ Agent Memory (via the sink) |
Notes:
- Observational Memory is a feature of
@mastra/memory'sMemoryclass; it does not apply toAgentMemory. - OM extractors run on the observer's existing LLM pass (no extra model call); Agent Memory extraction runs server-side and doesn't spend your app's tokens. The sink bridges the first into the second.
SurrealDB is a supported storage backend for Mastra's Observational Memory — the observer/reflector system that compresses long message histories into observations — including memory extractors, which pull structured facts out of conversations during observation cycles. Extracted values persist automatically through the same SurrealDB tables.
Requires @mastra/memory >= 1.1.0 (>= 1.22.0 for extractors) in your app:
import { Extractor, Memory } from '@mastra/memory';
import { SurrealDBStore } from '@surrealdb/mastra-ai';
import { z } from 'zod';
const store = new SurrealDBStore({
id: 'om-store',
url: 'ws://localhost:8000',
username: 'root',
password: 'root',
});
await store.init();
const memory = new Memory({
storage: store,
options: {
observationalMemory: {
model: 'anthropic/claude-haiku-4-5',
observation: {
extract: [
new Extractor({
name: 'User profile',
instructions: 'Extract stable user profile facts.',
schema: z.object({
preferredName: z.string().optional(),
timezone: z.string().optional(),
}),
}),
],
},
},
},
});Bridging extractors to Agent Memory. Agent Memory already performs server-side
fact extraction (remember(..., { infer })); OM extractors are the
client-side alternative. If you use both, agentMemoryExtractedSink pipes each
extracted value into Agent Memory as an onExtracted hook. Values are stored as
literal facts (infer: 'none') — extraction already happened client-side, so
Agent Memory doesn't run inference over them again (pass
remember: { infer: 'full' } to re-infer anyway). Failures are swallowed so a
Agent Memory outage never breaks the observation cycle:
import { AgentMemory, agentMemoryExtractedSink } from '@surrealdb/mastra-ai/agent-memory';
const agentMemory = new AgentMemory({ endpoint, context, apiKey });
new Extractor({
name: 'User profile',
instructions: 'Extract stable user profile facts.',
onExtracted: agentMemoryExtractedSink(agentMemory, {
remember: { memoryCategory: 'profile' },
}),
});| Example | Description |
|---|---|
| basic-agent | Multi-turn agent conversation with SurrealDB memory |
| workflow-persistence | Suspend/resume workflow with snapshot storage |
| rag-pipeline | Vector similarity search with SurrealDB HNSW indexes |
| agent-memory | Agent memory + tools + RAG backed by the Agent Memory platform |
| observational-memory | Observational Memory + extractors on SurrealDB, bridged to Agent Memory |
To run an example:
cd examples/basic-agent
bun install
ANTHROPIC_API_KEY=your-key bun startSurrealDB v3 includes native HNSW vector indexes. You can use SurrealDBClient directly for RAG:
import { SurrealDBClient } from '@surrealdb/mastra-ai';
const SCHEMA = `
DEFINE TABLE IF NOT EXISTS documents SCHEMAFULL;
DEFINE FIELD IF NOT EXISTS content ON documents TYPE string;
DEFINE FIELD IF NOT EXISTS embedding ON documents TYPE array<float>;
DEFINE INDEX IF NOT EXISTS idx_hnsw
ON documents FIELDS embedding HNSW DIMENSION 1536 DIST COSINE;
`;
const client = new SurrealDBClient({ id: 'rag', url: 'ws://localhost:8000', username: 'root', password: 'root' });
await client.connect();
await client.execute(SCHEMA);
await client.execute(
`UPSERT type::record('documents', $id) CONTENT $data`,
{ id: 'doc-1', data: { content: 'SurrealDB supports vector search.', embedding: [] } },
);
const results = await client.queryAll(
`SELECT content, vector::distance::cosine(embedding, $qe) AS dist
FROM documents WHERE embedding <|5|> $qe ORDER BY dist ASC`,
{ qe: [] },
);See examples/rag-pipeline for a full working example.
Agent Memory is SurrealDB's hosted memory
platform — it extracts facts, recalls them semantically, and manages documents.
It's a separate, standalone integration from the SurrealDB storage adapter
above: Agent Memory is a hosted service (reached over REST with an API key), not a
database you run. This package exposes it through the @surrealdb/mastra-ai/agent-memory
subpath as a Mastra memory provider, a set of agent tools, and RAG helpers.
Install zod alongside this package (the Agent Memory client ships with it):
bun add zodAgentMemory works standalone — no database required. Verbatim message
history is kept in-process while Agent Memory handles fact extraction, semantic
recall, and profile. Every Agent Memory call is guarded, so a service outage
degrades gracefully to verbatim-only behaviour and never breaks the agent loop.
import { Agent } from '@mastra/core/agent';
import { anthropic } from '@ai-sdk/anthropic';
import { AgentMemory } from '@surrealdb/mastra-ai/agent-memory';
const agent = new Agent({
name: 'assistant',
instructions: 'You are a helpful assistant with long-term memory.',
model: anthropic('claude-sonnet-4-5'),
memory: new AgentMemory({
endpoint: process.env.AGENT_MEMORY_ENDPOINT!,
context: process.env.AGENT_MEMORY_CONTEXT!,
apiKey: process.env.AGENT_MEMORY_API_KEY!,
}),
});Optional — combine with Mastra × SurrealDB. Pass a Mastra store as the durable system-of-record for verbatim threads/messages/working memory; Agent Memory then layers on as the intelligence tier. Reuse this package's own adapter:
import { SurrealDBStore } from '@surrealdb/mastra-ai';
const store = new SurrealDBStore({
id: 'agent-memory-demo',
url: 'ws://localhost:8000',
username: 'root',
password: 'root',
});
await store.init();
const memory = new AgentMemory({
endpoint: process.env.AGENT_MEMORY_ENDPOINT!,
context: process.env.AGENT_MEMORY_CONTEXT!,
apiKey: process.env.AGENT_MEMORY_API_KEY!,
storage: store, // durable verbatim history; omit to keep it in-process
});Let an agent call Agent Memory explicitly — store, recall, forget, fetch context, and search documents (RAG):
import { AgentMemory } from '@surrealdb/mastra-ai/agent-memory';
import { createAgentMemoryTools } from '@surrealdb/mastra-ai/agent-memory';
const client = new AgentMemory({
endpoint: process.env.AGENT_MEMORY_ENDPOINT!,
context: process.env.AGENT_MEMORY_CONTEXT!,
apiKey: process.env.AGENT_MEMORY_API_KEY!,
});
const agent = new Agent({
name: 'assistant',
instructions: 'Use agentMemoryRecall before answering questions about the user.',
model: anthropic('claude-sonnet-4-5'),
tools: createAgentMemoryTools(client),
});The toolset is agentMemoryRemember, agentMemoryRecall, agentMemoryForget,
agentMemoryContext, and agentMemorySearchDocuments.
import { ingestDocument, searchDocuments } from '@surrealdb/mastra-ai/agent-memory';
await ingestDocument(client, { file, title: 'Handbook' });
const results = await searchDocuments(client, { query: 'refund policy', k: 5 });- Fact cleanup is best-effort. Deleting a thread or messages removes the verbatim rows exactly, but facts Agent Memory already derived cannot be surgically removed by message id.
- Automatic recall injection needs a query.
AgentMemory.recall()only augments with Agent Memory hits when avectorSearchStringis supplied (this is decoupled from Mastra's vector-basedsemanticRecall, since Agent Memory embeds server-side). For agent-driven recall, prefer theagentMemoryRecalltool. - Isolation is soft under a shared API key.
resourceIdmaps to Agent Memory scopes/labels, not a hard tenant boundary; useclient.onBehalfOf(principal)for stronger isolation. One client is pinned to one Agent Memorycontext.
See examples/agent-memory for a full example.
| Method | Description |
|---|---|
init() |
Connect and apply all table schemas |
close() |
Disconnect |
client |
The underlying SurrealDBClient for raw queries |
stores |
Individual domain stores (memory, workflows, scores, observability) |
| Method | Description |
|---|---|
connect(config?) |
Open the WebSocket connection |
close() |
Disconnect |
queryAll<T>(surql, bindings?) |
Run a query, return all rows |
queryOne<T>(surql, bindings?) |
Run a query, return first row or null |
execute(surql, bindings?) |
Run a statement, no return value |
txBatch(statements, bindings?) |
Run statements in a single BEGIN/COMMIT TRANSACTION request (SurrealDB v3 has no cross-request transactions) |
Apache-2.0