Search
Search docs, blog posts, and ecosystem packages with citations.
Enter a query to see grounded citations.
We can't find the internet
Attempting to reconnect
Search docs, blog posts, and ecosystem packages with citations.
The runtime contract for state transitions, command handling, and directives in Jido.
This is the most important concept in Jido: an agent is an immutable struct, not a process.
There is no GenServer inside an agent. No message passing, no mailbox, no handle_call. An agent is a plain Elixir struct with schema-validated state and a command interface. You create one, pass it to cmd/2 with an action, and get back a new struct plus a list of directives. The original is unchanged.
This makes agents trivially testable, serializable, and composable. You can hold ten agents in a list, run the same action against all of them, and inspect results without starting any processes.
If you need a long-running process with supervision, message routing, and directive execution, that’s what AgentServer provides. It wraps an agent struct and runs it inside OTP. But the agent itself remains data.
Jido’s core has no AI dependency. You can build, test, and ship agents without any LLM, any API key, or any neural network. AI capabilities live in the separate jido_ai package, which treats machine learning as one more action an agent can run - not as the foundation of what an agent is.
The word “agent” has been hijacked. In 2024, “agent” became shorthand for “LLM in a loop with tools.” But agents have a 30-year history in computer science that predates large language models by decades.
In classical AI, an agent is an entity that perceives its environment through sensors and acts upon it through effectors. Stuart Russell and Peter Norvig defined this in Artificial Intelligence: A Modern Approach (1995). The BDI (Belief-Desire-Intention) model formalized how agents reason about goals. Behavior trees gave game AI agents deterministic decision-making. None of these required a language model.
Jido implements this classical definition. An agent has state (beliefs), receives signals (perceptions), executes actions (effectors), and emits directives (intentions). The execution model - how the agent decides what to do - is pluggable through strategies. A strategy can be a simple sequential pipeline, a behavior tree, a state machine, or yes, an LLM chain of thought. The agent contract is the same regardless.
This is why Jido agents are powerful in ways that LLM wrappers are not. You get deterministic testing, predictable performance, and composability that doesn’t depend on a network call to a model provider. When you do want AI, jido_ai adds reasoning strategies like ReAct, chain-of-thought, and tree-of-thoughts as pluggable strategies - not as the core abstraction.
Most agent systems fail in production for one reason: state transitions and side effects are coupled.
When one function both mutates state and performs external work, failures become hard to isolate. You get partial updates, retry ambiguity, and brittle tests. Jido solves this by making the command boundary explicit:
cmd/2The result is a system you can reason about under load.
A plain function is enough if your workflow is single-step and stateless. Reach for a Jido Agent when you need:
Define an agent with schema-backed state, then execute an action through cmd/2.
defmodule MyApp.CounterAgent do
use Jido.Agent,
name: "counter_agent",
description: "Tracks command executions",
schema: Zoi.object(%{
count: Zoi.integer() |> Zoi.default(0),
status: Zoi.atom() |> Zoi.default(:idle)
})
end
defmodule MyApp.Increment do
use Jido.Action,
name: "increment",
schema: Zoi.object(%{by: Zoi.integer() |> Zoi.default(1)})
@impl true
def run(ctx, params) do
agent = Map.get(ctx, :agent) || Map.get(ctx, "agent") || ctx
by = Map.get(params, :by) || Map.get(params, "by") || 1
{:ok, %{count: agent.state.count + by, status: :active}}
end
end
# Example invocation:
# agent = MyApp.CounterAgent.new()
# {updated_agent, directives} = MyApp.CounterAgent.cmd(agent, {MyApp.Increment, %{by: 2}})
# updated_agent.state.count
# # => 2
# directives
# # => []
cmd/2 workscmd/2 is the core contract for agent evolution.
At a high level:
on_before_cmd/2 runs (optional hook) on_after_cmd/3 runs (optional hook) {agent, directives}Two invariants matter:
agent is already complete; there is no “apply directives to state” step This example updates agent state and emits a signal directive for the runtime.
defmodule MyApp.RegistrationAgent do
use Jido.Agent,
name: "registration_agent",
schema: Zoi.object(%{
processed: Zoi.integer() |> Zoi.default(0),
last_user_id: Zoi.string() |> Zoi.optional()
})
end
defmodule MyApp.ProcessRegistration do
use Jido.Action,
name: "process_registration",
schema: Zoi.object(%{user_id: Zoi.string()})
alias Jido.Agent.Directive
@impl true
def run(ctx, params) do
agent = Map.get(ctx, :agent) || Map.get(ctx, "agent") || ctx
user_id = Map.get(params, :user_id) || Map.get(params, "user_id")
signal = %Jido.Signal{
id: "sig_registration_processed",
type: "registration.processed",
source: "my_app.registration",
data: %{user_id: user_id}
}
{:ok,
%{processed: agent.state.processed + 1, last_user_id: user_id},
%Directive.Emit{signal: signal}}
end
end
# Example invocation:
# agent = MyApp.RegistrationAgent.new()
# {agent, directives} =
# MyApp.RegistrationAgent.cmd(agent, {MyApp.ProcessRegistration, %{user_id: "usr_123"}})
# agent.state
# # => %{processed: 1, last_user_id: "usr_123"}
# directives
# # => [%Jido.Agent.Directive.Emit{...}]
In production, the runtime handles the directive dispatch; your state transition logic stays pure.
| Strategy | Best for | Tradeoff |
|---|---|---|
Jido.Agent.Strategy.Direct | Deterministic workflows and most business-state transitions | Single-pass execution; no built-in tick loop |
Custom strategy implementing Jido.Agent.Strategy | Multi-step planners, staged execution, or domain-specific orchestration | More control, but you own strategy semantics and testing |
Start with Direct unless you can name the specific runtime behavior you need from a custom strategy.
The most common failures are normalization errors, action execution failures, and schema violations.
Jido keeps those failures bounded:
Operationally, this is where AgentServer and supervision matter. Supervisors handle process lifecycle; your agent contract handles state correctness and effect intent.