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 (constructorsuper({ 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.
Related Topics
How is this guide?