# How FileArtifactStore Manages Artifact Storage in aisuite

> Learn how FileArtifactStore in aisuite manages artifact storage on disk with dedicated subdirectories, metadata.json, and efficient ArtifactRef objects.

- Repository: [Andrew Ng/aisuite](https://github.com/andrewyng/aisuite)
- Tags: internals
- Published: 2026-07-28

---

**FileArtifactStore persists large binary or textual data to disk under a configurable root directory (default `.aisuite/artifacts`), storing each artifact in its own subdirectory containing a `data` file and a [`metadata.json`](https://github.com/andrewyng/aisuite/blob/main/metadata.json) with SHA-256 hashes, while returning lightweight `ArtifactRef` objects that keep traces small.**

aisuite, the framework for building LLM-powered agents, treats large data outputs—such as tool stdout or file contents—as **artifacts** to prevent bloating execution traces. The `FileArtifactStore` class in [`aisuite/agents/artifact_store.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/agents/artifact_store.py) provides the production-ready, filesystem-based implementation of the `ArtifactStore` protocol, enabling persistent artifact storage with integrity verification and seamless integration with the framework's tracing system.

## Core Abstractions in artifact_store.py

The artifact system rests on three core components defined in the same source file.

### ArtifactRef and Artifact

The **`ArtifactRef`** class (lines 14-31) serves as a lightweight, JSON-serializable handle containing the artifact ID, URI (`artifact://...` or `memory://...`), media type, size in bytes, and optional metadata. This reference can be safely embedded in execution traces without inflating payload size.

The **`Artifact`** class (lines 44-52) wraps the actual binary payload alongside its reference and creation timestamp. It provides a convenience `text()` method for decoding binary data to strings.

### The ArtifactStore Protocol

Lines 54-66 define the **`ArtifactStore`** protocol, which mandates three operations that any store must implement:

- `put(data, media_type, metadata)` → returns `ArtifactRef`
- `get(ref)` → returns `Artifact`
- `delete(ref)` → removes the artifact

`FileArtifactStore` satisfies this protocol using the local filesystem.

## FileArtifactStore Implementation

`FileArtifactStore` maps the abstract protocol to concrete file operations, creating a predictable on-disk layout.

### Directory Structure and Initialization

The constructor (lines 11-14) accepts a configurable root path defaulting to `.aisuite/artifacts` and normalizes it using `pathlib.Path`:

```python
def __init__(self, root: str | Path = ".aisuite/artifacts"):
    self.root = Path(root)

```

Each artifact receives a unique subdirectory named after its generated ID, containing two files: **`data`** (raw binary) and **[`metadata.json`](https://github.com/andrewyng/aisuite/blob/main/metadata.json)** (reference and hash).

### Storing Artifacts with put()

The `put` method (lines 15-46) handles serialization, unique ID generation, and atomic writes:

1. Converts input to bytes via `_to_bytes(data)`
2. Generates a unique ID using `new_id("artifact")`
3. Creates the subdirectory `<root>/<artifact_id>/`
4. Writes the binary payload to a file named `data`
5. Computes a SHA-256 hash and writes the reference plus metadata to [`metadata.json`](https://github.com/andrewyng/aisuite/blob/main/metadata.json)

```python
payload = _to_bytes(data)
artifact_id = new_id("artifact")
artifact_dir = self.root / artifact_id
artifact_dir.mkdir(parents=True, exist_ok=False)
data_path = artifact_dir / "data"
meta_path = artifact_dir / "metadata.json"
ref = ArtifactRef(
    artifact_id=artifact_id,
    uri=f"artifact://{artifact_id}",
    media_type=media_type,
    size_bytes=len(payload),
    metadata=_artifact_metadata(payload, metadata),
)
data_path.write_bytes(payload)
meta_path.write_text(json.dumps({"ref": ref.to_dict(),
                                 "created_at": now()}, sort_keys=True) + "\n",
                     encoding="utf-8")

```

The method returns the `ArtifactRef`, which contains the `artifact://<uuid>` URI for later retrieval.

### Retrieving Artifacts with get()

The `get` method (lines 49-61) resolves references (either `ArtifactRef` objects or URI strings) and reconstructs complete `Artifact` instances:

```python
artifact_id = _artifact_id(ref)
artifact_dir = self.root / artifact_id
data_path = artifact_dir / "data"
meta_path = artifact_dir / "metadata.json"
if not data_path.exists() or not meta_path.exists():
    raise KeyError(...)
metadata = json.loads(meta_path.read_text(encoding="utf-8"))
return Artifact(
    ref=ArtifactRef.from_dict(metadata["ref"]),
    data=data_path.read_bytes(),
    created_at=metadata.get("created_at", ""),
)

```

### Deleting Artifacts with delete()

The `delete` method (lines 63-80) performs idempotent cleanup by attempting to remove the `data` file, [`metadata.json`](https://github.com/andrewyng/aisuite/blob/main/metadata.json), and the artifact directory, silently ignoring `FileNotFoundError` and `OSError` exceptions:

```python
artifact_id = _artifact_id(ref)
artifact_dir = self.root / artifact_id
try: (artifact_dir / "data").unlink()
except FileNotFoundError: pass
try: (artifact_dir / "metadata.json").unlink()
except FileNotFoundError: pass
try: artifact_dir.rmdir()
except (FileNotFoundError, OSError): pass

```

## Metadata Integrity and Helper Functions

Several helper functions (lines 82-108) ensure data consistency and safety:

- **`_artifact_id`**: Normalizes reference strings by stripping the `artifact://` prefix
- **`_to_bytes`**: Coerces payloads (strings or bytes) to binary format
- **`_artifact_metadata`**: Computes SHA-256 hashes of the payload and validates JSON-serializability using `ensure_json_serializable` from [`aisuite/agents/types.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/agents/types.py)

These guarantees allow the framework to verify artifact integrity and safely embed metadata in JSON traces.

## Integration with the aisuite Framework

When tools return large outputs—such as the stdout from a shell command—the framework automatically converts them to artifacts. The resulting `ArtifactRef` is stored in `step.data["result_artifacts"]` rather than the full payload, keeping network and storage overhead minimal. The viewer UI retrieves these via HTTP endpoints that internally call `FileArtifactStore.get`, while `InMemoryArtifactStore` provides a volatile alternative for unit testing.

## Working with FileArtifactStore

The following example demonstrates persistent artifact storage across process boundaries:

```python
from aisuite import FileArtifactStore

# Initialize store with a custom directory

store = FileArtifactStore("/tmp/aisuite_artifacts")

# Store large text output from a tool

ref = store.put(
    "large stdout content from long-running process...",
    media_type="text/plain",
    metadata={"source": "shell_command", "exit_code": 0}
)

# The reference contains a lightweight URI that can be serialized

print(ref.uri)  # artifact://<uuid>

# Retrieve later, possibly in a different process

artifact = store.get(ref)
print(artifact.text()[:50])  # First 50 characters of the stored data

# Cleanup the on-disk files

store.delete(ref)

```

## Summary

- **FileArtifactStore** implements the `ArtifactStore` protocol for persistent, filesystem-based artifact storage in aisuite
- Artifacts reside in dedicated subdirectories under `.aisuite/artifacts`, each containing a `data` file and [`metadata.json`](https://github.com/andrewyng/aisuite/blob/main/metadata.json) with SHA-256 hashes
- The `put` method generates deterministic unique IDs, computes integrity hashes, and returns serializable `ArtifactRef` objects with `artifact://` URIs
- The `get` method reconstructs `Artifact` instances from disk, while `delete` performs safe, idempotent cleanup
- Integration with tool execution automatically offloads large outputs to prevent trace bloat, with `InMemoryArtifactStore` available for testing scenarios

## Frequently Asked Questions

### What is the default storage location for FileArtifactStore?

By default, FileArtifactStore writes artifacts to the `.aisuite/artifacts` directory relative to the execution context. You can override this by passing a custom path to the constructor: `FileArtifactStore("/custom/path")`. The directory structure `<root>/<artifact_id>/` organizes each artifact's files independently.

### How does FileArtifactStore ensure data integrity?

During the `put` operation, the store computes a SHA-256 hash of the binary payload via `_artifact_metadata` and stores it in the [`metadata.json`](https://github.com/andrewyng/aisuite/blob/main/metadata.json) file alongside the `ArtifactRef`. This hash persists with the artifact, allowing validation whenever the data is retrieved via `get`.

### Can I use FileArtifactStore across different processes?

Yes. Since FileArtifactStore persists data to the filesystem using standard file operations, separate processes can initialize `FileArtifactStore` with the same root directory and retrieve artifacts using either the `ArtifactRef` object or the `artifact://<uuid>` URI string. This enables distributed agent workflows where producers and consumers run in different environments.

### What happens if I try to delete an artifact that doesn't exist?

The `delete` method is idempotent. It catches `FileNotFoundError` when attempting to unlink the `data` file or [`metadata.json`](https://github.com/andrewyng/aisuite/blob/main/metadata.json), and catches `OSError` if the directory is not empty or missing. All errors are silently ignored, so calling `delete` multiple times on the same reference does not raise exceptions.