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 agent is always complete—no "apply directives" step needed
  • directives describe 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: %{}]
  ]
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