TypeScriptADK-TS
Runtime

Components

Detailed look at Runner, agents, events, and services

The ADK Runtime consists of several key components that work together to provide a robust execution environment for agent applications.

Runner

The Runner is the main orchestrator and entry point for agent execution. It manages the overall invocation lifecycle and coordinates between agents and services.

Core Responsibilities

  • Session Management: Initialize and manage conversation sessions
  • Event Coordination: Process events from execution logic
  • Service Integration: Coordinate with SessionService, ArtifactService, MemoryService
  • Upstream Communication: Forward processed events to user interfaces

Runner Constructor

import { Runner, LlmAgent, InMemorySessionService } from '@iqai/adk';

const runner = new Runner({
  appName: "my_application",           // Application identifier
  agent: rootAgent,                    // Root agent to execute
  sessionService: sessionService,     // Required: Session management
  artifactService?: artifactService,  // Optional: File storage
  memoryService?: memoryService       // Optional: Long-term memory
});

Main Methods

runAsync() - Primary execution method:

for await (const event of runner.runAsync({
  userId: "user_123",
  sessionId: "session_456",
  newMessage: { parts: [{ text: "Hello!" }] },
  runConfig?: new RunConfig()  // Optional configuration
})) {
  // Process each event as it's generated
  console.log(`Event from ${event.author}:`, event.content);
}

run() - Synchronous wrapper (for testing):

// Synchronous interface for local testing
const eventGenerator = runner.run({
  userId: "user_123",
  sessionId: "session_456",
  newMessage: { parts: [{ text: "Hello!" }] }
});

for (const event of eventGenerator) {
  console.log('Sync event:', event);
}

InMemoryRunner

For quick testing and development:

import { InMemoryRunner, LlmAgent } from '@iqai/adk';

const agent = new LlmAgent({
  name: "test_agent",
  model: "gemini-2.5-flash",
  description: "Test agent",
  instruction: "You are helpful"
});

// Automatically sets up in-memory services
const runner = new InMemoryRunner(agent, {
  appName: "test_app"
});

Agent Selection Logic

The Runner determines which agent should handle each query:

// Agent selection process
private _findAgentToRun(session: Session, rootAgent: BaseAgent): BaseAgent {
  // 1. Check for function response continuation
  const functionCallEvent = this._findFunctionCallEventIfLastEventIsFunctionResponse(session);
  if (functionCallEvent?.author) {
    return rootAgent.findAgent(functionCallEvent.author);
  }

  // 2. Look for most recent agent that can handle transfers
  const nonUserEvents = session.events?.filter(e => e.author !== "user").reverse() || [];

  for (const event of nonUserEvents) {
    const agent = rootAgent.findSubAgent?.(event.author);
    if (agent && this._isTransferableAcrossAgentTree(agent)) {
      return agent;
    }
  }

  // 3. Default to root agent
  return rootAgent;
}

Agents

Agents are the core execution units that implement reasoning and decision-making logic through the async generator pattern.

BaseAgent

All agents extend from BaseAgent:

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

class CustomAgent extends BaseAgent {
  constructor(config: { name: string; description: string }) {
    super(config);
  }

  // Core execution method - must be implemented
  protected async *runAsyncImpl(
    ctx: InvocationContext
  ): AsyncGenerator<Event, void, unknown> {
    // Your agent logic here
    yield new Event({
      invocationId: ctx.invocationId,
      author: this.name,
      content: { parts: [{ text: "Hello from custom agent!" }] }
    });
  }

  // Optional: Live mode execution
  protected async *runLiveImpl(
    ctx: InvocationContext
  ): AsyncGenerator<Event, void, unknown> {
    // Live interaction logic
    throw new Error("Live mode not implemented");
  }
}

LlmAgent

The primary agent type for LLM-based interactions:

import { LlmAgent, FunctionTool } from '@iqai/adk';

const agent = new LlmAgent({
  name: "assistant",
  model: "gemini-2.5-flash",
  description: "A helpful assistant",
  instruction: "You are a helpful assistant",
  tools: [searchTool, calculatorTool],       // Optional tools
  subAgents: [specialistAgent],              // Optional sub-agents
  maxIterations: 10,                         // Optional iteration limit
  disallowTransferToParent: false           // Optional transfer control
});

Agent Lifecycle

Agents go through a specific lifecycle during execution:

