Sagents
Build interactive AI agents in Elixir with OTP supervision, middleware composition, human-in-the-loop approvals, sub-agent delegation, and real-time Phoenix LiveView integration. Built on LangChain.
Install / Use
/learn @sagents-ai/SagentsREADME
Sagents
Sage Agents - Combining the wisdom of a Sage with the power of LLM-based Agents
A sage is a person who has attained wisdom and is often characterized by sound judgment and deep understanding. Sagents brings this philosophy to AI agents: building systems that don't just execute tasks, but do so with thoughtful human oversight, efficient resource management, and extensible architecture.
Key Features
- Human-In-The-Loop (HITL) - Customizable permission system that pauses execution for approval on sensitive operations, including parallel tool calls where each action can be individually approved/rejected. Works across both main agents and SubAgents — interrupts propagate up to the parent for approval and resume seamlessly
- Composable Execution Modes - Agent run loops are explicit Elixir pipelines built from reusable steps. Mix and match built-in steps (
call_llm,execute_tools,check_pre_tool_hitl,propagate_state, etc.) or write your own. Different agents can use different modes in the same application - Structured Agent Completion (
until_tool) - Force agents to loop until they call a specific tool, returning the result as a clean{:ok, state, %ToolResult{}}tuple. No more hoping the LLM follows your output format — get structured data you can pattern match on - SubAgents - Delegate complex tasks to specialized child agents for efficient context management and parallel execution
- GenServer Architecture - Each agent runs as a supervised OTP process with automatic lifecycle management
- Phoenix.Presence Integration - Smart resource management that knows when to shut down idle agents
- PubSub Real-Time Events - Stream agent state, messages, and events to multiple LiveView subscribers
- Middleware System - Extensible plugin architecture for adding capabilities to agents, including composable observability callbacks for OpenTelemetry, metrics, or custom logging
- Cluster-Aware Distribution - Optional Horde-based distribution for running agents across a cluster of nodes with automatic state migration, or run locally on a single node (the default)
- State Persistence - Save and restore agent conversations via optional behaviour modules for agent state and display messages
- Virtual Filesystem - Isolated, in-memory file operations with optional persistence
See it in action! Try the agents_demo application to experience Sagents interactively, or add the sagents_live_debugger to your app for real-time insights into agent configuration, state, and event flows.

