Skip to main content
Every time a node completes, dagraph writes its text output to a content-addressed artifact store before the next wave of nodes runs. Downstream nodes never pass large blobs directly between themselves — they reference outputs by node ID, and dagraph resolves the stored content at render time. This keeps prompts bounded, makes retries idempotent, and gives you a complete, inspectable record of every node’s output after the run.

Run directory structure

Each run produces a directory under runs/ in your working directory:
runs/<run_id>/
├── state.db              # SQLite checkpoint: node statuses, run metadata
├── trace.jsonl           # OpenTelemetry-compatible spans (one JSON object per line)
└── artifacts/
    ├── index.json        # Maps node_id → sha256 digest
    └── sha256/
        └── <hash>.bin    # The actual content, stored once per unique value
state.db is the source of truth for run state. If a run is interrupted, dagraph reads state.db to determine which nodes have already completed and skips them on resume. trace.jsonl records every LLM call, tool invocation, and node lifecycle event as an OpenTelemetry GenAI span. You can feed this into any OTel-compatible observability tool.

The artifact store

dagraph stores each node’s output as a UTF-8 text blob identified by its SHA-256 hash. When a node completes, the scheduler:
  1. Computes the SHA-256 digest of the output text.
  2. Writes the blob to artifacts/sha256/<digest>.bin (skipped if the file already exists).
  3. Updates artifacts/index.json to record node_id → digest.
Content-addressing means that if two nodes produce identical output — for example, two researchers who reach the same conclusion — the blob is stored exactly once on disk. The index maps both node IDs to the same digest. This gives you automatic deduplication with no extra configuration.

Referencing upstream outputs in prompts

Use a node’s id as a Jinja2 variable anywhere a downstream node has depends_on pointing to it. dagraph resolves the variable to the node’s stored output text at render time.
nodes:
  - id: research
    type: agent
    model: claude-haiku-4-5-20251001
    prompt: Research "{{ topic }}" and return 5 bullet points.

  - id: synthesizer
    type: agent
    model: claude-sonnet-4-6
    depends_on: [research]
    prompt: |
      Synthesise these research findings into a short report:

      {{ research }}
When synthesizer runs, dagraph fetches the stored artifact for research and injects it into the prompt. You never manage file paths or artifact hashes manually. For map nodes, the output is a JSON array of per-item strings. Iterate over it with the fromjson filter:
- id: synthesize
  type: agent
  depends_on: [summaries]
  model: claude-sonnet-4-6
  prompt: |
    {% for item in summaries | fromjson %}
    - {{ item }}
    {% endfor %}
    Write a paragraph connecting these points.

Writing outputs to files

Use the top-level outputs map to write specific node outputs to files after a successful run. This is useful when you want to save a final report, generated code, or structured data to a predictable location for downstream scripts or CI steps.
outputs:
  report.md: synthesizer
  data/analysis.json: structured_output_node
dagraph writes each file only after the entire run completes successfully. Nothing is written for paused or failed runs — partial output files are worse than none.

Inspecting artifacts

Use agentgraph inspect to read stored artifacts without parsing the run directory manually.
# List all nodes and their artifact digests for a run
agentgraph inspect <run_id>

# Show the output text for a specific node
agentgraph inspect <run_id> --node synthesizer

# Show the full output of a node without truncation
agentgraph inspect <run_id> --node synthesizer --full
The <run_id> is printed to the console when you run a workflow. You can also list recent runs with:
agentgraph list

Deterministic replay

Every run automatically records a fixture.jsonl file alongside state.db. This file captures every LLM response in the order it was received. Use the --replay-from flag to replay a past run using the recorded responses instead of making live LLM calls:
agentgraph run workflow.yaml \
  --replay-from <run_id> \
  --input topic="quantum computing"
Replay is useful for:
  • Debugging — reproduce a run exactly to inspect intermediate state or test a prompt change.
  • Testing — run your DAG against recorded responses in CI without incurring LLM costs.
  • Iterating on prompts — change a downstream node’s prompt and replay to see the effect without re-running expensive upstream nodes.
Replay uses the LLM responses from the source run but re-evaluates all Jinja templates and DAG logic. This means you can change when conditions, retry policies, or outputs mappings and replay to verify the change without a full live run.

Secrets redaction

Before computing the SHA-256 digest and writing a blob, dagraph runs the content through a secrets redactor that replaces patterns matching common credential formats (API keys, tokens, passwords) with [REDACTED]. The on-disk blob and the index entry both reflect the redacted form. This means:
  • Secrets never land in artifact files even if a node accidentally echoes them.
  • Two outputs that differ only in their secret values hash to the same digest after redaction, giving you correct deduplication.