Agents
May 19, 2026 · View on GitHub
After: You can define agents with schemas, hooks, and the cmd/2/cmd/3 contract.
Agents are immutable data structures that hold state and respond to actions. The
core operation is cmd/2 (or cmd/3 with options), which processes actions and
returns an updated agent plus any runtime-owned directives.
Jido keeps agent decision logic pure. Actions may be pure or effectful. Directives are for effects you want the runtime to own.
Defining an Agent
defmodule MyAgent do
use Jido.Agent,
name: "my_agent", # Required - alphanumeric + underscores
description: "My custom agent", # Optional
category: "example", # Optional
tags: ["demo"], # Default: []
vsn: "1.0.0", # Optional
schema: [ # State schema (see below)
status: [type: :atom, default: :idle],
counter: [type: :integer, default: 0]
],
strategy: Jido.Agent.Strategy.Direct, # Default
plugins: [MyPlugin], # Default: []
default_plugins: true, # Load built-in plugins (Default: true)
schedules: [ # Declarative cron schedules (Default: [])
{"*/5 * * * *", "heartbeat.tick", job_id: :heartbeat}
]
end
The cmd/2 and cmd/3 Contract
The fundamental operation:
{agent, directives} = MyAgent.cmd(agent, action)
{agent, directives} = MyAgent.cmd(agent, action, opts)
Key invariants:
- The returned
agentis always complete—no "apply directives" step needed directivesdescribe runtime-owned external effects only—they never modify agent state- Agent decision logic stays explicit and testable
Use an effectful action when the current step needs a result back now to continue reasoning or update state. Use a directive when the workflow has already decided on an outbound effect and wants the runtime or integration layer to own delivery.
Action formats:
# Action module with no params
{agent, directives} = MyAgent.cmd(agent, MyAction)
# Action with params
{agent, directives} = MyAgent.cmd(agent, {MyAction, %{value: 42}})
# Action with params and context
{agent, directives} = MyAgent.cmd(agent, {MyAction, %{value: 42}, %{user_id: 123}})
# Action with params, context, and per-instruction opts
{agent, directives} = MyAgent.cmd(agent, {MyAction, %{value: 42}, %{}, [timeout: 5000]})
# Full instruction struct
{agent, directives} = MyAgent.cmd(agent, %Instruction{action: MyAction, params: %{}})
# List of actions (processed in sequence)
{agent, directives} = MyAgent.cmd(agent, [Action1, {Action2, %{x: 1}}])
Execution options via cmd/3:
Pass options that apply to all actions in the command:
# With timeout (5 second limit per action)
{agent, directives} = MyAgent.cmd(agent, MyAction, timeout: 5000)
# With timeout and no retries
{agent, directives} = MyAgent.cmd(agent, MyAction, timeout: 1000, max_retries: 0)
# Options applied to all actions in a list
{agent, directives} = MyAgent.cmd(agent, [Action1, Action2], timeout: 5000)
Supported options:
:timeout— Maximum time (in ms) for each action to complete:max_retries— Maximum retry attempts on failure:backoff— Initial backoff time in ms (doubles with each retry)
State Management
set/2 — Update State
Deep-merges attributes into agent state:
{:ok, agent} = MyAgent.set(agent, %{status: :running})
{:ok, agent} = MyAgent.set(agent, counter: 5)
validate/2 — Validate Against Schema
# Validate state, keeping extra fields
{:ok, agent} = MyAgent.validate(agent)
# Strict mode: only schema-defined fields are kept
{:ok, agent} = MyAgent.validate(agent, strict: true)
Lifecycle Hooks
Optional callbacks for pure transformations before/after command processing.
on_before_cmd/2
Called before action processing. Transform agent or action:
def on_before_cmd(agent, action) do
# Example: log the action being processed
{:ok, agent} = set(agent, %{last_action: inspect(action)})
{:ok, agent, action}
end
Use cases:
- Mirror action params into agent state
- Add default params based on current state
- Enforce invariants before execution
on_after_cmd/3
Called after action processing. Transform agent or directives:
def on_after_cmd(agent, action, directives) do
# Example: auto-validate after every command
{:ok, agent} = validate(agent)
{:ok, agent, directives}
end
Use cases:
- Auto-validate state after changes
- Derive computed fields
- Add invariant checks
Schema Options
Agent state is validated against a schema. Two formats are supported:
NimbleOptions (legacy, familiar)
use Jido.Agent,
name: "my_agent",
schema: [
status: [type: :atom, default: :idle],
counter: [type: :integer, default: 0],
config: [type: {:map, :atom, :string}, default: %{}]
]
Zoi (recommended for new code)
use Jido.Agent,
name: "my_agent",
schema: Zoi.object(%{
status: Zoi.atom() |> Zoi.default(:idle),
counter: Zoi.integer() |> Zoi.default(0),
config: Zoi.map() |> Zoi.default(%{})
})
Both are handled transparently by the Agent module.
Creating Agents
# Create with defaults
agent = MyAgent.new()
# Create with custom ID
agent = MyAgent.new(id: "custom-id")
# Create with initial state
agent = MyAgent.new(state: %{counter: 10})
If the module is primarily a durable coordinator for named collaborators, use
Jido.Pod instead of Jido.Agent. Jido.Pod wraps the same agent model and
adds a canonical topology plus a reserved singleton pod plugin.
Further Reading
- Actions — Defining actions that transform agent state
- State Operations — Internal state transitions during
cmd/2 - Directives — External effects emitted by agents
- Strategies — Execution strategies for
cmd/2 - Plugins — Default Plugins — Built-in plugins (identity, thread) and how to override them
- Pods — Manager-led durable topologies built on top of agents
Jido.Agent— Full module documentation