Publish and download Artifacts
Publish an Artifact when an Agent-created file must leave its private Sandbox workspace and become an application output. This guide uses a stateful Turn, then verifies the Artifact's filename, media type, and exact downloaded bytes.
Outcome
The Agent will create and publish release.md. Your backend will find it in
the producing Session, download it, and assert its complete contract.
Before you begin
You need a file-enabled Agent, its Sandbox, and a backend SDK client. Choose a stable Session and Attribution scope so your application can find the intended output later.
Create and publish a file
Ask for both actions explicitly: writing creates mutable workspace state;
only save_artifacts publishes the selected path as an immutable Artifact.
const expected = "# Release\n\nReady to ship.\n";
const turn = await client.chat({
agentId,
userId: "end-user-42",
message: { id: crypto.randomUUID(), role: "user", parts: [{
type: "text",
text: `Write release.md with exactly ${JSON.stringify(expected)}, then use save_artifacts to publish release.md.`,
}] },
});
const sessionId = await turn.sessionId;
for await (const _chunk of turn.toUIMessageStream()) { /* drain */ }
const page = await client.artifacts.list(agentId, {
sessionId,
userId: "end-user-42",
});
const artifact = page.data.find((item) => item.filename === "release.md");
if (!artifact) throw new Error("release.md was not published");
const download = await client.artifacts.download(agentId, artifact.artifactId);
const bytes = new Uint8Array(await download.arrayBuffer());
const expectedBytes = new TextEncoder().encode(expected);
if (artifact.filename !== "release.md") throw new Error("filename differs");
if (artifact.mediaType !== "text/markdown") throw new Error("media type differs");
if (download.headers.get("content-type") !== artifact.mediaType) throw new Error("header differs");
if (bytes.length !== expectedBytes.length
|| !bytes.every((byte, index) => byte === expectedBytes[index])) throw new Error("bytes differ");List the Session Artifacts
client.artifacts.list() is Agent-scoped and accepts sessionId, userId,
includeDeleted, and cursor filters. Matching both the producing Session
and its Attribution narrows this example to the intended output.
Download from the backend
Download through an authenticated backend
client.artifacts.download() sends a tenant credential. Keep that
credential out of browser and mobile code; proxy the bytes through your
backend under your application's own access rules.
Download returns a standard Response. Keep the list item's filename
and mediaType, and consume the response body once as text, bytes, or a
stream.
Verify the download
The assertions verify the stored filename, stored media type, download
Content-Type, and exact body. The server also forces attachment behavior
and sends X-Content-Type-Options: nosniff.
Delete an Artifact
Delete an output when your retention policy no longer needs it:
await client.artifacts.delete(agentId, artifact.artifactId);
const tombstones = await client.artifacts.list(agentId, { includeDeleted: true });
console.log(tombstones.data.find((item) => item.artifactId === artifact.artifactId)?.deletedAt);The delete returns after the product row is tombstoned and durable cleanup is
recorded. Normal lists omit it, includeDeleted: true exposes deletedAt, and
later download or repeated delete returns HTTP 410.
Production notes
- Every Artifact belongs to one tenant, Agent, and stateful Session and
inherits the Session's
userIdandmetadata. Attribution filters data; it is not authorization. - Artifact bytes live in private object storage. Tenant credentials can access the tenant's resources, so enforce End-user access in your backend.
- Artifact content is immutable. Publish a new Artifact to replace a file.
- Deletion keeps a tombstone, does not rewrite the Session transcript, and reclaims private bytes asynchronously through durable cleanup.
- One
save_artifactscall accepts at most 10 paths, 10 MiB per file, and 25 MiB total. A Session can hold at most 100 active Artifacts. See limits and reliability.
Related capabilities
SDK and REST reference
- Publish in a stateful Turn:
client.chat()andPOST .../sessions - List Artifacts:
artifacts.list()andGET .../artifacts - Download bytes:
artifacts.download()andGET .../artifacts/:artifactId - Delete an Artifact:
artifacts.delete()andDELETE .../artifacts/:artifactId