ExAgent.Coordination (ExAgent v2.2.1)

View Source

Orchestration patterns on top of ExAgent.Session and the core loop.

These cover pydanticAI's complexity levels 2 and 3:

  • Delegation (level 2) — an agent calls another agent as a tool. The delegate runs its own model⇄tools loop and its output is returned to the parent; the shared execution scope accounts for the delegate's usage.
  • Hand-off (level 3) — application code (or a supervisor agent) transfers control between participants in a Session.

These complement — but don't replace — the BEAM-native coordination model: in ExAgent, multiple agents are usually separate supervised processes that message each other, rather than one agent calling another as a tool. Delegation is provided for parity with the pydanticAI pattern and for cases where nesting a sub-agent inside a tool is genuinely the simplest design.

ExAgent.Coordination.Composition executes and resumes trusted durable sequences. ExAgent.Coordination.Flow adds a versioned host router and bounded parallel fan-out/fan-in with ordered outcomes, fail-fast/collect and the same C7 approvals. Structural roots have no Model request; leaves use the original agent loop.

Summary

Functions

Build an ExAgent.Tool that delegates a sub-task to another agent.

Hand off control within a ExAgent.Session from the current participant to to_id, bypassing the normal turn policy. Emits :session_turn_changed.

Functions

delegation_tool(delegate, opts \\ [])

@spec delegation_tool(ExAgent.t() | (map(), map() -> ExAgent.t()), keyword()) ::
  ExAgent.Tool.t()

Build an ExAgent.Tool that delegates a sub-task to another agent.

When the parent agent's model calls this tool, delegate is run with the tool's prompt argument (a one-shot ExAgent.run/3) and its output is returned to the parent. Requests/tools are admitted against every ancestor's limits before execution and usage is reconciled in the shared ledger, including failed delegates. The returned tool value never adds that usage a second time.

Arguments

  • delegate — an ExAgent.t(), or a builder (ctx, args -> ExAgent.t()) so the delegate can be constructed lazily with the parent's context (e.g. to forward deps or pick a model per call).
  • opts — :name (default "delegate"), :description, :max_retries (default 1), :prompt_arg (string, default "prompt"). Optional :permissions, :approve, :deadline, :max_concurrent_requests and :estimate_cost configure child restrictions/pricing; none override an ancestor's policy or limits. Builders only choose the child definition.

Example

chat_model is an explicitly configured ExAgent.Models.ReqLLM instance with credentials and the qualified :chat_tools_v1 profile (see the README recipe).

delegate = ExAgent.new(model: chat_model, instructions: "You summarize.")
parent =
  ExAgent.new(
    model: chat_model,
    tools: [ExAgent.Coordination.delegation_tool(delegate, name: "summarize")]
  )

The parent model can then call summarize(prompt: "...") to hand a sub-task to the delegate and get its result back, with both runs' tokens counted together.

handoff(session, to_id)

@spec handoff(GenServer.server(), term()) :: {:ok, term()} | {:error, term()}

Hand off control within a ExAgent.Session from the current participant to to_id, bypassing the normal turn policy. Emits :session_turn_changed.

This is the BEAM-native equivalent of a programmatic hand-off: control moves directly between participants by message. After to_id takes its turn, the policy resumes its normal ordering from there.

Returns {:ok, to_id} or {:error, reason} (:not_running, :not_a_participant).