Artifacts
Artifacts are binary files — PDFs, images, spreadsheets, and other attachments — that capabilities produce or consume during tool invocations. The orchestrator manages the entire artifact lifecycle: capabilities never communicate with each other directly.
This page explains how the artifact system works and how to implement artifact support in your capability.
How it works
Section titled “How it works”There are two directions:
- Output artifacts — a capability generates a file and the orchestrator retrieves it.
- Input artifacts — the orchestrator sends a previously stored file to a capability.
The orchestrator sits in the middle, storing artifacts in memory with the following constraints:
| Constraint | Value |
|---|---|
| Maximum artifact size | 5 MB (MAX_ARTIFACT_BYTES) |
| Time-to-live | 6 hours (ARTIFACT_TTL) |
| gRPC chunk size | 256 KB |
Each stored artifact is scoped to a user_id and session_id, so artifacts cannot leak across users or sessions.
Output artifacts (capability produces a file)
Section titled “Output artifacts (capability produces a file)”When a capability generates a file that needs to be passed along, it follows this flow:
-
Capability generates the file. For example, a PDF agent renders a document.
-
Capability stores the file internally and returns an
artifact_idin the tool result JSON:{"ok": true, "artifact_id": "abc123", "filename": "report.pdf"} -
Orchestrator calls
DownloadOutputArtifactto stream the binary data from the capability in 256 KB chunks. -
Orchestrator stores the file as a
StoredArtifactwithuser_id,session_id,filename,mime_type, and the raw data. -
Orchestrator rewrites the tool result that the LLM sees, replacing the raw
artifact_idwith full metadata:{"ok": true,"artifact": {"artifact_id": "abc123","filename": "report.pdf","mime_type": "application/pdf","size_bytes": 245678}} -
The LLM can now reference
artifact_idin subsequent tool calls — for example, to email the file as an attachment.
gRPC RPC
Section titled “gRPC RPC”rpc DownloadOutputArtifact(DownloadOutputArtifactRequest) returns (stream ArtifactChunk);The orchestrator sends a DownloadOutputArtifactRequest with the artifact_id, and the capability streams back ArtifactChunk messages until done is true.
Input artifacts (capability consumes a file)
Section titled “Input artifacts (capability consumes a file)”When the LLM wants to pass a previously produced file to another capability (or the same one), it includes an attachments array in the tool arguments:
-
LLM calls a tool with an
attachmentsarray containingartifact_idreferences:{"to": "alice@example.com", "subject": "Report", "attachments": [{"artifact_id": "abc123"}]} -
Orchestrator intercepts the tool call in
prepare_attachment_artifacts_for_capability. -
Orchestrator verifies ownership — the
user_idandsession_idon the stored artifact must match. -
Orchestrator uploads the artifact to the target capability via
UploadInputArtifactgRPC streaming (256 KB chunks). -
Capability returns a
capability_artifact_id— its own internal reference for the uploaded file. -
Orchestrator rewrites the tool arguments, replacing each
artifact_idwith the capability’s own reference plus metadata:{"to": "alice@example.com","subject": "Report","attachments": [{"capability_artifact_id": "cap-xyz","filename": "report.pdf","mime_type": "application/pdf","size_bytes": 245678}]}
gRPC RPC
Section titled “gRPC RPC”rpc UploadInputArtifact(stream UploadInputArtifactChunk) returns (UploadInputArtifactResponse);The orchestrator streams UploadInputArtifactChunk messages to the capability. The capability reassembles the file and returns a UploadInputArtifactResponse containing the capability_artifact_id.
End-to-end example
Section titled “End-to-end example”Here is a concrete example of artifacts flowing through the system:
User: “Create a PDF about marine biology and email it to alice@example.com”
- The LLM calls
pdf__create_pdf_documentwith content arguments. - The PDF capability generates the PDF, stores it internally, and returns:
{"ok": true, "artifact_id": "abc123", "filename": "marine_biology.pdf"}
- The orchestrator downloads the PDF via
DownloadOutputArtifactand stores it. - The orchestrator rewrites the result so the LLM sees:
{"ok": true, "artifact": {"artifact_id": "abc123", "filename": "marine_biology.pdf", "mime_type": "application/pdf", "size_bytes": 245678}}
- The LLM calls
pim__send_emailwith:{"to": "alice@example.com", "subject": "Marine Biology", "attachments": [{"artifact_id": "abc123"}]} - The orchestrator uploads the PDF to the PIM capability via
UploadInputArtifact. - The PIM capability receives the file, constructs a MIME email with the attachment, and sends it.
Implementing output artifacts
Section titled “Implementing output artifacts”To produce artifacts from your capability, you need to:
- Store generated files internally, keyed by a unique ID.
- Return an
artifact_idfield in your tool result JSON. - Implement the
DownloadOutputArtifactRPC to stream the data back when requested.
import uuidimport jsonimport grpcfrom concurrent import futuresimport capability_pb2 as pb2import capability_pb2_grpc as pb2_grpc
class MyCapability(pb2_grpc.CapabilityServicer): def __init__(self): self._output_artifacts = {}
def Invoke(self, request, context): args = json.loads(request.args_json)
# ... generate the file ... pdf_bytes = generate_pdf(args)
artifact_id = str(uuid.uuid4()) self._output_artifacts[artifact_id] = { "data": pdf_bytes, "filename": "report.pdf", "mime_type": "application/pdf", }
return pb2.InvokeResponse( result_json=json.dumps({ "ok": True, "artifact_id": artifact_id, "filename": "report.pdf", }).encode() )
def DownloadOutputArtifact(self, request, context): artifact = self._output_artifacts.get(request.artifact_id) if not artifact: yield pb2.ArtifactChunk(error="Artifact not found") return
data = artifact["data"] chunk_size = 256 * 1024 # 256 KB for i in range(0, len(data), chunk_size): yield pb2.ArtifactChunk( data=data[i:i + chunk_size], filename=artifact["filename"], mime_type=artifact["mime_type"], done=(i + chunk_size >= len(data)), )
def Healthcheck(self, request, context): return pb2.HealthResponse(ready=True)Implementing input artifacts
Section titled “Implementing input artifacts”To consume artifacts that the orchestrator uploads to your capability:
- Implement the
UploadInputArtifactRPC to receive streamed data. - Store uploaded artifacts keyed by the returned
capability_artifact_id. - In your tool handler, look up files by
capability_artifact_idfrom the rewritten arguments.
class MyCapability(pb2_grpc.CapabilityServicer): def __init__(self): self._input_artifacts = {}
def UploadInputArtifact(self, request_iterator, context): chunks = [] filename = "" mime_type = ""
for chunk in request_iterator: if chunk.filename: filename = chunk.filename if chunk.mime_type: mime_type = chunk.mime_type if chunk.data: chunks.append(chunk.data)
artifact_id = str(uuid.uuid4()) self._input_artifacts[artifact_id] = { "data": b"".join(chunks), "filename": filename, "mime_type": mime_type, }
return pb2.UploadInputArtifactResponse( capability_artifact_id=artifact_id )
def Invoke(self, request, context): args = json.loads(request.args_json)
# The orchestrator has rewritten the attachments array — # each entry now has capability_artifact_id instead of artifact_id. for attachment in args.get("attachments", []): cap_id = attachment["capability_artifact_id"] file_data = self._input_artifacts[cap_id] # Use file_data["data"], file_data["filename"], etc.
return pb2.InvokeResponse( result_json=json.dumps({"ok": True}).encode() )
def Healthcheck(self, request, context): return pb2.HealthResponse(ready=True)Error scenarios
Section titled “Error scenarios”| Scenario | What happens |
|---|---|
artifact_id not found on the capability | DownloadOutputArtifact yields an ArtifactChunk with the error field set. The orchestrator surfaces the error to the LLM. |
| Artifact exceeds 5 MB | The orchestrator rejects the artifact and returns an error to the LLM. |
| Artifact TTL expired (6 hours) | The orchestrator has evicted the stored artifact. The LLM receives an error when trying to reference the artifact_id. |
Ownership mismatch (user_id/session_id) | The orchestrator refuses to upload the artifact to the target capability and returns an error. |
UploadInputArtifact stream fails | The orchestrator reports a gRPC error to the LLM and the tool call is not executed. |
Next steps
Section titled “Next steps”- Full proto reference — Complete
capability.protodefinition with all artifact message types (ArtifactChunk,DownloadOutputArtifactRequest,UploadInputArtifactChunk,UploadInputArtifactResponse). - gRPC Interface — Overview of the full gRPC service contract.
- Container Guidelines — Network and resource rules for capability containers.