Skip to content

Agent Object Storage

This document describes how to use the S3Storage class from the agent_core_libs package to work with an S3-compatible object storage backend (AWS S3, MinIO, Ceph, etc.).


Introduction

S3Storage is a thin wrapper around boto3 that provides:

  • Uploads from a file path, bytes, a file object, or a JSON-serializable object.
  • Downloads to a file path, a file object, bytes, or parsed JSON.
  • Server-side copy and move.
  • Deleting single objects or everything under a prefix.
  • Listing and iterating objects, reading metadata, and existence checks.
  • Bucket helpers and presigned URLs.

It supports:

  • Access-key and session-token authentication.
  • Custom endpoints (MinIO and other S3-compatible stores), with optional path-style addressing.
  • Automatic retries (standard retry mode) and multipart transfers for large files.

All methods log success, and log then re-raise botocore ClientError / BotoCoreError on failure.


Installation

S3Storage is part of agent_core_libs (libs/core-libs), which depends on boto3>=1.34.0.

pip install ./libs/core-libs

Import

from agent_core_libs.storage import S3Storage

Usage

Initialize the client

All arguments are keyword-only.

store = S3Storage(
    access_key_id="your-access-key",
    secret_access_key="your-secret-key",
    region_name="us-east-1",
)
Argument Default Description
access_key_id — (required) Access key
secret_access_key — (required) Secret key
region_name "us-east-1" Region
session_token None Session token for temporary credentials
endpoint_url None Custom S3 endpoint (e.g. MinIO)
max_attempts 10 Maximum retry attempts
multipart_threshold_mb 8 Use multipart transfers above this size (MB)
multipart_chunksize_mb 8 Multipart chunk size (MB)
use_path_style False Use path-style addressing (endpoint/bucket/key)
logger module logger Custom logging.Logger

Upload a file from a path

store.upload_file("my-bucket", "my-folder/my-file.txt", "/path/to/local/file.txt")

# With extra S3 arguments
store.upload_file(
    "my-bucket", "docs/report.pdf", "/tmp/report.pdf",
    extra_args={"ContentType": "application/pdf", "ACL": "private"},
)

Upload bytes

store.upload_bytes("my-bucket", "data/hello.txt", b"Hello, world!")

Upload from a file object (e.g. BytesIO)

import io

buffer = io.BytesIO(b"Buffered content")
store.upload_fileobj("my-bucket", "buffer/file.txt", buffer)

Upload JSON

store.put_json("my-bucket", "configs/settings.json", {"threshold": 0.8})
# Stored with ContentType "application/json"

Download to a local path

store.download_file("my-bucket", "my-folder/my-file.txt", "/path/to/save/file.txt")

Download to a file object

import io

output_buffer = io.BytesIO()
store.download_to_fileobj("my-bucket", "data/hello.txt", output_buffer)
print(output_buffer.getvalue())

Download as bytes

data = store.download_as_bytes("my-bucket", "data/hello.txt")
print(data.decode())

Read JSON

settings = store.get_json("my-bucket", "configs/settings.json")

Copy and move

store.copy_object("src-bucket", "a/file.txt", "dest-bucket", "b/file.txt")
store.move_object("src-bucket", "a/file.txt", "dest-bucket", "b/file.txt")  # copy, then delete source

Delete

store.delete_object("my-bucket", "data/hello.txt")

deleted_count = store.delete_prefix("my-bucket", "tmp/")   # deletes in batches of 1000

List and inspect objects

objects = store.list_objects("my-bucket", prefix="data/", max_keys=100)
for obj in objects:
    print(obj["Key"], obj["Size"], obj["LastModified"])

# Lazily iterate over all objects under a prefix
for obj in store.iter_objects("my-bucket", prefix="logs/"):
    print(obj["Key"])

meta = store.head_object("my-bucket", "data/hello.txt")   # metadata only, no body
store.exists("my-bucket", "data/hello.txt")               # True / False

Buckets and presigned URLs

store.bucket_exists("my-bucket")      # True / False
store.create_bucket("my-bucket")      # no-op if it already exists

url = store.presigned_url("my-bucket", "data/hello.txt", expires_in=3600)              # download URL
put_url = store.presigned_url("my-bucket", "uploads/new.bin", method="put_object")     # upload URL

Custom Object Storage (MinIO, Ceph, …)

Pass the store's endpoint as endpoint_url. Most self-hosted stores also need path-style addressing:

store = S3Storage(
    access_key_id="your-key",
    secret_access_key="your-secret",
    region_name="us-east-1",
    endpoint_url="https://your-custom-object-store.local",
    use_path_style=True,
)

All methods work the same way against the custom endpoint.