Sessions
Sessions represent a series of interactions between a user and agents, maintaining conversation history, state, and metadata across multiple turns. ADK-TS provides a flexible session management system with pluggable storage backends.
Session Interface
A session contains all information about a conversation:
import type { Event } from "@iqai/adk";
interface Session {
/**
* Unique identifier of the session
*/
id: string;
/**
* Name of the application
*/
appName: string;
/**
* ID of the user
*/
userId: string;
/**
* Session state (key-value store)
*/
state: Record<string, any>;
/**
* Events in the session (conversation history)
*/
events: Event[];
/**
* Last update timestamp (seconds since epoch)
*/
lastUpdateTime: number;
}
Creating Sessions
Sessions are created using a SessionService:
import { InMemorySessionService } from "@iqai/adk";
const sessionService = new InMemorySessionService();
// Create a new session
const session = await sessionService.createSession(
"my-app", // App name
"user-123", // User ID
{ // Initial state (optional)
preferences: { theme: "dark" },
subscription: "premium",
},
"session-456" // Custom session ID (optional)
);
console.log(session.id); // "session-456" or auto-generated UUID
Session Services
BaseSessionService
All session services implement this interface:
export abstract class BaseSessionService {
/**
* Create a new session
*/
abstract createSession(
appName: string,
userId: string,
state?: Record<string, any>,
sessionId?: string,
): Promise<Session>;
/**
* Retrieve an existing session
*/
abstract getSession(
appName: string,
userId: string,
sessionId: string,
config?: GetSessionConfig,
): Promise<Session | undefined>;
/**
* List all sessions for a user
*/
abstract listSessions(
appName: string,
userId: string,
): Promise<ListSessionsResponse>;
/**
* Delete a session
*/
abstract deleteSession(
appName: string,
userId: string,
sessionId: string,
): Promise<void>;
}
InMemorySessionService
For development and testing:
import { InMemorySessionService } from "@iqai/adk";
const sessionService = new InMemorySessionService();
// Sessions stored in memory (lost when process ends)
InMemorySessionService is great for development but not suitable for production as sessions are lost when the process restarts.
Database Session Services
For production use, implement BaseSessionService with your database:
import { BaseSessionService, Session } from "@iqai/adk";
import { createClient } from "@supabase/supabase-js";
export class SupabaseSessionService extends BaseSessionService {
private supabase;
constructor(url: string, key: string) {
super();
this.supabase = createClient(url, key);
}
async createSession(
appName: string,
userId: string,
state?: Record<string, any>,
sessionId?: string,
): Promise<Session> {
const session: Session = {
id: sessionId || crypto.randomUUID(),
appName,
userId,
state: state || {},
events: [],
lastUpdateTime: Date.now() / 1000,
};
await this.supabase
.from("sessions")
.insert({
id: session.id,
app_name: appName,
user_id: userId,
state: session.state,
events: session.events,
last_update_time: session.lastUpdateTime,
});
return session;
}
async getSession(
appName: string,
userId: string,
sessionId: string,
): Promise<Session | undefined> {
const { data, error } = await this.supabase
.from("sessions")
.select("*")
.eq("id", sessionId)
.eq("app_name", appName)
.eq("user_id", userId)
.single();
if (error || !data) return undefined;
return {
id: data.id,
appName: data.app_name,
userId: data.user_id,
state: data.state,
events: data.events,
lastUpdateTime: data.last_update_time,
};
}
// Implement other methods...
}
State Management
Session state is a scoped key-value store with three prefixes:
import { State } from "@iqai/adk";
// State prefixes
State.APP_PREFIX; // "app:" - Application-wide state
State.USER_PREFIX; // "user:" - User-specific state
State.TEMP_PREFIX; // "temp:" - Temporary session state
Using State
const agent = new LlmAgent({
name: "stateful_agent",
model: "gpt-4o",
beforeAgentCallback: async (ctx) => {
// Read state
const userName = ctx.state.get("user:name", "Guest");
const theme = ctx.state.get("app:theme", "light");
const sessionCounter = ctx.state.get("temp:counter", 0);
// Update state
ctx.state.set("temp:counter", sessionCounter + 1);
ctx.state.set("user:last_seen", Date.now());
// Check if key exists
if (ctx.state.has("user:preferences")) {
const prefs = ctx.state.get("user:preferences");
}
},
});
State Deltas
State uses a delta-based system for efficiency:
const state = new State(
{ existing: "value" }, // Current value
{} // Pending delta
);
state.set("new", "data");
// Only "new" key is in delta
console.log(state.hasDelta()); // true
// Both current and delta values are accessible
console.log(state.toDict()); // { existing: "value", new: "data" }
State changes are automatically persisted by the session service after each agent invocation.
Events
Events represent individual turns in the conversation:
import { Event } from "@iqai/adk";
const event = new Event({
id: Event.newId(),
author: "user", // "user" or agent name
content: {
role: "user",
parts: [{ text: "Hello, agent!" }],
},
invocationId: "inv-123",
timestamp: Math.floor(Date.now() / 1000),
});
// Check event type
event.isFinalResponse(); // true if no pending function calls
// Extract function calls
const functionCalls = event.getFunctionCalls();
const functionResponses = event.getFunctionResponses();
Event Structure
interface Event {
/** Unique event ID */
id: string;
/** Who created this event */
author: string; // "user" or agent name
/** Event content */
content?: {
role: string;
parts: Part[];
};
/** Text extracted from content */
text?: string;
/** Invocation ID (links related events) */
invocationId: string;
/** Event timestamp */
timestamp: number;
/** Branch for multi-agent isolation */
branch?: string;
/** Actions to perform */
actions: EventActions;
/** Partial response (streaming) */
partial?: boolean;
}
Using Sessions with Runner
import { Runner, InMemorySessionService, LlmAgent } from "@iqai/adk";
const sessionService = new InMemorySessionService();
const agent = new LlmAgent({
name: "assistant",
model: "gpt-4o",
});
const runner = new Runner({
appName: "my-app",
agent,
sessionService,
});
// Create session
const session = await sessionService.createSession("my-app", "user-123");
// Run conversation
for await (const event of runner.runAsync({
userId: "user-123",
sessionId: session.id,
newMessage: { parts: [{ text: "Hello!" }] },
})) {
console.log(event.text);
}
// Retrieve updated session
const updatedSession = await sessionService.getSession(
"my-app",
"user-123",
session.id
);
console.log(updatedSession.events.length); // Includes new events
Session Configuration
Control what gets loaded:
interface GetSessionConfig {
/** Load only N recent events */
numRecentEvents?: number;
/** Load only events from specific branch */
branch?: string;
/** Load events after specific timestamp */
afterTimestamp?: number;
}
const session = await sessionService.getSession(
"my-app",
"user-123",
"session-456",
{
numRecentEvents: 10, // Only last 10 events
}
);
Event Compaction
Reduce token usage by compacting old events:
import { EventsCompactionConfig } from "@iqai/adk";
const agent = new LlmAgent({
name: "efficient_agent",
model: "gpt-4o",
compactionConfig: new EventsCompactionConfig({
targetEvents: 20, // Keep last 20 events
minEventsBeforeCompact: 50, // Compact when >50 events
summarizeOldEvents: true, // Summarize compacted events
}),
});
Event compaction automatically summarizes old conversation history to reduce context length while preserving important information.
Multi-User Sessions
Manage sessions for multiple users:
// List all sessions for a user
const { sessions } = await sessionService.listSessions(
"my-app",
"user-123"
);
for (const sessionSummary of sessions) {
console.log(`Session ${sessionSummary.id}`);
console.log(`Last active: ${sessionSummary.lastUpdateTime}`);
}
// Delete old sessions
for (const sessionSummary of sessions) {
const age = Date.now() / 1000 - sessionSummary.lastUpdateTime;
if (age > 30 * 24 * 60 * 60) { // 30 days
await sessionService.deleteSession(
"my-app",
"user-123",
sessionSummary.id
);
}
}
Session Rewinding
Rollback conversation to a previous state:
await runner.rewind({
userId: "user-123",
sessionId: session.id,
rewindBeforeInvocationId: "inv-456",
});
// Session now excludes events from invocation "inv-456" onwards
Rewinding is useful for implementing “undo” functionality or branching conversations.
Best Practices
-
Session IDs: Use meaningful session IDs (e.g.,
user-123_chat-20240301) for debugging.
-
State Scoping: Use appropriate prefixes:
app: for application-wide configuration
user: for user preferences and data
temp: for transient session data
-
Event Limits: Implement event compaction to prevent excessive context length.
-
Database Indexes: Index sessions by
(appName, userId, id) for fast lookups.
-
Session Cleanup: Periodically delete inactive sessions to manage storage costs.
-
Security: Never expose raw session IDs to clients; use signed tokens or user authentication.
- Agents - Learn how agents interact with sessions
- Memory - Implement long-term knowledge storage
- Flows - Understand how sessions flow through request processing