adk-artifact 2.1.0

Binary artifact storage for Rust Agent Development Kit (ADK-Rust) agents
Documentation

adk-artifact

Binary artifact storage for Rust Agent Development Kit (ADK-Rust) agents.

Crates.io Documentation License

Overview

adk-artifact provides versioned binary data storage for the Rust Agent Development Kit (ADK-Rust). Artifacts are useful for storing images, documents, audio, and other binary data that agents produce or consume.

Three storage backends are included:

Backend Type Persistence Use case
InMemoryArtifactService In-memory HashMap Process lifetime only Development, testing
FileArtifactService Local filesystem Durable Single-node production, local dev
GcsArtifactService (feature gcs) Google Cloud Storage Durable Deployed agents, Gemini Enterprise

All implement the ArtifactService trait, so you can swap backends without changing application code.

The GCS backend keeps byte-for-byte blob-name parity with adk-python's GcsArtifactService, so Rust agents, Python agents, and the Gemini Enterprise console read the same objects:

  • Session-scoped: {app_name}/{user_id}/{session_id}/{filename}/{version}
  • User-namespaced (filename starts with user:): {app_name}/{user_id}/user/{filename}/{version}

Auto-assigned versions start at 0 (matching adk-python; the in-memory and file backends start at 1). Authentication uses Application Default Credentials via google-cloud-auth.

