Table of Contents

Cloudflare R2

OptunaSharp.Artifacts.R2 supplies R2ArtifactStore, an OptunaSharp extension for R2's S3-compatible object API. It configures an AWS S3 client and delegates every artifact operation to Boto3ArtifactStore. Installing it brings the S3 artifact package and AWSSDK.S3; it targets .NET 8 and .NET 10. Package definition.

dotnet add package OptunaSharp.Artifacts.R2

Account credentials or an injected client

R2ArtifactStore(
    string accountId,
    string accessKeyId,
    string secretAccessKey,
    string bucketName,
    bool avoidBufCopy = false);

R2ArtifactStore(IAmazonS3 client, string bucketName, bool avoidBufCopy = false);

The first overload expects the Cloudflare account ID and R2 S3 API access-key credentials. It builds this configuration:

Setting Value
Service endpoint https://<accountId>.r2.cloudflarestorage.com
Bucket addressing ForcePathStyle = true
Signing region AuthenticationRegion = "auto"

The constructor does not create a bucket, discover credentials, or contact the endpoint to validate them. The second overload uses your supplied S3 client as-is, giving you control over credentials, endpoint, signing settings, and SDK options. Endpoint configuration and constructors, configuration tests.

using System;
using System.IO;
using OptunaSharp;
using OptunaSharp.Artifacts;

static string Required(string name) => Environment.GetEnvironmentVariable(name)
    ?? throw new InvalidOperationException($"Configure {name}.");

var store = new R2ArtifactStore(
    accountId: Required("R2_ACCOUNT_ID"),
    accessKeyId: Required("R2_ACCESS_KEY_ID"),
    secretAccessKey: Required("R2_SECRET_ACCESS_KEY"),
    bucketName: "optimization-artifacts",
    avoidBufCopy: true);

var study = Optuna.CreateStudy();
// model.bin must already exist, and the bucket must already be provisioned.
string id = ArtifactFunctions.UploadArtifact(store, "model.bin", study);
Directory.CreateDirectory("restored");
ArtifactFunctions.DownloadArtifact(store, Path.Combine("restored", $"{id}.bin"), id);

The environment-variable names above belong to this example; the store reads the strings passed to its constructor. When explicit client lifetime is needed, create and own an AmazonS3Client with the settings in the table and inject it. Neither R2ArtifactStore nor its S3 delegate implements IDisposable or disposes an injected client. The credential constructor creates a client internally without exposing a disposal hook. Wrapper implementation, S3 delegate.

Transfer behavior inherited from S3

The R2 object key is the artifact ID verbatim. Core generates GUID IDs and stores filenames, MIME types, and encodings in study/trial metadata. R2 adds no prefix, public URL, object headers, or provider metadata to that contract.

Writes buffer the full input by default; avoidBufCopy: true forwards the caller's stream instead. The delegate uses PutObjectAsync with AutoCloseStream = false, then blocks for completion. Reads return the S3 response stream, which DownloadArtifact disposes after copying. A missing read (NoSuchKey or HTTP 404) becomes ArtifactNotFound; other SDK errors propagate. Missing-key deletion is idempotent. S3 implementation.

There is no R2-specific retry, multipart, signed-URL, or cancellation API in this adapter. SDK options belong on an injected client, and Core's optional Backoff has the same synchronous, seekable-upload and stream-opening boundaries as with S3. The shared artifact guide explains metadata consistency and download conflicts.

Verification boundary

Unit tests pin the account endpoint, path-style addressing, and "auto" signing region, then verify delegation and forwarding of avoidBufCopy. The integration suite exercises R2 through an injected MinIO client: it tests the wrapper's S3 object behavior, not a live Cloudflare account. Empty and 2 MiB round trips, overwrite, nested keys, missing reads, and idempotent missing deletes are covered by that emulator contract. R2 unit tests, integration tests.