ADR-0038 — Workflows: mandatory steps, no root prompt, per-level model override
- Status: Accepted
- Date: 2026-06-12
- Constrained by: ADR-0006, ADR-0012, ADR-0019, ADR-0022, ADR-0037
- Amends: ADR-0019
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:
- ADR-0019 defined a stepless workflow — one prompt, one constrained agent run — and reserved Mastra
Workflowfor a future v1.x path. With steps real, we'd be carrying two execution models. - ADR-0019's workflow-level
system_promptwas 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. - Workflows are essentially (sub)agents, and per ADR-0022 config is per-agent — yet workflows and steps had no
modelfield. 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
...
modelis meaningful onagentsteps; it is invalid onconfirmandtasksteps (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: nullhas no special "revert to inherit" meaning; omit the field to inherit.nullkeeps 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:
stepsrequired non-empty;system_promptremoved fromWorkflowDefinition; optionalmodelonWorkflowDefinitionand onagentsteps. - 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_promptbecomes the single step'sprompt).
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.
confirmandtaskdon'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 baresystem_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.