@surrealdb/mastra-ai
v0.1.0
Published
SurrealDB storage adapter for Mastra AI
Readme
@surrealdb/mastra-ai
SurrealDB storage adapter for Mastra AI. Covers conversation memory, workflow snapshots, scoring, observability, and native vector search.
Features
- Conversation memory (threads, messages, working memory)
- Workflow suspend/resume with atomic snapshot storage
- Scores and observability spans
- HNSW vector indexes for RAG without a separate vector database
Requirements
- SurrealDB v3
- Bun >= 1.0 or Node >= 22
@mastra/core>= 1.31.0
Installation
bun add @surrealdb/mastra-aiStart SurrealDB
surreal start --user root --pass root memoryQuick start
import { 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();Configuration
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 });Workflow suspend/resume
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();Examples
| 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 | | spectron-memory | Agent memory + tools + RAG backed by the Spectron platform |
To run an example:
cd examples/basic-agent
bun install
ANTHROPIC_API_KEY=your-key bun startVector search
SurrealDB 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::thing('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.
Spectron memory (Mastra × Spectron)
Spectron 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: Spectron 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/spectron
subpath as a Mastra memory provider, a set of agent tools, and RAG helpers.
Install zod alongside this package (the Spectron client ships with it):
bun add zodSpectronMemory provider
SpectronMemory works standalone — no database required. Verbatim message
history is kept in-process while Spectron handles fact extraction, semantic
recall, and profile. Every Spectron 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 { SpectronMemory } from '@surrealdb/mastra-ai/spectron';
const agent = new Agent({
name: 'assistant',
instructions: 'You are a helpful assistant with long-term memory.',
model: anthropic('claude-sonnet-4-5'),
memory: new SpectronMemory({
endpoint: process.env.SPECTRON_ENDPOINT!,
context: process.env.SPECTRON_CONTEXT!,
apiKey: process.env.SPECTRON_API_KEY!,
}),
});Optional — combine with Mastra × SurrealDB. Pass a Mastra store as the durable system-of-record for verbatim threads/messages/working memory; Spectron then layers on as the intelligence tier. Reuse this package's own adapter:
import { SurrealDBStore } from '@surrealdb/mastra-ai';
const store = new SurrealDBStore({
id: 'spectron-demo',
url: 'ws://localhost:8000',
username: 'root',
password: 'root',
});
await store.init();
const memory = new SpectronMemory({
endpoint: process.env.SPECTRON_ENDPOINT!,
context: process.env.SPECTRON_CONTEXT!,
apiKey: process.env.SPECTRON_API_KEY!,
storage: store, // durable verbatim history; omit to keep it in-process
});Spectron tools
Let an agent call Spectron explicitly — store, recall, forget, fetch context, and search documents (RAG):
import { Spectron } from '@surrealdb/mastra-ai/spectron';
import { createSpectronTools } from '@surrealdb/mastra-ai/spectron';
const client = new Spectron({
endpoint: process.env.SPECTRON_ENDPOINT!,
context: process.env.SPECTRON_CONTEXT!,
apiKey: process.env.SPECTRON_API_KEY!,
});
const agent = new Agent({
name: 'assistant',
instructions: 'Use spectronRecall before answering questions about the user.',
model: anthropic('claude-sonnet-4-5'),
tools: createSpectronTools(client),
});The toolset is spectronRemember, spectronRecall, spectronForget,
spectronContext, and spectronSearchDocuments.
Documents / RAG
import { ingestDocument, searchDocuments } from '@surrealdb/mastra-ai/spectron';
await ingestDocument(client, { file, title: 'Handbook' });
const results = await searchDocuments(client, { query: 'refund policy', k: 5 });Limitations
- Fact cleanup is best-effort. Deleting a thread or messages removes the verbatim rows exactly, but facts Spectron already derived cannot be surgically removed by message id.
- Automatic recall injection needs a query.
SpectronMemory.recall()only augments with Spectron hits when avectorSearchStringis supplied (this is decoupled from Mastra's vector-basedsemanticRecall, since Spectron embeds server-side). For agent-driven recall, prefer thespectronRecalltool. - Isolation is soft under a shared API key.
resourceIdmaps to Spectron scopes/labels, not a hard tenant boundary; useclient.onBehalfOf(principal)for stronger isolation. One client is pinned to one Spectroncontext.
See examples/spectron-memory for a full example.
API
SurrealDBStore
| 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) |
SurrealDBClient
| 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 |
| tx<T>(fn) | Run fn inside BEGIN/COMMIT TRANSACTION, cancels on error |
License
Apache-2.0
