Skip to content

Building agents

Build one

The shortest path to a working Mastra agent: define a tool → define an agent → register it on the runtime → call it.

[!NOTE] APIs evolve across @mastra/core versions. The exact import paths and the version this targets are recorded in the research dossier at _INBOX/2-Research/Mastra/; treat the snippets below as the shape of the API and confirm against the version you install.

A tool is a typed function the agent can call. Input/output are validated with Zod.

import { createTool } from '@mastra/core/tools';
import { z } from 'zod';
export const weatherTool = createTool({
id: 'weather-tool',
description: 'Fetches current weather for a location',
inputSchema: z.object({ location: z.string().describe('City, e.g. "San Francisco"') }),
outputSchema: z.object({ weather: z.string() }),
execute: async (inputData) => {
const { location } = inputData;
const response = await fetch(`https://wttr.in/${location}?format=3`);
return { weather: await response.text() };
},
});

Bind instructions, a model, the tool, and (optionally) memory. The model is a model-router string ("provider/model"); you can also pass an AI SDK provider module directly (e.g. groq('gemma2-9b-it')).

import { Agent } from '@mastra/core/agent';
import { Memory } from '@mastra/memory';
import { LibSQLStore } from '@mastra/libsql';
import { weatherTool } from './tools/weather-tool';
export const weatherAgent = new Agent({
id: 'weather-agent',
name: 'Weather Agent',
instructions: 'You are a concise weather assistant. Use weather-tool before answering, and remember the user\u2019s preferences.',
model: 'openai/gpt-5.5', // model-router string; or an AI SDK module, e.g. groq('gemma2-9b-it')
tools: { weatherTool },
memory: new Memory({
storage: new LibSQLStore({ id: 'agent-memory', url: 'file:./mastra.db' }),
}),
});

The single Mastra instance owns every agent and workflow. Give it storage and a logger — memory and tracing need a storage provider.

import { Mastra } from '@mastra/core';
import { LibSQLStore } from '@mastra/libsql';
import { PinoLogger } from '@mastra/loggers';
import { weatherAgent } from './agents/weather-agent';
export const mastra = new Mastra({
agents: { weatherAgent },
storage: new LibSQLStore({ id: 'mastra-storage', url: 'file:./mastra.db' }),
logger: new PinoLogger({ name: 'Mastra', level: 'info' }),
});

Routes ask the runtime for the agent by id — they never import it directly. Pass memory (a resource + thread) so the agent keeps continuity per user/conversation.

import { mastra } from './mastra';
const agent = mastra.getAgentById('weather-agent');
// One-shot — returns text plus a traceId you can look up in observability.
const res = await agent.generate('What should I wear in Paris today?', {
memory: { resource: 'user-123', thread: 'weather-chat' },
});
console.log(res.text, res.traceId);
// Streaming
const stream = await agent.stream('And tomorrow?');
for await (const chunk of stream.textStream) process.stdout.write(chunk);

[!TIP] Streaming to an AI SDK UI frontend? Use handleChatStream from @mastra/ai-sdk — it bridges a registered agent to AI SDK UI / AI Elements message streams.


Reference: Agents · Using tools · Memory · Models

Next: Workflows — when one agent call isn’t enough.