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

# Artifact Tools

> get_artifact fetches artifact metadata and a download URI. Every backtest produces an 8-file bundle with SHA-256 content hashing for reproducibility.

The artifact tools family contains a single tool — `get_artifact` — that fetches artifact metadata and a download URI by `artifact_id`. Artifacts are the durable outputs of runs: every backtest produces an 8-file bundle, and every skill run produces its own artifacts. Each file is content-hashed with SHA-256 so reproducibility can be verified independently.

## `get_artifact`

Fetch artifact metadata and a download URI by `artifact_id`. The URI is a short-lived, signed URL — it expires after a few minutes. Fetch the content promptly after calling `get_artifact`.

### Arguments

| Argument      | Type   | Required | Description              |
| ------------- | ------ | -------- | ------------------------ |
| `artifact_id` | string | Yes      | The artifact identifier. |

### Return value

| Field          | Type              | Description                                            |
| -------------- | ----------------- | ------------------------------------------------------ |
| `artifact_id`  | string            | The artifact identifier.                               |
| `run_id`       | string            | The run that produced this artifact.                   |
| `filename`     | string            | The file name within the bundle (e.g. `metrics.json`). |
| `content_type` | string            | MIME type (e.g. `application/json`, `text/html`).      |
| `content_hash` | string            | SHA-256 hash of the file content.                      |
| `size_bytes`   | integer           | File size in bytes.                                    |
| `uri`          | string            | Short-lived signed download URL.                       |
| `expires_at`   | string (ISO 8601) | When the signed URL expires.                           |

### Example call

```json theme={null}
{ "artifact_id": "art_01HQKX2J3K4M5N6P7R8S9T0V2B" }
```

### Example response

```json theme={null}
{
  "artifact_id": "art_01HQKX2J3K4M5N6P7R8S9T0V2B",
  "run_id": "run_01HQKX2J3K4M5N6P7R8S9T0V2A",
  "filename": "metrics.json",
  "content_type": "application/json",
  "content_hash": "sha256:d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5",
  "size_bytes": 1843,
  "uri": "https://artifacts.doomberg.me/run_01.../metrics.json?sig=...",
  "expires_at": "2025-01-15T14:45:00.000Z"
}
```

## The 8-file backtest artifact bundle

Every `backtest_run` produces a bundle of exactly 8 files. Together they form a complete, reproducible record of the backtest. The bundle is stored as a group; the `artifact_bundle` returned by `backtest_run` points to all 8.

| File                    | Content type     | Description                                                                                                        |
| ----------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------ |
| `summary.json`          | application/json | Top-level summary: run ID, strategy ID, range, engine version, final stats, and pointers to the other 7 files.     |
| `metrics.json`          | application/json | Full statistics block: Sharpe, Sortino, CAGR, max drawdown, win rate, turnover, VaR, CVaR, and per-period returns. |
| `equity_curve.json`     | application/json | The complete equity curve as an array of `{ date, equity }` points.                                                |
| `positions.json`        | application/json | The position history: per-date weights for every symbol in the universe, including cash.                           |
| `data_manifest.json`    | application/json | Manifest of every data input: symbol, date range, interval, provider, and content hash for each OHLCV series used. |
| `environment_lock.json` | application/json | The execution environment: numpy version, engine version, Python version, and OS platform.                         |
| `run_trace.json`        | application/json | The chronological event log from the run: state transitions, job starts/ends, and timing.                          |
| `report.html`           | text/html        | A human-readable HTML report with charts (equity curve, drawdown), the stats table, and the risk attestation.      |

### Why 8 files

<Callout type="info">
  The bundle is designed so that a third party with no access to Ithaca can reproduce the backtest. Give them the bundle and they have everything: the strategy (in `summary.json`), the data (in `data_manifest.json`), the environment (in `environment_lock.json`), and the expected output (in `metrics.json` and `equity_curve.json`).
</Callout>

The split serves three purposes:

1. **Machine readability.** The 7 JSON files are structured and parseable. Tools can diff two bundles field-by-field.
2. **Human readability.** `report.html` renders the same information as a styled report with charts — no JSON parsing required.
3. **Selective access.** A reviewer can grab just `metrics.json` and `report.html` without downloading the full equity curve or position history.

## SHA-256 content hashing

Every file in the bundle has a SHA-256 content hash. The hash is computed over the exact bytes stored in the bundle, not a normalized form. This means:

* **Reproducibility check.** If you re-run a backtest with the same spec, range, engine version, and data hashes, the output file hashes will match. If they don't, something changed — and the `data_manifest.json` and `environment_lock.json` tell you what.
* **Tamper detection.** The hashes are stored on the artifact record in the database, separate from the file storage. If a file is modified in storage, the hash won't match the record.
* **Bundle integrity.** The `artifact_bundle.content_hash` returned by `backtest_run` is a hash over the concatenation of all 8 file hashes. This single hash verifies the entire bundle.

<Tip>
  When comparing two backtests, start by comparing their `artifact_bundle.content_hash` values. If they match, the runs are identical — no need to diff the individual files.
</Tip>

## Fetching the full bundle

To fetch all 8 files, call `get_run` first to get the `artifacts` array, then call `get_artifact` for each:

```python theme={null}
run = get_run(run_id="run_01HQKX2J3K4M5N6P7R8S9T0V2A")
for artifact in run["artifacts"]:
    meta = get_artifact(artifact_id=artifact["artifact_id"])
    # fetch meta["uri"] within the expiry window
```

<Callout type="warn">
  The signed URIs returned by `get_artifact` expire after a few minutes. If you need long-term access, download the content and store it yourself. The `content_hash` lets you verify that your stored copy hasn't drifted.
</Callout>

## Related

* [Backtest tools](/tools/backtest) — the tool that produces the bundle
* [Run management tools](/tools/runs) — `get_run` returns the `artifacts` array
* [Strategy artifacts](/strategy/artifacts) — the strategy-level view of the bundle
