TypeScriptADK-TS
Context

InvocationContext

Complete framework access for agent core implementation and invocation management

InvocationContext is the most comprehensive context object in ADK, providing complete access to the framework's capabilities. It's primarily used in agent core implementation and represents the full execution environment for a single invocation.

Overview

InvocationContext contains all the information and services needed for a complete agent invocation lifecycle. Unlike other context types, it's standalone and doesn't extend from ReadonlyContext, providing direct access to all framework components.

import { InvocationContext } from "@iqai/adk";

Core Properties

Service References

Direct access to all configured services:

readonly artifactService?: BaseArtifactService;
readonly sessionService: BaseSessionService;
readonly memoryService?: BaseMemoryService;

Invocation Identity

Unique identification and tracking for the current invocation:

readonly invocationId: string;
readonly branch?: string;

Agent and Session Management

Current execution context and session information:

agent: BaseAgent;
readonly session: Session;
readonly userContent?: Content;

Execution Control

Flags and controls for managing invocation lifecycle:

endInvocation: boolean;
liveRequestQueue?: LiveRequestQueue;
activeStreamingTools?: Record<string, ActiveStreamingTool>;
runConfig?: RunConfig;

Convenience Properties

Derived properties for common access patterns:

get appName(): string;
get userId(): string;

Key Methods

incrementLlmCallCount()

Tracks and enforces LLM call limits:

incrementLlmCallCount(): void

Throws LlmCallsLimitExceededError if the configured limit is exceeded.

createChildContext()

Creates a child context for sub-agent execution:

createChildContext(agent: BaseAgent): InvocationContext

This maintains the same invocation ID while updating the branch and agent references.

Agent Implementation Patterns

Basic Agent Structure

Most custom agents using InvocationContext follow this pattern:

import { BaseAgent, InvocationContext, Event } from "@iqai/adk";

export class CustomAgent extends BaseAgent {
  protected async *runAsyncImpl(
    context: InvocationContext
  ): AsyncGenerator<Event, void, unknown> {
    // Access full framework capabilities
    const session = context.session;
    const artifactService = context.artifactService;
    const memoryService = context.memoryService;

    // Your custom agent logic here
    yield* this.processWithFullContext(context);
  }

  private async *processWithFullContext(
    context: InvocationContext
  ): AsyncGenerator<Event, void, unknown> {
    // Use all available services and state
    if (context.memoryService) {
      const memories = await context.memoryService.searchMemory({
        query: "relevant context",
        appName: context.appName,
        userId: context.userId
      });

      // Process memories and generate events
    }

    // Access session state
    const currentState = context.session.state;

    // Generate appropriate events
    const event = new Event({
      invocationId: context.invocationId,
      author: this.name,
      content: {
        parts: [{ text: "Processing complete" }]
      }
    });

    yield event;
  }
}

Multi-Service Agent

An agent that coordinates multiple services:

export class MultiServiceAgent extends BaseAgent {
  protected async *runAsyncImpl(
    context: InvocationContext
  ): AsyncGenerator<Event, void, unknown> {
    try {
      // Initialize processing state
      const processingState = {
        started: new Date().toISOString(),
        invocationId: context.invocationId,
        services: {
          artifact: !!context.artifactService,
          memory: !!context.memoryService,
          session: !!context.sessionService
        }
      };

      // Update session state
      context.session.state.processing = processingState;

      // Process with available services
      if (context.memoryService && context.artifactService) {
        yield* this.processWithMemoryAndArtifacts(context);
      } else if (context.memoryService) {
        yield* this.processWithMemoryOnly(context);
      } else {
        yield* this.processBasic(context);
      }

    } catch (error) {
      // Handle errors with full context
      yield this.createErrorEvent(context, error);
    }
  }

