TypeScriptADK-TS

Custom Agents

Build specialized agents with custom logic and behavior

Custom agents provide ultimate flexibility by allowing you to define arbitrary orchestration logic. They go beyond predefined workflow patterns to enable highly specific and complex agent behaviors.

Advanced Concept

Building custom agents requires implementing core execution logic directly. We recommend understanding LLM agents and workflow agents first before tackling custom orchestration.

What Are Custom Agents?

Custom agents inherit from BaseAgent and implement their own control flow by defining the runAsyncImpl method. You have complete control over:

  • How sub-agents are called and orchestrated
  • State management and data flow
  • Conditional logic and decision making
  • External integrations and API calls

When to Use Custom Agents

Consider custom agents when you need:

  • Conditional Logic: Different execution paths based on runtime conditions
  • Complex State Management: Intricate logic for maintaining workflow state
  • External Integrations: Direct API calls, database operations, or custom libraries
  • Dynamic Agent Selection: Choosing sub-agents based on runtime evaluation
  • Unique Patterns: Orchestration logic that doesn't fit standard patterns

Design Decision

Use custom agents when predefined workflow agents (Sequential, Parallel, Loop) cannot express your required logic patterns.

Core Implementation

The runAsyncImpl method (TypeScript)

This is the heart of every custom agent in TypeScript:

  • Asynchronous generator that yields Event objects back to the runner
  • Receives InvocationContext (ctx) for session/state access and configuration
  • Forwards events from sub-agents, and can add its own events and EventActions
  • Implements any required control flow (branching, loops, retries, transfers)
import { BaseAgent, Event } from "@iqai/adk";

export class MyCustomAgent extends BaseAgent {
  constructor() {
    super({ name: "my_custom_agent", description: "Custom orchestration" });
  }

  protected async *runAsyncImpl(ctx: any) {
    // Announce start
    yield new Event({ author: this.name, content: { parts: [{ text: "Starting…" }] } });

    // ... your orchestration logic here ...

    // Announce done
    yield new Event({ author: this.name, content: { parts: [{ text: "Done" }] } });
  }
}

Key capabilities in runAsyncImpl

Sub-agent orchestration

Call and coordinate sub-agents with custom logic

State management

Share data via session state and EventActions

Control flow

Implement conditional, iterative, and retry logic

External integration

Call APIs, databases, or services as needed

Orchestrating sub-agents

// Forward events from a child agent
for await (const event of someChild.runAsync(ctx)) {
  // optionally inspect/transform event here
  yield event;
}

Reading/writing session state

// Read data set by a previous step
const status = ctx.session.state.get("status", "pending");

// Write data for downstream steps (often also done via outputKey of child agents)
ctx.session.state.set("processing_started_at", Date.now());

Using EventActions (stateDelta, escalate, transfer)

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

// Save a value via stateDelta on a yielded event
yield new Event({
  author: this.name,
  actions: new EventActions({ stateDelta: { current_stage: "validated" } }),
});

// Escalate to signal termination to a LoopAgent or parent logic
yield new Event({ author: this.name, actions: new EventActions({ escalate: true }) });

// Transfer to another agent by name (within the known hierarchy)
yield new Event({ author: this.name, actions: new EventActions({ transferToAgent: "target_agent" }) });

Error handling and reporting

try {
  // risky operation
} catch (err) {
  yield new Event({
    author: this.name,
    content: { parts: [{ text: `Error: ${err instanceof Error ? err.message : String(err)}` }] },
  });
}

Design Patterns

Conditional Workflows

Execute different agent paths based on runtime conditions or previous results.

State-Driven Orchestration

Use session state to coordinate complex data flows between multiple agents.

External Service Integration

Combine agent capabilities with external APIs and data sources.

Dynamic Agent Selection

Choose which agents to execute based on input analysis or environment conditions.

Managing Sub-Agents

Agent Hierarchy

  • Store sub-agents as instance attributes during initialization
  • Declare sub-agents in the subAgents list (constructor super({ subAgents: [...] })) for framework awareness
  • Use direct method calls for orchestration within your custom logic

State Coordination

  • Use ctx.session.state for data sharing between agents
  • Leverage output_key parameters for structured data flow
  • Implement state validation and error handling

Event Processing

  • Yield events from sub-agents to maintain streaming behavior
  • Optionally inspect, filter, or transform events
  • Handle error events and implement recovery logic

Best Practices

Design Considerations

  • Keep custom logic focused and well-documented
  • Design clear interfaces between sub-agents
  • Implement proper error handling and recovery
  • Test complex workflows thoroughly with various scenarios

Architecture Design

  • Break complex workflows into logical sub-agents
  • Use clear naming conventions for state keys
  • Document the expected data flow and dependencies
  • Design for testability and maintainability

Error Handling

  • Implement graceful degradation for agent failures
  • Use try-catch blocks around critical operations
  • Provide meaningful error messages and logging
  • Consider retry logic for transient failures

Performance Considerations

  • Minimize unnecessary state operations
  • Consider async/await patterns for external calls
  • Implement timeouts for long-running operations
  • Monitor resource usage in complex workflows

Testing Custom Agents

Unit Testing

  • Test individual logic components separately
  • Mock sub-agents for isolated testing
  • Verify state management logic
  • Test error conditions and edge cases

Integration Testing

  • Test complete workflows end-to-end
  • Verify state flow between agents
  • Test with realistic data scenarios
  • Validate error handling and recovery

Performance Testing

  • Measure execution times for complex workflows
  • Test resource usage under load
  • Verify timeout and limit behaviors
  • Monitor memory usage patterns

Common Use Cases

Multi-Stage Content Generation

Create content through generation, critique, revision, and validation stages with conditional regeneration.

Data Processing Pipelines

Implement complex ETL workflows with conditional transformations and quality checks.

Decision Support Systems

Build agents that analyze data, consult multiple sources, and provide structured recommendations.

Quality Assurance Workflows

Implement multi-step validation processes with conditional approval and revision cycles.

How is this guide?