> ## 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.

# Artifacts, run outputs, and deterministic replay in dagraph

> How dagraph stores node outputs in a content-addressed store, injects them in downstream prompts, writes output files, and replays runs without LLM calls.

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`.

<Note>
  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.
</Note>

## 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.

```yaml theme={null}
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:

```yaml theme={null}
- 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.

```yaml theme={null}
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.

```bash theme={null}
# 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:

```bash theme={null}
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:

```bash theme={null}
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.

<Tip>
  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.
</Tip>

## 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.
