Legion.Tools.AgentTool (Legion v0.6.0)

View Source

Built-in tool for delegating tasks to sub-agents.

The calling agent must explicitly list allowed sub-agents via tool_config/1:

def tool_config(Legion.Tools.AgentTool), do: [agents: [MyApp.WorkerAgent, MyApp.ResearchAgent]]

Only listed agents can be invoked. Attempts to call unlisted agents raise an error.

Left out when the agent is served over MCP; see Legion.MCP.Server.

call/2 with an agent module runs one task, start_link/1 returns a sub-agent id that call/2 and cast/2 take to hold a conversation and stop/1 ends, and parallel/2, pipeline/1 and then/3 compose tasks.

Two settings of the starting agent's config bound its sub-agents, set like any other in Legion.Agent: per agent in config/0, for every agent in config :legion, :config, or per start in Legion.start_link/2:

  • :max_sub_agents - how many long-lived ones may run at once, and how many tasks one parallel/2 takes (default: 10)
  • :sub_agent_idle_timeout - how long a long-lived one may go without a message before it stops (default: thirty minutes); it overrides the sub-agent's own :idle_timeout

Blocking calls (call/2, parallel/2, pipeline/1, then/3) run inside the parent's evaluation, so the whole sub-agent run counts against the parent's sandbox_timeout (60 seconds by default) and is killed with it. Raise it for agents that delegate.

Usage example from agent code (executed in sandbox)

AgentTool.call(WorkerAgent, "Summarize this data")

Listed sub-agents are aliased into the sandbox automatically, so their short names (the last segment of the module) resolve to the full module atom - no need to spell out MyApp.Agents.WorkerAgent.

Summary

Functions

Runs a task on an agent module, or sends a message to a sub-agent id from start_link/1, and waits for the reply. Returns {:ok, result} or {:cancel, reason}.

Sends message to a sub-agent from start_link/1 without waiting for the reply, like Legion.cast/2.

Callback implementation for Legion.Tool.description/0.

Runs multiple sub-agent tasks in parallel and collects results.

Runs sub-agent tasks sequentially, threading each result to the next step.

Starts a sub-agent that keeps its conversation across messages, like Legion.start_link/2. Returns {:ok, agent_id}: pass the id to call/2, cast/2 and stop/1. Unlike a pid, it crosses the Lua bridge and persists in bindings.

Starts a sub-agent like start_link/1 and casts task to it.

Stops a sub-agent from start_link/1, after the turn it is running, if any. Returns :ok.

Callback implementation for Legion.Tool.summary/0.

Chains a sub-agent call after a previous {:ok, result}. Passes {:cancel, reason} through unchanged.

Functions

call(agent_module, task)

Runs a task on an agent module, or sends a message to a sub-agent id from start_link/1, and waits for the reply. Returns {:ok, result} or {:cancel, reason}.

With a module, the sub-agent runs to completion and is discarded, like Legion.execute/2; raises if the agent is not in the allowed list. With an id, the sub-agent continues its conversation, like Legion.call/3; raises if this agent has no running sub-agent with that id.

cast(agent_id, message)

Sends message to a sub-agent from start_link/1 without waiting for the reply, like Legion.cast/2.

Raises if this agent has no running sub-agent with that id.

description()

Callback implementation for Legion.Tool.description/0.

parallel(tasks, timeout \\ :infinity)

Runs multiple sub-agent tasks in parallel and collects results.

Returns {:ok, [result1, result2, ...]} when every task succeeds, or the first {:cancel, reason}. Raises if any agent is not in the allowed list, or if there are more tasks than the agent's :max_sub_agents (default 10).

pipeline(steps)

Runs sub-agent tasks sequentially, threading each result to the next step.

Each step is {agent, task_or_fn}. If task_or_fn is a 1-arity function, it receives the previous step's result and must return the task for the next call. Halts early on the first {:cancel, reason}.

start_link(agent_module)

Starts a sub-agent that keeps its conversation across messages, like Legion.start_link/2. Returns {:ok, agent_id}: pass the id to call/2, cast/2 and stop/1. Unlike a pid, it crosses the Lua bridge and persists in bindings.

Only the agent that started the sub-agent reaches it by its id. The sub-agent stops when that agent stops, when stop/1 stops it, or after the starting agent's :sub_agent_idle_timeout without a message (default thirty minutes), so one whose id was lost does not run on. Unless the starting agent's :binding_scope is :conversation, it also stops when the turn that started it ends, with the variables that could hold its id.

The start is checked against the :max_agents of the rate limit the sub-agent inherits, so it counts from its start; a denied start returns {:cancel, {:rate_limited, violations}} and leaves nothing running. Raises if the agent is not in the allowed list, or if the starting agent already runs its :max_sub_agents (default 10).

start_link(agent_module, task)

Starts a sub-agent like start_link/1 and casts task to it.

stop(agent_id)

Stops a sub-agent from start_link/1, after the turn it is running, if any. Returns :ok.

Raises if this agent has no running sub-agent with that id.

summary()

Callback implementation for Legion.Tool.summary/0.

then(prev, agent, fun)

Chains a sub-agent call after a previous {:ok, result}. Passes {:cancel, reason} through unchanged.