  private async *processWithMemoryAndArtifacts(
    context: InvocationContext
  ): AsyncGenerator<Event, void, unknown> {
    // Search memory for context
    const memoryResults = await context.memoryService!.searchMemory({
      query: "user preferences and history",
      appName: context.appName,
      userId: context.userId
    });

    // Load relevant artifacts
    const artifacts = await context.artifactService!.listArtifactKeys({
      appName: context.appName,
      userId: context.userId,
      sessionId: context.session.id
    });

    // Process and combine information
    const combinedContext = {
      memories: memoryResults.memories || [],
      artifacts: artifacts,
      sessionHistory: context.session.events?.length || 0
    };

    // Generate comprehensive response
    yield new Event({
      invocationId: context.invocationId,
      author: this.name,
      content: {
        parts: [{
          text: `Found ${combinedContext.memories.length} relevant memories and ${combinedContext.artifacts.length} artifacts`
        }]
      }
    });
  }

  private createErrorEvent(context: InvocationContext, error: unknown): Event {
    return new Event({
      invocationId: context.invocationId,
      author: this.name,
      content: {
        parts: [{
          text: `Error in multi-service processing: ${error instanceof Error ? error.message : String(error)}`
        }]
      },
      errorCode: "MULTI_SERVICE_ERROR",
      errorMessage: error instanceof Error ? error.message : String(error)
    });
  }
}

Service Management Patterns

Conditional Service Usage

Handle optional services gracefully:

async function processWithAvailableServices(context: InvocationContext) {
  const results = {
    sessionData: context.session.state,
    artifacts: [] as string[],
    memories: [] as any[],
    processing: {
      timestamp: new Date().toISOString(),
      invocationId: context.invocationId
    }
  };

  // Use artifact service if available
  if (context.artifactService) {
    try {
      results.artifacts = await context.artifactService.listArtifactKeys({
        appName: context.appName,
        userId: context.userId,
        sessionId: context.session.id
      });
    } catch (error) {
      console.warn("Artifact service error:", error);
    }
  }

  // Use memory service if available
  if (context.memoryService) {
    try {
      const memoryResponse = await context.memoryService.searchMemory({
        query: "user context",
        appName: context.appName,
        userId: context.userId
      });
      results.memories = memoryResponse.memories || [];
    } catch (error) {
      console.warn("Memory service error:", error);
    }
  }

  return results;
}

Branching and Child Contexts

Sub-Agent Delegation

Use child contexts for sub-agent execution:

export class DelegatingAgent extends BaseAgent {
  protected async *runAsyncImpl(
    context: InvocationContext
  ): AsyncGenerator<Event, void, unknown> {
    // Determine if delegation is needed
    const userInput = context.userContent?.parts?.[0]?.text || "";

    if (this.requiresSpecialization(userInput)) {
      const specializedAgent = this.findSpecializedAgent(userInput);

      if (specializedAgent) {
        // Create child context for specialized agent
        const childContext = context.createChildContext(specializedAgent);

        // Update branch tracking
        console.log(`Delegating to ${specializedAgent.name}, branch: ${childContext.branch}`);

        // Execute specialized agent
        yield* specializedAgent.runAsync(childContext);

        // Post-delegation processing
        yield this.createDelegationSummary(context, specializedAgent.name);
      }
    } else {
      // Handle directly
      yield* this.handleDirectly(context);
    }
  }

  private requiresSpecialization(input: string): boolean {
    return input.includes("code") || input.includes("math") || input.includes("research");
  }

  private findSpecializedAgent(input: string): BaseAgent | null {
    // Return appropriate specialized agent based on input
    return null; // Implementation depends on your agent architecture
  }

  private createDelegationSummary(context: InvocationContext, agentName: string): Event {
    return new Event({
      invocationId: context.invocationId,
      author: this.name,
      content: {
        parts: [{
          text: `Completed delegation to ${agentName} for specialized processing`
        }]
      }
    });
  }
}

Branch-Aware Processing

Handle branch context for complex agent trees:

