ADR-0038 — Workflows: mandatory steps, no root prompt, per-level model override

Context

The step-based workflow model is in development: a workflow is an ordered list of typed steps (agent, confirm, task) with declared inputs/outputs and {{ }} data flow between them. Three structural questions fell out of building it against the ADR-0019 schema:

  1. ADR-0019 defined a stepless workflow — one prompt, one constrained agent run — and reserved Mastra Workflow for a future v1.x path. With steps real, we'd be carrying two execution models.
  2. ADR-0019's workflow-level system_prompt was composed as an implicit prefix (first onto the primary prompt, now potentially onto every step). ADR-0037 just killed implicit prompt composition everywhere else; keeping it here would reintroduce the same disease one level down.
  3. Workflows are essentially (sub)agents, and per ADR-0022 config is per-agent — yet workflows and steps had no model field. Real usage hit this immediately: the first authored workflow wanted a different model than the project default, and within a workflow, a technical step may warrant an expensive model while a simple step runs fine on a cheap one.

This ADR changes exactly three things. Everything else about workflows — inputs schema, file upload protocol, tool intersection, with: data flow, step kinds, lifecycle, audit — is unchanged.

Decision

1. A workflow must have at least one step

steps is required and non-empty. The stepless workflow concept ceases to exist; the ADR-0019 single-prompt workflow is now simply a one-step workflow.

This collapses ADR-0019's dual execution path ("constrained Agent path (v1)" vs "Mastra Workflow reserved for v1.x") into a single compilation target. One execution model to spec, implement, test, render, and audit.

2. Workflow-level system_prompt is removed

The field is deleted from WorkflowDefinition. Each step carries its own prompt, which per ADR-0037 is string | string[] — shared content is reused by listing the same file path in each step that wants it, explicitly.

A workflow-level prompt could only ever have been dead weight or an implicit prefix concatenated onto every step. The implicit prefix is exactly the pattern ADR-0037 abolished. Consistent rule: content composes explicitly.

The workflow level retains its contract-and-constraints role: description, inputs, tools, confirm_required, invokable_by, timeout_seconds, model (below), and steps. Steps are the content.

3. model is an optional override at workflow root and at each step

Inheritance chain: project (root agent) → workflow root → step. Omitted means inherit; the last explicitly supplied value in the chain wins.

workflows:
  testimonial.add:
    model: anthropic/claude-haiku-4-5      # workflow override (optional)
    steps:
      - id: draft
        kind: agent
        model: anthropic/claude-opus-4-8   # step override (optional)
        ...
      - id: commit
        kind: agent                        # no model → inherits workflow's haiku
        ...
  • model is meaningful on agent steps; it is invalid on confirm and task steps (parse error).
  • Model identifiers are validated at project load against configured providers — a typo'd model name fails at load, not mid-run on step 4. Fits the existing validation ladder (DSL parse → project load → session start → invocation).
  • model: null has no special "revert to inherit" meaning; omit the field to inherit. null keeps its ADR-0015 meaning (object nullification) and nothing else.
  • The resolved model per step is recorded in the run record and trace metadata. A single workflow run now spans models; cost and behavior audits need to see which step ran on what.

The consistent principle, shared with ADR-0037: scalars inherit, content composes explicitly. Model is a scalar pick with an unambiguous last-one-wins; prompts are content and never inherit.

Consequences

What this commits us to

  • DSL schema: steps required non-empty; system_prompt removed from WorkflowDefinition; optional model on WorkflowDefinition and on agent steps.
  • Harness: single compilation path (step pipeline); per-step model resolution; resolved model in run record + trace metadata.
  • Spec amendments: specs/workflows.md (schema, composition, execution model sections), specs/project-dsl.md, JSON Schema. ADR-0019 amendment note pointing here.
  • Migration: any existing stepless workflow definition is invalid; rewrite as a one-step workflow (mechanical: system_prompt becomes the single step's prompt).

What this forecloses

  • No stepless workflows. There is no "simple mode" with a second execution path. Simple is one step.
  • No workflow-level prompt, ever. Including any future "shared step preamble" field — that's the implicit prefix wearing a hat. Shared content is an ADR-0037 array entry in each step.
  • No per-step model on non-agent steps. confirm and task don't run a model.

What becomes easier

  • One execution model: every workflow run is the same step pipeline, same rendering, same audit shape.
  • Right-sizing cost: expensive model only on the steps that need it.
  • Reasoning about a step: its prompt array + its with: block is its entire context. Nothing arrives from above.

What becomes harder

  • Minimal workflow YAML grows slightly (a steps: list with one entry instead of a bare system_prompt). Accepted: the uniformity is worth four lines.
  • Operators must understand the three-level model chain. Mitigated by load-time validation and resolved-model visibility in the run view.

Alternatives considered

Alternative A — Keep stepless workflows alongside steps

Why tempting: Smaller YAML for trivial cases; no migration.

Why rejected: Two execution models forever — two compile paths, two render paths, two audit shapes — to save four lines of YAML. The one-step workflow is the stepless workflow.

Alternative B — Keep workflow-level system_prompt as a prefix to every step

Why tempting: DRY for content every step shares; zero per-step effort.

Why rejected: It is the implicit-prefix pattern ADR-0037 just abolished, recreated one level down. Invisible concatenation dilutes step prompts and forces reading two sources to understand one step. Shared content is an explicit array entry.

Alternative C — Model override at workflow level only (no per-step)

Why tempting: Simpler chain (two levels); per-step felt speculative.

Why rejected: It isn't speculative — real workflows mix a heavy reasoning step with cheap mechanical steps, and a workflow-wide model forces paying the maximum everywhere. The chain is three trivially-resolved levels; the complexity is one ?? per level.