TypeScriptADK-TS
Sessions

State Management

A compact guide to session.state and scoped persistence

Within each Session (our conversation thread), the state attribute acts like the agent's dedicated scratchpad for that specific interaction. While session.events holds the full history, session.state is where the agent stores and updates dynamic details needed during the conversation.

What is session.state?

Conceptually, session.state is a collection (dictionary or Map) holding key-value pairs. It's designed for information the agent needs to recall or track to make the current conversation effective:

  • Personalize Interaction: Remember user preferences mentioned earlier (e.g., 'user_preference_theme': 'dark').
  • Track Task Progress: Keep tabs on steps in a multi-turn process (e.g., 'booking_step': 'confirm_payment').
  • Accumulate Information: Build lists or summaries (e.g., 'shopping_cart_items': ['book', 'pen']).
  • Make Informed Decisions: Store flags or values influencing the next response (e.g., 'user_is_authenticated': true).

Key Characteristics of State

  • Structure: string keys → JSON-serializable values (no functions, sockets, class instances)
  • Mutability: changes throughout the turn
  • Persistence: depends on the SessionService (in-memory vs database vs cloud)

Serialization Only

Store only JSON-serializable data. Save large blobs/files as artifacts and reference by name in state.

Organizing State with Prefixes: Scope Matters

  • No prefix: session-only (e.g., current_step)
  • user: user-wide across sessions (e.g., user:theme)
  • app: app-wide across users (e.g., app:feature_flags)
  • temp: transient for the current invocation (not persisted)

Accessing Session State in Agent Instructions

Use {key} placeholders inside LlmAgent.instruction (auto-injected). Add ? to make a key optional.

const agent = new LlmAgent({
  name: 'Guide',
  model: 'gemini-2.0-flash',
  instruction: 'Welcome back! Theme: {user:theme?}. Step: {current_step}.'
});

Using {key} Templating

  • {key} requires a value and throws if missing
  • {key?} is optional and injects empty string if missing
  • Supports artifact.filename via injectSessionState helper in InstructionProvider

Important Considerations

  • Keys must exist (or use ?)
  • Non-string values are converted to string
  • To bypass auto-injection, pass a function to instruction and inject manually:
import { injectSessionState } from '@iqai/adk';
async function provider(ctx: ReadonlyContext) {
  return injectSessionState('Hello {user:name?}', ctx);
}
  • Agent outputKey: save final response directly to session.state
const agent = new LlmAgent({ name: 'Greeter', model: 'gemini-2.0-flash', outputKey: 'last_greeting' });
  • Event actions: use EventActions.stateDelta for explicit bulk updates
const event = new Event({ actions: new EventActions({ stateDelta: { 'user:login_count': 1 } }) });
await sessionService.appendEvent(session, event);
  • Callback/Tool contexts: mutate context.state (delta-aware helpers)
// Callback
beforeModelCallback: ({ callbackContext }) => {
  const n = callbackContext.state.get('user:count', 0);
  callbackContext.state.set('user:count', n + 1);
  callbackContext.state['temp:flag'] = true; // bracket access works too
  return null;
}

// Tool
const addItem = createTool({
  name: 'add_item',
  schema: z.object({ item: z.string(), qty: z.number().default(1) }),
  fn: ({ item, qty }, context) => {
    const list = context.state.get('items', []);
    list.push({ item, qty });
    context.state.set('items', list);
    return { ok: true };
  }
});

⚠️ A Warning About Direct State Modification

Avoid mutating session.state on a retrieved Session outside of events/callbacks/tools. It bypasses event history, may not persist, and is not thread-safe. Prefer:

  • outputKey for agent responses
  • EventActions.stateDelta with appendEvent
  • callbackContext.state or toolContext.state

Best Practices for State Design Recap

  • Use clear prefixes (user:, app:, temp:) and descriptive keys
  • Keep values small and serializable; use artifacts for large blobs
  • Batch updates via stateDelta where possible
  • Read state directly; write via events, callbacks, or tools for persistence

How is this guide?