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
{ "artifact_id": "art_01HQKX2J3K4M5N6P7R8S9T0V2B" }
Example response
{
"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
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).
The split serves three purposes:
- Machine readability. The 7 JSON files are structured and parseable. Tools can diff two bundles field-by-field.
- Human readability.
report.html renders the same information as a styled report with charts — no JSON parsing required.
- 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.
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.
Fetching the full bundle
To fetch all 8 files, call get_run first to get the artifacts array, then call get_artifact for each:
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
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.