Note: Code examples in this guide are illustrative pseudocode showing recommended patterns. For working examples using the actual LLM4S API, see modules/samples.
Learn how to design systems where multiple agents collaborate, delegate tasks, and handle complex workflows. This guide covers agent communication patterns, handoff mechanisms, and failure recovery strategies.
Overview
Multi-agent systems allow you to decompose complex tasks into specialized agents. Instead of one agent doing everything, each agent focuses on specific domain expertise:
Routing Agent: Routes requests to appropriate specialists
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
object ParallelDelegation {
val financialAgent = Agent(
name = "Financial Agent",
model = openaiClient,
systemPrompt = "You are a financial analyst."
)
val technicalAgent = Agent(
name = "Technical Agent",
model = openaiClient,
systemPrompt = "You are a technical expert."
)
val marketAgent = Agent(
name = "Market Agent",
model = openaiClient,
systemPrompt = "You are a market researcher."
)
val aggregatorAgent = Agent(
name = "Aggregator",
model = openaiClient,
systemPrompt = "Synthesize viewpoints from multiple experts."
)
def analyzeCompany(company: String): Result[String] = {
// Run three agents in parallel
val financialFuture = Future(
financialAgent.run(s"Analyze finances of $company")
)
val technicalFuture = Future(
technicalAgent.run(s"Analyze tech stack of $company")
)
val marketFuture = Future(
marketAgent.run(s"Analyze market position of $company")
)
// Wait for all results
val allResults = for {
fin <- financialFuture
tech <- technicalFuture
market <- marketFuture
} yield (fin, tech, market)
// Convert Future to Result and aggregate
allResults.toResult.flatMap { case (fin, tech, market) =>
aggregatorAgent.run(
s"""
|Financial Analysis: ${fin.message}
|Technical Analysis: ${tech.message}
|Market Analysis: ${market.message}
|
|Provide a comprehensive assessment combining all three perspectives.
""".stripMargin
).map(_.message)
}
}
}
Advantages
✅ Parallel execution reduces total time
✅ Independent agents can scale separately
✅ Natural load distribution
Disadvantages
❌ Increased complexity
❌ Harder to debug concurrent issues
❌ Requires aggregation logic
Pattern 3: Handoff Mechanism
Use Case
Transferring control from one agent to another when current agent reaches its limits or recognizes need for specialist.
import org.llm4s.agents.handoff._
object HandoffPattern {
// Routing agent that delegates to specialists
val routingAgent = Agent(
name = "Routing Agent",
model = openaiClient,
systemPrompt = """
You are a routing agent. Analyze the request and determine
which specialist agent should handle it:
- Technical questions → technical_specialist
- Account issues → account_specialist
- Billing issues → billing_specialist
- Escalated issues → escalation_team
"""
)
// Specialist agents
val technicalSpecialist = Agent(
name = "Technical Specialist",
model = openaiClient,
systemPrompt = "Solve technical problems with deep expertise."
)
val accountSpecialist = Agent(
name = "Account Specialist",
model = openaiClient,
systemPrompt = "Help with account management and user issues."
)
val billingSpecialist = Agent(
name = "Billing Specialist",
model = openaiClient,
systemPrompt = "Resolve billing and payment issues."
)
// Handoff decision
sealed trait HandoffTarget
case object Technical extends HandoffTarget
case object Account extends HandoffTarget
case object Billing extends HandoffTarget
case object EscalationTeam extends HandoffTarget
def routeAndHandle(userRequest: String): Result[String] = for {
// Step 1: Route to determine target
routing <- routingAgent.run(
s"Request: $userRequest\nDetermine target: technical_specialist, account_specialist, billing_specialist, or escalation_team"
)
// Step 2: Extract routing decision
target = routing.message match {
case s if s.contains("technical_specialist") => Technical
case s if s.contains("account_specialist") => Account
case s if s.contains("billing_specialist") => Billing
case _ => EscalationTeam
}
// Step 3: Hand off to appropriate specialist
response <- target match {
case Technical => technicalSpecialist.run(userRequest)
case Account => accountSpecialist.run(userRequest)
case Billing => billingSpecialist.run(userRequest)
case EscalationTeam =>
Result.failure(
"This issue requires human escalation. Ticket created."
)
}
} yield response.message
}
Advantages
✅ Automatic routing based on request type
✅ Specialists have focused expertise
✅ Scales to many agent types
✅ Built on LLM4S handoff support
Disadvantages
❌ Routing errors can send request to wrong agent
❌ Context loss during handoff
❌ Overhead of routing step
Pattern 4: Hierarchical Agent Teams
Use Case
Complex systems with multiple management levels (team leads, managers, executives) with clear authority chains.