
Mastra Hono
- 4 installs
- Updated January 23, 2026
- jwynia/teach
Helps with ai & agent building tasks.
About
mastra-hono is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- mastra-hono
- AI & Agent Building
- AI-coding skill
Mastra Hono by the numbers
- 4 all-time installs (skills.sh)
- +1 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #13,348 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/jwynia/teach --skill mastra-honoAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 4 |
|---|---|
| Last updated | January 23, 2026 |
| Repository | jwynia/teach ↗ |
What it does
Helps with ai & agent building tasks.
Files
Mastra + Hono Development
Build production-ready AI agents, tools, and workflows using Mastra v1 Beta with Hono API servers. This skill covers the complete stack from agent definition to deployment.
Target version: Mastra v1 Beta (stable release expected January 2026)
When to Use This Skill
Use when:
- Creating Mastra agents with tools and memory
- Defining tools with Zod input/output schemas
- Building workflows with multi-step data flow
- Setting up Hono API servers with Mastra adapters
- Implementing agent networks for multi-agent collaboration
- Authoring MCP servers to expose agents/tools
- Integrating RAG and conversation memory
Do NOT use when:
- Working with Mastra stable (0.24.x) - patterns differ significantly
- Building non-AI web APIs (use Hono directly)
- Simple LLM calls without agents/tools
Prerequisites
- Node.js 22.13.0+ (required for v1 Beta)
- Package manager: npm, pnpm, or bun
- API keys: OpenAI, Anthropic, or other supported providers
# Install v1 Beta packages
npm install @mastra/core@beta @mastra/hono@beta
npm install @ai-sdk/openai # or other provider
npm install zod hono @hono/node-serverQuick Start
Minimal Agent + Tool + Hono Server
// src/mastra/tools/weather-tool.ts
import { createTool } from "@mastra/core/tools";
import { z } from "zod";
export const weatherTool = createTool({
id: "get-weather",
description: "Fetches current weather for a location",
inputSchema: z.object({
location: z.string().describe("City name, e.g., 'Seattle'"),
}),
outputSchema: z.object({
temperature: z.number(),
conditions: z.string(),
}),
// v1 Beta signature: (inputData, context)
execute: async (inputData, context) => {
const { location } = inputData;
const { abortSignal } = context;
if (abortSignal?.aborted) throw new Error("Aborted");
// Fetch weather data...
return { temperature: 72, conditions: "sunny" };
},
});// src/mastra/agents/weather-agent.ts
import { Agent } from "@mastra/core/agent";
import { openai } from "@ai-sdk/openai";
import { weatherTool } from "../tools/weather-tool.js";
export const weatherAgent = new Agent({
name: "weather-agent",
instructions: "You are a helpful weather assistant.",
model: openai("gpt-4o-mini"),
tools: { weatherTool }, // Object, not array
});// src/mastra/index.ts
import { Mastra } from "@mastra/core/mastra";
import { weatherAgent } from "./agents/weather-agent.js";
export const mastra = new Mastra({
agents: { weatherAgent },
});// src/index.ts
import { serve } from "@hono/node-server";
import { Hono } from "hono";
import { MastraServer } from "@mastra/hono";
import { mastra } from "./mastra/index.js";
const app = new Hono();
const server = new MastraServer({ app, mastra });
await server.init();
app.get("/", (c) => c.text("Mastra + Hono Server"));
serve({ fetch: app.fetch, port: 3000 });
console.log("Server running at http://localhost:3000");
// Agent endpoint: POST /api/agents/weather-agent/generate---
Core Patterns
Agent Definition
import { Agent } from "@mastra/core/agent";
const agent = new Agent({
name: "my-agent", // Required: unique identifier
instructions: "You are a helpful assistant.", // Required: system prompt
model: openai("gpt-4o-mini"), // Required: LLM model
tools: { weatherTool, searchTool }, // Optional: object with named tools
});
// Model routing (1113+ models from 53 providers)
model: "openai/gpt-4o-mini"
model: "anthropic/claude-3-5-sonnet"
model: "google/gemini-2.5-flash"
// Model fallbacks for resilience
model: [
{ model: "anthropic/claude-3-opus", maxRetries: 3 },
{ model: "openai/gpt-4o", maxRetries: 2 },
{ model: "google/gemini-pro", maxRetries: 1 },
]
// Agent execution with memory
const response = await agent.generate("Remember my name is Alex", {
memory: {
thread: "conversation-123", // Isolates conversation
resource: "user-456", // Associates with user
},
});Tool Signatures (v1 Beta - CRITICAL)
// v1 Beta: execute(inputData, context)
execute: async (inputData, context) => {
const { location } = inputData; // First parameter: parsed input
const { mastra, runtimeContext, abortSignal } = context; // Second: context
// Access nested agents via mastra
const helper = mastra?.getAgent("helperAgent");
// Always check abort signal for long operations
if (abortSignal?.aborted) throw new Error("Aborted");
return { temperature: 72, conditions: "sunny" };
}
// WRONG for v1 Beta:
execute: async ({ context }) => { ... } // This is stable 0.24.x signatureWorkflow Data Flow (CRITICAL)
This is where most errors occur. See references/workflow-data-flow.md for complete patterns.
import { createWorkflow, createStep } from "@mastra/core/workflows";
const step1 = createStep({
id: "step-1",
inputSchema: z.object({ message: z.string() }),
outputSchema: z.object({ formatted: z.string() }),
execute: async ({ inputData }) => {
// inputData = workflow input (for first step)
return { formatted: inputData.message.toUpperCase() };
},
});
const step2 = createStep({
id: "step-2",
inputSchema: z.object({ formatted: z.string() }), // MUST match step1 output
outputSchema: z.object({ emphasized: z.string() }),
execute: async ({ inputData }) => {
// inputData = step1's return value directly
return { emphasized: `${inputData.formatted}!!!` };
},
});
const workflow = createWorkflow({
id: "my-workflow",
inputSchema: z.object({ message: z.string() }), // MUST match step1 input
outputSchema: z.object({ emphasized: z.string() }), // MUST match final output
})
.then(step1)
.then(step2)
.commit();Schema matching rules:
| Rule | Description |
|---|---|
| Workflow input → Step 1 input | Must match exactly |
| Step N output → Step N+1 input | Must match exactly |
| Final step output → Workflow output | Must match exactly |
Data access in steps:
execute: async ({
inputData, // Previous step's output (or workflow input for step 1)
getStepResult, // Access ANY step's output by ID
getInitData, // Get original workflow input
mastra, // Access agents, tools, storage
}) => {
const step1Result = getStepResult("step-1");
const originalInput = getInitData();
return { result: inputData.formatted };
}Hono Server Setup
import { MastraServer } from "@mastra/hono";
const app = new Hono();
const server = new MastraServer({ app, mastra });
await server.init();
// Endpoints auto-registered:
// POST /api/agents/{agent-name}/generate
// POST /api/agents/{agent-name}/stream
// POST /api/workflows/{workflow-id}/startCustom route prefix:
const server = new MastraServer({
app,
mastra,
prefix: "/v1/ai" // Routes at /v1/ai/agents/...
});Custom API routes:
import { registerApiRoute } from "@mastra/core/server";
registerApiRoute("/my-custom-route", {
method: "POST",
handler: async (c) => {
const mastra = c.get("mastra");
const agent = mastra.getAgent("my-agent");
const result = await agent.generate("Hello");
return c.json({ response: result.text });
},
});---
Common Mistakes Quick Reference
| Topic | Wrong | Correct |
|---|---|---|
| Imports | import { Agent } from "@mastra/core" | import { Agent } from "@mastra/core/agent" |
| Tools array | tools: [tool1, tool2] | tools: { tool1, tool2 } |
| Memory context | { threadId: "123" } | { memory: { thread: "123", resource: "user" } } |
| Workflow data | context.steps.step1.output | inputData or getStepResult("step-1") |
| After parallel | inputData.result | inputData["step-id"].result |
| After branch | inputData.result | inputData["step-id"]?.result (optional) |
| Nested agents | import agent; agent.generate() | mastra.getAgent("name").generate() |
| State mutation | state.counter++ | setState({ ...state, counter: state.counter + 1 }) |
| v1 tool exec | execute: async ({ context }) | execute: async (inputData, context) |
---
Scripts Reference
| Script | Purpose | Usage |
|---|---|---|
scaffold-project.ts | Create new Mastra+Hono project | deno run --allow-all scripts/scaffold-project.ts --name my-project |
scaffold-agent.ts | Create agent with tools | deno run --allow-all scripts/scaffold-agent.ts --name weather |
scaffold-workflow.ts | Create workflow with steps | deno run --allow-all scripts/scaffold-workflow.ts --name process-data |
scaffold-tool.ts | Create tool with schemas | deno run --allow-all scripts/scaffold-tool.ts --name fetch-weather |
validate-workflow-schemas.ts | Validate step schema matching | deno run --allow-read scripts/validate-workflow-schemas.ts ./src/mastra/workflows/ |
check-version-patterns.ts | Detect v1/stable pattern mixing | deno run --allow-read scripts/check-version-patterns.ts ./src/mastra/ |
---
Additional Resources
Reference Files
- `references/workflow-data-flow.md` - Complete data flow patterns (CRITICAL)
- `references/common-mistakes.md` - Extended anti-patterns and fixes
- `references/agent-patterns.md` - Agent definition deep-dive
- `references/tool-patterns.md` - Tool signatures, wrappers
- `references/hono-server-patterns.md` - Server setup, routes, middleware
- `references/testing-patterns.md` - Vitest setup, mocking LLMs
- `references/mcp-server-patterns.md` - Model Context Protocol authoring
- `references/agent-networks.md` - Multi-agent collaboration, A2A
- `references/rag-memory-patterns.md` - Vector stores, embeddings, memory
- `references/context-network-memory.md` - Context networks for agent memory
Asset Templates
- `assets/agent-template.ts` - Agent boilerplate
- `assets/tool-template.ts` - Tool boilerplate (v1 signature)
- `assets/workflow-template.ts` - Workflow boilerplate
- `assets/hono-server-template.ts` - Hono+Mastra server setup
- `assets/vitest-setup-template.ts` - Test configuration
External Documentation
---
Limitations
- Targets v1 Beta only; stable (0.24.x) patterns differ significantly
- Scripts require Deno runtime with appropriate permissions
- MCP server patterns require MCP-compatible clients
- Agent networks require all participating agents to be registered in same Mastra instance
Related Skills
- research-workflow - Deep dive research for AI agent design
- web-search - Real-time information retrieval for agents
/**
* Mastra Agent Template (v1 Beta)
*
* Usage: Copy this file and customize for your agent.
*
* Replace:
* - AGENT_NAME: Your agent's unique identifier
* - AGENT_INSTRUCTIONS: System prompt for the agent
* - MODEL: The LLM model to use
* - TOOLS: Object containing tools available to this agent
*/
import { Agent } from "@mastra/core/agent";
import { openai } from "@ai-sdk/openai";
// import { myTool } from "../tools/my-tool.js";
export const AGENT_NAME = new Agent({
// Required: Unique identifier for this agent
name: "AGENT_NAME",
// Required: System prompt that defines agent behavior
instructions: `You are a helpful assistant.
Your capabilities:
- [List what this agent can do]
- [Another capability]
Guidelines:
- [How to behave]
- [Response format preferences]
When you don't know something, admit it clearly.`,
// Required: LLM model
// Options:
// - SDK: openai("gpt-4o-mini"), anthropic("claude-3-5-sonnet-20241022")
// - Router string: "openai/gpt-4o-mini", "anthropic/claude-3-5-sonnet"
// - Fallback array: [{ model: "openai/gpt-4o", maxRetries: 3 }, ...]
model: openai("gpt-4o-mini"),
// Optional: Tools available to this agent (MUST be object, not array)
tools: {
// myTool,
// anotherTool,
},
});
// Export for registration in Mastra instance
/*
// Example usage:
import { Mastra } from "@mastra/core/mastra";
export const mastra = new Mastra({
agents: { AGENT_NAME },
});
// Then call:
const response = await AGENT_NAME.generate("Hello!", {
memory: {
thread: "conversation-123",
resource: "user-456",
},
});
console.log(response.text);
*/
/**
* Mastra + Hono Server Template (v1 Beta)
*
* This template sets up a complete Hono server with Mastra integration.
*
* Features:
* - MastraServer adapter for auto-registering agent/workflow endpoints
* - Custom API routes
* - Middleware configuration
* - Health check endpoints
*
* Usage: Copy this file as your src/index.ts entry point.
*/
import { serve } from "@hono/node-server";
import { Hono } from "hono";
import { cors } from "hono/cors";
import { logger } from "hono/logger";
import { MastraServer } from "@mastra/hono";
import { RuntimeContext } from "@mastra/core";
// Import your Mastra instance
import { mastra } from "./mastra/index.js";
// ============================================================================
// Create Hono App
// ============================================================================
const app = new Hono();
// ============================================================================
// Middleware (add BEFORE MastraServer.init())
// ============================================================================
// Request logging
app.use("*", logger());
// CORS configuration
app.use(
"*",
cors({
origin: ["http://localhost:3000"], // Add your frontend origins
allowMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
allowHeaders: ["Content-Type", "Authorization"],
credentials: true,
})
);
// Optional: Authentication middleware
// app.use("/api/*", bearerAuth({ token: process.env.API_TOKEN! }));
// ============================================================================
// Initialize Mastra Server
// ============================================================================
const server = new MastraServer({
app,
mastra,
// Optional: Custom route prefix (default: /api)
// prefix: "/v1/ai",
});
await server.init();
// ============================================================================
// Custom Routes
// ============================================================================
// Health check
app.get("/health", (c) => {
return c.json({
status: "healthy",
timestamp: new Date().toISOString(),
version: process.env.npm_package_version || "1.0.0",
});
});
// Readiness check
app.get("/ready", (c) => {
return c.json({ ready: true });
});
// Custom agent endpoint with context
app.post("/chat", async (c) => {
const body = await c.req.json();
const { message, userId, sessionId } = body;
// Get Mastra from context
const mastraInstance = c.get("mastra");
// Create runtime context
const runtimeContext = new RuntimeContext();
runtimeContext.set("user-id", userId);
runtimeContext.set("session-id", sessionId);
// Get agent and generate response
const agent = mastraInstance.getAgent("chat-agent");
if (!agent) {
return c.json({ error: "Agent not found" }, 404);
}
const response = await agent.generate(message, {
runtimeContext,
memory: {
thread: sessionId,
resource: userId,
},
});
return c.json({
response: response.text,
traceId: response.traceId,
});
});
// Custom workflow trigger
app.post("/process", async (c) => {
const body = await c.req.json();
const mastraInstance = c.get("mastra");
const workflow = mastraInstance.getWorkflow("data-pipeline");
if (!workflow) {
return c.json({ error: "Workflow not found" }, 404);
}
const run = workflow.createRun();
const result = await run.start({
inputData: body,
});
return c.json({
status: result.status,
output: result.output,
runId: run.id,
});
});
// ============================================================================
// Error Handling
// ============================================================================
app.onError((err, c) => {
console.error("Server error:", err);
if (err.message.includes("not found")) {
return c.json({ error: "Resource not found" }, 404);
}
if (err.message.includes("unauthorized")) {
return c.json({ error: "Unauthorized" }, 401);
}
return c.json({ error: "Internal server error" }, 500);
});
app.notFound((c) => {
return c.json({ error: "Route not found" }, 404);
});
// ============================================================================
// Start Server
// ============================================================================
const port = parseInt(process.env.PORT || "3000");
serve({
fetch: app.fetch,
port,
});
console.log(`
🚀 Mastra + Hono Server running at http://localhost:${port}
Endpoints:
GET /health - Health check
GET /ready - Readiness check
POST /chat - Custom chat endpoint
POST /process - Custom workflow trigger
POST /api/agents/{name}/generate - Agent generate
POST /api/agents/{name}/stream - Agent stream
POST /api/workflows/{id}/start - Start workflow
`);
// ============================================================================
// Mastra Instance Template (src/mastra/index.ts)
// ============================================================================
/*
import { Mastra } from "@mastra/core/mastra";
import { LibSQLStore } from "@mastra/libsql";
// Import agents
import { chatAgent } from "./agents/chat-agent.js";
import { assistantAgent } from "./agents/assistant-agent.js";
// Import workflows
import { dataPipeline } from "./workflows/data-pipeline.js";
export const mastra = new Mastra({
agents: {
"chat-agent": chatAgent,
"assistant-agent": assistantAgent,
},
workflows: {
"data-pipeline": dataPipeline,
},
storage: new LibSQLStore({
url: process.env.DATABASE_URL || "file:./mastra.db",
}),
server: {
port: 3000,
timeout: 30000,
},
observability: {
default: { enabled: true },
},
});
*/
/**
* Mastra Tool Template (v1 Beta)
*
* IMPORTANT: v1 Beta uses a different execute signature than stable!
*
* v1 Beta: execute: async (inputData, context) => { ... }
* Stable: execute: async ({ context, mastra }) => { ... }
*
* Usage: Copy this file and customize for your tool.
*
* Replace:
* - TOOL_ID: Unique identifier for this tool
* - TOOL_DESCRIPTION: What this tool does (helps LLM decide when to use it)
* - Input/Output schemas: Define with Zod
* - Execute logic: Implement your tool's functionality
*/
import { createTool } from "@mastra/core/tools";
import { z } from "zod";
export const TOOL_ID = createTool({
// Required: Unique identifier
id: "TOOL_ID",
// Required: Description helps LLM understand when to use this tool
// Be specific! Include:
// - What the tool does
// - When to use it
// - What input it expects
description:
"TOOL_DESCRIPTION. Use when the user asks to [scenario]. " +
"Input: [expected input format].",
// Required: Zod schema for input validation
// Always add .describe() to help LLM understand each field
inputSchema: z.object({
// Example fields - replace with your schema
query: z.string().describe("The search query or input text"),
limit: z
.number()
.optional()
.default(10)
.describe("Maximum number of results (1-100)"),
}),
// Required: Zod schema for output validation
// Always define this to prevent validation issues
outputSchema: z.object({
// Example fields - replace with your schema
results: z.array(z.string()),
count: z.number(),
success: z.boolean(),
}),
// Required: Execute function
// v1 Beta signature: (inputData, context) => Promise<Output>
execute: async (inputData, context) => {
// Destructure parsed input from first parameter
const { query, limit } = inputData;
// Destructure context from second parameter
const {
mastra, // Access to Mastra instance (agents, workflows, tools)
runtimeContext, // Request-specific values
abortSignal, // Abort controller signal
} = context;
// Always check abort signal for long operations
if (abortSignal?.aborted) {
throw new Error("Operation aborted");
}
// Access runtime context values if needed
// const userId = runtimeContext.get("user-id");
// const tier = runtimeContext.get("user-tier");
// Access other agents if needed (for agent-as-tool pattern)
// const helper = mastra?.getAgent("helper-agent");
// const helperResult = await helper?.generate("...", { runtimeContext });
// Your tool logic here
// ...
// Return must match outputSchema
return {
results: ["result1", "result2"],
count: 2,
success: true,
};
},
});
/*
// Example: API Integration Tool
export const apiTool = createTool({
id: "fetch-data",
description: "Fetches data from external API",
inputSchema: z.object({
endpoint: z.string().describe("API endpoint path"),
}),
outputSchema: z.object({
data: z.any(),
status: z.number(),
}),
execute: async (inputData, context) => {
const { endpoint } = inputData;
const { abortSignal, runtimeContext } = context;
const apiKey = runtimeContext.get("api-key");
const response = await fetch(`https://api.example.com${endpoint}`, {
headers: { Authorization: `Bearer ${apiKey}` },
signal: abortSignal,
});
return {
data: await response.json(),
status: response.status,
};
},
});
*/
/*
// Example: Agent-as-Tool Pattern
export const copywriterTool = createTool({
id: "copywriter",
description: "Writes content using the copywriter agent",
inputSchema: z.object({
topic: z.string(),
}),
outputSchema: z.object({
content: z.string(),
}),
execute: async (inputData, context) => {
const { topic } = inputData;
const { mastra, runtimeContext } = context;
// Get agent from Mastra (NOT direct import)
const agent = mastra?.getAgent("copywriter-agent");
if (!agent) throw new Error("Copywriter agent not found");
const result = await agent.generate(`Write about: ${topic}`, {
runtimeContext, // Propagate context
});
return { content: result.text };
},
});
*/
/**
* Vitest Configuration and Setup for Mastra Projects
*
* This template provides:
* - vitest.config.ts configuration
* - Test setup file with common mocks
* - Example test patterns
*
* Usage:
* 1. Copy vitest.config.ts to your project root
* 2. Copy test/setup.ts to your test directory
* 3. Use the example patterns for your tests
*/
// ============================================================================
// vitest.config.ts
// ============================================================================
/*
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
globals: true,
environment: "node",
setupFiles: ["./test/setup.ts"],
include: ["**\/*.{test,spec}.{ts,tsx}"],
coverage: {
provider: "v8",
reporter: ["text", "json", "html"],
exclude: [
"node_modules/",
"test/",
"**\/*.d.ts",
"**\/*.config.*",
],
},
testTimeout: 30000,
hookTimeout: 30000,
},
});
*/
// ============================================================================
// test/setup.ts
// ============================================================================
import { beforeAll, afterAll, beforeEach, afterEach, vi } from "vitest";
// Mock environment variables
beforeAll(() => {
process.env.OPENAI_API_KEY = "test-key";
process.env.DATABASE_URL = "file:./test.db";
process.env.NODE_ENV = "test";
});
// Reset mocks between tests
beforeEach(() => {
vi.clearAllMocks();
});
afterEach(() => {
vi.restoreAllMocks();
});
// Cleanup after all tests
afterAll(async () => {
// Clean up test database if needed
// await fs.unlink("./test.db").catch(() => {});
});
// ============================================================================
// Mock Helpers
// ============================================================================
/**
* Creates a mock context for tool testing
*/
export function createMockContext(overrides: Partial<ToolContext> = {}) {
const { RuntimeContext } = require("@mastra/core");
return {
mastra: undefined,
runtimeContext: new RuntimeContext(),
abortSignal: new AbortController().signal,
...overrides,
};
}
/**
* Creates a mock Mastra instance
*/
export function createMockMastra(agents: Record<string, any> = {}) {
return {
getAgent: vi.fn((name: string) => agents[name]),
getWorkflow: vi.fn(),
getTool: vi.fn(),
storage: {
getMessages: vi.fn(),
addMessage: vi.fn(),
createThread: vi.fn(),
listThreads: vi.fn(),
},
vectors: {
default: {
query: vi.fn(),
upsert: vi.fn(),
},
},
};
}
/**
* Creates a mock agent for testing
*/
export function createMockAgent(responses: Record<string, string> = {}) {
return {
generate: vi.fn(async (message: string) => ({
text: responses[message] || "Default mock response",
usage: { promptTokens: 10, completionTokens: 20 },
})),
stream: vi.fn(async () => ({
textStream: (async function* () {
yield "Mocked ";
yield "stream";
})(),
})),
};
}
// Type for context
interface ToolContext {
mastra: any;
runtimeContext: any;
abortSignal: AbortSignal;
}
// ============================================================================
// Example Test Patterns
// ============================================================================
/*
// test/tools/weather-tool.test.ts
import { describe, it, expect, vi } from "vitest";
import { weatherTool } from "../../src/mastra/tools/weather-tool";
import { createMockContext, createMockMastra } from "../setup";
describe("Weather Tool", () => {
it("should return weather data for valid location", async () => {
const result = await weatherTool.execute(
{ location: "Seattle", units: "celsius" },
createMockContext()
);
expect(result).toHaveProperty("temperature");
expect(result).toHaveProperty("conditions");
expect(typeof result.temperature).toBe("number");
});
it("should handle abort signal", async () => {
const controller = new AbortController();
controller.abort();
await expect(
weatherTool.execute(
{ location: "Seattle" },
createMockContext({ abortSignal: controller.signal })
)
).rejects.toThrow("Aborted");
});
it("should use nested agent when available", async () => {
const mockAgent = {
generate: vi.fn().mockResolvedValue({ text: "Weather analysis" }),
};
const mockMastra = createMockMastra({ "analyzer-agent": mockAgent });
const result = await weatherTool.execute(
{ location: "Seattle" },
createMockContext({ mastra: mockMastra as any })
);
expect(mockMastra.getAgent).toHaveBeenCalledWith("analyzer-agent");
});
});
*/
/*
// test/workflows/data-workflow.test.ts
import { describe, it, expect } from "vitest";
import { z } from "zod";
import { dataWorkflow } from "../../src/mastra/workflows/data-workflow";
describe("Data Workflow", () => {
describe("Schema Compatibility", () => {
it("step1 output matches step2 input", () => {
const step1Output = { processed: "HELLO", count: 5 };
const step2InputSchema = z.object({
processed: z.string(),
count: z.number(),
});
const result = step2InputSchema.safeParse(step1Output);
expect(result.success).toBe(true);
});
});
describe("Workflow Execution", () => {
it("should process data through all steps", async () => {
const run = dataWorkflow.createRun();
const result = await run.start({
inputData: { value: "hello" },
});
expect(result.status).toBe("success");
expect(result.steps["step-1"]).toBeDefined();
expect(result.steps["step-2"]).toBeDefined();
});
it("should handle errors gracefully", async () => {
const run = dataWorkflow.createRun();
const result = await run.start({
inputData: { value: "" }, // Empty string triggers error
});
expect(result.status).toBe("failed");
});
});
});
*/
/*
// test/api/routes.test.ts
import { describe, it, expect, beforeAll } from "vitest";
import { Hono } from "hono";
import { MastraServer } from "@mastra/hono";
import { mastra } from "../../src/mastra";
describe("API Routes", () => {
let app: Hono;
beforeAll(async () => {
app = new Hono();
const server = new MastraServer({ app, mastra });
await server.init();
// Add test routes
app.get("/health", (c) => c.json({ status: "healthy" }));
});
it("should respond to health check", async () => {
const res = await app.request("/health");
expect(res.status).toBe(200);
const body = await res.json();
expect(body.status).toBe("healthy");
});
it("should handle agent generate endpoint", async () => {
const res = await app.request("/api/agents/test-agent/generate", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
messages: [{ role: "user", content: "Hello" }],
}),
});
// May be 200 or 404 depending on agent registration
expect([200, 404]).toContain(res.status);
});
});
*/
// Export for use in test files
export { vi } from "vitest";
/**
* Mastra Workflow Template (v1 Beta)
*
* CRITICAL: Workflow data flow is the most error-prone area!
*
* Key rules:
* 1. inputData is ONLY the previous step's output (not a container of all steps)
* 2. Schemas MUST chain: workflow input → step1 input → step1 output → step2 input → ...
* 3. Use getStepResult("step-id") to access any step's output
* 4. After .parallel(), inputData is keyed by step ID
* 5. After .branch(), use .optional() for branch outputs
*
* Usage: Copy this file and customize for your workflow.
*/
import { createWorkflow, createStep } from "@mastra/core/workflows";
import { z } from "zod";
// ============================================================================
// Step 1: Define your steps
// ============================================================================
const step1 = createStep({
id: "step-1",
// Input schema MUST match workflow inputSchema (for first step)
// or previous step's outputSchema (for subsequent steps)
inputSchema: z.object({
value: z.string(),
}),
// Output schema MUST match next step's inputSchema
outputSchema: z.object({
processed: z.string(),
metadata: z.object({
length: z.number(),
}),
}),
execute: async ({ inputData, getStepResult, getInitData, mastra, state, setState }) => {
// inputData = workflow input (for step 1)
const { value } = inputData;
// getInitData() returns original workflow input
const originalInput = getInitData();
// getStepResult("step-id") returns any step's output
// const prevStep = getStepResult("previous-step");
// state/setState for cross-step shared state
// setState({ ...state, processedAt: new Date().toISOString() });
// mastra for accessing agents, tools, workflows
// const agent = mastra?.getAgent("helper-agent");
return {
processed: value.toUpperCase(),
metadata: { length: value.length },
};
},
});
const step2 = createStep({
id: "step-2",
// MUST match step1's outputSchema
inputSchema: z.object({
processed: z.string(),
metadata: z.object({
length: z.number(),
}),
}),
outputSchema: z.object({
result: z.string(),
stats: z.object({
original: z.number(),
final: z.number(),
}),
}),
execute: async ({ inputData }) => {
// inputData = step1's return value DIRECTLY
const { processed, metadata } = inputData;
return {
result: `${processed}!!!`,
stats: {
original: metadata.length,
final: processed.length + 3,
},
};
},
});
// ============================================================================
// Step 2: Define the workflow
// ============================================================================
export const myWorkflow = createWorkflow({
id: "my-workflow",
// MUST match first step's inputSchema
inputSchema: z.object({
value: z.string(),
}),
// MUST match final step's outputSchema
outputSchema: z.object({
result: z.string(),
stats: z.object({
original: z.number(),
final: z.number(),
}),
}),
// Optional: Retry configuration
retryConfig: {
attempts: 3,
delay: 1000,
},
})
.then(step1)
.then(step2)
.commit();
// ============================================================================
// Example: Parallel Execution
// ============================================================================
/*
const formatStep = createStep({
id: "format-step",
inputSchema: z.object({ text: z.string() }),
outputSchema: z.object({ formatted: z.string() }),
execute: async ({ inputData }) => ({
formatted: inputData.text.toUpperCase(),
}),
});
const countStep = createStep({
id: "count-step",
inputSchema: z.object({ text: z.string() }),
outputSchema: z.object({ count: z.number() }),
execute: async ({ inputData }) => ({
count: inputData.text.length,
}),
});
// Step after parallel MUST expect keyed structure
const combineStep = createStep({
id: "combine-step",
inputSchema: z.object({
"format-step": z.object({ formatted: z.string() }),
"count-step": z.object({ count: z.number() }),
}),
outputSchema: z.object({ result: z.string() }),
execute: async ({ inputData }) => {
// Access by step ID
const formatted = inputData["format-step"].formatted;
const count = inputData["count-step"].count;
return { result: `${formatted} (${count} chars)` };
},
});
const parallelWorkflow = createWorkflow({
id: "parallel-workflow",
inputSchema: z.object({ text: z.string() }),
outputSchema: z.object({ result: z.string() }),
})
.parallel([formatStep, countStep])
.then(combineStep)
.commit();
*/
// ============================================================================
// Example: Conditional Branching
// ============================================================================
/*
const highValueStep = createStep({
id: "high-value-step",
outputSchema: z.object({ result: z.string() }),
execute: async () => ({ result: "Premium processing" }),
});
const lowValueStep = createStep({
id: "low-value-step",
outputSchema: z.object({ result: z.string() }),
execute: async () => ({ result: "Basic processing" }),
});
// Step after branch uses .optional()
const afterBranchStep = createStep({
id: "after-branch",
inputSchema: z.object({
"high-value-step": z.object({ result: z.string() }).optional(),
"low-value-step": z.object({ result: z.string() }).optional(),
}),
outputSchema: z.object({ message: z.string() }),
execute: async ({ inputData }) => {
const result =
inputData["high-value-step"]?.result ||
inputData["low-value-step"]?.result ||
"No branch executed";
return { message: result };
},
});
const branchWorkflow = createWorkflow({
id: "branch-workflow",
inputSchema: z.object({ value: z.number() }),
outputSchema: z.object({ message: z.string() }),
})
.branch([
[async ({ inputData }) => inputData.value > 1000, highValueStep],
[async ({ inputData }) => inputData.value <= 1000, lowValueStep],
])
.then(afterBranchStep)
.commit();
*/
// ============================================================================
// Example: Using .map() for Schema Transformation
// ============================================================================
/*
const transformWorkflow = createWorkflow({
id: "transform-workflow",
inputSchema: z.object({ text: z.string() }),
outputSchema: z.object({ result: z.string() }),
})
.then(step1) // outputs: { processed: string, metadata: {...} }
.map(async ({ inputData }) => {
// Transform to match next step's expected input
return { value: inputData.processed };
})
.then(anotherStep) // expects: { value: string }
.commit();
*/
// ============================================================================
// Usage
// ============================================================================
/*
import { Mastra } from "@mastra/core/mastra";
const mastra = new Mastra({
workflows: { myWorkflow },
});
// Run the workflow
const run = myWorkflow.createRun();
const result = await run.start({
inputData: { value: "hello world" },
});
console.log(result.status); // "success" | "failed"
console.log(result.steps); // Step results by ID
console.log(result.output); // Final output
*/
Agent Networks
Guide to multi-agent collaboration, A2A protocol, and agent orchestration patterns.
Overview
Agent networks enable multiple specialized agents to collaborate on complex tasks. Mastra supports:
- AgentNetwork class - Coordinates multiple agents
- A2A Protocol - Agent-to-Agent communication (based on Google's A2A standard)
- Supervisor patterns - Hierarchical agent organization
- Tool-based delegation - Agents calling other agents as tools
Basic Agent Network
Creating an Agent Network
import { AgentNetwork } from "@mastra/core/agent";
import { researcherAgent, writerAgent, editorAgent } from "./agents";
const contentTeam = new AgentNetwork({
name: "content-team",
description: "A team that researches, writes, and edits content",
agents: [researcherAgent, writerAgent, editorAgent],
});Using the Network
const result = await contentTeam.generate(
"Create a blog post about AI trends in 2025"
);
console.log(result.text);
console.log(result.agentPath); // Which agents were involvedRouting Strategies
Automatic Routing (Default)
The network automatically routes to the most appropriate agent based on the request.
const network = new AgentNetwork({
name: "auto-network",
agents: [weatherAgent, calculatorAgent, searchAgent],
// Routing happens automatically based on agent instructions/descriptions
});
// Routes to weather agent
await network.generate("What's the weather in Tokyo?");
// Routes to calculator agent
await network.generate("What is 15% of 250?");Custom Router Function
const network = new AgentNetwork({
name: "custom-routed",
agents: [researcherAgent, writerAgent, editorAgent],
router: async ({ message, context }) => {
const keywords = message.toLowerCase();
if (keywords.includes("research") || keywords.includes("find")) {
return "researcher-agent";
}
if (keywords.includes("write") || keywords.includes("create")) {
return "writer-agent";
}
if (keywords.includes("edit") || keywords.includes("review")) {
return "editor-agent";
}
// Default to researcher for ambiguous requests
return "researcher-agent";
},
});LLM-Based Routing
import { openai } from "@ai-sdk/openai";
const network = new AgentNetwork({
name: "llm-routed",
agents: [agentA, agentB, agentC],
router: async ({ message }) => {
// Use LLM to decide routing
const routerAgent = new Agent({
name: "router",
model: openai("gpt-4o-mini"),
instructions: `You are a router. Given a message, respond with ONLY the name of the best agent to handle it.
Available agents:
- agent-a: Handles data analysis
- agent-b: Handles content creation
- agent-c: Handles customer support`,
});
const response = await routerAgent.generate(message);
return response.text.trim();
},
});Supervisor Pattern
Hierarchical Agent Structure
// Create specialized worker agents
const dataAnalyst = new Agent({
name: "data-analyst",
instructions: "You analyze data and provide insights.",
model: openai("gpt-4o-mini"),
tools: { dataQueryTool, chartTool },
});
const reportWriter = new Agent({
name: "report-writer",
instructions: "You write clear, professional reports.",
model: openai("gpt-4o-mini"),
tools: { formatTool },
});
// Create supervisor agent with delegation tools
const supervisor = new Agent({
name: "supervisor",
instructions: `You are a project supervisor. You coordinate work between specialists.
Available team members:
- data-analyst: For data queries and analysis
- report-writer: For writing and formatting reports
Delegate tasks appropriately and synthesize results.`,
model: openai("gpt-4o"),
tools: {
delegateToAnalyst: createTool({
id: "delegate-analyst",
description: "Delegate a data analysis task to the data analyst",
inputSchema: z.object({
task: z.string().describe("The analysis task to perform"),
}),
execute: async (input, context) => {
const analyst = context.mastra?.getAgent("data-analyst");
const result = await analyst?.generate(input.task);
return { analysis: result?.text };
},
}),
delegateToWriter: createTool({
id: "delegate-writer",
description: "Delegate a writing task to the report writer",
inputSchema: z.object({
task: z.string().describe("The writing task"),
data: z.string().optional().describe("Data to include"),
}),
execute: async (input, context) => {
const writer = context.mastra?.getAgent("report-writer");
const prompt = input.data
? `${input.task}\n\nData to use:\n${input.data}`
: input.task;
const result = await writer?.generate(prompt);
return { report: result?.text };
},
}),
},
});Using the Supervisor
// Register all agents
const mastra = new Mastra({
agents: {
supervisor,
"data-analyst": dataAnalyst,
"report-writer": reportWriter,
},
});
// Supervisor coordinates the work
const result = await supervisor.generate(
"Analyze our Q4 sales data and create a summary report"
);A2A Protocol
Direct Agent Communication
// Agent with A2A capabilities
const coordinatorAgent = new Agent({
name: "coordinator",
instructions: "You coordinate complex tasks across multiple agents.",
model: openai("gpt-4o"),
tools: {
sendToAgent: createTool({
id: "send-to-agent",
description: "Send a message to another agent and get a response",
inputSchema: z.object({
agentName: z.string().describe("Name of the target agent"),
message: z.string().describe("Message to send"),
context: z.any().optional().describe("Additional context"),
}),
outputSchema: z.object({
response: z.string(),
success: z.boolean(),
}),
execute: async (input, context) => {
const { agentName, message } = input;
const { mastra, runtimeContext } = context;
const targetAgent = mastra?.getAgent(agentName);
if (!targetAgent) {
return { response: `Agent ${agentName} not found`, success: false };
}
try {
const result = await targetAgent.generate(message, { runtimeContext });
return { response: result.text, success: true };
} catch (error) {
return { response: error.message, success: false };
}
},
}),
},
});Message Passing Patterns
// Define message types
interface AgentMessage {
from: string;
to: string;
type: "request" | "response" | "notification";
content: string;
metadata?: Record<string, any>;
}
// Message broker tool
const messageBroker = createTool({
id: "message-broker",
description: "Routes messages between agents",
inputSchema: z.object({
to: z.string(),
type: z.enum(["request", "response", "notification"]),
content: z.string(),
}),
execute: async (input, context) => {
const { to, type, content } = input;
const { mastra, runtimeContext } = context;
// Log the message
console.log(`[A2A] ${runtimeContext.get("current-agent")} -> ${to}: ${type}`);
if (type === "notification") {
// Fire and forget for notifications
const agent = mastra?.getAgent(to);
agent?.generate(content, { runtimeContext }).catch(console.error);
return { sent: true };
}
// For requests, wait for response
const agent = mastra?.getAgent(to);
const response = await agent?.generate(content, { runtimeContext });
return { response: response?.text };
},
});Parallel Agent Execution
Running Agents in Parallel
const parallelNetwork = new AgentNetwork({
name: "parallel-workers",
agents: [researchAgent, factCheckAgent, summaryAgent],
mode: "parallel", // All agents process simultaneously
});
// All agents receive the same input and process in parallel
const result = await parallelNetwork.generate("Analyze this article about AI");
// Results from all agents are combined
console.log(result.responses); // { researcher: "...", factChecker: "...", summarizer: "..." }Fan-Out/Fan-In Pattern
const fanOutInNetwork = new AgentNetwork({
name: "fan-out-in",
agents: [analyst1, analyst2, analyst3],
aggregator: async (responses) => {
// Combine all responses into final output
const combined = responses.map(r => r.text).join("\n\n");
// Optionally use another agent to synthesize
const synthesizer = new Agent({
name: "synthesizer",
instructions: "Combine and synthesize multiple analyses.",
model: openai("gpt-4o-mini"),
});
const final = await synthesizer.generate(
`Synthesize these analyses:\n\n${combined}`
);
return final.text;
},
});Pipeline Pattern
Sequential Agent Processing
const pipeline = new AgentNetwork({
name: "content-pipeline",
agents: [researcherAgent, writerAgent, editorAgent],
mode: "sequential", // Each agent passes output to the next
});
// Each agent's output becomes the next agent's input
const result = await pipeline.generate("Create an article about quantum computing");
// researcher -> writer -> editorCustom Pipeline Logic
const customPipeline = {
async run(input: string) {
// Step 1: Research
const research = await researcherAgent.generate(
`Research the following topic: ${input}`
);
// Step 2: Write draft based on research
const draft = await writerAgent.generate(
`Write an article based on this research:\n\n${research.text}`
);
// Step 3: Edit and polish
const final = await editorAgent.generate(
`Edit and improve this draft:\n\n${draft.text}`
);
return {
research: research.text,
draft: draft.text,
final: final.text,
};
},
};Consensus Pattern
Multiple Agents Voting
const consensusNetwork = {
agents: [expert1, expert2, expert3],
async decide(question: string) {
// Get all expert opinions
const opinions = await Promise.all(
this.agents.map(agent =>
agent.generate(question, {
output: z.object({
answer: z.string(),
confidence: z.number().min(0).max(1),
reasoning: z.string(),
}),
})
)
);
// Find consensus
const answers = opinions.map(o => o.object);
const grouped = groupBy(answers, a => a.answer);
// Return answer with highest combined confidence
const winner = Object.entries(grouped)
.map(([answer, votes]) => ({
answer,
totalConfidence: votes.reduce((sum, v) => sum + v.confidence, 0),
count: votes.length,
}))
.sort((a, b) => b.totalConfidence - a.totalConfidence)[0];
return {
answer: winner.answer,
confidence: winner.totalConfidence / this.agents.length,
votesFor: winner.count,
totalExperts: this.agents.length,
};
},
};Registering Networks
const mastra = new Mastra({
agents: {
supervisor,
researcher: researcherAgent,
writer: writerAgent,
editor: editorAgent,
},
networks: {
contentTeam,
analysisTeam,
},
});
// Access network
const network = mastra.getNetwork("content-team");
const result = await network?.generate("Create a report");Best Practices
1. Clear Agent Responsibilities
// Good: Specific, focused agents
const dataAgent = new Agent({
name: "data-analyst",
instructions: "You ONLY analyze data. Do not write reports or make recommendations.",
});
// Bad: Overlapping responsibilities
const vagueAgent = new Agent({
name: "helper",
instructions: "You help with various tasks.",
});2. Proper Error Handling
const resilientNetwork = new AgentNetwork({
name: "resilient",
agents: [primaryAgent, backupAgent],
onError: async (error, agentName, context) => {
console.error(`Agent ${agentName} failed:`, error);
// Fallback to backup agent
if (agentName !== "backup-agent") {
const backup = context.mastra?.getAgent("backup-agent");
return backup?.generate(context.originalMessage);
}
throw error;
},
});3. Context Propagation
// Always propagate context through the network
const result = await network.generate(message, {
runtimeContext, // User ID, session info, etc.
memory: { thread: "conversation-123" },
});4. Logging and Tracing
const observableNetwork = new AgentNetwork({
name: "observable",
agents: [agent1, agent2],
onAgentStart: (agentName, input) => {
console.log(`[${agentName}] Starting with input:`, input.slice(0, 100));
},
onAgentComplete: (agentName, output, duration) => {
console.log(`[${agentName}] Completed in ${duration}ms`);
},
});5. Resource Management
// Limit concurrent agent executions
const managedNetwork = new AgentNetwork({
name: "managed",
agents: [agent1, agent2, agent3],
maxConcurrent: 2, // Only 2 agents run at a time
timeout: 30000, // 30 second timeout per agent
});Agent Patterns
Complete guide to defining and using Mastra agents in v1 Beta.
Basic Agent Definition
import { Agent } from "@mastra/core/agent";
import { openai } from "@ai-sdk/openai";
export const myAgent = new Agent({
name: "my-agent", // Required: unique identifier
instructions: "You are a helpful assistant.", // Required: system prompt
model: openai("gpt-4o-mini"), // Required: LLM model
tools: { weatherTool, searchTool }, // Optional: named tools object
});Model Configuration
Model Router Strings
Mastra supports 1113+ models from 53+ providers through model router strings.
// OpenAI
model: "openai/gpt-4o"
model: "openai/gpt-4o-mini"
model: "openai/o1"
// Anthropic
model: "anthropic/claude-3-5-sonnet"
model: "anthropic/claude-3-opus"
// Google
model: "google/gemini-2.5-flash"
model: "google/gemini-pro"
// Others
model: "groq/llama-3.1-70b-versatile"
model: "mistral/mistral-large"
model: "cohere/command-r-plus"SDK Model Instances
import { openai } from "@ai-sdk/openai";
import { anthropic } from "@ai-sdk/anthropic";
import { google } from "@ai-sdk/google";
// Direct SDK usage
model: openai("gpt-4o-mini")
model: anthropic("claude-3-5-sonnet-20241022")
model: google("gemini-1.5-pro")
// With options
model: openai("gpt-4o", { temperature: 0.7 })Model Fallbacks
Configure automatic fallbacks for resilience.
const agent = new Agent({
name: "resilient-agent",
model: [
{ model: "openai/gpt-4o", maxRetries: 3 },
{ model: "anthropic/claude-3-5-sonnet", maxRetries: 2 },
{ model: "google/gemini-pro", maxRetries: 1 },
],
// Automatically falls back on 5xx, 429, or timeout errors
});Dynamic Model Selection
const agent = new Agent({
name: "dynamic-agent",
model: ({ runtimeContext }) => {
const provider = runtimeContext.get("provider-id");
const tier = runtimeContext.get("user-tier");
// Select model based on context
if (tier === "premium") {
return `${provider}/gpt-4o`;
}
return `${provider}/gpt-4o-mini`;
},
});Instructions Patterns
Basic Instructions
instructions: "You are a helpful customer support agent for Acme Corp."Detailed Instructions
instructions: `You are an expert weather analyst.
Your capabilities:
- Fetch current weather data for any city
- Provide detailed forecasts
- Explain weather patterns
Guidelines:
- Always include temperature in both Celsius and Fahrenheit
- Mention humidity and wind conditions
- Be concise but thorough
When you don't have data, say so clearly rather than guessing.`Dynamic Instructions
const agent = new Agent({
name: "personalized-agent",
instructions: ({ runtimeContext }) => {
const userName = runtimeContext.get("user-name");
const preferences = runtimeContext.get("preferences");
return `You are a personal assistant for ${userName}.
Their preferences:
${JSON.stringify(preferences, null, 2)}
Always address them by name and respect their preferences.`;
},
});Tool Integration
Adding Tools
import { weatherTool } from "../tools/weather-tool.js";
import { searchTool } from "../tools/search-tool.js";
import { calculatorTool } from "../tools/calculator-tool.js";
const agent = new Agent({
name: "multi-tool-agent",
instructions: "You help users with weather, search, and calculations.",
model: openai("gpt-4o-mini"),
tools: {
weatherTool, // Key becomes tool's id in agent context
searchTool,
calculatorTool,
},
});Tool Selection by Model
The agent automatically decides which tools to use based on the user's request and the tool descriptions.
// Tool with good description = better selection
export const weatherTool = createTool({
id: "get-weather",
description: "Fetches current weather conditions for a specific city. " +
"Use this when the user asks about weather, temperature, " +
"or climate conditions in a location.",
// ...
});Memory Configuration
Basic Memory
const response = await agent.generate("Remember my name is Alex", {
memory: {
thread: "conversation-123", // Conversation isolation
resource: "user-456", // User association
},
});
// Later in same thread
const response2 = await agent.generate("What's my name?", {
memory: {
thread: "conversation-123",
resource: "user-456",
},
});
// Agent remembers: "Your name is Alex"Memory with Storage
import { Mastra } from "@mastra/core/mastra";
import { LibSQLStore } from "@mastra/libsql";
const mastra = new Mastra({
agents: { myAgent },
storage: new LibSQLStore({
url: "file:./mastra.db",
}),
});
// Conversations now persist across restartsAgent Execution
Generate (Non-Streaming)
const response = await agent.generate("What's the weather in Tokyo?");
console.log(response.text); // Full response text
console.log(response.usage); // Token usage
console.log(response.traceId); // Trace ID for observabilityStream (Real-Time)
const stream = await agent.stream("Tell me about Seattle");
for await (const chunk of stream.textStream) {
process.stdout.write(chunk);
}With Runtime Context
import { RuntimeContext } from "@mastra/core";
const runtimeContext = new RuntimeContext();
runtimeContext.set("user-id", "user-123");
runtimeContext.set("user-tier", "premium");
const response = await agent.generate("What's my account status?", {
runtimeContext,
});With Memory
const response = await agent.generate("My favorite color is blue", {
memory: {
thread: "preferences-thread",
resource: "user-456",
},
});Structured Output
With Output Schema
import { z } from "zod";
const response = await agent.generate("List three cities in Japan", {
output: z.object({
cities: z.array(z.object({
name: z.string(),
population: z.number().optional(),
})),
}),
});
// response.object is typed as { cities: { name: string; population?: number }[] }
console.log(response.object.cities);Complex Structured Output
const analysisSchema = z.object({
sentiment: z.enum(["positive", "negative", "neutral"]),
confidence: z.number().min(0).max(1),
keywords: z.array(z.string()),
summary: z.string(),
});
const response = await agent.generate("Analyze: Great product, fast shipping!", {
output: analysisSchema,
});Agent Networks
Multi-Agent Collaboration
import { AgentNetwork } from "@mastra/core/agent";
const network = new AgentNetwork({
name: "research-team",
agents: [researcherAgent, writerAgent, editorAgent],
router: async ({ message }) => {
// Route to appropriate agent based on task
if (message.includes("research")) return "researcher-agent";
if (message.includes("write")) return "writer-agent";
return "editor-agent";
},
});
const result = await network.generate("Research and write about AI trends");A2A Protocol (Agent-to-Agent)
// Agents can communicate directly within a network
const supervisorAgent = new Agent({
name: "supervisor",
instructions: "You coordinate a team of specialized agents.",
model: openai("gpt-4o"),
tools: {
delegateToResearcher: createTool({
id: "delegate-research",
description: "Delegate research task to researcher agent",
inputSchema: z.object({ task: z.string() }),
execute: async (input, context) => {
const researcher = context.mastra?.getAgent("researcher");
const result = await researcher?.generate(input.task);
return { research: result?.text };
},
}),
},
});Registering in Mastra
Single Agent
import { Mastra } from "@mastra/core/mastra";
export const mastra = new Mastra({
agents: { weatherAgent },
});Multiple Agents
export const mastra = new Mastra({
agents: {
weatherAgent,
searchAgent,
assistantAgent,
},
});Accessing Agents
// In tools or workflows
const agent = mastra.getAgent("weather-agent");
const result = await agent?.generate("Hello");
// In custom routes
registerApiRoute("/custom", {
method: "POST",
handler: async (c) => {
const mastra = c.get("mastra");
const agent = mastra.getAgent("weather-agent");
// ...
},
});Observability
AI Tracing
const mastra = new Mastra({
agents: { myAgent },
observability: {
default: { enabled: true },
},
storage: new LibSQLStore({ url: "file:./mastra.db" }),
});
// Traces automatically captured for all agent callsCustom Trace Context
import { trace } from "@opentelemetry/api";
const currentSpan = trace.getActiveSpan();
const spanContext = currentSpan?.spanContext();
const result = await agent.generate("Analyze data", {
tracingOptions: {
traceId: spanContext?.traceId,
parentSpanId: spanContext?.spanId,
},
});
console.log("Trace ID:", result.traceId);Best Practices
1. Descriptive Names
// Good: Descriptive, indicates purpose
name: "customer-support-agent"
name: "weather-forecast-agent"
name: "code-review-agent"
// Bad: Vague, non-descriptive
name: "agent1"
name: "my-agent"
name: "test"2. Clear Instructions
// Good: Specific, actionable
instructions: `You are a customer support agent for TechCorp.
Responsibilities:
- Answer product questions
- Help with billing issues
- Escalate complex issues
Tone: Professional but friendly
Response format: Keep answers under 200 words unless detailed explanation needed`
// Bad: Vague, no guidance
instructions: "Help users"3. Tool Descriptions
// Good: Explains what, when, and how
description: "Fetches current weather for a city. Use when user asks about " +
"temperature, conditions, or forecast. Input: city name as string."
// Bad: Minimal, unhelpful
description: "Gets weather"4. Error Handling
try {
const response = await agent.generate(userMessage);
return response.text;
} catch (error) {
if (error.message.includes("rate limit")) {
// Handle rate limiting
await delay(1000);
return await agent.generate(userMessage);
}
throw error;
}Common Mistakes Reference
Quick reference for the most frequent errors when generating Mastra code. Each entry shows the wrong pattern and the correct fix.
Import Errors
Root Import (Deprecated)
// WRONG: Root import removed in v1
import { Agent } from "@mastra/core";
import { createTool } from "@mastra/core";
// CORRECT: Subpath imports (required in v1)
import { Agent } from "@mastra/core/agent";
import { createTool } from "@mastra/core/tools";
import { createWorkflow, createStep } from "@mastra/core/workflows";
import { Mastra } from "@mastra/core/mastra";Agent Definition Errors
Tools as Array
// WRONG: Tools must be an object, not an array
const agent = new Agent({
name: "my-agent",
tools: [weatherTool, searchTool], // Array - WRONG
});
// CORRECT: Tools as named object
const agent = new Agent({
name: "my-agent",
tools: { weatherTool, searchTool }, // Object with named keys
});Missing Required Fields
// WRONG: Missing instructions and model
const agent = new Agent({
name: "my-agent",
});
// CORRECT: All required fields
const agent = new Agent({
name: "my-agent",
instructions: "You are a helpful assistant.",
model: openai("gpt-4o-mini"),
});Deprecated Memory Options
// WRONG: Deprecated threadId/resourceId
await agent.generate("Hello", {
threadId: "123",
resourceId: "456",
});
// CORRECT: Use memory object
await agent.generate("Hello", {
memory: {
thread: "conversation-123",
resource: "user-456",
},
});Tool Signature Errors
v1 Beta vs Stable Signature
// WRONG for v1 Beta: Stable (0.24.x) signature
export const myTool = createTool({
execute: async ({ context, mastra, runtimeContext }) => {
const { location } = context; // context contains input in stable
return { result: location };
},
});
// CORRECT for v1 Beta: Separate inputData and context
export const myTool = createTool({
execute: async (inputData, context) => {
const { location } = inputData; // First param: parsed input
const { mastra, runtimeContext, abortSignal } = context; // Second: context
return { result: location };
},
});Missing Abort Signal Check
// WRONG: No abort handling for long operations
execute: async (inputData, context) => {
const result = await longRunningOperation(); // Could hang
return result;
}
// CORRECT: Check abort signal
execute: async (inputData, context) => {
const { abortSignal } = context;
if (abortSignal?.aborted) throw new Error("Aborted");
const result = await longRunningOperation();
// Check again for very long operations
if (abortSignal?.aborted) throw new Error("Aborted");
return result;
}Workflow Data Flow Errors
Legacy Context Pattern
// WRONG: Legacy API pattern
execute: async ({ context }) => {
const triggerData = context.triggerData;
const prevOutput = context.steps.step1.output;
return { result: prevOutput };
}
// CORRECT: New API pattern
execute: async ({ inputData, getStepResult, getInitData }) => {
const originalInput = getInitData();
const step1Output = getStepResult("step-1");
const previousOutput = inputData; // Direct previous step
return { result: previousOutput };
}Assuming inputData Contains All Steps
// WRONG: inputData is NOT a container
execute: async ({ inputData }) => {
const step1 = inputData.step1; // WRONG!
const step2 = inputData.step2; // WRONG!
}
// CORRECT: inputData is ONLY previous step output
execute: async ({ inputData, getStepResult }) => {
const previousOutput = inputData;
const step1 = getStepResult("step-1");
const step2 = getStepResult("step-2");
}Schema Mismatch Between Steps
// WRONG: Output doesn't match next step's input
const step1 = createStep({
outputSchema: z.object({ formatted: z.string() }),
});
const step2 = createStep({
inputSchema: z.object({ message: z.string() }), // Different field!
});
// CORRECT: Schemas must match, or use .map()
const step2 = createStep({
inputSchema: z.object({ formatted: z.string() }), // Matches step1 output
});
// OR use .map() to transform
workflow.then(step1).map(async ({ inputData }) => ({
message: inputData.formatted
})).then(step2);Missing Optional After Branch
// WRONG: Both branches won't execute
inputSchema: z.object({
"branch-a": z.object({ result: z.string() }),
"branch-b": z.object({ result: z.string() }),
})
// CORRECT: Use .optional() for branch outputs
inputSchema: z.object({
"branch-a": z.object({ result: z.string() }).optional(),
"branch-b": z.object({ result: z.string() }).optional(),
})Parallel Output Access
// WRONG: Expecting direct result
execute: async ({ inputData }) => {
const formatted = inputData.formatted; // WRONG after parallel
}
// CORRECT: Access by step ID after parallel
execute: async ({ inputData }) => {
const formatted = inputData["format-step"].formatted;
const count = inputData["count-step"].count;
}State Mutation Errors
Direct State Mutation
// WRONG: Direct mutation
execute: async ({ state }) => {
state.counter++; // Direct mutation - WRONG
state.items.push(newItem); // Direct mutation - WRONG
}
// CORRECT: Use setState with new object
execute: async ({ state, setState }) => {
setState({
...state,
counter: state.counter + 1,
items: [...state.items, newItem],
});
}Nested Agent/Workflow Access
Direct Import of Agents
// WRONG: Direct import loses observability
import { copywriterAgent } from "./agents";
execute: async () => {
await copywriterAgent.generate("Hello"); // No logging, no tracing
}
// CORRECT: Use mastra.getAgent()
execute: async (inputData, context) => {
const { mastra } = context;
const agent = mastra.getAgent("copywriterAgent");
await agent.generate("Hello"); // Full observability
}Forgetting to Propagate Context
// WRONG: Context not propagated
execute: async (inputData, context) => {
const { mastra } = context;
const agent = mastra.getAgent("nestedAgent");
await agent.generate("Hello"); // Missing runtimeContext
}
// CORRECT: Propagate runtimeContext
execute: async (inputData, context) => {
const { mastra, runtimeContext } = context;
const agent = mastra.getAgent("nestedAgent");
await agent.generate("Hello", { runtimeContext });
}Zod Schema Errors
Missing Descriptions
// WRONG: No descriptions (LLM won't understand usage)
inputSchema: z.object({
location: z.string(),
units: z.enum(["celsius", "fahrenheit"]),
})
// CORRECT: Descriptions help LLM understand
inputSchema: z.object({
location: z.string().describe("City name, e.g., 'Seattle'"),
units: z.enum(["celsius", "fahrenheit"]).describe("Temperature unit"),
})Missing Output Schema
// WRONG: No outputSchema (causes validation issues)
export const myTool = createTool({
inputSchema: z.object({ query: z.string() }),
// outputSchema missing!
execute: async (inputData) => {
return { result: "..." };
},
});
// CORRECT: Always include outputSchema
export const myTool = createTool({
inputSchema: z.object({ query: z.string() }),
outputSchema: z.object({ result: z.string() }),
execute: async (inputData) => {
return { result: "..." };
},
});Hono Server Errors
Missing Await on init()
// WRONG: init() is async
const server = new MastraServer({ app, mastra });
server.init(); // Missing await!
// CORRECT: Await init()
const server = new MastraServer({ app, mastra });
await server.init();Wrong Context Access
// WRONG: Incorrect context access
app.get("/custom", (c) => {
const mastra = c.mastra; // Wrong property access
});
// CORRECT: Use c.get()
app.get("/custom", (c) => {
const mastra = c.get("mastra");
});Observability Errors
Using Deprecated Telemetry Config
// WRONG: Deprecated in v1
export const mastra = new Mastra({
telemetry: {
enabled: true,
serviceName: "my-service",
},
});
// CORRECT: Use observability config
export const mastra = new Mastra({
observability: {
default: { enabled: true },
},
});Quick Reference Table
| Category | Wrong | Correct |
|---|---|---|
| Imports | @mastra/core | @mastra/core/agent, @mastra/core/tools |
| Tools | tools: [a, b] | tools: { a, b } |
| Memory | { threadId } | { memory: { thread, resource } } |
| Tool exec (v1) | ({ context }) | (inputData, context) |
| Workflow data | context.steps.x.output | inputData or getStepResult("x") |
| After parallel | inputData.result | inputData["step-id"].result |
| After branch | inputData.result | inputData["step-id"]?.result |
| Nested agents | Direct import | mastra.getAgent("name") |
| State | state.x++ | setState({ ...state, x: state.x + 1 }) |
| Observability | telemetry: | observability: |
Context Network Memory Patterns
Guide to integrating context networks across agent knowledge, conversation memory, and developer documentation.
Overview
Context networks provide a structured approach to organizing information at three levels:
1. Agent Knowledge (RAG) - Facts and documents agents can retrieve 2. Conversation Memory - Thread-based context and insights 3. Developer Documentation - Project knowledge that informs agent behavior
This integration creates a unified knowledge layer where information flows between all three levels.
Level 1: Agent Knowledge as Context Network
Organizing Knowledge with Atomic Notes
// Knowledge as atomic, interconnected notes
interface KnowledgeNode {
id: string;
title: string;
content: string;
type: "fact" | "concept" | "procedure" | "decision";
connections: string[]; // IDs of related nodes
metadata: {
source: string;
createdAt: string;
confidence: number;
};
}
// Example knowledge nodes
const knowledgeBase: KnowledgeNode[] = [
{
id: "api-auth",
title: "API Authentication",
content: "The API uses Bearer token authentication...",
type: "procedure",
connections: ["api-endpoints", "security-policies"],
metadata: { source: "docs", createdAt: "2024-01-15", confidence: 1.0 },
},
{
id: "api-endpoints",
title: "API Endpoints",
content: "Available endpoints include /users, /orders, /products...",
type: "concept",
connections: ["api-auth", "rate-limits"],
metadata: { source: "docs", createdAt: "2024-01-15", confidence: 1.0 },
},
];Indexing with Relationship Metadata
// Index knowledge nodes with connection information
async function indexKnowledgeNetwork(nodes: KnowledgeNode[]) {
const embeddings = await embedMany({
model: openai.embedding("text-embedding-3-small"),
values: nodes.map(n => `${n.title}\n\n${n.content}`),
});
await mastra.vectors?.default.upsert({
indexName: "knowledge-network",
vectors: nodes.map((node, i) => ({
id: node.id,
vector: embeddings.embeddings[i],
metadata: {
title: node.title,
content: node.content,
type: node.type,
connections: node.connections,
...node.metadata,
},
})),
});
}Graph-Aware Retrieval
export const contextNetworkSearch = createTool({
id: "context-network-search",
description: "Search knowledge network with relationship expansion",
inputSchema: z.object({
query: z.string(),
expandConnections: z.boolean().optional().default(true),
maxDepth: z.number().optional().default(1),
}),
execute: async (input, context) => {
const { query, expandConnections, maxDepth } = input;
const { mastra } = context;
// Initial semantic search
const { embedding } = await embed({
model: openai.embedding("text-embedding-3-small"),
value: query,
});
const directResults = await mastra?.vectors?.default.query({
indexName: "knowledge-network",
queryVector: embedding,
topK: 5,
});
if (!expandConnections) {
return { nodes: directResults };
}
// Expand connections
const expanded = new Map();
const queue = directResults?.map(r => ({ node: r, depth: 0 })) || [];
while (queue.length > 0) {
const { node, depth } = queue.shift()!;
if (expanded.has(node.id) || depth > maxDepth) continue;
expanded.set(node.id, node);
// Fetch connected nodes
if (depth < maxDepth && node.metadata.connections) {
for (const connId of node.metadata.connections) {
const connected = await mastra?.vectors?.default.get({
indexName: "knowledge-network",
id: connId,
});
if (connected) {
queue.push({ node: connected, depth: depth + 1 });
}
}
}
}
return {
nodes: Array.from(expanded.values()),
connections: directResults?.flatMap(r => r.metadata.connections || []),
};
},
});Level 2: Conversation Memory as Context Network
Thread Organization Patterns
// Threads organized by context type
interface ThreadContext {
threadId: string;
contextType: "support" | "research" | "planning" | "general";
topic: string;
connections: string[]; // Related threads
insights: ConversationInsight[];
}
interface ConversationInsight {
id: string;
type: "preference" | "fact" | "decision" | "question";
content: string;
extractedFrom: string; // Message ID
confidence: number;
}Extracting Insights from Conversations
const insightExtractor = createStep({
id: "extract-insights",
execute: async ({ inputData, mastra }) => {
const { threadId, messages } = inputData;
// Use LLM to extract insights
const extractor = new Agent({
name: "insight-extractor",
model: openai("gpt-4o-mini"),
instructions: `Extract key insights from conversations.
Categories:
- preference: User preferences (e.g., "prefers dark mode")
- fact: Facts about user/context (e.g., "works at TechCorp")
- decision: Decisions made (e.g., "chose Plan B")
- question: Unanswered questions`,
});
const conversation = messages.map(m => `${m.role}: ${m.content}`).join("\n");
const insights = await extractor.generate(conversation, {
output: z.object({
insights: z.array(z.object({
type: z.enum(["preference", "fact", "decision", "question"]),
content: z.string(),
confidence: z.number(),
})),
}),
});
// Store insights in knowledge network
for (const insight of insights.object.insights) {
await mastra?.vectors?.default.upsert({
indexName: "conversation-insights",
vectors: [{
id: `${threadId}-${Date.now()}`,
vector: await getEmbedding(insight.content),
metadata: {
...insight,
threadId,
extractedAt: new Date().toISOString(),
},
}],
});
}
return { insights: insights.object.insights };
},
});Cross-Session Context
export const retrieveUserContext = createTool({
id: "retrieve-user-context",
description: "Retrieve relevant context from user's conversation history",
inputSchema: z.object({
userId: z.string(),
currentQuery: z.string(),
}),
execute: async (input, context) => {
const { userId, currentQuery } = input;
const { mastra } = context;
// Get insights relevant to current query
const { embedding } = await embed({
model: openai.embedding("text-embedding-3-small"),
value: currentQuery,
});
const relevantInsights = await mastra?.vectors?.default.query({
indexName: "conversation-insights",
queryVector: embedding,
topK: 10,
filter: { userId },
});
// Get recent conversation context
const recentThreads = await mastra?.storage?.listThreads({
resourceId: userId,
limit: 5,
});
return {
insights: relevantInsights?.map(i => ({
type: i.metadata.type,
content: i.metadata.content,
from: i.metadata.threadId,
})),
recentTopics: recentThreads?.map(t => t.metadata?.topic),
};
},
});Memory Consolidation to Knowledge
const consolidateToKnowledge = async (threadId: string, userId: string) => {
const insights = await mastra.vectors?.default.query({
indexName: "conversation-insights",
filter: { threadId },
topK: 100,
});
// High-confidence insights become permanent knowledge
const permanentInsights = insights?.filter(i => i.metadata.confidence > 0.8);
for (const insight of permanentInsights || []) {
await mastra.vectors?.default.upsert({
indexName: "user-knowledge",
vectors: [{
id: `${userId}-${insight.id}`,
vector: insight.vector,
metadata: {
userId,
content: insight.metadata.content,
type: insight.metadata.type,
source: "conversation",
originalThread: threadId,
consolidatedAt: new Date().toISOString(),
},
}],
});
}
return { consolidated: permanentInsights?.length || 0 };
};Level 3: Developer Documentation as Context
Project Context Network
// Structure mirrors .context-network.md
interface ProjectContext {
architecture: {
decisions: ArchitectureDecision[];
patterns: Pattern[];
constraints: Constraint[];
};
agents: {
[agentName: string]: AgentContext;
};
tasks: {
completed: TaskRecord[];
inProgress: TaskRecord[];
};
}
interface ArchitectureDecision {
id: string;
title: string;
context: string;
decision: string;
consequences: string[];
date: string;
status: "active" | "superseded";
}Agent Self-Documentation
// Agent can query its own documentation
export const selfDocsTool = createTool({
id: "query-my-docs",
description: "Query documentation about this agent's capabilities and constraints",
inputSchema: z.object({
query: z.string().describe("What to look up about my capabilities"),
}),
execute: async (input, context) => {
const { query } = input;
const { runtimeContext, mastra } = context;
const agentName = runtimeContext.get("current-agent");
// Search project context network
const { embedding } = await embed({
model: openai.embedding("text-embedding-3-small"),
value: query,
});
const docs = await mastra?.vectors?.default.query({
indexName: "project-context",
queryVector: embedding,
filter: {
$or: [
{ type: "agent-docs", agentName },
{ type: "architecture-decision" },
{ type: "project-constraint" },
],
},
topK: 5,
});
return {
capabilities: docs?.filter(d => d.metadata.type === "agent-docs"),
relevantDecisions: docs?.filter(d => d.metadata.type === "architecture-decision"),
constraints: docs?.filter(d => d.metadata.type === "project-constraint"),
};
},
});Decision Record Integration
// Decisions from context network inform agent behavior
const decisionAwareAgent = new Agent({
name: "decision-aware-agent",
instructions: async ({ runtimeContext, mastra }) => {
// Fetch relevant architectural decisions
const decisions = await mastra?.vectors?.default.query({
indexName: "project-context",
filter: { type: "architecture-decision", status: "active" },
topK: 10,
});
const decisionContext = decisions
?.map(d => `- ${d.metadata.title}: ${d.metadata.decision}`)
.join("\n");
return `You are a development assistant.
Respect these architectural decisions:
${decisionContext}
When in doubt about approaches, query the project documentation.`;
},
model: openai("gpt-4o-mini"),
tools: { selfDocsTool },
});Cross-Layer Integration
Unified Query Tool
export const unifiedContextSearch = createTool({
id: "unified-context-search",
description: "Search across all context layers: knowledge, memory, and docs",
inputSchema: z.object({
query: z.string(),
layers: z.array(z.enum(["knowledge", "memory", "docs"])).optional(),
}),
execute: async (input, context) => {
const { query, layers = ["knowledge", "memory", "docs"] } = input;
const { mastra, runtimeContext } = context;
const { embedding } = await embed({
model: openai.embedding("text-embedding-3-small"),
value: query,
});
const results: Record<string, any[]> = {};
if (layers.includes("knowledge")) {
results.knowledge = await mastra?.vectors?.default.query({
indexName: "knowledge-network",
queryVector: embedding,
topK: 5,
}) || [];
}
if (layers.includes("memory")) {
const userId = runtimeContext.get("user-id");
results.memory = await mastra?.vectors?.default.query({
indexName: "conversation-insights",
queryVector: embedding,
filter: userId ? { userId } : undefined,
topK: 5,
}) || [];
}
if (layers.includes("docs")) {
results.docs = await mastra?.vectors?.default.query({
indexName: "project-context",
queryVector: embedding,
topK: 5,
}) || [];
}
return results;
},
});Feedback Loops
// Conversation insights update knowledge
const feedbackLoop = createWorkflow({
id: "context-feedback-loop",
inputSchema: z.object({
threadId: z.string(),
userId: z.string(),
}),
outputSchema: z.object({ updated: z.number() }),
})
.then(createStep({
id: "extract-insights",
execute: async ({ inputData, mastra }) => {
// Extract new insights from conversation
const messages = await mastra?.storage?.getMessages({
threadId: inputData.threadId,
limit: 50,
});
// ... insight extraction logic
return { insights: extractedInsights };
},
}))
.then(createStep({
id: "validate-insights",
execute: async ({ inputData }) => {
// Filter high-confidence insights
return {
validInsights: inputData.insights.filter(i => i.confidence > 0.7),
};
},
}))
.then(createStep({
id: "update-knowledge",
execute: async ({ inputData, mastra }) => {
// Update knowledge network
let updated = 0;
for (const insight of inputData.validInsights) {
// Check if this updates existing knowledge
const existing = await mastra?.vectors?.default.query({
indexName: "knowledge-network",
queryVector: await getEmbedding(insight.content),
topK: 1,
scoreThreshold: 0.95,
});
if (existing?.length) {
// Update existing node
await updateKnowledgeNode(existing[0].id, insight);
} else {
// Create new node
await createKnowledgeNode(insight);
}
updated++;
}
return { updated };
},
}))
.commit();Developer Decisions Propagate to Agents
// When a new architectural decision is made
async function recordDecision(decision: ArchitectureDecision) {
// 1. Store in project context
await mastra.vectors?.default.upsert({
indexName: "project-context",
vectors: [{
id: decision.id,
vector: await getEmbedding(`${decision.title} ${decision.decision}`),
metadata: {
type: "architecture-decision",
...decision,
},
}],
});
// 2. Update affected agent instructions
for (const agentName of getAffectedAgents(decision)) {
await refreshAgentInstructions(agentName);
}
// 3. Notify relevant conversation threads
const affectedThreads = await findAffectedThreads(decision);
for (const thread of affectedThreads) {
await mastra.storage?.addMessage({
threadId: thread.id,
role: "system",
content: `[CONTEXT UPDATE] New architectural decision may affect this conversation: ${decision.title}`,
});
}
}Best Practices
1. Atomic Knowledge Nodes
Keep knowledge nodes small and focused:
- One concept per node
- 100-300 words maximum
- Clear connections to related nodes
2. Explicit Connections
Always define relationships:
relatedTo: Conceptual similaritydependsOn: Prerequisite knowledgesupersedes: Updated informationconflictsWith: Contradictory information
3. Source Tracking
Always track where information came from:
source: "docs"- Official documentationsource: "conversation"- Extracted from usersource: "decision"- Architectural decisionsource: "inferred"- AI-generated connection
4. Confidence Scoring
Track reliability of information:
- 1.0: Verified fact
- 0.8+: High confidence
- 0.5-0.8: Moderate confidence
- <0.5: Speculative
5. Regular Maintenance
Schedule maintenance workflows:
- Consolidate conversation insights weekly
- Validate knowledge connections monthly
- Archive superseded decisions quarterly
Hono Server Patterns
Complete guide to setting up and configuring Hono servers with Mastra.
Basic Setup
Minimal Server
// src/index.ts
import { serve } from "@hono/node-server";
import { Hono } from "hono";
import { MastraServer } from "@mastra/hono";
import { mastra } from "./mastra/index.js";
const app = new Hono();
const server = new MastraServer({ app, mastra });
await server.init(); // Don't forget await!
app.get("/", (c) => c.text("Mastra + Hono Server"));
serve({ fetch: app.fetch, port: 3000 });Auto-Generated Endpoints
After server.init(), these endpoints are available:
| Endpoint | Method | Description |
|---|---|---|
/api/agents/{name}/generate | POST | Generate response from agent |
/api/agents/{name}/stream | POST | Stream response from agent |
/api/workflows/{id}/start | POST | Start workflow run |
/api/workflows/{id}/{runId}/status | GET | Get workflow run status |
Request Format
# Agent generate
curl -X POST http://localhost:3000/api/agents/weather-agent/generate \
-H "Content-Type: application/json" \
-d '{
"messages": [
{ "role": "user", "content": "What is the weather in Tokyo?" }
]
}'
# Agent stream
curl -X POST http://localhost:3000/api/agents/weather-agent/stream \
-H "Content-Type: application/json" \
-d '{"messages": [{"role": "user", "content": "Tell me about Seattle"}]}'
# Workflow start
curl -X POST http://localhost:3000/api/workflows/data-pipeline/start \
-H "Content-Type: application/json" \
-d '{"inputData": {"value": 42}}'Server Configuration
Custom Route Prefix
const server = new MastraServer({
app,
mastra,
prefix: "/v1/ai", // Routes at /v1/ai/agents/...
});
// Now endpoints are:
// /v1/ai/agents/{name}/generate
// /v1/ai/workflows/{id}/startMastra Instance Configuration
// src/mastra/index.ts
import { Mastra } from "@mastra/core/mastra";
import { LibSQLStore } from "@mastra/libsql";
export const mastra = new Mastra({
agents: { weatherAgent, assistantAgent },
workflows: { dataPipeline, reportGenerator },
storage: new LibSQLStore({
url: "file:./mastra.db",
}),
server: {
port: 3000, // Defaults to 4111
timeout: 30000, // Request timeout (default: 3 minutes)
},
cors: {
origin: ["https://example.com"],
allowMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
allowHeaders: ["Content-Type", "Authorization"],
credentials: false,
},
});Custom API Routes
Basic Custom Route
import { registerApiRoute } from "@mastra/core/server";
registerApiRoute("/health", {
method: "GET",
handler: async (c) => {
return c.json({ status: "healthy", timestamp: Date.now() });
},
});Accessing Mastra in Routes
registerApiRoute("/custom-agent-call", {
method: "POST",
handler: async (c) => {
const mastra = c.get("mastra");
const body = await c.req.json();
const agent = mastra.getAgent("my-agent");
if (!agent) {
return c.json({ error: "Agent not found" }, 404);
}
const result = await agent.generate(body.message);
return c.json({ response: result.text });
},
});Running Workflows from Routes
registerApiRoute("/process-data", {
method: "POST",
handler: async (c) => {
const mastra = c.get("mastra");
const body = await c.req.json();
const workflow = mastra.getWorkflow("data-pipeline");
const run = workflow.createRun();
const result = await run.start({
inputData: { data: body.data },
});
return c.json({
status: result.status,
output: result.steps?.["final-step"]?.output,
});
},
});Route with Middleware
import { bearerAuth } from "hono/bearer-auth";
registerApiRoute("/protected-route", {
method: "POST",
middleware: [
bearerAuth({ token: process.env.API_TOKEN! }),
],
handler: async (c) => {
const mastra = c.get("mastra");
// ... protected logic
return c.json({ success: true });
},
});Middleware Patterns
Global Middleware
const app = new Hono();
// Add middleware before MastraServer.init()
app.use("*", async (c, next) => {
console.log(`${c.req.method} ${c.req.url}`);
await next();
});
// CORS middleware
import { cors } from "hono/cors";
app.use("*", cors({
origin: ["https://example.com"],
allowMethods: ["GET", "POST", "OPTIONS"],
}));
// Then initialize Mastra
const server = new MastraServer({ app, mastra });
await server.init();Authentication Middleware
import { bearerAuth } from "hono/bearer-auth";
// Protect all /api routes
app.use("/api/*", bearerAuth({ token: process.env.API_TOKEN! }));
// Or use JWT
import { jwt } from "hono/jwt";
app.use("/api/*", jwt({ secret: process.env.JWT_SECRET! }));Rate Limiting
// Custom rate limiter middleware
const rateLimiter = new Map<string, { count: number; resetAt: number }>();
app.use("/api/*", async (c, next) => {
const ip = c.req.header("x-forwarded-for") || "unknown";
const now = Date.now();
const limit = rateLimiter.get(ip);
if (limit && now < limit.resetAt) {
if (limit.count >= 100) {
return c.json({ error: "Rate limit exceeded" }, 429);
}
limit.count++;
} else {
rateLimiter.set(ip, { count: 1, resetAt: now + 60000 });
}
await next();
});Request Logging
import { logger } from "hono/logger";
app.use("*", logger());RuntimeContext Pattern
Setting Context at Request Level
import { RuntimeContext } from "@mastra/core";
registerApiRoute("/user-query", {
method: "POST",
handler: async (c) => {
const mastra = c.get("mastra");
const body = await c.req.json();
const userId = c.req.header("x-user-id");
// Create runtime context with request-specific values
const runtimeContext = new RuntimeContext();
runtimeContext.set("user-id", userId);
runtimeContext.set("user-tier", body.tier || "free");
runtimeContext.set("request-id", crypto.randomUUID());
const agent = mastra.getAgent("my-agent");
const result = await agent.generate(body.message, {
runtimeContext,
});
return c.json({ response: result.text });
},
});Accessing Context in Tools
export const premiumTool = createTool({
id: "premium-feature",
execute: async (inputData, context) => {
const { runtimeContext } = context;
const tier = runtimeContext.get("user-tier");
if (tier !== "premium") {
throw new Error("Premium subscription required");
}
// Premium logic...
return { result: "Premium result" };
},
});OpenAPI Documentation
Enabling OpenAPI
const server = new MastraServer({
app,
mastra,
openapiPath: "/openapi.json", // Enable OpenAPI spec
});
await server.init();
// Access spec at http://localhost:3000/openapi.jsonAdding Swagger UI
import { swaggerUI } from "@hono/swagger-ui";
app.get("/docs", swaggerUI({ url: "/openapi.json" }));Error Handling
Global Error Handler
app.onError((err, c) => {
console.error("Server error:", err);
if (err.message.includes("not found")) {
return c.json({ error: "Resource not found" }, 404);
}
if (err.message.includes("unauthorized")) {
return c.json({ error: "Unauthorized" }, 401);
}
return c.json({ error: "Internal server error" }, 500);
});Not Found Handler
app.notFound((c) => {
return c.json({ error: "Route not found" }, 404);
});Deployment Patterns
Node.js Server
import { serve } from "@hono/node-server";
serve({
fetch: app.fetch,
port: parseInt(process.env.PORT || "3000"),
});Bun Server
export default {
fetch: app.fetch,
port: parseInt(process.env.PORT || "3000"),
};Cloudflare Workers
export default app;Docker Configuration
FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["node", "dist/index.js"]Health Checks
registerApiRoute("/health", {
method: "GET",
handler: async (c) => {
const mastra = c.get("mastra");
// Check storage connection
try {
await mastra.storage?.getThreads({ limit: 1 });
} catch (err) {
return c.json({ status: "unhealthy", error: "Storage unavailable" }, 503);
}
return c.json({
status: "healthy",
timestamp: new Date().toISOString(),
version: process.env.npm_package_version,
});
},
});
registerApiRoute("/ready", {
method: "GET",
handler: async (c) => {
return c.json({ ready: true });
},
});Streaming Responses
Server-Sent Events (SSE)
import { streamSSE } from "hono/streaming";
registerApiRoute("/stream-updates", {
method: "GET",
handler: async (c) => {
return streamSSE(c, async (stream) => {
for (let i = 0; i < 10; i++) {
await stream.writeSSE({
data: JSON.stringify({ count: i }),
event: "update",
});
await new Promise((r) => setTimeout(r, 1000));
}
});
},
});Streaming Agent Response
registerApiRoute("/chat-stream", {
method: "POST",
handler: async (c) => {
const mastra = c.get("mastra");
const body = await c.req.json();
const agent = mastra.getAgent("chat-agent");
const stream = await agent.stream(body.message);
return new Response(stream.textStream, {
headers: { "Content-Type": "text/event-stream" },
});
},
});MCP Server Patterns
Guide to creating and using Model Context Protocol (MCP) servers with Mastra.
Overview
MCP (Model Context Protocol) is a standardized way to expose tools, agents, and resources to AI systems. Mastra supports both authoring MCP servers and consuming external MCP servers.
Creating an MCP Server
Basic MCP Server
// src/mcp/index.ts
import { createMCPServer } from "@mastra/core/mcp";
import { mastra } from "../mastra/index.js";
const mcpServer = createMCPServer({
name: "my-mcp-server",
version: "1.0.0",
mastra,
});
// Start the server
mcpServer.listen({ port: 8080 });Exposing Tools via MCP
import { createMCPServer } from "@mastra/core/mcp";
import { weatherTool, searchTool, calculatorTool } from "../mastra/tools";
const mcpServer = createMCPServer({
name: "tools-server",
version: "1.0.0",
tools: {
weatherTool,
searchTool,
calculatorTool,
},
});
// Tools are now accessible via MCP protocolExposing Agents via MCP
import { createMCPServer } from "@mastra/core/mcp";
import { weatherAgent, assistantAgent } from "../mastra/agents";
const mcpServer = createMCPServer({
name: "agents-server",
version: "1.0.0",
agents: {
weatherAgent,
assistantAgent,
},
});
// Agents accessible as MCP resourcesCombined Server
const mcpServer = createMCPServer({
name: "full-server",
version: "1.0.0",
mastra, // Exposes all registered agents, tools, workflows
// Or explicitly specify what to expose
tools: { weatherTool },
agents: { weatherAgent },
resources: {
"config://settings": {
type: "text",
content: JSON.stringify(config),
},
},
});MCP Server Configuration
Transport Options
// stdio transport (for CLI tools)
mcpServer.listen({ transport: "stdio" });
// HTTP transport
mcpServer.listen({
transport: "http",
port: 8080,
});
// WebSocket transport
mcpServer.listen({
transport: "websocket",
port: 8081,
});Server Metadata
const mcpServer = createMCPServer({
name: "my-server",
version: "1.0.0",
description: "Provides weather and search capabilities",
vendor: "MyCompany",
capabilities: {
tools: true,
resources: true,
prompts: true,
},
});Defining MCP Resources
Static Resources
const mcpServer = createMCPServer({
name: "resource-server",
resources: {
"config://app-settings": {
type: "text",
mimeType: "application/json",
content: JSON.stringify({
theme: "dark",
language: "en",
}),
},
"file://readme": {
type: "text",
mimeType: "text/markdown",
content: "# My Application\n\nWelcome to the docs.",
},
},
});Dynamic Resources
const mcpServer = createMCPServer({
name: "dynamic-server",
resources: {
"data://users": {
type: "dynamic",
handler: async (uri) => {
const users = await database.getUsers();
return {
type: "text",
mimeType: "application/json",
content: JSON.stringify(users),
};
},
},
},
});Resource Templates
const mcpServer = createMCPServer({
name: "template-server",
resourceTemplates: {
"user://": {
description: "User profiles by ID",
handler: async (uri) => {
const userId = uri.replace("user://", "");
const user = await database.getUser(userId);
return {
type: "text",
mimeType: "application/json",
content: JSON.stringify(user),
};
},
},
},
});MCP Prompts
Defining Prompts
const mcpServer = createMCPServer({
name: "prompt-server",
prompts: {
"summarize": {
description: "Summarizes text content",
arguments: [
{ name: "text", description: "Text to summarize", required: true },
{ name: "length", description: "Target length", required: false },
],
handler: async ({ text, length }) => {
return {
messages: [
{
role: "user",
content: `Summarize the following text${length ? ` in ${length} words` : ""}:\n\n${text}`,
},
],
};
},
},
"translate": {
description: "Translates text to another language",
arguments: [
{ name: "text", required: true },
{ name: "targetLanguage", required: true },
],
handler: async ({ text, targetLanguage }) => {
return {
messages: [
{
role: "user",
content: `Translate to ${targetLanguage}: ${text}`,
},
],
};
},
},
},
});Consuming MCP Servers
Using MCP Tools in Agents
import { Agent } from "@mastra/core/agent";
import { mcpTools } from "@mastra/core/mcp";
// Load tools from external MCP server
const externalTools = await mcpTools({
server: "http://localhost:8080",
// Or stdio:
// command: "python",
// args: ["mcp_server.py"],
});
const agent = new Agent({
name: "mcp-agent",
instructions: "You can use external MCP tools.",
model: openai("gpt-4o-mini"),
tools: {
...externalTools, // Spread MCP tools
...localTools, // Add local tools
},
});MCP Client Configuration
import { MCPClient } from "@mastra/core/mcp";
const client = new MCPClient({
server: "http://localhost:8080",
timeout: 30000,
retries: 3,
});
// List available tools
const tools = await client.listTools();
// Call a tool
const result = await client.callTool("weather-tool", {
location: "Seattle",
});
// Get a resource
const config = await client.getResource("config://settings");
// Run a prompt
const prompt = await client.getPrompt("summarize", {
text: "Long text here...",
});Stdio MCP Servers
// Connect to a Python MCP server
const tools = await mcpTools({
command: "python",
args: ["./mcp_server.py"],
env: {
API_KEY: process.env.API_KEY,
},
});
// Connect to an npm package MCP server
const tools = await mcpTools({
command: "npx",
args: ["-y", "@company/mcp-server"],
});Integration with Mastra
MCP + Hono Server
import { serve } from "@hono/node-server";
import { Hono } from "hono";
import { MastraServer } from "@mastra/hono";
import { createMCPServer } from "@mastra/core/mcp";
import { mastra } from "./mastra/index.js";
const app = new Hono();
// HTTP API
const server = new MastraServer({ app, mastra });
await server.init();
// MCP Server on different port
const mcpServer = createMCPServer({
name: "dual-protocol-server",
mastra,
});
mcpServer.listen({ port: 8081 });
// Main HTTP server
serve({ fetch: app.fetch, port: 3000 });
console.log("HTTP API: http://localhost:3000");
console.log("MCP Server: http://localhost:8081");MCP Tools in Workflows
import { createStep } from "@mastra/core/workflows";
import { mcpTools } from "@mastra/core/mcp";
// Load MCP tools once
const externalTools = await mcpTools({
server: "http://mcp-server:8080",
});
const analyzeStep = createStep({
id: "analyze",
execute: async ({ inputData, mastra }) => {
// Use MCP tool directly
const analysisResult = await externalTools["analyze-data"].execute(
{ data: inputData.rawData },
{ mastra, runtimeContext: new RuntimeContext() }
);
return { analysis: analysisResult };
},
});Error Handling
Server-Side Errors
const mcpServer = createMCPServer({
name: "error-handling-server",
tools: {
riskyTool: createTool({
id: "risky-tool",
execute: async (input) => {
try {
const result = await riskyOperation(input);
return { success: true, data: result };
} catch (error) {
// MCP-compliant error response
throw new MCPError({
code: -32000,
message: error.message,
data: { input },
});
}
},
}),
},
});Client-Side Error Handling
try {
const result = await client.callTool("risky-tool", { value: 42 });
} catch (error) {
if (error instanceof MCPError) {
console.error("MCP Error:", error.code, error.message);
// Handle specific error codes
if (error.code === -32601) {
console.error("Tool not found");
}
} else {
console.error("Connection error:", error);
}
}Security Considerations
Authentication
const mcpServer = createMCPServer({
name: "secure-server",
mastra,
middleware: [
async (req, next) => {
const token = req.headers.get("authorization");
if (!token || !verifyToken(token)) {
throw new Error("Unauthorized");
}
return next();
},
],
});Tool Permissions
const mcpServer = createMCPServer({
name: "permission-server",
tools: {
publicTool: { ...weatherTool, permissions: ["public"] },
adminTool: { ...dangerousTool, permissions: ["admin"] },
},
authorize: async (toolId, context) => {
const userRole = context.headers.get("x-user-role");
const tool = tools[toolId];
if (tool.permissions.includes("admin") && userRole !== "admin") {
return false;
}
return true;
},
});Testing MCP Servers
import { describe, it, expect, beforeAll, afterAll } from "vitest";
import { MCPClient } from "@mastra/core/mcp";
describe("MCP Server", () => {
let server: MCPServer;
let client: MCPClient;
beforeAll(async () => {
server = createMCPServer({ name: "test", tools: { testTool } });
await server.listen({ port: 9999 });
client = new MCPClient({ server: "http://localhost:9999" });
});
afterAll(async () => {
await server.close();
});
it("should list tools", async () => {
const tools = await client.listTools();
expect(tools).toContainEqual(
expect.objectContaining({ name: "test-tool" })
);
});
it("should call tool", async () => {
const result = await client.callTool("test-tool", { input: "value" });
expect(result.success).toBe(true);
});
});Best Practices
1. Version your MCP servers - Use semantic versioning for compatibility 2. Document all tools - Clear descriptions help AI clients understand usage 3. Validate inputs - Use Zod schemas for all tool inputs 4. Handle errors gracefully - Return MCP-compliant error responses 5. Use authentication - Protect sensitive tools and resources 6. Monitor usage - Log tool calls for debugging and analytics 7. Test thoroughly - Unit test tools, integration test the server
RAG and Memory Patterns
Guide to implementing Retrieval-Augmented Generation (RAG) and conversation memory in Mastra.
Overview
Mastra supports multiple memory and retrieval patterns:
- Conversation Memory - Thread-based message history
- Vector Storage - Semantic search over documents
- RAG Integration - Combining retrieval with generation
- Hybrid Search - Vector + keyword search
Conversation Memory
Basic Memory Configuration
import { Mastra } from "@mastra/core/mastra";
import { LibSQLStore } from "@mastra/libsql";
const mastra = new Mastra({
agents: { myAgent },
storage: new LibSQLStore({
url: "file:./mastra.db",
}),
});Using Memory in Agent Calls
// First interaction
const response1 = await agent.generate("My name is Alex and I work at TechCorp", {
memory: {
thread: "conversation-123", // Unique thread ID
resource: "user-456", // User identifier
},
});
// Later in same thread - agent remembers context
const response2 = await agent.generate("What company do I work for?", {
memory: {
thread: "conversation-123",
resource: "user-456",
},
});
// Agent responds: "You work at TechCorp"Thread Management
// Create new thread
const thread = await mastra.storage?.createThread({
resourceId: "user-456",
metadata: {
topic: "customer-support",
createdAt: new Date().toISOString(),
},
});
// List threads for a user
const threads = await mastra.storage?.listThreads({
resourceId: "user-456",
page: 1,
perPage: 10,
});
// Get thread messages
const messages = await mastra.storage?.getMessages({
threadId: "thread-123",
page: 1,
perPage: 50,
});
// Delete thread
await mastra.storage?.deleteThread("thread-123");Message History Window
// Limit context window for cost/performance
const response = await agent.generate("Continue our conversation", {
memory: {
thread: "conversation-123",
resource: "user-456",
options: {
maxMessages: 10, // Last 10 messages
maxTokens: 4000, // Or token limit
},
},
});Vector Storage
Setting Up Vector Store
import { Mastra } from "@mastra/core/mastra";
import { PgVector } from "@mastra/pg-vector";
const mastra = new Mastra({
agents: { myAgent },
vectors: {
default: new PgVector({
connectionString: process.env.DATABASE_URL,
tableName: "embeddings",
}),
},
});Indexing Documents
import { embedMany } from "@ai-sdk/openai";
// Chunk and embed documents
const documents = [
{ id: "doc-1", content: "Mastra is a TypeScript AI framework...", metadata: { source: "docs" } },
{ id: "doc-2", content: "Agents in Mastra can use tools...", metadata: { source: "docs" } },
];
// Generate embeddings
const { embeddings } = await embedMany({
model: openai.embedding("text-embedding-3-small"),
values: documents.map(d => d.content),
});
// Store in vector database
await mastra.vectors?.default.upsert({
indexName: "knowledge-base",
vectors: documents.map((doc, i) => ({
id: doc.id,
vector: embeddings[i],
metadata: {
content: doc.content,
...doc.metadata,
},
})),
});Querying Vectors
import { embed } from "@ai-sdk/openai";
// Embed query
const { embedding } = await embed({
model: openai.embedding("text-embedding-3-small"),
value: "How do Mastra agents work?",
});
// Search
const results = await mastra.vectors?.default.query({
indexName: "knowledge-base",
queryVector: embedding,
topK: 5,
filter: { source: "docs" },
});
// results: [{ id, score, metadata: { content, source } }, ...]RAG Implementation
Basic RAG Tool
export const ragTool = createTool({
id: "knowledge-search",
description: "Search the knowledge base for relevant information",
inputSchema: z.object({
query: z.string().describe("Search query"),
limit: z.number().optional().default(5),
}),
outputSchema: z.object({
results: z.array(z.object({
content: z.string(),
score: z.number(),
source: z.string(),
})),
}),
execute: async (input, context) => {
const { query, limit } = input;
const { mastra } = context;
// Embed query
const { embedding } = await embed({
model: openai.embedding("text-embedding-3-small"),
value: query,
});
// Search
const results = await mastra?.vectors?.default.query({
indexName: "knowledge-base",
queryVector: embedding,
topK: limit,
});
return {
results: results?.map(r => ({
content: r.metadata.content,
score: r.score,
source: r.metadata.source,
})) || [],
};
},
});RAG-Enabled Agent
const ragAgent = new Agent({
name: "rag-agent",
instructions: `You are a helpful assistant with access to a knowledge base.
When answering questions:
1. First search the knowledge base for relevant information
2. Use the retrieved information to inform your response
3. Cite sources when possible
4. If the knowledge base doesn't have relevant info, say so`,
model: openai("gpt-4o-mini"),
tools: { ragTool },
});Advanced RAG with Reranking
export const advancedRagTool = createTool({
id: "advanced-search",
description: "Search with semantic reranking",
inputSchema: z.object({
query: z.string(),
limit: z.number().optional().default(10),
}),
execute: async (input, context) => {
const { query, limit } = input;
const { mastra } = context;
// Initial retrieval (over-fetch)
const { embedding } = await embed({
model: openai.embedding("text-embedding-3-small"),
value: query,
});
const candidates = await mastra?.vectors?.default.query({
indexName: "knowledge-base",
queryVector: embedding,
topK: limit * 3, // Fetch 3x for reranking
});
// Rerank with LLM
const reranker = new Agent({
name: "reranker",
model: openai("gpt-4o-mini"),
instructions: "Score relevance 0-10 for each document to the query.",
});
const reranked = await Promise.all(
candidates?.map(async (c) => {
const response = await reranker.generate(
`Query: ${query}\nDocument: ${c.metadata.content}\nScore (0-10):`,
{ output: z.object({ score: z.number() }) }
);
return { ...c, rerankedScore: response.object.score };
}) || []
);
// Sort by reranked score and take top results
return {
results: reranked
.sort((a, b) => b.rerankedScore - a.rerankedScore)
.slice(0, limit)
.map(r => ({
content: r.metadata.content,
score: r.rerankedScore,
})),
};
},
});Hybrid Search
Combining Vector and Keyword Search
export const hybridSearchTool = createTool({
id: "hybrid-search",
description: "Search using both semantic and keyword matching",
inputSchema: z.object({
query: z.string(),
keywords: z.array(z.string()).optional(),
}),
execute: async (input, context) => {
const { query, keywords } = input;
const { mastra } = context;
// Semantic search
const { embedding } = await embed({
model: openai.embedding("text-embedding-3-small"),
value: query,
});
const semanticResults = await mastra?.vectors?.default.query({
indexName: "knowledge-base",
queryVector: embedding,
topK: 10,
});
// Keyword search (if storage supports full-text search)
const keywordResults = keywords?.length
? await mastra?.storage?.search({
query: keywords.join(" "),
limit: 10,
})
: [];
// Merge and deduplicate
const merged = new Map();
semanticResults?.forEach(r => {
merged.set(r.id, { ...r, semanticScore: r.score, keywordScore: 0 });
});
keywordResults?.forEach((r: any) => {
if (merged.has(r.id)) {
merged.get(r.id).keywordScore = r.score;
} else {
merged.set(r.id, { ...r, semanticScore: 0, keywordScore: r.score });
}
});
// Combine scores (weighted average)
const results = Array.from(merged.values())
.map(r => ({
...r,
combinedScore: r.semanticScore * 0.7 + r.keywordScore * 0.3,
}))
.sort((a, b) => b.combinedScore - a.combinedScore);
return { results: results.slice(0, 10) };
},
});Memory Consolidation
Summarizing Long Conversations
const summarizeThread = async (threadId: string) => {
const messages = await mastra.storage?.getMessages({
threadId,
page: 1,
perPage: 100,
});
const summarizer = new Agent({
name: "summarizer",
model: openai("gpt-4o-mini"),
instructions: "Create a concise summary of the conversation.",
});
const conversation = messages
?.map(m => `${m.role}: ${m.content}`)
.join("\n");
const summary = await summarizer.generate(
`Summarize this conversation:\n\n${conversation}`
);
// Store summary as new message type
await mastra.storage?.addMessage({
threadId,
role: "system",
content: `[SUMMARY] ${summary.text}`,
metadata: { type: "summary", originalMessageCount: messages?.length },
});
return summary.text;
};Periodic Consolidation
// Workflow for periodic memory consolidation
const consolidationWorkflow = createWorkflow({
id: "memory-consolidation",
inputSchema: z.object({ threadId: z.string() }),
outputSchema: z.object({ consolidated: z.boolean() }),
})
.then(
createStep({
id: "check-thread-size",
execute: async ({ inputData, mastra }) => {
const messages = await mastra?.storage?.getMessages({
threadId: inputData.threadId,
page: 1,
perPage: 1,
});
// @ts-ignore - checking total count
const totalCount = messages?.totalCount || 0;
return { needsConsolidation: totalCount > 50 };
},
})
)
.branch([
[
async ({ inputData }) => inputData.needsConsolidation,
createStep({
id: "consolidate",
execute: async ({ inputData, mastra }) => {
await summarizeThread(inputData.threadId);
// Optionally archive old messages
return { consolidated: true };
},
}),
],
[
async () => true,
createStep({
id: "skip",
execute: async () => ({ consolidated: false }),
}),
],
])
.commit();Document Processing Pipeline
Chunking and Indexing Workflow
const indexDocumentWorkflow = createWorkflow({
id: "index-document",
inputSchema: z.object({
documentId: z.string(),
content: z.string(),
metadata: z.record(z.any()),
}),
outputSchema: z.object({
chunksIndexed: z.number(),
}),
})
.then(
createStep({
id: "chunk-document",
execute: async ({ inputData }) => {
// Split into chunks
const chunks = chunkDocument(inputData.content, {
maxChunkSize: 500,
overlap: 50,
});
return {
chunks: chunks.map((chunk, i) => ({
id: `${inputData.documentId}-chunk-${i}`,
content: chunk,
metadata: {
...inputData.metadata,
chunkIndex: i,
documentId: inputData.documentId,
},
})),
};
},
})
)
.then(
createStep({
id: "embed-chunks",
execute: async ({ inputData }) => {
const { embeddings } = await embedMany({
model: openai.embedding("text-embedding-3-small"),
values: inputData.chunks.map(c => c.content),
});
return {
vectors: inputData.chunks.map((chunk, i) => ({
id: chunk.id,
vector: embeddings[i],
metadata: chunk.metadata,
})),
};
},
})
)
.then(
createStep({
id: "store-vectors",
execute: async ({ inputData, mastra }) => {
await mastra?.vectors?.default.upsert({
indexName: "documents",
vectors: inputData.vectors,
});
return { chunksIndexed: inputData.vectors.length };
},
})
)
.commit();Best Practices
1. Chunk Size Optimization
// Smaller chunks for precise retrieval
const preciseChunks = chunkDocument(content, { maxChunkSize: 200 });
// Larger chunks for more context
const contextualChunks = chunkDocument(content, { maxChunkSize: 1000 });2. Metadata Enrichment
// Rich metadata enables better filtering
await vectorStore.upsert({
vectors: [{
id: "doc-1",
vector: embedding,
metadata: {
content: text,
source: "docs",
category: "api-reference",
createdAt: new Date().toISOString(),
author: "engineering-team",
version: "2.0",
},
}],
});3. Cache Embeddings
const embeddingCache = new Map<string, number[]>();
async function getEmbedding(text: string): Promise<number[]> {
const cacheKey = createHash("md5").update(text).digest("hex");
if (embeddingCache.has(cacheKey)) {
return embeddingCache.get(cacheKey)!;
}
const { embedding } = await embed({
model: openai.embedding("text-embedding-3-small"),
value: text,
});
embeddingCache.set(cacheKey, embedding);
return embedding;
}4. Thread Isolation
// Use unique thread IDs per conversation context
const threadId = `${userId}-${sessionId}`;
// Or per topic
const threadId = `${userId}-support-ticket-${ticketId}`;5. Memory Limits
// Prevent runaway context costs
const response = await agent.generate(message, {
memory: {
thread: threadId,
resource: userId,
options: {
maxMessages: 20,
maxTokens: 8000,
summarizeAfter: 15, // Auto-summarize after 15 messages
},
},
});#!/usr/bin/env -S deno run --allow-read
/**
* Check Mastra Version Patterns
*
* This script detects mixing of v1 Beta and stable (0.24.x) patterns
* that would cause runtime errors.
*
* Usage:
* deno run --allow-read scripts/check-version-patterns.ts ./src/mastra/
* deno run --allow-read scripts/check-version-patterns.ts ./src/mastra/tools/
*
* Detects:
* - Mixed tool signatures (v1 vs stable)
* - Deprecated imports from @mastra/core root
* - Deprecated memory options (threadId/resourceId)
* - Deprecated telemetry config
* - Legacy workflow data access patterns
*/
import { parse } from "https://deno.land/std@0.208.0/flags/mod.ts";
import { walk } from "https://deno.land/std@0.208.0/fs/walk.ts";
interface VersionIssue {
file: string;
line: number;
pattern: "v1" | "stable" | "deprecated";
issue: string;
found: string;
fix: string;
}
const PATTERNS = {
// Tool signature patterns
v1ToolSignature: {
pattern: /execute:\s*async\s*\(\s*(\w+)\s*,\s*(\w+)\s*\)\s*=>/g,
version: "v1" as const,
description: "v1 Beta tool signature: execute(inputData, context)",
},
stableToolSignature: {
pattern: /execute:\s*async\s*\(\s*\{\s*context/g,
version: "stable" as const,
description: "Stable tool signature: execute({ context, ... })",
},
// Import patterns
deprecatedImport: {
pattern: /import\s*\{[^}]+\}\s*from\s*["']@mastra\/core["']/g,
version: "deprecated" as const,
description: "Deprecated root import",
fix: 'Use subpath imports: @mastra/core/agent, @mastra/core/tools, etc.',
},
// Memory option patterns
deprecatedMemory: {
pattern: /threadId:\s*["'][^"']+["']/g,
version: "deprecated" as const,
description: "Deprecated threadId option",
fix: "Use memory: { thread: '...', resource: '...' }",
},
deprecatedResource: {
pattern: /resourceId:\s*["'][^"']+["']/g,
version: "deprecated" as const,
description: "Deprecated resourceId option",
fix: "Use memory: { thread: '...', resource: '...' }",
},
// Telemetry config
deprecatedTelemetry: {
pattern: /telemetry:\s*\{/g,
version: "deprecated" as const,
description: "Deprecated telemetry config",
fix: "Use observability: { default: { enabled: true } }",
},
// Legacy workflow patterns
legacyTriggerData: {
pattern: /context\.triggerData/g,
version: "stable" as const,
description: "Legacy triggerData access",
fix: "Use getInitData() to access original workflow input",
},
legacyStepsAccess: {
pattern: /context\.steps\.\w+\.output/g,
version: "stable" as const,
description: "Legacy steps.*.output access",
fix: 'Use inputData or getStepResult("step-id")',
},
// v1 specific patterns
v1RuntimeContext: {
pattern: /RequestContext/g,
version: "v1" as const,
description: "v1 Beta RequestContext (renamed from RuntimeContext)",
},
};
async function main() {
const args = parse(Deno.args, {
string: ["format", "target"],
boolean: ["help", "fix"],
default: {
format: "text",
target: "v1",
},
});
if (args.help || args._.length === 0) {
console.log(`
Check Mastra Version Patterns
Detects mixing of v1 Beta and stable (0.24.x) patterns that cause runtime errors.
Usage:
deno run --allow-read scripts/check-version-patterns.ts <path> [options]
Arguments:
<path> Path to file or directory to check
Options:
--target Target version: v1 (default), stable
--format Output format: text (default), json
--help Show this help
Examples:
deno run --allow-read scripts/check-version-patterns.ts ./src/mastra/
deno run --allow-read scripts/check-version-patterns.ts ./src/mastra/tools/ --target v1
`);
Deno.exit(0);
}
const targetPath = String(args._[0]);
const targetVersion = args.target as "v1" | "stable";
const issues: VersionIssue[] = [];
const stat = await Deno.stat(targetPath);
if (stat.isFile) {
const fileIssues = await checkFile(targetPath, targetVersion);
issues.push(...fileIssues);
} else if (stat.isDirectory) {
for await (const entry of walk(targetPath, {
exts: [".ts", ".tsx"],
includeDirs: false,
})) {
const fileIssues = await checkFile(entry.path, targetVersion);
issues.push(...fileIssues);
}
}
// Output results
if (args.format === "json") {
console.log(JSON.stringify({ issues, targetVersion }, null, 2));
} else {
outputText(issues, targetVersion);
}
// Exit with error if critical issues found
const criticalCount = issues.filter(
(i) => i.pattern !== targetVersion && i.pattern !== "deprecated"
).length;
if (criticalCount > 0) {
Deno.exit(1);
}
}
async function checkFile(
filePath: string,
targetVersion: "v1" | "stable"
): Promise<VersionIssue[]> {
const issues: VersionIssue[] = [];
const content = await Deno.readTextFile(filePath);
const lines = content.split("\n");
// Check each pattern
for (const [name, config] of Object.entries(PATTERNS)) {
const pattern = new RegExp(config.pattern.source, config.pattern.flags);
let match;
while ((match = pattern.exec(content)) !== null) {
const lineNumber = content.substring(0, match.index).split("\n").length;
const line = lines[lineNumber - 1];
// Determine if this is an issue based on target version
let isIssue = false;
let issueDescription = "";
let fix = "";
if (config.version === "deprecated") {
isIssue = true;
issueDescription = config.description;
fix = (config as any).fix || "Update to current API";
} else if (config.version !== targetVersion) {
isIssue = true;
issueDescription = `${config.description} (targeting ${targetVersion})`;
fix = targetVersion === "v1"
? "Update to v1 Beta pattern"
: "Update to stable pattern";
}
if (isIssue) {
issues.push({
file: filePath,
line: lineNumber,
pattern: config.version,
issue: issueDescription,
found: match[0].substring(0, 50) + (match[0].length > 50 ? "..." : ""),
fix,
});
}
}
}
// Check for mixed signatures in the same file
const hasV1Signature = PATTERNS.v1ToolSignature.pattern.test(content);
const hasStableSignature = PATTERNS.stableToolSignature.pattern.test(content);
if (hasV1Signature && hasStableSignature) {
issues.push({
file: filePath,
line: 0,
pattern: "deprecated",
issue: "File contains both v1 and stable tool signatures - this will cause errors",
found: "Mixed signatures detected",
fix: `Convert all tools to ${targetVersion} signature`,
});
}
return issues;
}
function outputText(issues: VersionIssue[], targetVersion: string) {
console.log(`\n🔍 Version Pattern Check (target: ${targetVersion})\n`);
if (issues.length === 0) {
console.log("✅ No version conflicts found");
return;
}
// Group by file
const byFile = new Map<string, VersionIssue[]>();
for (const issue of issues) {
const existing = byFile.get(issue.file) || [];
existing.push(issue);
byFile.set(issue.file, existing);
}
for (const [file, fileIssues] of byFile) {
console.log(`📄 ${file}`);
for (const issue of fileIssues) {
const icon = issue.pattern === "deprecated" ? "⚠️" : "❌";
console.log(` ${icon} Line ${issue.line}: ${issue.issue}`);
console.log(` Found: ${issue.found}`);
console.log(` Fix: ${issue.fix}`);
}
console.log();
}
// Summary
const deprecated = issues.filter((i) => i.pattern === "deprecated").length;
const wrongVersion = issues.filter((i) => i.pattern !== "deprecated" && i.pattern !== targetVersion).length;
console.log("📊 Summary:");
console.log(` Deprecated patterns: ${deprecated}`);
console.log(` Wrong version patterns: ${wrongVersion}`);
if (wrongVersion > 0) {
console.log(`\n❌ Found ${wrongVersion} patterns incompatible with ${targetVersion}`);
}
}
// Run main function
main();