Current section
Files
Jump to
Current section
Files
lib/adk/agent/parallel_agent.ex
defmodule ADK.Agent.ParallelAgent do
@moduledoc """
An agent that runs all its sub-agents concurrently.
Each sub-agent runs in its own `Task` with an isolated branch. Events from
all sub-agents are collected and returned in a single flat list.
Branch naming follows the pattern `"parent.sub"` (or `"existing.parent.sub"`
if the context already has a branch set).
Since sub-agents run concurrently, the order of events across sub-agents
is nondeterministic.
"""
@behaviour ADK.Agent
alias ADK.Agent.InvocationContext
@type t :: %__MODULE__{
name: String.t(),
description: String.t(),
sub_agents: [struct()]
}
@enforce_keys [:name]
defstruct [:name, description: "", sub_agents: []]
@impl ADK.Agent
def name(%__MODULE__{name: name}), do: name
@impl ADK.Agent
def description(%__MODULE__{description: desc}), do: desc
@impl ADK.Agent
def sub_agents(%__MODULE__{sub_agents: agents}), do: agents
@impl ADK.Agent
def run(%__MODULE__{} = agent, %InvocationContext{} = ctx) do
Stream.resource(
fn -> {:run, agent, ctx} end,
&next/1,
fn _ -> :ok end
)
end
defp next(:done), do: {:halt, :done}
defp next({:run, agent, ctx}) do
parent_name = agent.name
tasks =
Enum.map(agent.sub_agents, fn sub_agent ->
sub_name = sub_agent.__struct__.name(sub_agent)
branch = compute_branch(ctx.branch, parent_name, sub_name)
sub_ctx = ctx |> InvocationContext.with_agent(sub_agent) |> InvocationContext.with_branch(branch)
Task.async(fn ->
sub_agent.__struct__.run(sub_agent, sub_ctx) |> Enum.to_list()
end)
end)
results = Task.await_many(tasks, 30_000)
events = List.flatten(results)
{events, :done}
end
defp compute_branch(nil, parent, sub), do: "#{parent}.#{sub}"
defp compute_branch(existing, parent, sub), do: "#{existing}.#{parent}.#{sub}"
end