> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.astropods.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.astropods.com/_mcp/server.

# Ask for input from a Mastra tool

A Mastra tool asks the user for input by suspending. The adapter maps
`suspend()` onto the platform's form and maps the answer back onto `resumeData`,
so the tool needs no platform-specific code.

Read [Ask the user for input](/ask-for-input) first for what the user sees, the
actions they can take, and the behavior to design around. This page covers the
Mastra side.

## Write the tool

Add `suspendSchema` and `resumeSchema` to the tool, call `suspend()` where you
want to pause, and branch on `resumeData` when the tool runs again.

```typescript
import { createTool } from "@mastra/core/tools";
import { z } from "zod";

export const archiveRecordTool = createTool({
  id: "archiveRecord",
  description: "Archive a record. Asks the user to confirm before writing.",
  inputSchema: z.object({ recordId: z.string() }),
  suspendSchema: z.object({ message: z.string() }),
  resumeSchema: z.object({
    confirm: z.boolean().describe("Set true to archive. False cancels."),
  }),
  outputSchema: z.object({ archived: z.boolean(), message: z.string() }),
  execute: async (input, ctx) => {
    const resumeData = ctx?.agent?.resumeData as { confirm?: boolean } | undefined;

    if (resumeData?.confirm === true) {
      await archive(input.recordId);
      return { archived: true, message: `Archived ${input.recordId}.` };
    }
    if (resumeData) {
      return { archived: false, message: "Canceled. Nothing was archived." };
    }

    const record = await load(input.recordId);
    await ctx.agent!.suspend({
      message: `Archive "${record.name}"? This cannot be undone.`,
    });

    return { archived: false, message: "Waiting for confirmation." };
  },
});
```

`resumeSchema` is the shape of the answer, so it is also the shape of the form.
A confirmation is one boolean. Use a richer `resumeSchema` to collect a value,
and the form grows the fields to match.

Three details matter.

`resumeData` is on `ctx.agent`, not on the context root.

The tool runs again from the top after the user answers, so check `resumeData`
first. Treat anything other than an explicit confirmation as a decline.

The user sees the `message` field of your suspend payload. `prompt`, `reason`,
and `question` also work. Put the specifics in it: name the record, not just its
id, so the person is confirming the thing they think they are.

## Give the agent durable storage

A suspended run is a snapshot. Without somewhere to persist it, the user answers
and the resume fails with `could not find a suspended run`.

Register the agent on a Mastra instance with storage:

```typescript
import { Mastra } from "@mastra/core";
import { LibSQLStore } from "@mastra/libsql";

export const mastra = new Mastra({
  agents: { "my-agent": agent },
  storage: new LibSQLStore({ id: "runs", url: "file:/data/agent-runs.db" }),
});
```

An agent's `Memory` store does not cover this. Memory holds conversation
history; suspended runs live in the workflows store on the Mastra instance.

Write to `/data`, the mounted volume, rather than to `:memory:`. A pending
question has no timeout, so it may outlive a restart, and a file-backed snapshot
survives one.

## Next steps

* [Ask the user for input](/ask-for-input): the platform primitive, and the framework-agnostic path
* [Mastra adapter](/adapters/mastra): what else the adapter wires up
* [Store data with SQLite](/agent-sqlite): the mounted volume this storage writes to