function processBranchContext(context: InvocationContext) {
  const branchInfo = {
    current: context.branch || "root",
    depth: context.branch?.split('.').length || 0,
    parentAgents: context.branch?.split('.').slice(0, -1) || [],
    currentAgent: context.agent.name
  };

  // Update session with branch tracking
  context.session.state.branchHistory =
    context.session.state.branchHistory || [];

  context.session.state.branchHistory.push({
    branch: branchInfo.current,
    agent: branchInfo.currentAgent,
    timestamp: new Date().toISOString(),
    invocationId: context.invocationId
  });

  // Keep only recent branch history
  context.session.state.branchHistory =
    context.session.state.branchHistory.slice(-20);

  return branchInfo;
}

Cost and Limit Management

LLM Call Tracking

Monitor and enforce LLM usage limits:

export class CostAwareAgent extends BaseAgent {
  protected async *runAsyncImpl(
    context: InvocationContext
  ): AsyncGenerator<Event, void, unknown> {
    try {
      // Track LLM call
      context.incrementLlmCallCount();

      // Update cost tracking in state
      const costTracking = context.session.state.costTracking || {
        llmCalls: 0,
        totalCost: 0
      };

      costTracking.llmCalls += 1;
      context.session.state.costTracking = costTracking;

      // Process with cost awareness
      yield* this.processWithCostTracking(context);

    } catch (error) {
      if (error instanceof LlmCallsLimitExceededError) {
        yield this.createLimitExceededEvent(context);
        return;
      }
      throw error;
    }
  }

  private createLimitExceededEvent(context: InvocationContext): Event {
    return new Event({
      invocationId: context.invocationId,
      author: this.name,
      content: {
        parts: [{
          text: "LLM call limit exceeded for this invocation. Please try again later."
        }]
      },
      errorCode: "LLM_LIMIT_EXCEEDED"
    });
  }
}

Advanced Patterns

State Management Across Services

Coordinate state changes across all services:

async function synchronizeState(context: InvocationContext, updates: Record<string, any>) {
  const syncId = `sync_${Date.now()}`;

  try {
    // Apply updates to session state
    Object.assign(context.session.state, updates, {
      lastSync: {
        id: syncId,
        timestamp: new Date().toISOString(),
        invocationId: context.invocationId
      }
    });

    // Persist session changes
    await context.sessionService.updateSession(context.session);

    // Save state snapshot to artifacts if artifact service available
    if (context.artifactService) {
      await context.artifactService.saveArtifact({
        appName: context.appName,
        userId: context.userId,
        sessionId: context.session.id,
        filename: `state_snapshot_${syncId}.json`,
        artifact: {
          text: JSON.stringify(context.session.state, null, 2)
        }
      });
    }

    // Update memory with state changes if memory service available
    if (context.memoryService && updates.userPreferences) {
      // Example: Store user preferences in memory for future sessions
      await context.memoryService.saveMemory({
        appName: context.appName,
        userId: context.userId,
        content: `User preferences updated: ${JSON.stringify(updates.userPreferences)}`,
        metadata: {
          type: "user_preferences",
          syncId: syncId
        }
      });
    }

    return { success: true, syncId };

  } catch (error) {
    // Handle synchronization failure
    context.session.state.lastSyncError = {
      syncId,
      error: error instanceof Error ? error.message : String(error),
      timestamp: new Date().toISOString()
    };

    throw error;
  }
}

Invocation Lifecycle Management

Manage the complete invocation lifecycle:

export class LifecycleAwareAgent extends BaseAgent {
  protected async *runAsyncImpl(
    context: InvocationContext
  ): AsyncGenerator<Event, void, unknown> {
    // Initialize invocation
    yield* this.initializeInvocation(context);

    try {
      // Main processing
      yield* this.processMain(context);

      // Check for early termination
      if (context.endInvocation) {
        yield* this.handleEarlyTermination(context);
        return;
      }

      // Finalize invocation
      yield* this.finalizeInvocation(context);

    } catch (error) {
      yield* this.handleInvocationError(context, error);
    }
  }

