Changelog

View Source

v0.6.0 - 2026-10-13

Highlights

  • MCP server - Legion.MCP.Server serves a Lua agent to MCP hosts as repl and help tools; see the MCP guide
    • Over stdio or Streamable HTTP; Legion.MCP.Plug refuses browser requests from origins other than localhost unless listed in :allowed_origins
    • OAuth 2.1, and a session/1 callback choosing the agent, vault and rate limits per call
    • Legion.Tools.AgentTool and Legion.Tools.HumanTool are left out
    • Needs the optional {:anubis_mcp, "~> 2.0"} dependency
  • Legion.eval/3 - runs code in a running agent as if its model had written it, with no LLM call: same tools, variables and limits, saved and rate-limited as one step of the conversation
  • On-demand tool docs - tool_docs: :on_demand lists tools in the prompt by a one-line summary/0, and Help.help(Name) returns a tool's full description; the default over MCP
  • Sub-agents from Lua - Lua agents hold sub-agent conversations through Legion.Tools.AgentTool, whose ids cross the Lua bridge

Breaking

Agents

  • Default model is openai:gpt-5.6-luna
  • An agent id belongs to one agent module - starting another module under an id whose stored conversation is not its own returns {:error, {:agent_module_mismatch, stored}}
  • Legion.resume/2 and Legion.recover/2 run the model only to finish an interrupted turn - one stopped behind a checkpoint or on a prompt with nothing after it; resume/2 restores any other conversation and waits for the next message

Sub-agents

  • Legion.Tools.AgentTool works with ids, not pids - start_link/1,2 return the sub-agent's id, and call/2, cast/2 and the new stop/1 take it
    • Only the agent that started a sub-agent reaches it
    • A sub-agent stops when that agent stops, when the turn that started it ends unless :binding_scope is :conversation, or after :sub_agent_idle_timeout without a message (default thirty minutes)
    • An agent runs at most :max_sub_agents at once (default 10)
    • A start is checked against the rate limit, returning {:cancel, {:rate_limited, violations}} when denied

Tools

  • Help is a reserved tool name - Legion.Tools.Help is in every agent's sandbox, so a tool of your own with the short name Help is no longer reachable; use Legion.Agent warns about it
  • summary/0 and mcp?/0 are Legion.Tool callbacks - use Legion.Tool defines them, so Lua code no longer reaches a tool function of either name, and a summary/0 not marked @impl Legion.Tool warns. A module that implements @behaviour Legion.Tool without use must define both

Rate limiting

  • :max_agents caps agents, checked at start - a denied Legion.start_link/2 returns {:error, {:rate_limited, violations}} with no process started and nothing saved, and Legion.execute/3 returns {:cancel, {:rate_limited, violations}}. Resumed and recovered runs are not checked
  • :max_tokens, :max_evals and :max_running_agents need the Postgres store - with Legion.RateLimiter.Postgres, an agent whose rules set any of them raises at start unless its store is a Legion.Store.Postgres on the limiter's repo and table, since those limits count what that store writes. With config :legion, :track_usage, false, rules setting :max_tokens or :max_evals raise at start too

Persistence

  • Usage entries may have no token counts - a Legion.eval/3 step records %{"at" => ..., "evals" => 1, "message_index" => ...}, and an LLM entry whose action ran code carries "evals" => 1; code summing token keys over usage must allow for entries without them. See Legion.Store
  • Custom Legion.Store adapters must keep :base_bindings - a saved :conversation_state carries it next to :bindings; an adapter that keeps only the keys it knew drops it, and an agent restarted mid-turn loses the variables that turn started with

Sandbox

  • :max_bindings_bytes caps leftover variables - default 10_000_000; an execution that would leave larger variables behind fails. Set it to :infinity for the old behaviour
  • Legion.Sandbox.Lua check/2 no longer reports syntax errors - parsing moved inside the sandbox process, under its limits, and syntax errors come back from execute/5

Other changes

  • Rate limiting - :max_evals in Legion.RateLimiter.Policy caps code evaluations per window
  • Legion.start_link/2 takes :vault, put in the agent process for its tools, and :idle_timeout, which stops an agent nobody calls
  • max_message_length defaults to 40_000 bytes and also bounds code sent with Legion.eval/3
  • :step stores persist usage with every checkpoint instead of only at turn end

v0.5.1 - 2026-09-25

Changes

v0.5.0 - 2026-09-01

Changes

v0.4.0 - 2026-05-17

Security

  • Harden Legion.Sandbox.ASTChecker against a class of RCE paths. After this release, most (if not all) RCE vectors should be closed. Legion is still vulnerable to DoS kinds of attacks, but we assume that having a system prompt instruction to behave well AND improving sandbox should be enough for now.

Changes

  • Broaden the sandbox surface for common LLM idioms: allow Map.values/1, JSON, URI, :erlang.float_to_binary/2, additional String/Enum/Date/DateTime functions, and the Access protocol (map[:k])
  • Document the sandbox constraints with concrete idioms in the system prompt
  • Fix tool source extraction breaking on heredocs and charlists
  • Correct documentation for telemetry events, source registry, and AgentTool.start_link/2

v0.3.0 - 2026-04-21

Changes

  • Replace share_bindings boolean with binding_scope (:iteration, :turn, :conversation) for fine-grained control over variable lifetime across code executions
  • Add action_types/0 callback to restrict which actions an agent can use (e.g. ~w(return done) for read-only agents)
  • Add max_message_length config with truncation support to prevent unbounded message growth
  • Add multimedia message support: {:image, data, media_type}, {:image_url, url}, and {:multipart, parts}
  • Add Legion.get_messages/1 to retrieve conversation history from a running agent
  • Expand AgentTool with parallel/2, pipeline/1, then/3, and extra_allowed_modules/0 for sub-agent orchestration; sub-agents are auto-aliased in the sandbox
  • Generate dynamic AgentTool.description/0 from sub-agent moduledocs
  • Move system prompt resolution to AgentPrompt, respecting custom system_prompt/0 overrides
  • Validate config keys at startup with warnings for unknown keys
  • Add @moduledoc compile-time validation via __before_compile__
  • Harden sandbox: block def/defp/__ENV__, additional :erlang functions (process_flag, list_to_atom, system_info), catch throws and exits, surface compiler diagnostics on errors
  • Handle executor exceptions gracefully instead of crashing the agent loop
  • Add Calendar to sandbox safe-module list
  • Emit :exception telemetry events for iteration, LLM, and sandbox spans; use System.convert_time_unit/3 for duration reporting
  • Extensive new test coverage for AgentServer, Executor, Sandbox, and ASTChecker

v0.2.1 - 2026-03-24

  • Improve source code extraction for tool definitions
  • Adjust system prompt to better reflect capabilities

v0.2.0 - 2026-03-15

Changes

  • Simplified and refactored internals
  • Improved documentation and general library intent

v0.1.0 - 2025-12-29

New 🔥

  • Initial release of Legion - an Elixir-native agentic AI framework
  • Legion.AIAgent behaviour for building AI agents with customizable tools and configurations
  • Legion.Tool behaviour for defining tools that agents can use
  • Integration with req_llm for LLM communication
  • Legion.Sandbox for secure code evaluation using Dune
  • Legion.call/2 and Legion.cast/2 for synchronous and asynchronous message passing
  • Legion.start_link/2 for spawning long-lived agents
  • Telemetry events for monitoring and debugging agent execution
  • Support for agent-to-agent communication and delegation