Agents and tools
Add an agent
An agent is a plain object. Create agents/src/agents/support.ts:
import type { AgentSpec } from '@rubrion/mastra-cloudflare';
export const support: AgentSpec = {
id: 'support',
name: 'Support',
// The "auto" router picks agents by this line: say what the agent is for.
description: 'Answers questions about our product, plans and billing.',
instructions: `You are our support agent. Be brief and kind.
If you don't know, say so and offer the contact page.`,
maxOutputTokens: 800,
};
List it in agents/src/index.ts:
export const agents = defineAgents([writer, researcher, support], { memory });
It appears in /v1/models, on the settings page (where you can turn it off) and in auto routing.
:::tip Write the description for the router
auto routing reads only the agents' descriptions. Name what the agent is for and the kind of request it takes ("Answers questions about our plans and billing"), not how it works.
:::
Add a tool
Tools are Mastra tools: an id, a description the model reads, an input schema and a function.
import { createTool, z } from '@rubrion/mastra-cloudflare';
import { env } from 'cloudflare:workers';
export const lookupOrder = createTool({
id: 'lookup_order',
description: 'Status of an order by its number.',
inputSchema: z.object({ order: z.string() }),
execute: async ({ order }) => {
const res = await fetch(`https://api.example.com/orders/${order}`, {
headers: { authorization: `Bearer ${env.ORDERS_TOKEN}` },
});
return res.ok ? res.json() : { error: `HTTP ${res.status}` };
},
});
Give it to an agent: tools: { lookup_order: lookupOrder }, and mention it in the agent's instructions ("Use lookup_order for order status").
A secret a tool needs goes in the agents Worker, never in a prompt:
cd agents && npx wrangler secret put ORDERS_TOKEN
Locally, put it in agents/.dev.vars. Declare it in agents/src/env.d.ts so TypeScript knows it.
Models
Every agent uses, in this order:
- its own
model, when it has one; - the provider set on the settings page (any OpenAI-compatible endpoint);
- the built-in Workers AI model,
DEFAULT_MODELinagents/src/index.ts(Gemma 4). It must support tool calls.
An agent with its own model:
import { openAICompatible, workersAI } from '@rubrion/mastra-cloudflare';
import { env } from 'cloudflare:workers';
export const coder: AgentSpec = {
id: 'coder',
name: 'Coder',
description: 'Writes and reviews code.',
instructions: 'You write correct, idiomatic code. Explain briefly.',
model: () => openAICompatible({ baseURL: 'https://openrouter.ai/api/v1', apiKey: env.OPENROUTER_KEY, model: 'qwen/qwen3-coder' }),
// or, on Workers AI with its thinking on:
// model: () => workersAI(env.AI, '@cf/qwen/qwen3-30b-a3b-fp8', { thinking: true }),
};
Routing
With model: "auto", a planner (on the same provider) reads the agents' descriptions and the last few turns, and decides who answers the last message:
- one agent, when that's enough (most of the time);
- up to three agents for clearly distinct deliverables ("summarize this page and write a tweet"). Each gets only its part, and their replies are merged under their names.
When the planner fails, the first agent answers. To turn routing off, pass planner: false to createAgentServer in agents/src/index.ts; clients then name an agent.
Memory
Clients send the conversation in messages, so agents are stateless by default: that is how rubry.ai, Open WebUI and the OpenAI SDK work.
A client can send an X-Session-Id header instead. Then only the new message is needed: the agent recalls the session's last messages from D1 (the MEMORY binding), per agent. Change how many with lastMessages in agents/src/index.ts.