---
title: "Mastra"
description: "Mastra is a TypeScript framework for agents, tools, and workflows. The Dex adapter runs a Mastra agent inside a durable session. It is available for TypeScript."
canonical_url: "https://docs.superdurable.io/agent-frameworks/mastra/"
source: "docs/content/agent-frameworks/mastra.mdx"
locale: "en"
---

# Mastra

Mastra is a TypeScript framework for agents, tools, and workflows. The Dex adapter runs a Mastra agent inside a durable session. It is available for TypeScript.

## When to use it

Use it when your agent already uses Mastra. Keep the agent, its tools, and its model router, and let Dex own the session.

## How a session runs

Every agent tool, read tools included, goes to Mastra as a client tool. So Mastra stops after the model call with the tool calls. Each **RunModel** Step makes one model call.

Each tool call runs the agent's original tool once, in its own Step, in parallel: **CallTool** for a read or an idempotent write, **CallSingleAttemptTool** for any other write. A call that needs approval waits in **AwaitToolApproval** first. The next **RunModel** adds the recorded results and calls the model again.

A turn that looks up an order runs **RunModel**, **CallTool**, **ResumeTurn**, and **RunModel**.

## Install

Install the adapter with its framework. Each adapter brings in the Dex Agents core.

**TypeScript**

```bash
npm i @superdurable/dex-agents-mastra @superdurable/dex-agents @superdurable/dex @mastra/core zod
```