// Internal agent execution flow
private async *runAsyncInternal(parentContext: InvocationContext): AsyncGenerator<Event> {
  const ctx = this.createInvocationContext(parentContext);

  // 1. Before-agent callbacks
  const beforeEvent = await this.handleBeforeAgentCallback(ctx);
  if (beforeEvent) yield beforeEvent;
  if (ctx.endInvocation) return;

  // 2. Core agent logic
  for await (const event of this.runAsyncImpl(ctx)) {
    yield event;
  }
  if (ctx.endInvocation) return;

  // 3. After-agent callbacks
  const afterEvent = await this.handleAfterAgentCallback(ctx);
  if (afterEvent) yield afterEvent;
}

Specialized Agent Types

Sequential Agent:

import { SequentialAgent } from '@iqai/adk';

const sequentialAgent = new SequentialAgent({
  name: "workflow",
  description: "Executes sub-agents in sequence",
  subAgents: [analyzeAgent, processAgent, reportAgent]
});

Parallel Agent:

import { ParallelAgent } from '@iqai/adk';

const parallelAgent = new ParallelAgent({
  name: "multi_perspective",
  description: "Gets multiple perspectives simultaneously",
  subAgents: [expertAgent1, expertAgent2, expertAgent3]
});

Loop Agent:

import { LoopAgent } from '@iqai/adk';

const loopAgent = new LoopAgent({
  name: "iterative_solver",
  description: "Iteratively refines solutions",
  agent: problemSolvingAgent,
  maxIterations: 5
});

Events

Events are the primary communication mechanism between components, carrying content, actions, and metadata.

Event Structure

import { Event, EventActions } from '@iqai/adk';

const event = new Event({
  invocationId: "inv_123",              // Unique invocation identifier
  author: "agent_name",                 // Who generated this event
  content: {                            // Message content
    parts: [
      { text: "Hello!" },
      { functionCall: { name: "search", args: {} } }
    ]
  },
  actions: new EventActions({           // Side effects
    stateDelta: { "key": "value" },
    artifactDelta: { "file.txt": "content" },
    transferToAgent: "specialist"
  }),
  branch: "agent1.agent2",             // Agent hierarchy path
  partial: false,                       // Whether event should be persisted
  timestamp: Math.floor(Date.now() / 1000)
});

Event Methods

Content Analysis:

// Check if event represents final response
const isFinal = event.isFinalResponse();

// Extract function calls
const functionCalls = event.getFunctionCalls();
// Returns: [{ name: "search", args: { query: "..." } }]

// Extract function responses
const functionResponses = event.getFunctionResponses();
// Returns: [{ name: "search", response: { results: [...] } }]

// Check for trailing code execution
const hasCodeResult = event.hasTrailingCodeExecutionResult();

Event Types

User Message Events:

const userEvent = new Event({
  invocationId: "inv_123",
  author: "user",
  content: {
    parts: [{ text: "What's the weather like?" }]
  }
});

Agent Response Events:

const agentEvent = new Event({
  invocationId: "inv_123",
  author: "weather_agent",
  content: {
    parts: [{ text: "It's sunny and 72°F" }]
  }
});

Function Call Events:

const functionCallEvent = new Event({
  invocationId: "inv_123",
  author: "weather_agent",
  content: {
    parts: [{
      functionCall: {
        name: "get_weather",
        args: { location: "San Francisco" }
      }
    }]
  }
});

State Change Events:

const stateEvent = new Event({
  invocationId: "inv_123",
  author: "agent",
  actions: new EventActions({
    stateDelta: {
      "user_location": "San Francisco",
      "last_weather_check": new Date().toISOString()
    }
  })
});

Event Actions

Events can carry side effects through EventActions:

import { EventActions } from '@iqai/adk';

const actions = new EventActions({
  // State changes
  stateDelta: {
    "app_state.theme": "dark",
    "user_state.preferences": { notifications: true }
  },

  // Artifact changes
  artifactDelta: {
    "report.pdf": "binary_content",
    "config.json": JSON.stringify({ setting: "value" })
  },

  // Agent transfer
  transferToAgent: "specialist_agent",

  // Skip summarization (for debugging/verbose output)
  skipSummarization: true
});

Services

Services provide backend functionality for persistence, storage, and external integrations.

SessionService

Manages conversation sessions and their persistence:

import { BaseSessionService, InMemorySessionService, DatabaseSessionService } from '@iqai/adk';