The AgentsDemo chat interface showing the use of a virtual filesystem, tool call execution, composable middleware, supervised Agentic GenServer assistant, and much more!
Who Is This For?
Sagents is designed for Elixir developers building interactive AI applications where:
- Users have real-time conversations with AI agents
- Human oversight is required for certain operations (file deletes, API calls, etc.)
- Multiple concurrent conversations need isolated agent processes
- Agent state must persist across sessions
- Real-time UI updates are essential (Phoenix LiveView)
If you're building a simple CLI tool or batch processing pipeline, the core LangChain library may be sufficient. Sagents adds the orchestration layer needed for production interactive applications.
What about non-interactive agents? Certainly! Sagents works perfectly well for background agents without a UI. You'd simply skip the UI state management helpers and omit middleware like HumanInTheLoop. The agent still runs as a supervised GenServer with all the benefits of state persistence, middleware capabilities, and SubAgent delegation. The sagents_live_debugger package remains valuable for gaining visibility into what your agents are doing, even without an end-user interface.
Installation
Add sagents to your list of dependencies in mix.exs:
def deps do
[
{:sagents, "~> 0.3.0"}
]
end
LangChain is automatically included as a dependency.
Supervision Tree Setup
Add Sagents.Supervisor to your application's supervision tree:
# lib/my_app/application.ex
children = [
# ... your other children (Repo, PubSub, etc.)
Sagents.Supervisor
]
This starts the process registry, dynamic supervisors, and filesystem supervisor that Sagents uses to manage agents.
Configuration
Sagents builds on the Elixir LangChain library for LLM integration. To use Sagents, you need to configure an LLM provider by setting the appropriate API key as an environment variable:
# For Anthropic (Claude)
export ANTHROPIC_API_KEY="your-api-key"
# For OpenAI (GPT)
export OPENAI_API_KEY="your-api-key"
# For Google (Gemini)
export GOOGLE_API_KEY="your-api-key"
Then specify the model when creating your agent:
# Anthropic Claude
alias LangChain.ChatModels.ChatAnthropic
model = ChatAnthropic.new!(%{model: "claude-sonnet-4-5-20250929"})
# OpenAI GPT
alias LangChain.ChatModels.ChatOpenAI
model = ChatOpenAI.new!(%{model: "gpt-4o"})
# Google Gemini
alias LangChain.ChatModels.ChatGoogleAI
model = ChatGoogleAI.new!(%{model: "gemini-2.0-flash-exp"})
For detailed configuration options, start here LangChain documentation.
Quick Start
1. Create an Agent
alias Sagents.{Agent, AgentServer, State}
alias Sagents.Middleware.{TodoList, FileSystem, HumanInTheLoop}
alias LangChain.ChatModels.ChatAnthropic
alias LangChain.Message
# Create agent with middleware capabilities
{:ok, agent} = Agent.new(%{
agent_id: "my-agent-1",
model: ChatAnthropic.new!(%{model: "claude-sonnet-4-5-20250929"}),
base_system_prompt: "You are a helpful coding assistant.",
middleware: [
TodoList,
FileSystem,
{HumanInTheLoop, [
interrupt_on: %{
"write_file" => true,
"delete_file" => true
}
]}
]
})
2. Start the AgentServer
# Create initial state
state = State.new!(%{
messages: [Message.new_user!("Create a hello world program")]
})
# Start the AgentServer (runs as a supervised GenServer)
{:ok, _pid} = AgentServer.start_link(
agent: agent,
initial_state: state,
pubsub: {Phoenix.PubSub, :my_app_pubsub},
inactivity_timeout: 3_600_000 # 1 hour
)
# Subscribe to real-time events
AgentServer.subscribe("my-agent-1")
# Execute the agent
:ok = AgentServer.execute("my-agent-1")
3. Handle Events
# In your LiveView or GenServer
def handle_info({:agent, event}, socket) do
case event do
{:status_changed, :running, nil} ->
# Agent started processing
{:noreply, assign(socket, status: :running)}
{:llm_deltas, deltas} ->
# Streaming tokens received
{:noreply, stream_tokens(socket, deltas)}
{:llm_message, message} ->
# Complete message received
{:noreply, add_message(socket, message)}
{:todos_updated, todos} ->
# Agent's TODO list changed
{:noreply, assign(socket, todos: todos)}
{:status_changed, :interrupted, interrupt_data} ->
# Human approval needed
{:noreply, show_approval_dialog(socket, interrupt_data)}
{:status_changed, :idle, nil} ->
# Agent completed
{:noreply, assign(socket, status: :idle)}
{:agent_shutdown, metadata} ->
# Agent shutting down (inactivity or no viewers)
{:noreply, handle_shutdown(socket, metadata)}
end
end
4. Handle Human-In-The-Loop Approvals
# When agent needs approval, it returns interrupt data
# User reviews and provides decisions
decisions = [
%{type: :approve}, # Approve first tool call
%{type: :edit, arguments: %{"path" => "safe.txt"}}, # Edit second tool call
%{type: :reject} # Reject third tool call
]
# Resume execution with decisions
:ok = AgentServer.resume("my-agent-1", decisions)
5. Structured Completion with until_tool
Force an agent to return structured output by calling a specific tool:
# Agent loops until "deliver_answer" is called, then returns the tool result
case Agent.execute(agent, state, until_tool: "deliver_answer") do
{:ok, final_state, %ToolResult{} = result} ->
# Structured data from the target tool
IO.inspect(result.content)
{:error, reason} ->
# LLM stopped without calling the target tool
IO.puts("Error: #{inspect(reason)}")
end
This works through HITL interrupt/resume cycles and with SubAgents — the contract is enforced at every level.
Provided Middleware
Sagents includes several pre-built middleware components:
| Middleware | Description |
|------------|-------------|
| TodoList | Task management with write_todos tool for tracking multi-step work |
| FileSystem | Virtual filesystem with ls, read_file, write_file, edit_file, search_text, edit_lines, delete_file |
| HumanInTheLoop | Pause execution for human approval on configurable tools |
| SubAgent | Delegate tasks to specialized child agents for parallel execution |
| Summarization | Automatic conversation compression when token limits approach |
| PatchToolCalls | Fix dangling tool calls from interrupted conversations |
| ConversationTitle | Auto-generate conversation titles from first user message |
FileSystem Middleware
{:ok, agent} = Agent.new(%{
# ...
middleware: [
{FileSystem, [
enabled_tools: ["ls", "read_file", "write_file
