> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dagraph.app/llms.txt
> Use this file to discover all available pages before exploring further.

# dagraph workflow YAML: structure, fields, and syntax

> Learn how to write a dagraph YAML workflow file: top-level fields, input declarations, node lists, output mappings, and Jinja2 template variables.

A dagraph workflow lives in a single YAML file. The file declares a set of named nodes, their dependencies, and the inputs they accept. When you run the file, dagraph validates the structure, resolves all dependency edges, and executes nodes in parallel waves — running every node whose dependencies have already completed.

## Top-level structure

A minimal DAG file needs only a `name` and a `nodes` list. Every other top-level field is optional and adds progressively more behaviour.

```yaml theme={null}
name: research
description: Parallel three-angle research on a topic, synthesized by Opus.

inputs:
  topic:
    type: string
    required: true
    description: The topic to research.

budget:
  max_tokens: 50000
  max_usd: 2.00

nodes:
  - id: research_a
    type: agent
    model: claude-haiku-4-5-20251001
    prompt: Research "{{ topic }}" from a technical perspective.

  - id: synthesizer
    type: agent
    model: claude-sonnet-4-6
    depends_on: [research_a]
    prompt: |
      Synthesize the findings:
      {{ research_a }}

outputs:
  report.md: synthesizer

hooks:
  on_dag_complete:
    - type: command
      command: echo "Done"
```

## Fields reference

<ParamField path="name" type="string" required>
  The identifier for this workflow. Used in run logs and trace spans. Must be unique across your workflow library.
</ParamField>

<ParamField path="description" type="string">
  A human-readable summary of what the workflow does. Shown by `agentgraph validate` and in run listings.
</ParamField>

<ParamField path="nodes" type="list" required>
  The ordered list of node specifications. Node execution order is determined by dependency edges, not list order. See [Node types](/concepts/node-types) for all available types and their fields.
</ParamField>

<AccordionGroup>
  <Accordion title="inputs — declare workflow parameters">
    The `inputs` map declares the parameters your workflow accepts from the CLI or a webhook call. Each key is the input name; the value is a schema object.

    <ParamField path="inputs.<name>.type" type="string">
      One of `string`, `integer`, `number`, or `boolean`. Defaults to `string`. dagraph coerces the raw CLI string to this type before the first node runs, so a typo like `--input count=three` fails immediately with a clear error.
    </ParamField>

    <ParamField path="inputs.<name>.required" type="boolean">
      Whether the input must be provided. Defaults to `true`. Set to `false` and provide a `default` for optional parameters.
    </ParamField>

    <ParamField path="inputs.<name>.default" type="string | integer | number | boolean">
      Default value used when the input is absent and `required: false`.
    </ParamField>

    <ParamField path="inputs.<name>.description" type="string">
      Documents the parameter. Displayed by `agentgraph validate` to self-document the DAG.
    </ParamField>

    ```yaml theme={null}
    inputs:
      topic:
        type: string
        required: true
        description: The subject to research.
      depth:
        type: integer
        required: false
        default: 3
        description: How many research angles to spawn.
    ```

    Pass inputs at run time:

    ```bash theme={null}
    agentgraph run research.yaml --input topic="quantum computing" --input depth=5
    ```
  </Accordion>

  <Accordion title="budget — cap token and dollar spend">
    The `budget` block sets a hard upper limit on the entire run. If either limit is reached, dagraph stops all in-flight nodes immediately and marks the run as failed.

    <ParamField path="budget.max_tokens" type="integer">
      Maximum number of billable tokens (input + output) across all nodes. Cache-creation overhead from the `claude_code` backend is excluded.
    </ParamField>

    <ParamField path="budget.max_usd" type="float">
      Maximum equivalent API cost in US dollars. Useful when mixing models with different per-token prices.
    </ParamField>

    ```yaml theme={null}
    budget:
      max_tokens: 100000
      max_usd: 5.00
    ```

    You can also set a per-node budget on any individual node using the same `budget:` field. See [Node types](/concepts/node-types) for details.
  </Accordion>

  <Accordion title="outputs — write node results to files">
    The `outputs` map writes the text output of specific nodes to files on disk after a successful run. Nothing is written for paused or failed runs.

    <ParamField path="outputs.<file-path>" type="string">
      Maps an output file path (relative to the working directory) to a node ID. The node's text output is written to that path when the run completes successfully. Parent directories are created automatically.
    </ParamField>

    ```yaml theme={null}
    outputs:
      report.md: synthesizer
      data/analysis.txt: analysis_node
    ```
  </Accordion>

  <Accordion title="hooks — lifecycle callbacks">
    The `hooks` map attaches callbacks to DAG lifecycle events. Hook failures never fail the DAG itself.

    <ParamField path="hooks.<event>" type="list">
      Supported events: `on_dag_start`, `on_dag_complete`, `on_dag_paused`, `on_dag_failed`. Each value is a list of hook specifications — either a webhook (JSON POST) or a shell command.
    </ParamField>

    ```yaml theme={null}
    hooks:
      on_dag_complete:
        - type: webhook
          url: https://hooks.example.com/notify
      on_dag_failed:
        - type: command
          command: "echo 'Run {{ run_id }} failed' >> /var/log/dagraph.log"
    ```
  </Accordion>
