Artifacts enable agents to save, retrieve, and version files and binary data. Unlike state which stores structured data, artifacts handle documents, images, code files, and other file-based content.
BaseArtifactService
All artifact services implement this interface:
// packages/adk/src/artifacts/base-artifact-service.ts:3
export interface BaseArtifactService {
saveArtifact ( args : {
appName : string ;
userId : string ;
sessionId : string ;
filename : string ;
artifact : Part ; // Google GenAI Part format
}) : Promise < number >; // Returns version number
loadArtifact ( args : {
appName : string ;
userId : string ;
sessionId : string ;
filename : string ;
version ?: number ; // Defaults to latest
}) : Promise < Part | null >;
listArtifactKeys ( args : {
appName : string ;
userId : string ;
sessionId : string ;
}) : Promise < string []>;
deleteArtifact ( args : {
appName : string ;
userId : string ;
sessionId : string ;
filename : string ;
}) : Promise < void >;
listVersions ( args : {
appName : string ;
userId : string ;
sessionId : string ;
filename : string ;
}) : Promise < number []>;
}
Artifacts use Google GenAI’s Part format:
type Part =
| { text : string } // Text content
| { inlineData : { // Binary data
mimeType : string ;
data : string ; // Base64 encoded
}}
| { fileData : { // File reference
mimeType : string ;
fileUri : string ;
}};
Artifact Namespacing
Artifacts can be scoped to sessions or users:
Session-Scoped
User-Scoped
Default: artifacts are tied to a specific session await artifactService . saveArtifact ({
appName: 'my-app' ,
userId: 'user-123' ,
sessionId: 'session-456' ,
filename: 'report.pdf' ,
artifact: { inlineData: { data: pdfData , mimeType: 'application/pdf' } },
});
Storage path: {app}/{user}/{session}/{filename}/{version}Use for:
Conversation-specific files
Temporary outputs
Session artifacts
Use user: prefix for user-level persistence: await artifactService . saveArtifact ({
appName: 'my-app' ,
userId: 'user-123' ,
sessionId: 'any-session' , // Doesn't matter
filename: 'user:profile.jpg' , // user: prefix
artifact: { inlineData: { data: imageData , mimeType: 'image/jpeg' } },
});
Storage path: {app}/{user}/user/{filename}/{version}Use for:
User profile data
User preferences files
Cross-session artifacts
Saving Artifacts
Prepare Content
// Text artifact
const textArtifact = {
text: 'Hello, world!' ,
};
// Binary artifact (base64 encoded)
const imageArtifact = {
inlineData: {
mimeType: 'image/png' ,
data: Buffer . from ( imageData ). toString ( 'base64' ),
},
};
// File reference
const fileRefArtifact = {
fileData: {
mimeType: 'application/pdf' ,
fileUri: 'gs://bucket/path/to/file.pdf' ,
},
};
Save Artifact
const version = await artifactService . saveArtifact ({
appName: session . appName ,
userId: session . userId ,
sessionId: session . id ,
filename: 'output.txt' ,
artifact: textArtifact ,
});
console . log ( 'Saved as version:' , version );
Versioning
Each save creates a new version:
// First save
const v0 = await artifactService . saveArtifact ({
appName: 'my-app' ,
userId: 'user-123' ,
sessionId: 'session-456' ,
filename: 'document.txt' ,
artifact: { text: 'Version 1' },
});
console . log ( 'Version:' , v0 ); // 0
// Update (new version)
const v1 = await artifactService . saveArtifact ({
appName: 'my-app' ,
userId: 'user-123' ,
sessionId: 'session-456' ,
filename: 'document.txt' ,
artifact: { text: 'Version 2' },
});
console . log ( 'Version:' , v1 ); // 1
// Load specific version
const oldVersion = await artifactService . loadArtifact ({
appName: 'my-app' ,
userId: 'user-123' ,
sessionId: 'session-456' ,
filename: 'document.txt' ,
version: 0 ,
});
console . log ( oldVersion ?. text ); // 'Version 1'
Loading Artifacts
Latest Version
Specific Version
Negative Index
// Load the most recent version
const artifact = await artifactService . loadArtifact ({
appName: 'my-app' ,
userId: 'user-123' ,
sessionId: 'session-456' ,
filename: 'report.pdf' ,
});
if ( artifact ) {
console . log ( 'MIME type:' , artifact . inlineData ?. mimeType );
console . log ( 'Data:' , artifact . inlineData ?. data );
}
Listing Artifacts
const files = await artifactService . listArtifactKeys ({
appName: 'my-app' ,
userId: 'user-123' ,
sessionId: 'session-456' ,
});
console . log ( 'Artifacts:' , files );
// ['report.pdf', 'chart.png', 'data.json', 'user:profile.jpg']
const versions = await artifactService . listVersions ({
appName: 'my-app' ,
userId: 'user-123' ,
sessionId: 'session-456' ,
filename: 'report.pdf' ,
});
console . log ( 'Versions:' , versions );
// [0, 1, 2, 3, 4]
Deleting Artifacts
// Delete all versions of a file
await artifactService . deleteArtifact ({
appName: 'my-app' ,
userId: 'user-123' ,
sessionId: 'session-456' ,
filename: 'report.pdf' ,
});
Deleting an artifact removes ALL versions. There is no undo.
Artifact Services
InMemoryArtifactService
GcsArtifactService
Custom Service
In-memory storage for development: // packages/adk/src/artifacts/in-memory-artifact-service.ts:5
import { InMemoryArtifactService } from '@iqai/adk' ;
const artifacts = new InMemoryArtifactService ();
const agent = await AgentBuilder
. withModel ( 'gpt-4' )
. withArtifactService ( artifacts )
. build ();
Features:
Fast and simple
No persistence (lost on restart)
Automatic versioning
Supports user: namespace
Perfect for testing
Storage structure: // Map<path, Part[]>
private artifacts : Map < string , Part [] > = new Map ();
// Path format:
// Session: "app/user/session/file.txt"
// User: "app/user/user/user:file.txt"
Google Cloud Storage for production: // packages/adk/src/artifacts/gcs-artifact-service.ts:9
import { GcsArtifactService } from '@iqai/adk' ;
const artifacts = new GcsArtifactService ( 'my-bucket' , {
projectId: 'my-project' ,
keyFilename: '/path/to/service-account.json' ,
});
const agent = await AgentBuilder
. withModel ( 'gpt-4' )
. withArtifactService ( artifacts )
. build ();
Features:
Persistent cloud storage
Scalable and reliable
Supports large files
Automatic versioning
Pay per use
Storage structure: gs://bucket/
├─ app1/
│ ├─ user1/
│ │ ├─ session1/
│ │ │ ├─ file1.txt/
│ │ │ │ ├─ 0
│ │ │ │ ├─ 1
│ │ │ │ └─ 2
│ │ │ └─ file2.pdf/
│ │ │ └─ 0
│ │ └─ user/
│ │ └─ user:profile.jpg/
│ │ └─ 0
Implement your own storage: import type { BaseArtifactService , Part } from '@iqai/adk' ;
class S3ArtifactService implements BaseArtifactService {
constructor ( private s3Client : S3Client , private bucket : string ) {}
async saveArtifact ( args : {
appName : string ;
userId : string ;
sessionId : string ;
filename : string ;
artifact : Part ;
}) : Promise < number > {
// Get existing versions
const versions = await this . listVersions ( args );
const version = versions . length > 0 ? Math . max ( ... versions ) + 1 : 0 ;
// Save to S3
const key = this . buildKey ( args . appName , args . userId ,
args . sessionId , args . filename , version );
await this . s3Client . putObject ({
Bucket: this . bucket ,
Key: key ,
Body: args . artifact . inlineData ?. data || args . artifact . text ,
ContentType: args . artifact . inlineData ?. mimeType || 'text/plain' ,
});
return version ;
}
async loadArtifact ( args : {
appName : string ;
userId : string ;
sessionId : string ;
filename : string ;
version ?: number ;
}) : Promise < Part | null > {
// Implement S3 loading
}
// Implement other methods...
}
Tools can save and load artifacts via ToolContext:
import { BaseTool , ToolContext } from '@iqai/adk' ;
import { z } from 'zod' ;
class SaveReportTool extends BaseTool < typeof SaveReportTool . argsSchema > {
name = 'save_report' ;
description = 'Save a report to a file' ;
static argsSchema = z . object ({
filename: z . string (). describe ( 'Output filename' ),
content: z . string (). describe ( 'Report content' ),
});
async execute (
args : z . infer < typeof SaveReportTool . argsSchema >,
context : ToolContext
) {
const { artifactService , session } = context ;
if ( ! artifactService ) {
return {
content: 'Artifact service not configured' ,
isError: true ,
};
}
// Save the artifact
const version = await artifactService . saveArtifact ({
appName: session . appName ,
userId: session . userId ,
sessionId: session . id ,
filename: args . filename ,
artifact: { text: args . content },
});
return {
content: `Saved ${ args . filename } (version ${ version } )` ,
};
}
}
class LoadReportTool extends BaseTool < typeof LoadReportTool . argsSchema > {
name = 'load_report' ;
description = 'Load a report from a file' ;
static argsSchema = z . object ({
filename: z . string (). describe ( 'Filename to load' ),
version: z . number (). optional (). describe ( 'Specific version (defaults to latest)' ),
});
async execute (
args : z . infer < typeof LoadReportTool . argsSchema >,
context : ToolContext
) {
const { artifactService , session } = context ;
if ( ! artifactService ) {
return {
content: 'Artifact service not configured' ,
isError: true ,
};
}
// Load the artifact
const artifact = await artifactService . loadArtifact ({
appName: session . appName ,
userId: session . userId ,
sessionId: session . id ,
filename: args . filename ,
version: args . version ,
});
if ( ! artifact ) {
return {
content: `File ${ args . filename } not found` ,
isError: true ,
};
}
return {
content: artifact . text || 'Binary file loaded' ,
};
}
}
Complete Example
From the examples directory:
// apps/examples/src/04-persistence-and-sessions/agents/tools.ts
import { BaseTool , ToolContext } from '@iqai/adk' ;
import { z } from 'zod' ;
class SaveCounterReportTool extends BaseTool < typeof SaveCounterReportTool . argsSchema > {
name = 'save_counter_report' ;
description = 'Save counter values to a report file' ;
static argsSchema = z . object ({
filename: z . string (). describe ( 'Report filename' ),
});
async execute (
args : z . infer < typeof SaveCounterReportTool . argsSchema >,
context : ToolContext
) {
const { artifactService , session } = context ;
if ( ! artifactService ) {
return {
content: 'No artifact service configured' ,
isError: true ,
};
}
// Generate report from state
const counters = Object . entries ( session . state )
. filter (([ key ]) => ! key . includes ( ':' ))
. map (([ key , value ]) => ` ${ key } : ${ value } ` )
. join ( ' \n ' );
const report = `Counter Report \n ${ '=' . repeat ( 40 ) } \n ${ counters } ` ;
// Save as artifact
const version = await artifactService . saveArtifact ({
appName: session . appName ,
userId: session . userId ,
sessionId: session . id ,
filename: args . filename ,
artifact: { text: report },
});
return {
content: `Report saved to ${ args . filename } (version ${ version } )` ,
};
}
}
// Usage
const { runner } = await AgentBuilder
. withModel ( 'gpt-4' )
. withTools ( new SaveCounterReportTool ())
. withArtifactService ( new InMemoryArtifactService ())
. build ();
await runner . ask ( 'Save counter report to "report.txt"' );
Best Practices
Use descriptive filenames
Include file extensions
Use user: prefix for user-scoped files
Avoid special characters
Keep names short and readable
// Good
'invoice-2024-03.pdf'
'user:avatar.jpg'
'analysis-report.json'
// Avoid
'file!!!.txt'
'really-long-filename-with-many-words-2024-03-15-final-v2.pdf'
'../../../etc/passwd'
Versions start at 0
New saves always create new versions
List versions to find latest
Use negative indexing for recent versions
Clean up old versions periodically
// Get last 3 versions
const versions = await artifactService . listVersions ( args );
const recent = versions . slice ( - 3 );
// Load each
for ( const v of recent ) {
const artifact = await artifactService . loadArtifact ({ ... args , version: v });
}
Always base64 encode binary data
Set correct MIME types
Consider file size limits
Compress large files
Stream when possible
// Encode binary data
const imageBuffer = fs . readFileSync ( 'image.png' );
const artifact = {
inlineData: {
mimeType: 'image/png' ,
data: imageBuffer . toString ( 'base64' ),
},
};
Delete unused artifacts
Implement retention policies
Monitor storage costs
Clean up test data
Consider session cleanup triggers
// Clean up old sessions
async function cleanupOldArtifacts (
artifactService : BaseArtifactService ,
appName : string ,
userId : string ,
sessionId : string
) {
const files = await artifactService . listArtifactKeys ({
appName ,
userId ,
sessionId ,
});
for ( const file of files ) {
await artifactService . deleteArtifact ({
appName ,
userId ,
sessionId ,
filename: file ,
});
}
}
Artifacts vs State
Choose the right storage:
Feature State Artifacts Data Type Structured (JSON) Files, binary Size Small (KB) Any size Versioning Delta tracking Full versioning Use Case Flags, counters, config Documents, images, code Access Direct (session.state) Async (loadArtifact) Persistence With session Independent
// Use state for:
session . state . counter = 5 ;
session . state [ 'user:theme' ] = 'dark' ;
session . state . cart_items = [ 'item1' , 'item2' ];
// Use artifacts for:
await artifactService . saveArtifact ({
filename: 'report.pdf' ,
artifact: { inlineData: { data: pdfData , mimeType: 'application/pdf' } },
});
Artifacts are optional. Many agents work fine without file storage. Add artifact service only when you need to save generated files.