Skip to content

Artifacts

After this page you can find everything a run produced, fetch and verify its bytes, and know which outputs a run keeps rather than discards — because the manifest and the content are two separate things you fetch separately.

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…",
"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.
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.

The summary artifact and the last message are not duplicates

Section titled “The summary artifact and the last message are not duplicates”

A run’s final wamp.message.created event and its result.summary artifact usually carry the same words, and that is the one place an integrator records the agent’s answer twice. They are two carriers with two jobs:

wamp.message.created the result.summary artifact
How many per run one per assistant message, so many exactly one, or none
What it holds the conversation as it happened, in order the final handoff only
Cap 16384 characters per event, under a budget of 10000 captured facts per session 100000 bytes, content-addressed by its SHA-256
When it is absent never, once a message exists when the run ended with no prose at all
After the fact immutable — events are append-only immutable — a second write with different bytes is rejected as a producer error

Render the events when you are showing work happening: progress, a live timeline, a chat transcript. Messages and progress arrive during the run, and every event is ordered.

Store the artifact when you are keeping an outcome: a ticket comment, a digest, a record your own product owns. It is one stable, addressable document per run rather than a stream you reassemble, and its SHA-256 is what makes storing it idempotent.

Recording both means holding the same prose twice with no rule for which wins when the two caps clip it differently. Pick by what the surface is for.

A manifest you can list is a manifest whose bytes you can fetch: the platform runs no reclamation, so an artifact does not expire and the content endpoint has no “gone” answer. The decisions worth making about artifact bytes are the ones on this page that are enforced: what a run keeps at all, and what it discards for size.

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 you can act on an output, which makes it the right place to trigger a fetch. 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.

What a run keeps is bounded per file, per run and by count, and the caps are enforced when the artifact is registered, not when you read it. The numbers are in Limits; none of them is advertised through the API, so read them there rather than probing for them.

An output that would exceed a 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.

Terminal window
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 sha256

The response is raw bytes — not base64, not wrapped in JSON. Its full header set is in API conventions; two things follow from it that change how you write the caller.

First, the digest is free: the ETag is the manifest’s sha256, so compare it 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.

A presented.file can be the input of a turn in another session without passing through your backend. Name it in the turn’s attachments:

PUT /v1/sessions/{targetSessionId}/turns/{turnId}
Content-Type: application/json
{
"message": "Flash this firmware onto the test board.",
"attachments": [{ "sessionId": "<source session>", "artifactId": "<artifact id>" }]
}
  • Any session your credential can read may be the source. Only presented.file artifacts can be handed over; anything else is 400 invalid_request naming attachments[<index>].
  • At most 24 files and 64 MiB per turn.
  • The bytes are copied into the target session when the turn is admitted, so the source session can be archived afterwards. A replayed turn compares the copied files’ digests and creates nothing new.
  • The agent finds each file at .wamp/inputs/<turnId>/<name> in its workspace, and its prompt names the paths. Images, PDFs and small text files are also shown to it inline. The directory is kept out of git.
  • A new sandbox for the same session gets earlier turns’ files back, newest first, up to 256 MiB; the agent is told which files are not there.

An agent hands files to the worker sessions it started the same way, limited to its own session and its workers.

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