Artifacts
After this page you can find everything a run produced, fetch and verify its bytes, and write code that keeps working after the bytes are gone — because the record outlives the content by design.
What an artifact is
Section titled “What an artifact is”An artifact is a durable record of something a run produced: a written summary, a file the agent chose to show you, the pull request it opened. Each artifact has two halves you fetch separately:
- The manifest — small JSON, listed and read from the session. It says what the thing is, how big it is, and what its digest is.
- The content — the raw bytes, from a separate endpoint that returns them as-is with no JSON wrapper.
That split is the reason artifacts are useful in a durable integration: the manifest is cheap, stays readable, and is enough to reconcile against your own records without transferring anything.
{ "artifact": { "id": "b0d5f5a2-8c3e-4d21-9a44-2f1c7b8e9012", "sessionId": "3f7d4f4c-2b6a-4a2e-9c1a-1f2b3c4d5e6f", "runId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "kind": "result.summary", "role": "output", "contentType": "text/markdown; charset=utf-8", "size": 1284, "sha256": "9f2b…", "state": "available", "metadata": { "truncated": false }, "createdAt": "2026-08-11T09:04:12.882Z" }}| Field | Notes |
|---|---|
runId |
Present when the artifact belongs to a run. Absent for session-level records. |
publicationId |
Present on an artifact produced by publishing — the github.pull_request record. |
kind |
An open string. Match the ones you handle; ignore the rest. |
role |
An open string; output is what the public API produces today. |
contentType |
The MIME type the content endpoint will serve. |
size |
Bytes, exact. |
sha256 |
The digest of the content. Also served as the ETag. |
state |
available or pruned. |
metadata |
At most truncated, fileName, label, description. |
The kinds you will see on the public API today:
kind |
What it is |
|---|---|
result.summary |
The run’s written summary of what it did. Markdown. |
presented.file |
A file the agent deliberately surfaced. metadata.fileName carries its name. |
github.pull_request |
The record of a pull request a publication opened. Carries publicationId. |
kind and role are strings in the contract, not enumerations. New kinds can
appear without a version change, so treat an unrecognized kind as data you skip
rather than as an error.
metadata is allow-listed on the way out: only those four keys can ever appear,
and anything a producer attached beyond them is dropped before the response.
Do not expect to smuggle context through it.
Lifecycle: two states, one transition
Section titled “Lifecycle: two states, one transition”available ──▶ prunedavailable means the bytes are fetchable. pruned means they have been
reclaimed and are gone for good — there is no un-prune, and no cold-storage
retrieval behind it.
The manifest survives pruning. That asymmetry is the contract:
| You call | available |
pruned |
|---|---|---|
GET …/artifacts |
listed | listed, with state: "pruned" |
GET …/artifacts/{artifactId} |
200 |
200, with state: "pruned" |
GET …/artifacts/{artifactId}/content |
200 + bytes |
410 cloud_artifact_pruned |
So a 410 on the content endpoint is not a bug in your code and not a missing
resource. It is the documented answer to “fetch something whose bytes were
reclaimed”, and any long-lived integration will meet it.
The retention window is not documented. There is a pruning policy, but no
duration is published, and you should not infer one from observation. The
correct engineering response is to copy the bytes you care about into your own
storage at the moment you learn the artifact exists, and to treat your copy as
authoritative afterwards. Keep the sha256 alongside it so you can prove the two
are the same object.
Finding artifacts
Section titled “Finding artifacts”Two paths, and you want both.
From the event log, as soon as it happens:
{ "type": "wamp.artifact.created", "subject": { "sessionId": "…", "runId": "…" }, "data": { "artifactId": "b0d5f5a2-8c3e-4d21-9a44-2f1c7b8e9012", "kind": "result.summary", "role": "output", "contentType": "text/markdown; charset=utf-8", "size": 1284, "sha256": "9f2b…", "state": "available", "truncated": false }}This is the earliest moment the bytes are guaranteed present, which makes it the right place to trigger a copy. See Follow a run live.
From the list, when you need to reconcile:
GET /v1/sessions/{sessionId}/artifacts?after=<artifactId>&limit=<1..100>Oldest first. after is an artifact id, not a number and not a timestamp —
it acts as an exclusive cursor, and the response’s nextAfter is the id to send
next. hasMore tells you whether the page was capped. There is no filter
parameter: no filtering by runId, by kind, or by date. Page and filter on your
side; unknown query parameters are rejected with 400 invalid_request rather than
ignored.
Internal artifacts — the checkpoints that make a session restorable — are never listed and never fetchable. What you see is the output surface, not the storage layer.
When a run produced more than is kept
Section titled “When a run produced more than is kept”Retention is bounded per file and per run, and the caps are enforced when the artifact is registered, not when you read it:
| Cap | Value |
|---|---|
| One presented file | 32 MiB |
| All presented files from one run | 64 MiB |
An output that would exceed either cap is not retained, and the run says so rather than under-reporting:
{ "type": "wamp.artifact.omitted", "subject": { "sessionId": "…", "runId": "…" }, "data": { "omittedArtifacts": 2, "reason": "not_retained" }}Treat that event as information for the human in the loop: the agent produced more than the session keeps, and if those outputs matter the task should be scoped to produce fewer or smaller ones — or the agent should be asked to write them into the repository, where publishing carries them out.
Separately, a retained artifact may be metadata.truncated: true, meaning the
content itself was cut to fit. The size and sha256 always describe the bytes
you will actually receive, truncated or not.
Fetching and verifying content
Section titled “Fetching and verifying content”curl -sS -D headers.txt -o summary.md \ "$WAMP_API/v1/sessions/$SESSION_ID/artifacts/$ARTIFACT_ID/content" \ -H "Authorization: Bearer $WAMP_TOKEN"
shasum -a 256 summary.md # compare against the manifest's sha256The response is raw bytes — not base64, not wrapped in JSON — with these headers:
| Header | Value |
|---|---|
Content-Type |
The artifact’s own type. |
Content-Length |
Matches the manifest’s size. |
ETag |
"<sha256>" — the same digest as the manifest. |
Cache-Control |
private, no-store |
Content-Security-Policy |
sandbox |
X-Content-Type-Options |
nosniff |
Content-Disposition |
attachment with the file name, when metadata.fileName is present. |
Two things follow from those headers. First, the digest is free: compare ETag
or the manifest’s sha256 against what you stored, and you never have to guess
whether a re-fetch changed. Second, the bytes are agent output and are not
trusted content. They are served with sniffing disabled and a sandboxing
policy for a reason. If you render an artifact in your own product, sandbox it on
your side too; do not inline agent-produced markup into a trusted page.
Whole-object reads only: there is no range request, no partial fetch, and no resume. A 32 MiB file is one call.
What you may assume, and what you must handle
Section titled “What you may assume, and what you must handle”Assume. A manifest, once created, stays readable for the life of the session —
archiving does not remove it. size and sha256 describe exactly the bytes the
content endpoint returns. Artifacts are immutable: the same id never serves
different content. The list is stably ordered oldest-first.
Handle. 410 cloud_artifact_pruned, at any age. 404 cloud_artifact_not_found
for an id that never existed in this session. Kinds you do not recognize.
wamp.artifact.omitted, meaning outputs existed that you will never see.
metadata.truncated. And the possibility that a run you consider successful
produced no artifacts at all — nothing guarantees a run writes one.
Related
Section titled “Related”- Sessions, turns, runs — what a
runIdon an artifact refers to. - Publications — the artifact carrying a pull request.
- Follow a run live — catching
wamp.artifact.createdas it happens. - Artifacts operations — the generated endpoint reference, and the API reference for the manifest field by field.
- Events and Limits.