Author a workflow
You can describe a workflow in English and let zen-workflow-builder write the YAML for you. This page is the other path: writing the file yourself so you understand the shape and know which knob to turn when the builder's output isn't quite right.
The shape of a workflow file
Every workflow is a YAML file with a name, a description, and a list of nodes:
name: research-sprint
description: Take a topic, run three rounds of search, draft a memo.
nodes:
- id: round-one
type: prompt
prompt: |
Search the web for the most authoritative recent sources on "$TOPIC".
Return five sources with one-line summaries.
- id: round-two
type: prompt
depends_on: [round-one]
prompt: |
Take the round-one sources and find three contrarian or critical takes
on the same topic. Same format.
- id: draft-memo
type: prompt
depends_on: [round-one, round-two]
prompt: |
Combine both rounds into a one-page memo. Headline, three sections,
bottom-line recommendation.Drop this in .zen/workflows/research-sprint.yaml and you can fire it with zen workflow run research-sprint --vars TOPIC="board agenda for Q3".
The node types you'll use most
prompt. The default. Sends rendered text to an AI provider and captures the response. Use this for anything you'd normally type into a chat.
bash. Runs a shell command. Captures stdout, stderr, and exit code. Use this for fetching data, posting to webhooks, kicking off CLI tools.
- id: pull-events
type: bash
command: gcalcli agenda --tsvsubagent. Hands off a chunk of work to a sub-workflow or a different model. Use this when one logical step is big enough to want its own DAG.
approval. Pauses and waits for a human to approve or reject. The reply (and any reason on rejection) becomes a variable downstream nodes can read.
loop. Runs a body multiple times. Either fixed iterations, or until a condition the body returns is true.
The full per-type schema is in Parallel steps & dependencies and the per-type pages under Advanced.
Variables
Workflows interpolate variables into prompts and commands. The common ones:
$BASE_BRANCH. The project's main branch.$DOCS_DIR. Where docs live in this project.$RUN_ID. The current run's identifier.- Custom vars from
vars:at the top of the workflow, or passed in with--vars KEY=value. - Outputs from earlier nodes by id (
$node-id.output).
See Variables for the full list.
How parallel execution works
Z.E.N. reads the depends_on: of every node. Nodes with no unresolved dependencies run in parallel. As each one finishes, it unblocks whatever was waiting on it. There's no separate "run in parallel" flag; the dependency graph IS the parallelism story.
Two pull- nodes that don't depend on each other will fire at the same time. A combine node that depends on both will wait for both. That's the whole model.
Where the output goes
The last node's output is the workflow's output. By default it streams back to whoever fired the workflow (your terminal, the Web UI, your agent). If you want it to land somewhere external, the last node calls a connector:
- id: post-to-slack
type: prompt
depends_on: [draft-memo]
prompt: |
Post this memo to #strategy: $draft-memo.outputSlack and other connectors live under Integrations.
Picking the provider
The provider is the AI that runs a single node. Set it per-node:
- id: summarize
type: prompt
provider: codex
prompt: |
...If you don't set one, the workflow inherits from the project's default in .zen/config.yaml. See Configuration for the shape.
Coding workflows are real, just not the lead
Z.E.N. ships with coding workflows (PR review loops, ralph-dag, the PIV loop). They're under the Advanced section so the default surface stays automation-first. If you're a developer using Z.E.N., Bundled workflows lists them with examples.
Next
- Parallel steps & dependencies for the full node schema and DAG rules.
- Schedules to fire workflows on a recurring cadence.
- Bundled workflows to see what ships out of the box.