Skip to navigation

Mastra adapter

Connect a Mastra Agent to the Astropods runtime
View as Markdown

@astropods/adapter-mastra runs a Mastra Agent on Astropods. With one call your agent chats with users, streams its progress, handles voice, and is traced, with no extra wiring.

Install

bun add @astropods/adapter-mastra
# or: npm install @astropods/adapter-mastra

Requires @mastra/core >= 1.14.0 as a peer dependency.

Quick start

import { Agent } from '@mastra/core/agent';
import { serve } from '@astropods/adapter-mastra';
const agent = new Agent({
id: 'my-agent',
name: 'My Agent',
instructions: 'You are a helpful assistant.',
model: 'openai/gpt-4o',
});
serve(agent);

serve(agent) runs until the process exits. The same code works locally and when deployed.

API

serve(agent, options?)

Connects a Mastra Agent to the messaging service and starts listening.

ParameterTypeRequiredNotes
agentAgent (from @mastra/core)yesA Mastra Agent. Must have at least id, name, model, and instructions.
optionsServeOptions & MastraAdapterOptionsnoBridge overrides and agent capabilities.

ServeOptions

FieldTypeDefaultNotes
serverAddressstringprocess.env.GRPC_SERVER_ADDR || 'localhost:9090'Override the messaging gRPC address.

MastraAdapterOptions

FieldTypeDefaultNotes
supportsFilesbooleanfalseEnable the chat’s upload controls. Until an agent sets this, the paperclip is greyed out and drag-and-drop is inert. See Files in chat.
supportsHistorybooleantrueLet users edit sent messages in the web chat. Set false if the agent keeps conversation state outside Mastra memory. See Edited messages.
serve(agent, { supportsFiles: true });

The model sees attached images directly. For any other file it gets the file’s path, so give an agent that accepts documents a tool that reads a file from a path. Without one, the model tends to describe a file it never read. For full control over attachments, write a custom adapter.

MastraAdapter

The class behind serve(). Use it directly if you need custom lifecycle control:

import { MastraAdapter } from '@astropods/adapter-mastra';
import { serve } from '@astropods/adapter-core';
serve(new MastraAdapter(agent));

Memory

Conversation memory and per-user traces work without setup. Each conversation is its own memory thread, and each user is its own resource.

When a user edits a message in the web chat, the adapter rebuilds the conversation’s thread from the turns before the edit, so the agent forgets the replaced turns. Thread-scoped working memory starts over with it.

Voice

Configure a Mastra voice provider on the agent (see Mastra’s voice docs) to accept voice messages. The agent transcribes what the user said, replies, and speaks the reply when the provider can synthesize speech. Without a voice provider, the user gets a friendly error for a voice message.

Tracing

A deployed agent is traced with no code changes. Every model call and tool call appears as a span, and each response, along with any feedback on it, links back to its trace.

Example: an agent with tools

import { Agent } from '@mastra/core/agent';
import { createTool } from '@mastra/core/tools';
import { serve } from '@astropods/adapter-mastra';
import { z } from 'zod';
const lookup = createTool({
id: 'customer_lookup',
description: 'Look up a customer by ID',
inputSchema: z.object({ id: z.string() }),
execute: async ({ context }) => {
return await fetch(`https://api.example.com/customers/${context.id}`).then(r => r.json());
},
});
const agent = new Agent({
id: 'support-agent',
name: 'Support Agent',
instructions: 'Help the user troubleshoot. Use customer_lookup when you need account details.',
model: 'anthropic/claude-sonnet-4-6',
tools: { lookup },
});
serve(agent);

Tool names and descriptions show in the playground. While a tool runs, the user sees Running customer_lookup, then Finished customer_lookup.

Sandboxes

Give a Mastra Workspace an Astropods sandbox, and its built-in tools run commands and edit files in an isolated VM instead of on the agent’s own machine. Declare a sandbox in astropods.yml first, as Sandbox client describes.

import { AstroSandbox } from '@astropods/adapter-mastra';
import { Workspace } from '@mastra/core/workspace';
const workspace = new Workspace({
sandbox: new AstroSandbox({ name: threadId }),
});

Use the thread id as name, so a resumed conversation gets its own files back and two threads never share them. AstroSandboxOptions accepts every SandboxClient option, plus client to share one client across sandboxes.

The workspace tells the model about the sandbox, including that long work belongs in a background process, so you do not add that to the agent’s instructions.

  • executeCommand runs its command through a shell. Pass onStdout or onStderr to stream output while the command runs.
  • stop() suspends the sandbox, and destroy() deletes it and its files.
  • snapshot() also suspends. There are no checkpoints to restore later.

See Sandbox client for lifecycle thresholds, caps, and limitations. Sandboxes are experimental.

Local development

ast project start

Chat with the agent at http://localhost:3100. See ast project for the other commands.

FAQ

To the Astropods messaging service at localhost:9090, or GRPC_SERVER_ADDR when set. ast project start and a deployment both set it, so you only pass serverAddress to override it.

Mastra fullStream chunkHook call
text-deltaonChunk(payload.text)
reasoning-startonStatusUpdate({ status: 'THINKING' })
reasoning-endonStatusUpdate({ status: 'GENERATING' })
tool-call-input-streaming-startonStatusUpdate({ status: 'PROCESSING', customMessage: 'Running <tool>' })
tool-call-input-streaming-endonStatusUpdate({ status: 'ANALYZING', customMessage: 'Finished <tool>' })
finishonFinish()
erroronError(err)

Other chunk types are ignored. To map more, subclass MastraAdapter and override stream().

The adapter receives the audio, transcribes it with voice.listen, and shows the transcript in the conversation. It then runs the agent on the transcript and, when voice.speak exists, streams the spoken reply back. The audio format comes from the message’s encoding (see Audio).

Astropods sets OTEL_EXPORTER_OTLP_ENDPOINT on a deployed agent, and serve() turns on Mastra’s observability when it is set. The adapter attaches each turn’s trace context to its responses, which is how feedback links to a trace (see Trace context).

MastraSandbox call
start()One attach.
executeCommand(command, args, options)exec, or a background process when options.onStdout or options.onStderr is set.
writeFiles(files)writeFile for string content, writeFileBytes for Uint8Array.
processesspawn, processes, poll, and kill.
handle.wait()Polls to exit, passing each chunk to the handle’s callbacks.
stop()stop, which suspends the VM.
destroy()delete.
snapshot()stop.
getInfo()get, reporting createdAt, lastUsedAt, and timeoutAt.

start() always reports the created outcome, because Astropods does not say whether an attach launched a new VM or resumed a suspended one. Reporting created repeats setup rather than risk skipping it on a new machine. Keep that setup safe to run twice.

No. Mastra reads the sandbox’s status at the start of every workspace tool, and getInfo() reports a sandbox that does not exist yet without creating one.

Troubleshooting

SymptomLikely cause
Waiting for messaging service (attempt N/10, ...)Sidecar isn’t up yet. Connection retries with exponential backoff.
Agent has no voice provider configuredAn audio message arrived but agent.voice is not configured.
Status indicator shows Running undefinedA tool was defined without an id. Set id on every createTool call.
Traces missingCheck OTEL_EXPORTER_OTLP_ENDPOINT is set.