Important: save and load must resolve the same app_name — the engine ID when deployed. adk-python had a save/load path mismatch (googleapis/python-aiplatform#6521); this implementation takes app_name verbatim from each request and never rewrites it.

[dependencies]
adk-artifact = { version = "2.1.0", features = ["gcs"] }
# or via the umbrella crate:
adk-rust = { version = "2.1.0", features = ["minimal", "gcs-artifacts"] }
use adk_artifact::GcsArtifactService;

fn main() -> adk_core::Result<()> {
    let service = GcsArtifactService::new_with_adc("my-artifact-bucket")?;
    Ok(())
}

Installation

[dependencies]
adk-artifact = "2.1.0"

Or via the umbrella crate:

[dependencies]
adk-rust = { version = "2.1.0", features = ["artifacts"] }

Quick Start

use adk_artifact::{InMemoryArtifactService, ArtifactService, SaveRequest, LoadRequest};
use adk_core::Part;

let service = InMemoryArtifactService::new();

// Save an artifact (version auto-increments from 1)
let resp = service.save(SaveRequest {
    app_name: "my_app".to_string(),
    user_id: "user_123".to_string(),
    session_id: "session_456".to_string(),
    file_name: "report.pdf".to_string(),
    part: Part::InlineData {
        mime_type: "application/pdf".to_string(),
        data: pdf_bytes,
    },
    version: None,
}).await?;

println!("Saved as version {}", resp.version); // 1

// Load the latest version
let resp = service.load(LoadRequest {
    app_name: "my_app".to_string(),
    user_id: "user_123".to_string(),
    session_id: "session_456".to_string(),
    file_name: "report.pdf".to_string(),
    version: None, // None = latest
}).await?;

Storage Backends

InMemoryArtifactService

Stores artifacts in a thread-safe HashMap behind RwLock. Data is lost when the process exits.

use adk_artifact::InMemoryArtifactService;

let service = InMemoryArtifactService::new();

FileArtifactService

Persists artifacts to the local filesystem. Each version is stored as a JSON-serialized file under a directory hierarchy:

<base_dir>/<app_name>/<user_id>/<session_id>/<file_name>/v1.json
                                                         v2.json
use adk_artifact::FileArtifactService;

let service = FileArtifactService::new("/var/data/artifacts")?;

FileArtifactService includes a health_check() method that verifies the base directory is writable.

ArtifactService Trait

All backends implement the full ArtifactService trait:

Method Description
save(SaveRequest) Store an artifact. Auto-increments version when version: None.
load(LoadRequest) Retrieve an artifact. Loads latest version when version: None.
delete(DeleteRequest) Delete a specific version, or all versions when version: None.
list(ListRequest) List all artifact filenames in a session (includes user-scoped artifacts).
versions(VersionsRequest) List all available version numbers for a specific artifact.
health_check() Verify backend connectivity. Default impl returns Ok(()).

Versioning

Every artifact supports multiple versions. When saving with version: None, the version auto-increments starting from 1. You can also write to an explicit version:

// Auto-increment: first save → v1, second save → v2
service.save(SaveRequest { version: None, ..req }).await?;
service.save(SaveRequest { version: None, ..req }).await?;

// Explicit version
service.save(SaveRequest { version: Some(42), ..req }).await?;

// List all versions (returned in descending order)
let resp = service.versions(VersionsRequest {
    app_name: "app".into(),
    user_id: "user".into(),
    session_id: "sess".into(),
    file_name: "report.pdf".into(),
}).await?;
// resp.versions == [2, 1]

Scoping

Artifacts are scoped by three dimensions: app_name, user_id, and session_id. This means two sessions for the same user can each have a report.pdf without conflict.

User-Scoped Artifacts

Prefix a filename with user: to make it accessible across all sessions for that user:

// Save from session A
service.save(SaveRequest {
    session_id: "session_A".into(),
    file_name: "user:profile.png".into(),
    ..req
}).await?;

// Load from session B — same artifact
service.load(LoadRequest {
    session_id: "session_B".into(),
    file_name: "user:profile.png".into(),
    ..req
}).await?;

// list() from any session returns user-scoped artifacts too
let resp = service.list(ListRequest {
    session_id: "session_B".into(),
    ..list_req
}).await?;
// resp.file_names includes "user:profile.png"

Internally, user-scoped artifacts are stored under a shared _user_scoped_ directory (filesystem) or a "user" session key (in-memory), so they're visible regardless of which session ID is used.

Deleting Artifacts

use adk_artifact::DeleteRequest;

// Delete a specific version
service.delete(DeleteRequest {
    app_name: "app".into(),
    user_id: "user".into(),
    session_id: "sess".into(),
    file_name: "report.pdf".into(),
    version: Some(1),
}).await?;

// Delete all versions
service.delete(DeleteRequest {
    version: None,
    ..del_req
}).await?;

Deleting a non-existent artifact or version is a no-op (no error returned).

ScopedArtifacts

ScopedArtifacts wraps an ArtifactService and implements the simpler adk_core::Artifacts trait by automatically injecting app_name, user_id, and session_id into every request. This is the interface agents use at runtime.

use adk_artifact::{ScopedArtifacts, InMemoryArtifactService};
use adk_core::{Artifacts, Part};
use std::sync::Arc;

let service = Arc::new(InMemoryArtifactService::new());
let artifacts = ScopedArtifacts::new(
    service,
    "my_app".to_string(),
    "user_123".to_string(),
    "session_456".to_string(),
);

// Simple three-method API
let version = artifacts.save("chart.png", &part).await?;
let loaded = artifacts.load("chart.png").await?;
let files = artifacts.list().await?;

File Name Validation

Both backends validate artifact filenames to prevent path traversal attacks. The following are rejected with an AdkError::Artifact error:

  • Empty names
  • Names containing / or \
  • Names equal to . or ..
  • Names containing .. anywhere
// These all return Err:
service.save(SaveRequest { file_name: "".into(), .. }).await;           // empty
service.save(SaveRequest { file_name: "../etc/passwd".into(), .. }).await; // traversal
service.save(SaveRequest { file_name: "foo/bar.txt".into(), .. }).await;  // path separator

Use with LoadArtifactsTool

Artifacts integrate with agents via LoadArtifactsTool from adk-tool:

use adk_tool::LoadArtifactsTool;

let tool = LoadArtifactsTool::new();

let agent = LlmAgentBuilder::new("assistant")
    .tool(Arc::new(tool))
    .build()?;

The LLM can then call this tool to load artifacts by name into the conversation.

Related Crates

License

Apache-2.0

Part of ADK-Rust

This crate is part of the ADK-Rust framework for building AI agents in Rust.