@mastra/core
Version:
292 lines (221 loc) • 12.5 kB
Markdown
> Discover all available pages from the documentation index: https://mastra.ai/llms.txt
# Stored workflow definition
> **Beta:** Stored workflows are in beta. Breaking changes may occur without a major version bump until the API is stable.
A stored workflow definition is a JSON-compatible `StoredWorkflowGraph` accepted by [`Mastra.addStoredWorkflow()`](https://mastra.ai/reference/core/addStoredWorkflow), the stored-workflow server routes, and the Client SDK workflows API.
See [Stored workflows](https://mastra.ai/docs/workflows/stored-workflows) for a complete setup and usage example.
## Definition fields
| Field | Type | Required | Description |
| ---------------------- | --------------------------- | -------- | ------------------------------------------------------------------------------ |
| `id` | `string` | Yes | Unique workflow ID. This is also the ID used to retrieve and run the workflow. |
| `description` | `string` | No | Human-readable description |
| `inputSchema` | `JsonSchema` | Yes | JSON Schema for the workflow input |
| `outputSchema` | `JsonSchema` | Yes | JSON Schema for the workflow output |
| `stateSchema` | `JsonSchema` | No | JSON Schema for shared workflow state |
| `requestContextSchema` | `JsonSchema` | No | JSON Schema for values read from the request context |
| `metadata` | `Record<string, unknown>` | No | Arbitrary JSON metadata preserved through storage |
| `graph` | `SerializedStepFlowEntry[]` | Yes | Step entries that make up the workflow |
Schemas use JSON Schema rather than Zod so the definition can round-trip through JSON. Mastra converts each schema to Zod when it registers the workflow.
```json
{
"id": "greeting-workflow",
"description": "Returns a greeting for the supplied name",
"inputSchema": {
"type": "object",
"properties": { "name": { "type": "string" } },
"required": ["name"]
},
"outputSchema": {
"type": "object",
"properties": { "message": { "type": "string" } },
"required": ["message"]
},
"graph": [
{
"type": "mapping",
"id": "create-greeting",
"mapConfig": "{\"message\":{\"template\":\"Hello, ${initData.name}!\"}}"
}
]
}
```
## Graph entries
Entries in the `graph` run in order. Each entry receives the previous entry's output, and the first entry receives the workflow input.
| Entry type | Description |
| ------------- | ------------------------------------------------------ |
| `agent` | Invoke a registered agent |
| `tool` | Invoke a registered tool |
| `mapping` | Reshape data between steps |
| `workflow` | Invoke a registered workflow as a nested step |
| `parallel` | Run several steps concurrently and merge their outputs |
| `conditional` | Run every branch whose predicate is true, concurrently |
| `foreach` | Run one step per item of an array input |
| `loop` | Repeat a step while or until a predicate holds |
| `sleep` | Pause for a fixed duration |
| `sleepUntil` | Pause until a fixed date |
Code-defined workflows that use [`.agent()`](https://mastra.ai/reference/workflows/workflow-methods/agent) and [`.tool()`](https://mastra.ai/reference/workflows/workflow-methods/tool) produce the same declarative entries when serialized.
### Agent steps
An `agent` entry invokes a registered agent by ID. Agent steps accept `{ prompt: string }` as input and return `{ text: string }` by default.
```json
{
"type": "agent",
"id": "summarize",
"agentId": "support-agent"
}
```
The `id` identifies this call site within the workflow. Later steps address the result as `stepResults.summarize`, regardless of the agent's own ID.
Add an `outputSchema` to request structured output from the agent:
```json
{
"type": "agent",
"id": "extract-subtopics",
"agentId": "support-agent",
"outputSchema": {
"type": "array",
"items": {
"type": "object",
"properties": { "title": { "type": "string" } },
"required": ["title"]
}
}
}
```
Use a `mapping` entry before an agent to build its `{ prompt }` input from workflow data.
Agent entries accept an optional `description` and an `options` object:
```json
{
"type": "agent",
"id": "summarize",
"agentId": "support-agent",
"description": "Summarize the incoming request",
"options": { "retries": 2, "metadata": { "team": "support" } }
}
```
Only `retries` and `metadata` persist. Function-valued options such as `onFinish` and function-valued `toolChoice` are rejected when a code-defined workflow is stored. Other agent call options don't persist.
### Tool steps
A `tool` entry invokes a tool by its registration key from the `Mastra` `tools` object. Mastra resolves the tool's input and output schemas from the registry when it registers the workflow.
```json
{
"type": "tool",
"id": "lookup",
"toolId": "lookup-customer"
}
```
Tool entries accept the same optional `description` and `options` fields as agent entries. Only `retries` and `metadata` persist.
### Mapping steps
A `mapping` entry reshapes data. Its `mapConfig` is a JSON string that encodes an object. Each key becomes a key in the step output, and each descriptor defines one source.
| Descriptor | Description |
| -------------------------------------- | ----------------------------------------- |
| `{ "value": ... }` | A constant JSON value |
| `{ "template": "..." }` | A string built from `${...}` placeholders |
| `{ "initData": true, "path": "a.b" }` | A value from the workflow input |
| `{ "step": "step-id", "path": "a.b" }` | A value from a preceding step's output |
| `{ "requestContextPath": "a.b" }` | A value from the request context |
The `step` source also accepts an array of step IDs:
```json
{ "step": ["escalate", "auto-reply"], "path": "text" }
```
The first listed step with a non-empty result supplies the value. This can select the branch that ran after a `conditional` entry.
Templates resolve placeholders against `initData`, `inputData`, `state`, `requestContext`, and `stepResults.<step-id>`:
```json
{
"type": "mapping",
"id": "build-prompt",
"mapConfig": "{\"prompt\":{\"template\":\"Summarize this request: ${initData.request}\"}}"
}
```
Objects and arrays resolved by a template are stringified as JSON. A `null` value inside a present result renders as an empty string. A template that references a step without a successful output fails the run.
Mapping entries must be top-level graph entries. They can't be placed inside `parallel`, `conditional`, `foreach`, or `loop` containers.
### Nested workflow steps
A `workflow` entry invokes another registered workflow. The target can be code-defined or stored.
```json
{
"type": "workflow",
"id": "lookup-first",
"workflowId": "lookup-customer-workflow"
}
```
The `id` identifies the call site. The same nested workflow can appear several times under different call-site IDs, and later steps address each result as `stepResults.<id>`. A `workflow` entry also accepts an optional `description`.
### Parallel entries
A `parallel` entry runs several single steps concurrently and merges their outputs into an object keyed by step ID.
```json
{
"type": "parallel",
"steps": [
{ "type": "tool", "id": "first", "toolId": "lookup-customer" },
{ "type": "tool", "id": "second", "toolId": "lookup-customer" }
]
}
```
Each child must be an `agent`, `tool`, or `workflow` entry. All children receive the parallel entry's input directly.
### Conditional entries
A `conditional` entry pairs each step with a declarative predicate and runs every branch whose predicate is true.
```json
{
"type": "conditional",
"steps": [
{ "type": "agent", "id": "escalate", "agentId": "support-agent" },
{ "type": "agent", "id": "auto-reply", "agentId": "support-agent" }
],
"predicates": [
{ "op": "eq", "left": { "path": "inputData.priority" }, "right": { "literal": "urgent" } },
{ "op": "ne", "left": { "path": "inputData.priority" }, "right": { "literal": "urgent" } }
]
}
```
Each child must be an `agent`, `tool`, or `workflow` entry, and each child needs a predicate. All children receive the conditional entry's input directly.
### Predicates
Conditional entries and loops use a JSON predicate DSL. Operands are `{ "path": "..." }` references or `{ "literal": ... }` values. Paths resolve against `initData`, `inputData`, `stepResults`, and `state`.
| Operator | Shape |
| ------------------------------------ | -------------------------------------------- |
| `eq`, `ne`, `lt`, `lte`, `gt`, `gte` | `{ "op": "eq", "left": ..., "right": ... }` |
| `in`, `notIn` | `{ "op": "in", "value": ..., "set": [...] }` |
| `exists`, `notExists` | `{ "op": "exists", "path": "..." }` |
| `truthy`, `falsy` | `{ "op": "truthy", "value": ... }` |
| `and`, `or` | `{ "op": "and", "args": [...] }` |
| `not` | `{ "op": "not", "arg": ... }` |
Missing paths don't throw. Path-based operators return `false` when the path can't be resolved. Use `exists` or `notExists` to distinguish a missing value from a falsy value.
### Foreach entries
A `foreach` entry runs its body once for each item in an array input. The preceding entry must produce a raw array. Results preserve input order, and concurrency defaults to `1`.
```json
{
"type": "foreach",
"step": { "type": "workflow", "id": "write-blurb", "workflowId": "blurb-workflow" },
"opts": { "concurrency": 3 }
}
```
The body can be an `agent`, `tool`, or `workflow` entry, but not a `mapping` entry.
### Loop entries
A `loop` repeats one step while (`dowhile`) or until (`dountil`) a predicate holds.
```json
{
"type": "loop",
"loopType": "dountil",
"step": { "type": "tool", "id": "poll", "toolId": "check-status" },
"predicate": {
"op": "eq",
"left": { "path": "inputData.status" },
"right": { "literal": "done" }
}
}
```
The loop body must be a single step, and stored loops require a declarative predicate.
### Sleep entries
A `sleep` entry pauses for a fixed number of milliseconds. A `sleepUntil` entry pauses until a fixed date represented by an ISO date string. Stored definitions require literal values.
```json
{ "type": "sleep", "id": "wait", "duration": 5000 }
```
```json
{ "type": "sleepUntil", "id": "wait-for-launch", "date": "2027-01-01T00:00:00.000Z" }
```
Use a code-defined workflow when the duration or date must be calculated at runtime.
## Validation
Mastra validates definitions before it persists or registers them:
- Structure: Entry shapes and required fields, including placement rules such as top-level-only mappings.
- References: Each `agentId` and `workflowId` must resolve against the live registries or the same bundle. A `toolId` must match a tool registration key.
- Schema flow: Each entry's input must be compatible with the preceding output, including inferred mapping outputs.
Validation errors include a dotted path, such as `graph.2.steps.0`, that identifies the invalid entry.
## Related
- [Use stored workflows](https://mastra.ai/docs/workflows/stored-workflows)
- [`Mastra.addStoredWorkflow()`](https://mastra.ai/reference/core/addStoredWorkflow)
- [`Mastra.addStoredWorkflows()`](https://mastra.ai/reference/core/addStoredWorkflows)
- [Client SDK workflows API](https://mastra.ai/reference/client-js/workflows)