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()
};
}
Related Contexts
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.