// In-memory implementation (for testing)
const sessionService = new InMemorySessionService();

// Database implementation (for production)
const sessionService = new DatabaseSessionService({
  databaseUrl: "postgresql://...",
  tableName: "sessions"
});

// Core operations
const session = await sessionService.createSession("app_name", "user_id");
await sessionService.appendEvent(session, event);
const retrievedSession = await sessionService.getSession("app_name", "user_id", "session_id");

ArtifactService

Handles binary data and file storage:

import { BaseArtifactService, InMemoryArtifactService, GcsArtifactService } from '@iqai/adk';

// In-memory implementation
const artifactService = new InMemoryArtifactService();

// Google Cloud Storage implementation
const artifactService = new GcsArtifactService({
  bucketName: "my-artifacts-bucket",
  credentials: googleCloudCredentials
});

// Artifact operations
await artifactService.saveArtifact({
  appName: "my_app",
  userId: "user_123",
  sessionId: "session_456",
  filename: "document.pdf",
  artifact: { inlineData: { mimeType: "application/pdf", data: "..." } }
});

const artifact = await artifactService.loadArtifact({
  appName: "my_app",
  userId: "user_123",
  sessionId: "session_456",
  filename: "document.pdf"
});

MemoryService

Provides long-term knowledge storage and retrieval:

import { BaseMemoryService, InMemoryMemoryService, VertexAiRagMemoryService } from '@iqai/adk';

// In-memory implementation
const memoryService = new InMemoryMemoryService();

// Vertex AI RAG implementation
const memoryService = new VertexAiRagMemoryService({
  projectId: "my-gcp-project",
  location: "us-central1",
  corpusId: "my-corpus"
});

// Memory operations
await memoryService.addSessionToMemory(session);

const memories = await memoryService.searchMemory({
  appName: "my_app",
  userId: "user_123",
  query: "previous conversations about weather",
  topK: 5
});

Service Configuration

Services are typically configured at the Runner level:

import { Runner, LlmAgent, DatabaseSessionService, GcsArtifactService, VertexAiRagMemoryService } from '@iqai/adk';

const runner = new Runner({
  appName: "production_app",
  agent: mainAgent,

  // Production-ready services
  sessionService: new DatabaseSessionService({
    databaseUrl: process.env.DATABASE_URL,
    tableName: "agent_sessions"
  }),

  artifactService: new GcsArtifactService({
    bucketName: process.env.GCS_BUCKET,
    credentials: googleCredentials
  }),

  memoryService: new VertexAiRagMemoryService({
    projectId: process.env.GCP_PROJECT_ID,
    location: "us-central1",
    corpusId: process.env.CORPUS_ID
  })
});

Component Integration

Service Discovery

Components access services through the InvocationContext:

// Within agent implementation
protected async *runAsyncImpl(ctx: InvocationContext): AsyncGenerator<Event> {
  // Access session
  const session = ctx.session;

  // Search memory if available
  if (ctx.memoryService) {
    const memories = await ctx.memoryService.searchMemory({
      appName: session.appName,
      userId: session.userId,
      query: "relevant information",
      topK: 3
    });
  }

  // Save artifacts if available
  if (ctx.artifactService) {
    await ctx.artifactService.saveArtifact({
      appName: session.appName,
      userId: session.userId,
      sessionId: session.id,
      filename: "output.txt",
      artifact: { inlineData: { mimeType: "text/plain", data: "content" } }
    });
  }
}

Error Handling

Components implement graceful degradation:

// Service availability checking
protected async *runAsyncImpl(ctx: InvocationContext): AsyncGenerator<Event> {
  try {
    if (ctx.memoryService) {
      const memories = await ctx.memoryService.searchMemory(params);
      // Use memories in processing
    } else {
      // Continue without memory service
      console.warn("Memory service not available, continuing without historical context");
    }
  } catch (error) {
    console.error("Memory service error:", error);
    // Continue execution with fallback behavior
  }
}

Component Lifecycle

Components follow a consistent lifecycle pattern:

  1. Initialization: Components are configured and instantiated
  2. Service Registration: Services are registered with the Runner
  3. Context Creation: InvocationContext provides access to services
  4. Execution: Components coordinate through events and service calls
  5. Cleanup: Resources are properly disposed when invocations complete

Component Extensibility

The ADK Runtime is designed for extensibility. You can implement custom agents, services, and event processors by extending the base classes and following the established patterns.