Skip to content
Sign In

AI frameworks

Mastra

Use SurrealDB as the storage backend for Mastra agents, covering conversation memory, workflow snapshots, scores, observability spans, and native vector search.

Mastra is a TypeScript framework for building AI agents, workflows, and RAG pipelines. @surrealdb/mastra-ai provides SurrealDBStore, a storage adapter that backs Mastra with a single SurrealDB instance: conversation memory (threads, messages, and working memory), workflow suspend/resume snapshots, scores, observability spans, and HNSW vector indexes for RAG.

Because SurrealDB is multi-model, one database covers everything a Mastra application persists. Message history, workflow state, and vector embeddings live in the same ACID-compliant engine, so there is no separate vector database to deploy or keep in sync.

Note

This page covers the storage adapter, which runs against a SurrealDB instance you manage. The same package also integrates with SurrealDB Agent Memory, a hosted memory provider for fact extraction and semantic recall.

Start SurrealDB locally, either installed directly:

surreal start --user root --pass secret memory

or with Docker:

docker run --rm --pull always -p 8000:8000 surrealdb/surrealdb:latest start --user root --pass secret

Alternatively, create a managed instance on SurrealDB Cloud and connect with its wss:// URL.

bun add @surrealdb/mastra-ai

Create a store, pass it to Mastra as storage, and call store.init() to connect and apply the table schemas. The agent's conversation history then persists in SurrealDB, keyed by resourceId and threadId:

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: "secret",
    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 configuration shapes. namespace and database are optional in the first two and both default to mastra.

Username and password:

new SurrealDBStore({
    id: "my-store",
    url: "ws://localhost:8000",
    username: "root",
    password: "secret",
    namespace: "mastra",
    database: "my_app",
});

Token authentication, for example against a SurrealDB Cloud instance:

new SurrealDBStore({
    id: "my-store",
    url: "wss://cloud.surrealdb.com",
    token: "<your-jwt-token>",
    namespace: "mastra",
    database: "my_app",
});

A pre-connected Surreal instance from the JavaScript SDK, when your application already manages its own connection:

import { Surreal } from "surrealdb";

const db = new Surreal();
await db.connect("ws://localhost:8000");

new SurrealDBStore({ id: "my-store", db });

Mastra workflows can suspend mid-run and resume later, for example to wait for human approval. With SurrealDBStore as storage, each snapshot is written atomically, so a run survives a process restart and resumes from the suspended step:

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: "secret" });
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();

SurrealDBStore is a supported backend for Mastra's Observational Memory, the observer/reflector system in @mastra/memory that compresses long message histories into observations. Memory extractors also work, pulling structured facts out of conversations during observation cycles, and extracted values persist through the same SurrealDB tables. Observational Memory requires @mastra/memory 1.1.0+, and extractors require 1.22.0+:

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: "secret",
});
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(),
                        }),
                    }),
                ],
            },
        },
    },
});

SurrealDB v3 includes native HNSW vector indexes, so the same database that stores agent memory also serves RAG queries. The package exposes SurrealDBClient for raw SurrealQL, letting you define a schema, upsert documents with embeddings, and run a k-nearest-neighbour search:

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: "secret" });
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: [] },
);
MemberDescription
init()Connect and apply all table schemas
close()Disconnect
clientThe underlying SurrealDBClient for raw queries
storesIndividual domain stores (memory, workflows, scores, observability)
MethodDescription
connect(config?)Open the WebSocket connection
close()Disconnect
queryAll<T>(surql, bindings?)Run a query and return all rows
queryOne<T>(surql, bindings?)Run a query and return the first row or null
execute(surql, bindings?)Run a statement with no return value
txBatch(statements, bindings?)Run statements in a single BEGIN/COMMIT TRANSACTION request

The repository includes runnable examples for each area:

For hosted memory with fact extraction and semantic recall, see the SurrealDB Agent Memory integration for Mastra.

Was this page helpful?