  private async *initializeInvocation(
    context: InvocationContext
  ): AsyncGenerator<Event, void, unknown> {
    // Set up invocation tracking
    context.session.state.currentInvocation = {
      id: context.invocationId,
      started: new Date().toISOString(),
      agent: this.name,
      branch: context.branch
    };

    yield new Event({
      invocationId: context.invocationId,
      author: this.name,
      content: {
        parts: [{ text: "Invocation initialized" }]
      }
    });
  }

  private async *finalizeInvocation(
    context: InvocationContext
  ): AsyncGenerator<Event, void, unknown> {
    // Clean up and finalize
    if (context.session.state.currentInvocation) {
      context.session.state.currentInvocation.completed = new Date().toISOString();
    }

    // Save final state if needed
    if (context.artifactService) {
      await context.artifactService.saveArtifact({
        appName: context.appName,
        userId: context.userId,
        sessionId: context.session.id,
        filename: `invocation_${context.invocationId}_final.json`,
        artifact: {
          text: JSON.stringify({
            invocationId: context.invocationId,
            finalState: context.session.state,
            completed: new Date().toISOString()
          }, null, 2)
        }
      });
    }

    yield new Event({
      invocationId: context.invocationId,
      author: this.name,
      content: {
        parts: [{ text: "Invocation completed successfully" }]
      }
    });
  }
}

Best Practices

Service Error Handling

async function robustServiceOperation(context: InvocationContext) {
  const results = {
    services: {
      session: { available: true, success: false },
      artifact: { available: !!context.artifactService, success: false },
      memory: { available: !!context.memoryService, success: false }
    },
    errors: [] as string[]
  };

  // Session service (always available)
  try {
    await context.sessionService.updateSession(context.session);
    results.services.session.success = true;
  } catch (error) {
    results.errors.push(`Session: ${error instanceof Error ? error.message : String(error)}`);
  }

  // Artifact service (optional)
  if (context.artifactService) {
    try {
      await context.artifactService.listArtifactKeys({
        appName: context.appName,
        userId: context.userId,
        sessionId: context.session.id
      });
      results.services.artifact.success = true;
    } catch (error) {
      results.errors.push(`Artifact: ${error instanceof Error ? error.message : String(error)}`);
    }
  }

  // Memory service (optional)
  if (context.memoryService) {
    try {
      await context.memoryService.searchMemory({
        query: "test",
        appName: context.appName,
        userId: context.userId
      });
      results.services.memory.success = true;
    } catch (error) {
      results.errors.push(`Memory: ${error instanceof Error ? error.message : String(error)}`);
    }
  }

  return results;
}

Resource Cleanup

async function cleanupInvocationResources(context: InvocationContext) {
  // Clean up temporary state
  delete context.session.state.temp;

  // Clean up old tracking data
  if (context.session.state.invocationHistory) {
    // Keep only last 10 invocations
    context.session.state.invocationHistory =
      context.session.state.invocationHistory.slice(-10);
  }

  // Clean up old artifacts if artifact service available
  if (context.artifactService) {
    const artifacts = await context.artifactService.listArtifactKeys({
      appName: context.appName,
      userId: context.userId,
      sessionId: context.session.id
    });

    // Remove temp artifacts
    const tempArtifacts = artifacts.filter(name => name.startsWith('temp_'));
    for (const tempArtifact of tempArtifacts) {
      try {
        await context.artifactService.deleteArtifact({
          appName: context.appName,
          userId: context.userId,
          sessionId: context.session.id,
          filename: tempArtifact
        });
      } catch (error) {
        console.warn(`Failed to clean up ${tempArtifact}:`, error);
      }
    }
  }

  // Update session with cleanup info
  context.session.state.lastCleanup = {
    invocationId: context.invocationId,
    timestamp: new Date().toISOString()
  };
}

InvocationContext provides the foundation for all other context types:

  • ReadonlyContext: Provides safe read-only access to basic invocation information
  • CallbackContext: Extends ReadonlyContext with state management capabilities
  • ToolContext: Extends CallbackContext with memory search and enhanced tool features

All other contexts can be derived from or work alongside InvocationContext in different execution scenarios.