# How to Implement Custom Artifact Stores for Agent Outputs in aisuite

> Learn how to implement custom artifact stores for agent outputs in aisuite. Create artifact store classes to manage large outputs, improving LLM context efficiency.

- Repository: [Andrew Ng/aisuite](https://github.com/andrewyng/aisuite)
- Tags: how-to-guide
- Published: 2026-06-15

---

**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`](https://github.com/andrewyng/aisuite/blob/main/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`](https://github.com/andrewyng/aisuite/blob/main/aisuite/agents/artifact_store.py):

- **`ArtifactRef`** – Lightweight metadata that identifies a stored blob through a unique ID and content type
- **`Artifact`** – 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`](https://github.com/andrewyng/aisuite/blob/main/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`](https://github.com/andrewyng/aisuite/blob/main/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:

```json
{
  "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`](https://github.com/andrewyng/aisuite/blob/main/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 ID
- **`get(ref: ArtifactRef) -> Artifact`** – Retrieve the artifact by reference, returning an object with `.text()` and `.bytes()` methods
- **`delete(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:

```python
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`](https://github.com/andrewyng/aisuite/blob/main/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`](https://github.com/andrewyng/aisuite/blob/main/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:

```python
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:

```python
store = ai.FileArtifactStore(".aisuite/artifacts")

```

This creates a folder per artifact (e.g., `.aisuite/artifacts/art_1/`) containing `data` and [`metadata.json`](https://github.com/andrewyng/aisuite/blob/main/metadata.json). The web viewer in [`aisuite/tracing/viewer.py`](https://github.com/andrewyng/aisuite/blob/main/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`](https://github.com/andrewyng/aisuite/blob/main/aisuite/agents/runner.py) (lines 49-58). Pass your store instance to `Runner.run_sync` or `Runner.run`:

```python
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`](https://github.com/andrewyng/aisuite/blob/main/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:

```python

# 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:

```python
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`, and `delete` to create a custom store compatible with aisuite's artifact system
- **Handle `ArtifactRef` objects**: Return references from `put` and accept them in `get` and `delete`
- **Inject at runtime**: Pass your store to `Runner.run_sync()` via the `artifact_store` parameter
- **Automatic optimization**: Outputs exceeding 20,000 characters automatically offload to your store via `artifactize_value` in [`aisuite/agents/artifacts.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/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`](https://github.com/andrewyng/aisuite/blob/main/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`](https://github.com/andrewyng/aisuite/blob/main/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`](https://github.com/andrewyng/aisuite/blob/main/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`](https://github.com/andrewyng/aisuite/blob/main/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.