Example: [agents/typescript/mastra/README.md](https://github.com/superdurable/dex/tree/main/agents/typescript/mastra/README.md)

## The example

The example is the [order support agent](https://docs.superdurable.io/agent-frameworks.md#the-examples) that every framework page uses.

The Flow type is **MastraOrderSupportAgent**. The full example is under [examples/typescript/src/products/agent-harnesses](https://github.com/superdurable/dex/tree/main/examples/typescript/src/products/agent-harnesses).

### Agent and tools

The tools are Mastra tools on a Mastra **Agent**. The tool policies name each tool.

**TypeScript**

```typescript
export function createMastraOrderSupportFlow(
  orderDesk: OrderDesk,
  model: MastraModelConfig,
  slack: Connection | undefined,
): AgentSessionFlow {
  const lookUpOrder = createTool({
    id: "look_up_order",
    description: "Look up one order by its order ID and return its items and status.",
    inputSchema: z.object({ order_id: z.string() }),
    execute: async ({ order_id }) => orderDesk.describeOrder(order_id),
  });
  const cancelOrder = createTool({
    id: "cancel_order",
    description: "Cancel one order that has not shipped yet.",
    inputSchema: z.object({ order_id: z.string(), reason: z.string() }),
    execute: async ({ order_id, reason }) => orderDesk.cancelOrder(order_id, reason),
  });
  const agent = new Agent({
    id: "order-support",
    name: "OrderSupport",
    instructions: orderSupportInstructions,
    model,
    tools: { look_up_order: lookUpOrder, cancel_order: cancelOrder },
  });
  return createMastraAgentSessionFlow(agent, mastraOrderSupportFlowType, {
    toolPolicies: {
      look_up_order: { effect: "read" },
      cancel_order: { effect: "non_idempotent_write" },
    },
    toolSources: createSlackConnectorToolSources(slack),
  });
}
```

Example: [examples/typescript/src/products/agent-harnesses/mastra/mastra-order-support-flow.ts](https://github.com/superdurable/dex/tree/main/examples/typescript/src/products/agent-harnesses/mastra/mastra-order-support-flow.ts)

### Worker

One Worker serves the session Flows of every harness in the example. Build the same session Flow in every process that starts sessions or calls their RPCs.

**TypeScript**

```typescript
// One Slack connection for the agent examples; without it they offer no Slack tools.
const slack = await openSlackConnectionFromProject();
// ...
// One order desk for the four agent harness examples; each harness registers its own session Flow type.
const orderDesk = new OrderDesk();
// ...
const mastraOrderSupportFlow = createMastraOrderSupportFlow(orderDesk, loadMastraOrderSupportModel(), slack);
```

Example: [examples/typescript/src/registry.ts](https://github.com/superdurable/dex/tree/main/examples/typescript/src/registry.ts)

### Session routes

Every harness serves these same routes. [Session routes](https://docs.superdurable.io/agent-frameworks.md#session-routes) explains them and the session token.

**TypeScript**

```typescript
  router.post("/sessions", async (request, response) => {
    const initialMessage = optionalString(requireBody(request), "message");
    // The server picks the session ID, so a caller cannot name another session.
    const sessionId = `order-support-${randomUUID()}`;
    await startAgentSession(client, flow, sessionId, { initialMessage }, { timeoutMs: 30 * DAY_MS });
    response.json({ session_id: sessionId, session_token: issueSessionToken(sessionId) });
  });
  router.get("/sessions/:id/snapshot", async (request, response) => {
    response.json(convertAgentSnapshotToJson(await client.invokeRPC(flow.getAgentSnapshot, sessionIdOf(request))));
  });
  router.post("/sessions/:id/messages", async (request, response) => {
    const sessionId = sessionIdOf(request);
    const message = { text: requireString(requireBody(request), "text") };
    sendAccepted(response, await client.invokeRPC(flow.sendUserMessage, sessionId, message));
  });
  router.post("/sessions/:id/interrupts/:interruptId/approve", async (request, response) => {
    const sessionId = sessionIdOf(request);
    const approval = { interruptId: String(request.params.interruptId), approved: true, rejectionMessage: null };
    sendAccepted(response, await client.invokeRPC(flow.resolveInterrupt, sessionId, approval));
  });
```

Example: [examples/typescript/src/products/agent-harnesses/order-support/order-support-routes.ts](https://github.com/superdurable/dex/tree/main/examples/typescript/src/products/agent-harnesses/order-support/order-support-routes.ts)

## Run it locally

### Start Dex and the example

In a first terminal, render the session graph, then start Dex. Dex Web reads the graph when it starts, to show the session and its approval controls.

**TypeScript**

```bash
cd examples/typescript
npm install
mkdir -p definitions
npx dex-agents-session-graph --flow-type MastraOrderSupportAgent \
  --permission order-support.operate --out definitions/mastra.json
dexcli dev --flow-rendering-dir ./definitions
```

Example: [examples/typescript/src/products/agent-harnesses/README.md](https://github.com/superdurable/dex/tree/main/examples/typescript/src/products/agent-harnesses/README.md)

**dexcli dev** stays in the foreground. In a second terminal, start the example:

**TypeScript**

```bash
cd examples/typescript
OPENAI_API_KEY=... MASTRA_TELEMETRY_DISABLED=1 npm start
```

Example: [examples/typescript/src/products/agent-harnesses/README.md](https://github.com/superdurable/dex/tree/main/examples/typescript/src/products/agent-harnesses/README.md)

The app serves on **127.0.0.1:8080**. Dex Web is at **<http://127.0.0.1:8802>**.

The example has no demo model. Its model router reads the provider key, such as **OPENAI_API_KEY**, from the Worker's environment.

| Variable                      | Default                        | Meaning                                      |
| ----------------------------- | ------------------------------ | -------------------------------------------- |
| **DEX_MASTRA_MODEL**          | **openai/gpt-5-mini**          | A Mastra model router ID.                    |
| **OPENAI_API_KEY**            | none; required for the default | The router's provider key.                   |
| **MASTRA_TELEMETRY_DISABLED** | unset                          | Set to **1** to turn off Mastra's telemetry. |
| **DEX_AGENT_SESSION_KEY**     | a random key                   | The key that signs session tokens.           |

### Call the session

Start a session, ask the agent to cancel order 1002, and approve the call:

**TypeScript**

```bash
base=http://127.0.0.1:8080/products/agent-harnesses/mastra
session=$(curl -s -X POST "$base/sessions" -H 'Content-Type: application/json' \
  -d '{"message": "Please cancel order 1002"}')
id=$(echo "$session" | jq -r .session_id)
token=$(echo "$session" | jq -r .session_token)
until interrupt=$(curl -s "$base/sessions/$id/snapshot" -H "X-Agent-Session-Token: $token" \
  | jq -re '.pending_interrupts[0].interrupt_id'); do sleep 1; done
curl -s -X POST "$base/sessions/$id/interrupts/$interrupt/approve" -H "X-Agent-Session-Token: $token"
```

Example: [examples/typescript/src/products/agent-harnesses/README.md](https://github.com/superdurable/dex/tree/main/examples/typescript/src/products/agent-harnesses/README.md)

The loop reads the snapshot until the agent asks to run **cancel_order**. That call waits for approval, then runs once. Read the snapshot again to see the agent's answer.

### Approve in Dex Web

Instead of the last command, you can approve the call in Dex Web. Follow the steps in [Approve in Dex Web](https://docs.superdurable.io/agent-frameworks.md#approve-in-dex-web).

<a id="connect-connectors"></a>

## Connect Connectors

The agent can call [Connector](https://docs.superdurable.io/concepts/connector.md) operations as tools, as [Connectors as tools](https://docs.superdurable.io/agent-frameworks.md#connectors-as-tools) describes. The example adds the Slack tool **slack_post_thread_reply**, which replies in a Slack thread.

<a id="offer-connector-tools"></a>

### Offer the tool

Install **@superdurable/connectors-sdk** and the Connector package, such as **@superdurable/connector-slack**. The install commands are in [Connector tools](https://docs.superdurable.io/reference/agents/connector-tools.md#install).

The Worker opens the Slack connection **support-slack** from the project configuration. **connectorTool** turns the operation into a tool, and **ConnectorToolSource** holds it. Without the connection, the agent gets no Slack tool.

**TypeScript**

```typescript
export async function openSlackConnectionFromProject(
  environment: NodeJS.ProcessEnv = process.env,
): Promise<Connection | undefined> {
  // ...
  const project = await loadFromEnvironment(environment);
  // ...
  return newProjectConnection(project, slackConnectionName);
}

// ...

export function createSlackConnectorToolSources(slack: Connection | undefined): ToolSource[] {
  if (slack === undefined) {
    return [];
  }
  return [
    new ConnectorToolSource([
      connectorTool(slack.operations.postThreadReply(), slack.reference, {
        manifest,
        inputValidator: PostThreadReplyInputSchema,
        // The manifest does not ask for approval; this agent asks before every post.
        requiresApproval: true,
      }),
    ]),
  ];
}
```

Example: [examples/typescript/src/shared/slack-connector-tools.ts](https://github.com/superdurable/dex/tree/main/examples/typescript/src/shared/slack-connector-tools.ts)

The Worker opens the connection once, at startup, and every agent uses it:

**TypeScript**

```typescript
// One Slack connection for the agent examples; without it they offer no Slack tools.
const slack = await openSlackConnectionFromProject();
```

Example: [examples/typescript/src/registry.ts](https://github.com/superdurable/dex/tree/main/examples/typescript/src/registry.ts)

The agent passes the tool source next to its own tools:

**TypeScript**

```typescript
  return createMastraAgentSessionFlow(agent, mastraOrderSupportFlowType, {
    // ...
    toolSources: createSlackConnectorToolSources(slack),
  });
```

Example: [examples/typescript/src/products/agent-harnesses/mastra/mastra-order-support-flow.ts](https://github.com/superdurable/dex/tree/main/examples/typescript/src/products/agent-harnesses/mastra/mastra-order-support-flow.ts)

<a id="connector-tool-calls"></a>

### How a call runs

Each call of **slack_post_thread_reply** runs once, in its own **CallSingleAttemptTool** Step, after a person approves it. [The Slack Connector tool](https://docs.superdurable.io/agent-frameworks.md#slack-connector-tool) explains why.

The adapter passes the Connector tools to Mastra as client tools without **execute**. A call never runs inside the model call. Mastra approval settings do not apply to it; the source decides approval. With tool sources, a **toolPolicies** entry never makes a tool one that Mastra runs itself; only **mastraRunToolNames** does.

<a id="configure-connector-connection"></a>

### Configure the connection

[Connections](https://docs.superdurable.io/reference/agents/connector-tools.md#connector-tools-connection) explains where the Worker finds the **support-slack** connection and what the Slack app needs.

To try it, start the example with **DEX_CONNECTOR_CONFIG_FILE** set. Then ask the agent to reply in a Slack thread, with the channel ID and the thread timestamp:

```text
Reply in Slack thread C0123456789 1710000000.000100: Order 1002 ships on Thursday.
```

The post waits for approval, like **cancel_order**.

## Limits

- Tools Mastra runs itself, such as sub-agents, workflows, and Memory tools, cannot run in a Step. The adapter offers one only when **toolPolicies** names it as a read without approval. It then runs inside **RunModel** and repeats when that Step reruns.
- Mastra Memory is not used. Do not select a Memory thread in the agent's defaults or scopes.
- **beforeToolCall** and **afterToolCall** hooks run only for tools Mastra runs itself.
- A tool result reaches the model as text. **toModelOutput** is not applied.
- A tool that calls **suspend()** fails. Use the session's approval instead.
- Tool names must be at most 63 characters of letters, digits, **\_**, and **-**, and must not start with a digit or **-**.

The adapter README lists every limit. See [Limits for long sessions](https://docs.superdurable.io/reference/agents/session-limits.md) for the server limits.

## Related

- [Agent Frameworks](https://docs.superdurable.io/agent-frameworks.md)
- [Agents reference](https://docs.superdurable.io/reference/agents.md)
- [TypeScript adapter README](https://github.com/superdurable/dex/tree/main/agents/typescript/mastra)
