Workflow anatomy
The workflow config format. The top-level keys, the anatomy of an action, how one action can contain a whole block of others, and how a step's output flows to every step that follows it.
A workflow is a single, self-contained definition. It declares some inputs and variables, then lists the actions to run. An action runs one component, and the result of every action is available to every action that follows it.
An action is not always a single leaf step. Some components hold a block of their own actions, so a workflow is a tree, not a flat list. A loop, a branch, or a group is just an action whose body is more actions, nested as deep as the work needs. This is what makes a workflow modular. You build small pieces and nest or reuse them, rather than writing one long script.
The definition is portable. It reads clearly to a person and to a language model, it runs the same on any operating system, and it is launched the same way from the command-line app, the REST API, or the web app. The examples here are written in YAML for readability, and JSON works identically. What matters is the shape below, not the serialization.
The top-level keys
id: awee.ai/library/comment-reader
name: Read and summarize comments
vars:
provider: anthropic
model: claude-opus-4-20250514
inputs:
- name: topic
type: string
actions:
- name: summary
component: inference
vars:
provider: "{{ var.provider }}"
model: "{{ var.model }}"
prompt: "Summarize: {{ topic }}"
outputs:
summary: "{{ summary.content }}"| Key | Purpose |
|---|---|
id | The stable, canonical name of the workflow, for example awee.ai/library/comment-reader. It is how another workflow refers to this one. |
name | A human-readable title. |
vars | Default variables, read in expressions as {{ var.<name> }}. |
actions | The ordered list of steps. This is the body of the workflow. |
inputs | The typed inputs a caller supplies, also rendered as a form by the platform. |
outputs | A map of output name to a template expression, evaluated after the run. This is what the workflow returns. |
output_schema | A JSON Schema describing the output shape, so a programmatic caller or an external agent connecting over MCP knows the result contract. |
data | Named data providers, each with a provider and a token. |
assets | Named blobs (their name and content) available to the run. |
tags | Labels such as table, table:row, or table:cell that classify the workflow. |
The inputs and outputs blocks are the workflow's contract. outputs maps a name to an expression that reads from any step, while output_schema describes the resulting shape. Together they let a workflow be called like a function, by a person, by another workflow, or by an external tool.
The anatomy of an action
Every step in actions is one action. The only two required fields are a name and a component.
- name: page
component: http
vars:
url: "https://example.com/{{ topic | slug }}"
cache:
for: 1h
retry:
count: 3
delay: 10
if: "{{ topic | default:'' | length | gt:0 }}"| Field | Meaning |
|---|---|
name | The step's identity. Downstream steps read its output as {{ <name>.<field> }}. |
component | Which component runs, for example http, inference, each, or the id of another workflow. |
vars | The component's configuration. Every value may contain {{ }} expressions. |
actions | Child steps, for container components like each, map, and group. |
if | A condition. When it is falsy the step is skipped. |
else | A single action that runs when if is falsy. |
cache | A duration to cache the result under, for example { for: 1h }. |
retry | A retry policy, { count, delay } where delay is in seconds. |
description | A note describing the step, surfaced in the plan. |
disabled | When true the step is planned but never executed. |
Steps that contain steps
The actions field is what makes a workflow a tree. A container component does no work of its own beyond directing its children. Set the component and put the body under actions, and the whole block becomes one step in the parent.
- name: per_row
component: each
vars:
items: "{{ invoices.rows }}"
actions:
- name: charge
component: http
vars:
method: POST
url: "{{ env.API }}/charge"
body: "{{ per_row.output | json }}"
- name: receipt
component: file:write
vars:
path: "receipts/{{ per_row.index }}.txt"
content: "{{ charge.body }}"Here each is one action in the parent workflow, but it runs two child actions for every invoice. The children nest further if they need to. A child of charge could be another loop, and so on down.
Note
There is no each:, map:, or group: key on an action. Looping, mapping, and grouping are ordinary components. Set component: each and put the body under actions. The else branch of an if is a nested action too. Keeping one action shape everywhere is what lets the engine treat the whole workflow as a plain tree of identical nodes, however deeply it nests.
This uniformity is the root of the engine's modularity. Because a block of steps is itself just a step, you can wrap related work in a group, run any block once per item with each, or swap a hand-written block for a call to another workflow, all without changing the shape around it. Composition takes this further, using a whole separate workflow as a single step.
Wiring steps together
An action's output is addressed by the action's name. Give a step a noun that names the value it produces, then read that value downstream.
actions:
- name: invoices
component: app:records:query
vars:
schema: billing
table: invoices
- name: total
component: var
vars:
var: total
value: "{{ invoices.rows | pluck:'amount' | sum }}"The second step reads {{ invoices.rows }}, the rows field of the invoices step. Naming the query step invoices (the thing it returns) rather than fetch_invoices (the act of fetching) is the house convention. It reads as data, not as a procedure.
Outputs stay visible no matter how deeply a later step is nested. A child step three loops deep can still read a top-level step by its short name. The last writer wins, so inside a loop each iteration overwrites the previous one's values.
Continue with the templating language to see what goes inside those {{ }} expressions.