Quick Start
Mix.install([
{:jido, "~> 2.0"}
])
Overview
This guide gets you from zero to a working Jido agent in 5 minutes. You'll learn:
- How to start Jido's supervision tree
- How to define an agent with state
- How to use
cmd/2for pure state transformations - How to run agents in AgentServer for real applications
Step 1: Start Jido
Jido uses instance-scoped supervisors. Define an instance module and add it to your supervision tree:
# In lib/my_app/jido.ex
defmodule MyApp.Jido do
use Jido, otp_app: :my_app
end
# In config/config.exs
config :my_app, MyApp.Jido,
max_tasks: 1000,
agent_pools: []
# In your application.ex
children = [
MyApp.Jido
]
Supervisor.start_link(children, strategy: :one_for_one)
For this Livebook, we'll start it directly:
defmodule Demo.Jido do
use Jido, otp_app: :demo
end
{:ok, _pid} = Demo.Jido.start_link()
Step 2: Define an Agent
Agents are immutable structs with a schema defining their state:
defmodule CounterAgent do
use Jido.Agent,
name: "counter",
description: "A simple counter agent",
schema: [
count: [type: :integer, default: 0],
status: [type: :atom, default: :idle]
]
end
Step 3: Use cmd/2 for State Transformations
The core pattern in Jido is cmd/2: pass an agent and an action, then receive
the updated agent and any directives for runtime-owned effects:
{agent, directives} = MyAgent.cmd(agent, action)
Let's define an action and use it:
defmodule IncrementAction do
use Jido.Action,
name: "increment",
description: "Increments the counter by a given amount",
schema: [
amount: [type: :integer, default: 1]
]
@impl true
def run(params, context) do
current = context.state[:count] || 0
{:ok, %{count: current + params.amount}}
end
end
Now use it:
agent = CounterAgent.new()
IO.inspect(agent.state, label: "Initial state")
{agent, _directives} = CounterAgent.cmd(agent, {IncrementAction, %{amount: 5}})
IO.inspect(agent.state, label: "After increment by 5")
{agent, _directives} = CounterAgent.cmd(agent, IncrementAction)
IO.inspect(agent.state, label: "After increment by 1")
Key points:
- The returned agent is fully updated
- Actions may be pure or effectful when they need a result back now
- Directives describe runtime-owned effects and never modify state
Step 4: Route Signals with AgentServer
For real applications, agents run inside AgentServer - a GenServer that handles signals and executes directives.
AgentServer routes each signal type to an Action. We already have
IncrementAction, so define the other counter actions and declare the complete
signal routes on the agent:
defmodule DecrementAction do
use Jido.Action,
name: "decrement",
description: "Decrements the counter by a given amount",
schema: [
amount: [type: :integer, default: 1]
]
@impl true
def run(params, context) do
current = context.state[:count] || 0
{:ok, %{count: current - params.amount}}
end
end
defmodule ResetAction do
use Jido.Action,
name: "reset",
description: "Resets the counter to zero",
schema: []
@impl true
def run(_params, _context) do
{:ok, %{count: 0}}
end
end
defmodule CounterAgentWithSignals do
use Jido.Agent,
name: "counter_with_signals",
description: "Counter that responds to signals",
schema: [
count: [type: :integer, default: 0],
status: [type: :atom, default: :idle]
],
signal_routes: [
{"counter.increment", IncrementAction},
{"counter.decrement", DecrementAction},
{"counter.reset", ResetAction}
]
end
Now start the agent under Jido and send signals:
{:ok, pid} = Demo.Jido.start_agent(CounterAgentWithSignals, id: "my-counter")
Create and send signals:
alias Jido.Signal
increment_signal = Signal.new!("counter.increment", %{amount: 10}, source: "/user")
{:ok, agent} = Jido.AgentServer.call(pid, increment_signal)
IO.inspect(agent.state, label: "After increment by 10")
decrement_signal = Signal.new!("counter.decrement", %{amount: 3}, source: "/user")
{:ok, agent} = Jido.AgentServer.call(pid, decrement_signal)
IO.inspect(agent.state, label: "After decrement by 3")
For async processing, use cast/2:
reset_signal = Signal.new!("counter.reset", %{}, source: "/admin")
:ok = Jido.AgentServer.cast(pid, reset_signal)
Process.sleep(50)
{:ok, state} = Jido.AgentServer.state(pid)
IO.inspect(state.agent.state, label: "After reset")
Step 5: Query and Manage Agents
Your instance module provides utilities for managing agents:
Demo.Jido.list_agents()
|> IO.inspect(label: "All agents")
Demo.Jido.agent_count()
|> IO.inspect(label: "Agent count")
found_pid = Demo.Jido.whereis("my-counter")
IO.inspect(found_pid == pid, label: "Found by ID?")
Stop the agent when done:
:ok = Demo.Jido.stop_agent("my-counter")
Summary
You've learned the core Jido workflow:
| Concept | Purpose |
|---|---|
use Jido, otp_app: :my_app |
Define an instance module for your agents |
use Jido.Agent |
Define agents with schemas |
cmd/2 |
Pure state transformations |
MyApp.Jido.start_agent/2 |
Run agents in AgentServer |
AgentServer.call/3 |
Synchronous signal processing |
AgentServer.cast/2 |
Async signal processing |
Signal.new!/3 |
Create signals to send to agents |
Next Steps
- Core Loop — Understand "Signal → Action → cmd/2 → {agent, directives}"
- Your First Plugin — Package actions with isolated state
- Your First Sensor — Feed external events into your agent
- Seeing What Happened — Debug with logs and telemetry