# Artifacts How a run's outputs are recorded as content-addressed artifacts, how to fetch and verify their bytes, and which of them a run keeps at all. Source: https://docs.cloud.vampikez.fun/concepts/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. ## 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. ```json { "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 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. ## Nothing reclaims artifact bytes 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. ## Finding artifacts Two paths, and you want both. **From the event log**, as soon as it happens: ```json { "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](/guides/follow-a-run/). **From the list**, when you need to reconcile: ```text GET /v1/sessions/{sessionId}/artifacts?after=&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 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](/reference/limits/#artifact-sizes); 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: ```json { "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 ```bash 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](/reference/api/#content-types-and-accept-negotiation); 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. ## Handing a file to another session 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`: ```http PUT /v1/sessions/{targetSessionId}/turns/{turnId} Content-Type: application/json { "message": "Flash this firmware onto the test board.", "attachments": [{ "sessionId": "", "artifactId": "" }] } ``` - 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[]`. - 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//` 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 **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. ## Related - [Sessions, turns, runs](/concepts/sessions-turns-runs/) — what a `runId` on an artifact refers to. - [Publications](/concepts/publications/) — the artifact carrying a pull request. - [Follow a run live](/guides/follow-a-run/) — catching `wamp.artifact.created` as it happens. - [Artifacts operations](/api/operations/tags/artifacts/) — the generated endpoint reference, and [the API reference](/api/) for the manifest field by field. - [Events](/reference/events/) and [Limits](/reference/limits/).