Context Integration
Save and load artifacts inside ADK-TS agent callbacks and tools using CallbackContext and ToolContext.
When you write a callback or a tool, the context object passed to your function already has artifact methods built in — you do not need to import or pass the service manually.
CallbackContext— available inbeforeAgentCallback,afterAgentCallback,beforeModelCallback, andafterModelCallback. ProvidessaveArtifactandloadArtifact.ToolContext— available inside tools. ExtendsCallbackContextand addslistArtifacts(), which lets a tool discover what files are available in the current session.
Auto-saving user uploads
If you enable saveInputBlobsAsArtifacts: true in your run config, the runner
automatically saves any inlineData parts from the user's message as
artifacts before the agent runs — no callback code needed. See Runner
Configuration
for setup details.
Available methods
| Method | Available on | Description |
|---|---|---|
saveArtifact(filename, part) | Both | Saves a new version of the artifact. Returns the version number (starts at 0). |
loadArtifact(filename, version?) | Both | Loads the latest version, or a specific version if you pass a number. Returns undefined if not found. |
listArtifacts() | ToolContext only | Returns the filenames of all artifacts in the current session. |
Deletion is not on contexts
deleteArtifact is intentionally not exposed through context objects — call
it directly on the service instance. See Cleanup and
Deletion.
Using artifacts in agent callbacks
CallbackContext is available in both beforeAgentCallback and afterAgentCallback. Use beforeAgent to load data the agent needs before it starts, and afterAgent to persist data once it finishes.
Load user preferences and conversation history from previous sessions, then write them into state so the agent can reference them:
import { AgentBuilder, InMemoryArtifactService } from "@iqai/adk";
import type { CallbackContext } from "@iqai/adk";
const { runner } = await AgentBuilder.create("my_agent")
.withModel("gemini-2.5-flash")
.withArtifactService(new InMemoryArtifactService())
.withBeforeAgentCallback(async (ctx: CallbackContext) => {
// Load user preferences (user-scoped — persists across sessions)
const prefsArtifact = await ctx.loadArtifact("user:preferences.json");
if (prefsArtifact?.inlineData) {
const prefs = JSON.parse(
Buffer.from(prefsArtifact.inlineData.data, "base64").toString(),
);
ctx.state.set("theme", prefs.theme ?? "light");
ctx.state.set("user_preferences", prefs);
}
// Load conversation history (session-scoped)
const historyArtifact = await ctx.loadArtifact("conversation_history.json");
if (historyArtifact?.inlineData) {
const history = JSON.parse(
Buffer.from(historyArtifact.inlineData.data, "base64").toString(),
);
ctx.state.set("conversation_count", history.length);
}
return undefined;
})
.build();Persist the conversation history and a session summary after each turn so they're available next time:
import { AgentBuilder, InMemoryArtifactService } from "@iqai/adk";
import type { CallbackContext } from "@iqai/adk";
const { runner } = await AgentBuilder.create("my_agent")
.withModel("gemini-2.5-flash")
.withArtifactService(new InMemoryArtifactService())
.withAfterAgentCallback(async (ctx: CallbackContext) => {
// Save conversation history
const history = ctx.state.get("conversation_history") ?? [];
await ctx.saveArtifact("conversation_history.json", {
inlineData: {
data: Buffer.from(JSON.stringify(history)).toString("base64"),
mimeType: "application/json",
},
});
// Save session summary if the agent produced one
const summary = ctx.state.get("session_summary");
if (summary) {
await ctx.saveArtifact("session_summary.json", {
inlineData: {
data: Buffer.from(JSON.stringify(summary)).toString("base64"),
mimeType: "application/json",
},
});
}
return undefined;
})
.build();Using artifacts in model callbacks
beforeModelCallback fires before every LLM call and afterModelCallback fires after. Use them to inject context into state before the model runs, or to capture metadata from the response.
Load a stored model config and inject its settings into state before each LLM call:
import { AgentBuilder, InMemoryArtifactService } from "@iqai/adk";
import type { CallbackContext, LlmRequest } from "@iqai/adk";
const { runner } = await AgentBuilder.create("my_agent")
.withModel("gemini-2.5-flash")
.withArtifactService(new InMemoryArtifactService())
.withBeforeModelCallback(async ({
callbackContext,
}: {
callbackContext: CallbackContext;
llmRequest: LlmRequest;
}) => {
const configArtifact = await callbackContext.loadArtifact("model_config.json");
if (configArtifact?.inlineData) {
const config = JSON.parse(
Buffer.from(configArtifact.inlineData.data, "base64").toString(),
);
callbackContext.state.set("temperature", config.temperature ?? 0.7);
callbackContext.state.set("max_tokens", config.maxTokens ?? 1000);
}
// Optionally load additional context to pass to the model
const contextArtifact = await callbackContext.loadArtifact("context_data.txt");
if (contextArtifact?.inlineData) {
const contextText = Buffer.from(
contextArtifact.inlineData.data,
"base64",
).toString();
callbackContext.state.set("additional_context", contextText);
}
return undefined;
})
.build();Record metadata about each model response — useful for auditing, cost tracking, or debugging:
import { AgentBuilder, InMemoryArtifactService } from "@iqai/adk";
import type { CallbackContext, LlmResponse } from "@iqai/adk";
const { runner } = await AgentBuilder.create("my_agent")
.withModel("gemini-2.5-flash")
.withArtifactService(new InMemoryArtifactService())
.withAfterModelCallback(async ({
callbackContext,
}: {
callbackContext: CallbackContext;
llmResponse: LlmResponse;
}) => {
const metadata = {
timestamp: new Date().toISOString(),
model: callbackContext.state.get("current_model"),
tokensUsed: callbackContext.state.get("tokens_used"),
};
await callbackContext.saveArtifact("response_metadata.json", {
inlineData: {
data: Buffer.from(JSON.stringify(metadata)).toString("base64"),
mimeType: "application/json",
},
});
return undefined;
})
.build();Using artifacts in tools
Inside a tool, ctx is a ToolContext. It has all the same methods as CallbackContext plus listArtifacts(), which returns the filenames of every artifact in the current session. Use it to check what's available before loading.
import { createTool } from "@iqai/adk";
import * as z from "zod";
export const inspectFile = createTool({
name: "inspect_file",
description: "Describe a specific artifact by filename",
schema: z.object({ filename: z.string() }),
fn: async ({ filename }, ctx) => {
// Check what's available before loading
const files = await ctx.listArtifacts();
if (!files.includes(filename)) {
return { error: "File not found", available: files };
}
const artifact = await ctx.loadArtifact(filename);
if (artifact?.text) {
return { filename, type: "text", sizeBytes: artifact.text.length };
}
if (artifact?.inlineData) {
return {
filename,
mimeType: artifact.inlineData.mimeType,
sizeBytes: Buffer.from(artifact.inlineData.data, "base64").length,
};
}
return { error: "Could not read file" };
},
});