</AccordionGroup>

## Template variables

dagraph uses [Jinja2](https://jinja.palletsprojects.com/) to render prompts and commands at run time. Two scopes of variables are available inside any prompt or command string.

### Input variables

Reference any declared input by name:

```yaml theme={null}
inputs:
  topic:
    type: string

nodes:
  - id: researcher
    type: agent
    model: claude-haiku-4-5-20251001
    prompt: Research the topic "{{ topic }}" and return 5 bullet points.
```

### Upstream node outputs

Reference the text output of any completed upstream node by its `id`:

```yaml theme={null}
nodes:
  - id: research_a
    type: agent
    model: claude-haiku-4-5-20251001
    prompt: Research "{{ topic }}" technically.

  - id: synthesizer
    type: agent
    model: claude-sonnet-4-6
    depends_on: [research_a]
    prompt: |
      Synthesize these findings:

      {{ research_a }}
```

When a node's output is a JSON array (such as from a `map` node), you can iterate over it:

```yaml theme={null}
prompt: |
  {% for item in summaries | fromjson %}
  - {{ item }}
  {% endfor %}
```

<Note>
  dagraph's Jinja environment uses `StrictUndefined`, so a typo in a variable name will fail the node immediately with a clear error message rather than silently inserting an empty string.
</Note>

## Externalising prompts with `prompt_file` and `system_file`

Long prompts are easier to read, review, and diff when stored in separate text files. Use `prompt_file` instead of `prompt`, and `system_file` instead of `system`. Paths resolve relative to the directory containing the YAML file.

```yaml theme={null}
nodes:
  - id: analyst
    type: agent
    model: claude-sonnet-4-6
    prompt_file: prompts/analyst.txt
    system_file: prompts/analyst-system.txt
```

<Warning>
  You cannot set both `prompt` and `prompt_file` on the same node — dagraph will reject the file at validation time.
</Warning>

## Complete annotated example

The following example is taken from the dagraph examples directory. It defines three parallel research agents followed by a synthesizer that fans in their outputs.

```yaml theme={null}
name: research
description: Parallel three-angle research on a topic, synthesized by Opus.

budget:
  max_tokens: 50000
  max_usd: 2.00

nodes:
  # Three independent researchers — no depends_on, so they run in parallel.
  - id: research_a
    type: agent
    model: claude-haiku-4-5-20251001
    max_output_tokens: 1500
    prompt: |
      Research the topic "{{ topic }}" from a TECHNICAL perspective.
      Cover: mechanisms, architecture, implementation details.
      Return 5–8 concise bullet points. No fluff.

  - id: research_b
    type: agent
    model: claude-haiku-4-5-20251001
    max_output_tokens: 1500
    prompt: |
      Research the topic "{{ topic }}" from an ECONOMIC perspective.
      Cover: market forces, cost structures, winners, losers.
      Return 5–8 concise bullet points. No fluff.

  - id: research_c
    type: agent
    model: claude-haiku-4-5-20251001
    max_output_tokens: 1500
    prompt: |
      Research the topic "{{ topic }}" from a HUMAN/SOCIAL perspective.
      Cover: behavior, adoption, second-order effects on people.
      Return 5–8 concise bullet points. No fluff.

  # Synthesizer waits for all three researchers before running.
  - id: synthesizer
    type: agent
    model: claude-sonnet-4-6
    max_output_tokens: 3000
    depends_on: [research_a, research_b, research_c]
    prompt: |
      Three independent researchers analyzed "{{ topic }}". Synthesize
      their findings into a unified report.

      == Technical perspective ==
      {{ research_a }}

      == Economic perspective ==
      {{ research_b }}

      == Human/Social perspective ==
      {{ research_c }}

outputs:
  report.md: synthesizer
```

Run it with:

```bash theme={null}
agentgraph run research.yaml --input topic="quantum computing"
```
