DocsWorkflowsWorkflow anatomy

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 }}"
KeyPurpose
idThe stable, canonical name of the workflow, for example awee.ai/library/comment-reader. It is how another workflow refers to this one.
nameA human-readable title.
varsDefault variables, read in expressions as {{ var.<name> }}.
actionsThe ordered list of steps. This is the body of the workflow.
inputsThe typed inputs a caller supplies, also rendered as a form by the platform.
outputsA map of output name to a template expression, evaluated after the run. This is what the workflow returns.
output_schemaA JSON Schema describing the output shape, so a programmatic caller or an external agent connecting over MCP knows the result contract.
dataNamed data providers, each with a provider and a token.
assetsNamed blobs (their name and content) available to the run.
tagsLabels 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 }}"
FieldMeaning
nameThe step's identity. Downstream steps read its output as {{ <name>.<field> }}.
componentWhich component runs, for example http, inference, each, or the id of another workflow.
varsThe component's configuration. Every value may contain {{ }} expressions.
actionsChild steps, for container components like each, map, and group.
ifA condition. When it is falsy the step is skipped.
elseA single action that runs when if is falsy.
cacheA duration to cache the result under, for example { for: 1h }.
retryA retry policy, { count, delay } where delay is in seconds.
descriptionA note describing the step, surfaced in the plan.
disabledWhen 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.