How to Implement Custom Artifact Stores for Agent Outputs in aisuite
To implement custom artifact stores for agent outputs in aisuite, create a class that implements put, get, and delete methods to handle ArtifactRef objects, then pass an instance to Runner.run_sync() via the artifact_store parameter to automatically offload large outputs from the LLM context.
aisuite provides a flexible artifact store abstraction that prevents large agent outputs—such as lengthy command results or big file contents—from bloating LLM context windows. By implementing custom artifact stores for agent outputs in aisuite, you control exactly how and where heavy payloads are persisted while keeping trace references lightweight and cost-efficient.
Understanding the Artifact Store Architecture
The artifact store system in aisuite/agents/artifact_store.py provides a protocol-like interface that decouples storage implementation from agent execution logic. When an output exceeds the default threshold of 20,000 characters, aisuite automatically creates an artifact reference that points to stored data rather than embedding the full payload in the trace.
Core Components
The mechanism relies on two primary classes defined in aisuite/agents/artifact_store.py:
ArtifactRef– Lightweight metadata that identifies a stored blob through a unique ID and content typeArtifact– The wrapper object retrieved from stores, exposing methods like.text()to decode the raw content
During trace generation, the artifactize_value function in aisuite/agents/artifacts.py (lines 45-78) automatically replaces oversized strings with artifact references. The dehydrate_messages function handles the reverse operation, ensuring that when messages are sent to the LLM, artifact references are resolved back to full content.
Automatic Artifactization Flow
When tools return large results, the automatic handoff occurs in aisuite/utils/tools.py (lines 76-84). If a string exceeds the threshold, artifactize_value calls artifact_store.put() to persist the data, returning a dict structure containing:
{
"type": "artifact_ref",
"preview": "first-few-chars…",
"artifact_ref": { … }
}
The preview keeps trace UIs readable while the full content lives in your custom store.
The Artifact Store Interface
To implement a custom artifact store, your class must conform to the interface used by Runner and the artifactization utilities. Based on the source code in aisuite/agents/artifact_store.py, any compatible store must implement three core methods:
put(data: bytes | str, content_type: str | None = None) -> ArtifactRef– Persist the raw data and return a reference object containing the artifact IDget(ref: ArtifactRef) -> Artifact– Retrieve the artifact by reference, returning an object with.text()and.bytes()methodsdelete(ref: ArtifactRef) -> None– Remove the artifact from storage, tolerating missing files gracefully
The return value of get() must expose the raw content such that calling .text() returns the decoded string, matching the behavior of InMemoryArtifactStore and FileArtifactStore.
Implementing a Custom Artifact Store
Here is a minimal example implementing a custom store that persists artifacts to an external API or database:
import uuid
from typing import Optional
from aisuite.agents.artifact_store import ArtifactRef, Artifact
class CustomArtifactStore:
def __init__(self, api_client):
self.api_client = api_client
self._cache = {}
def put(self, data: bytes | str, content_type: Optional[str] = None) -> ArtifactRef:
"""Store data externally and return a reference."""
if isinstance(data, str):
data = data.encode('utf-8')
artifact_id = str(uuid.uuid4())
# Your custom persistence logic here
self.api_client.upload(blob_id=artifact_id, payload=data, content_type=content_type)
return ArtifactRef(
id=artifact_id,
content_type=content_type or "application/octet-stream"
)
def get(self, ref: ArtifactRef) -> Artifact:
"""Retrieve artifact from external storage."""
# Fetch from your custom backend
raw_bytes = self.api_client.download(blob_id=ref.id)
# Return an object compatible with the Artifact interface
return CustomArtifact(raw_bytes, ref.content_type)
def delete(self, ref: ArtifactRef) -> None:
"""Remove artifact from external storage."""
try:
self.api_client.delete(blob_id=ref.id)
except Exception:
pass # Tolerate missing artifacts as per the interface contract
class CustomArtifact:
"""Wrapper compatible with aisuite's Artifact expectations."""
def __init__(self, data: bytes, content_type: str):
self._data = data
self.content_type = content_type
def text(self) -> str:
return self._data.decode('utf-8')
def bytes(self) -> bytes:
return self._data
This implementation follows the pattern established in aisuite/agents/artifact_store.py while allowing you to integrate with any backend—whether cloud blob storage, Redis, or a proprietary database.
Using Built-in Reference Implementations
Before building custom solutions, review the reference implementations in aisuite/agents/artifact_store.py:
InMemoryArtifactStore
The InMemoryArtifactStore class (lines 68-94) stores artifacts in a Python dictionary, making it ideal for testing or ephemeral workloads:
import aisuite as ai
store = ai.InMemoryArtifactStore()
This store keeps all data in process memory and evaporates when the program exits.
FileArtifactStore
The FileArtifactStore class (lines 111-148) persists artifacts under a configurable directory hierarchy:
store = ai.FileArtifactStore(".aisuite/artifacts")
This creates a folder per artifact (e.g., .aisuite/artifacts/art_1/) containing data and metadata.json. The web viewer in aisuite/tracing/viewer.py automatically serves these files via /api/artifacts/<artifact_id> endpoints.
Integrating Stores with Agent Runs
Whether using built-in or custom stores, integration occurs at the run entry point in aisuite/agents/runner.py (lines 49-58). Pass your store instance to Runner.run_sync or Runner.run:
from aisuite.agents import Agent, Runner
agent = Agent(
name="data-processor",
model="openai:gpt-4o",
tools=[*ai.toolkits.shell(cwd=".")],
instructions="Process large files and return results.",
)
# Inject your custom store
result = Runner.run_sync(
agent,
"Generate a 50,000 character report",
artifact_store=CustomArtifactStore(api_client=my_client),
)
The store is stored in the active run context (lines 68-71 of runner.py), making it available throughout the agent lifecycle for automatic artifactization.
Retrieving and Managing Artifacts
After a run completes, access stored artifacts programmatically through the store reference:
# Extract artifact reference from the final step
artifacts = result.steps[-1].data.get("result_artifacts", [])
if artifacts:
ref = ai.ArtifactRef.from_dict(artifacts[0]["artifact_ref"])
# Retrieve full content through your custom store
full_content = artifact_store.get(ref).text()
print(f"Retrieved {len(full_content)} characters from custom storage")
To clean up resources, call the delete method on your store instance:
artifact_store.delete(ref)
As implemented in the FileArtifactStore source, the delete method tolerates missing files and directory-removal errors, ensuring idempotent cleanup.
Summary
- Implement three methods:
put,get, anddeleteto create a custom store compatible with aisuite's artifact system - Handle
ArtifactRefobjects: Return references fromputand accept them ingetanddelete - Inject at runtime: Pass your store to
Runner.run_sync()via theartifact_storeparameter - Automatic optimization: Outputs exceeding 20,000 characters automatically offload to your store via
artifactize_valueinaisuite/agents/artifacts.py - Retrieve via
.text(): Stored artifacts expose content through the.text()method, keeping the interface consistent across storage backends
Frequently Asked Questions
What is the default size threshold for automatic artifactization?
According to the source code in aisuite/utils/tools.py, the default threshold is 20,000 characters. When a tool returns a string exceeding this length, artifactize_value automatically stores it via artifact_store.put and replaces it with a reference object. You can check this implementation in lines 76-84 of tools.py.
Can I use multiple artifact stores in the same application?
Each Runner.run_sync() call accepts a single artifact_store argument, so you cannot mix stores within a single run. However, different runs within the same application can use different store instances. For example, you might use InMemoryArtifactStore for quick tests and FileArtifactStore or a custom implementation for production traces.
How does the web viewer access artifacts from custom stores?
The built-in viewer in aisuite/tracing/viewer.py serves artifacts via /api/artifacts/<artifact_id> endpoints. If you implement a custom store, you must either integrate with the viewer's API or access artifacts programmatically using artifact_store.get(ref).text(). The viewer automatically handles FileArtifactStore paths, but custom backends require additional routing to expose stored data.
What happens if a custom store's get method fails during message hydration?
During run initialization, hydrate_messages in aisuite/agents/artifacts.py resolves artifact references back to text using the store. If your custom store's get method raises an exception, the hydration will fail and the run may error. Ensure your implementation handles missing artifacts gracefully or raises clear exceptions that aid debugging.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →