Skip to content

Pipelines

Pipelines wire multiple agents together for complex workflows. Define them as YAML files in your local workspace (~/.muaz/plugins/local/pipelines/).

From the REPL:

/run <name> [input] run a pipeline
/pipelines list available pipelines

From the shell (scriptable, no REPL):

Terminal window
muaz pipeline run code-review "the staged changes"
echo "the last 3 commits" | muaz pipeline run code-review
muaz pipeline list

A pipeline is a single ordered list of steps. Each step runs an agent (or a parallel: group), builds its input from a template over shared variables, and can gate itself with when:, retry on failure, cap its budget, and expose structured output as variables for later steps. There is no pattern field — sequential, parallel, and router flows are all expressed with steps.

The examples below name agents like researcher and writer to show the shape; they are agents you would author, not ones muaz ships. The one shipped pipeline is code-review.

A step’s input: is a template. These placeholders are substituted (an unknown placeholder is a hard error, so typos surface immediately):

PlaceholderValue
${input}the pipeline’s initial input
${vars.<name>}a top-level output field promoted from a step’s output_schema
${steps.<name>.output}a named step’s full text output
${steps.<name>.<field>}a structured field from that step’s output_schema

Chain steps by feeding one step’s output into the next. Reference steps by their name:

~/.muaz/plugins/local/pipelines/research-write.yaml
name: research-write
description: "Research a topic, then write from the sourced notes"
steps:
- name: research
agent: researcher # resolved across namespaces; use @plugin/name to disambiguate
input: "Research this topic and produce structured notes:\n\n${input}"
retry: { max: 1, backoff_secs: 2 }
- name: write
agent: writer
input: |
Write the piece requested below, working only from the research notes.
Request:
${input}
Research notes:
${steps.research.output}

A step may be a parallel: group whose members run concurrently. Later steps read each member’s output by name.

steps:
- name: reviews
parallel:
- name: security
agent: security-reviewer
input: "${input}"
- name: style
agent: style-reviewer
input: "${input}"
- name: summary
agent: editor
input: |
Security review:
${steps.security.output}
Style review:
${steps.style.output}

A router is just a step with an output_schema containing a route field; the downstream specialists gate on it with when:. No free-text parsing — the route is a typed, structured-output field. Bind the router to a cheap model and the specialists to a strong one.

name: triage
steps:
- name: route
agent: triage-router
output_schema:
type: object
properties:
route: { type: string, enum: [write, analyze, general] }
message: { type: string, description: The request, forwarded to the specialist. }
required: [route, message]
additionalProperties: false
budget: { max_tokens: 6000 }
- name: write
agent: writer
when: { value: "${steps.route.route}", equals: write }
input: "${steps.route.message}"
- name: analyze
agent: analyst
when: { value: "${steps.route.route}", equals: analyze }
input: "${steps.route.message}"
  • when: — run the step only if a condition holds. The form is { value, equals | not_equals | contains } (no loops, no free-form expressions): when: { value: "${steps.route.route}", equals: write }.
  • output_schema: — a JSON schema; the step returns structured output, and top-level fields become ${steps.<name>.<field>} (and, at the pipeline’s top level, ${vars.<field>}).
  • retry:{ max, backoff_secs } retries a failing step.
  • on_error:fail (default, abort the pipeline), continue (skip and move on), or fallback (use the step’s fallback path).
  • budget:{ max_cost_usd, max_tokens, max_seconds }, allowed per step and at the pipeline top level. Token/cost limits are checked at step boundaries; a max_seconds limit hard-times-out an in-flight step.

Every step names an agent, and that agent’s identity is what its model binding in models.yaml is keyed on. This is the reason a step cannot carry an inline prompt instead: an inline prompt has no name, so there would be nothing to bind, and running two steps on deliberately different models is most of what pipelines are for.

That leaves agents built purely to be pipeline steps. Mark them entrypoint: false and they stay out of the pickers that start a new chat:

name: review-disputer
description: "Code review step: attacks the proposed findings"
entrypoint: false
system_prompt: "./review-disputer.md"

A step agent is not a restricted agent. It resolves by name, appears in muaz agents list, on the UI Agents page (tagged step), and in the pipeline editor’s picker; it takes its own binding; and you can open a chat with it directly when you want to iterate on its prompt. The flag governs exactly one surface — the new-chat picker.

Pipelines resolve across namespaces (local → plugins → built-ins) just like agents, so a plugin’s pipeline is referenced by its qualified @plugin/name.

The one shipped built-in is code-review: an adversarial review where review-proposer finds defects, review-disputer attacks them, and review-arbiter decides which survive. Bind the proposer and disputer to different models — the dispute step is there to catch findings that are merely plausible, and two runs of the same model share the blind spot that produced them. See the pipelines guide.