Skip to content

Repository files navigation

@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)
  • 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

Requirements

  • SurrealDB v3
  • Bun >= 1.0 or Node >= 22
  • @mastra/core >= 1.31.0

Installation

The 1.0 line is currently in beta and is published under the beta dist-tag:

bun add @surrealdb/mastra-ai@beta

Start SurrealDB

surreal start --user root --pass root memory

Quick 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();

Choosing a memory setup

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's Memory class; it does not apply to AgentMemory.
  • 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.

Observational Memory & memory extractors

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' },
  }),
});

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
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 start

Vector 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::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 (Mastra × Agent Memory)

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 zod

AgentMemory provider

AgentMemory 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
});

Agent Memory tools

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.

Documents / RAG

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 });

Limitations

  • 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 a vectorSearchString is supplied (this is decoupled from Mastra's vector-based semanticRecall, since Agent Memory embeds server-side). For agent-driven recall, prefer the agentMemoryRecall tool.
  • Isolation is soft under a shared API key. resourceId maps to Agent Memory scopes/labels, not a hard tenant boundary; use client.onBehalfOf(principal) for stronger isolation. One client is pinned to one Agent Memory context.

See examples/agent-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
txBatch(statements, bindings?) Run statements in a single BEGIN/COMMIT TRANSACTION request (SurrealDB v3 has no cross-request transactions)

License

Apache-2.0

About

Mastra AI integration for SurrealDB & Agent Memory

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Used by

Contributors

Languages