Jido.Agent behaviour (Jido v2.0.0-rc.0)
View SourceAn Agent is an immutable data structure that holds state and can be updated via commands. This module provides a minimal, purely functional API:
new/1- Create a new agentset/2- Update state directlyvalidate/2- Validate agent state against schemacmd/2- Execute actions:(agent, action) -> {agent, directives}
Core Pattern
The fundamental operation is cmd/2:
{agent, directives} = MyAgent.cmd(agent, MyAction)
{agent, directives} = MyAgent.cmd(agent, {MyAction, %{value: 42}})
{agent, directives} = MyAgent.cmd(agent, [Action1, Action2])Key invariants:
- The returned
agentis always complete — no "apply directives" step needed directivesare external effects only — they never modify agent statecmd/2is a pure function — given same inputs, always same outputs
Action Formats
cmd/2 accepts actions in these forms:
MyAction- Action module with no params{MyAction, %{param: value}}- Action with params%Instruction{}- Full instruction struct[...]- List of any of the above (processed in sequence)
Directives
Directives are effect descriptions for the runtime to interpret. They are strictly outbound - the agent never receives directives as input.
Directives are bare structs (no tuple wrappers). Built-in directives
(see Jido.Agent.Directive):
%Directive.Emit{}- Dispatch a signal viaJido.Signal.Dispatch%Directive.Error{}- Signal an error (wrapsJido.Error.t())%Directive.Spawn{}- Spawn a child process%Directive.Schedule{}- Schedule a delayed message%Directive.Stop{}- Stop the agent process
The Emit directive integrates with Jido.Signal for dispatch:
# Emit with default dispatch config
%Directive.Emit{signal: my_signal}
# Emit to PubSub topic
%Directive.Emit{signal: my_signal, dispatch: {:pubsub, topic: "events"}}
# Emit to a specific process
%Directive.Emit{signal: my_signal, dispatch: {:pid, target: pid}}External packages can define custom directive structs without modifying core.
Directives never modify agent state — that's handled by the returned agent.
Usage
Defining an Agent Module
defmodule MyAgent do
use Jido.Agent,
name: "my_agent",
description: "My custom agent",
schema: [
status: [type: :atom, default: :idle],
counter: [type: :integer, default: 0]
]
endWorking with Agents
# Create a new agent (fully initialized including strategy state)
agent = MyAgent.new()
agent = MyAgent.new(id: "custom-id", state: %{counter: 10})
# Execute actions
{agent, directives} = MyAgent.cmd(agent, MyAction)
{agent, directives} = MyAgent.cmd(agent, {MyAction, %{value: 42}})
{agent, directives} = MyAgent.cmd(agent, [Action1, Action2])
# Update state directly
{:ok, agent} = MyAgent.set(agent, %{status: :running})Strategy Initialization
new/1 automatically calls strategy.init/2 to initialize strategy-specific
state. Any directives returned by strategy init are dropped here since they
require a runtime to execute. When using AgentServer, it handles strategy
init directives separately during startup.
Lifecycle Hooks
Agents support two optional callbacks:
on_before_cmd/2- Called before command processing (pure transformations only)on_after_cmd/3- Called after command processing (pure transformations only)
State Schema Types
Agent supports two schema formats for state validation:
NimbleOptions schemas (familiar, legacy):
schema: [ status: [type: :atom, default: :idle], counter: [type: :integer, default: 0] ]Zoi schemas (recommended for new code):
schema: Zoi.object(%{ status: Zoi.atom() |> Zoi.default(:idle), counter: Zoi.integer() |> Zoi.default(0) })
Both are handled transparently by the Agent module.
Pure Functional Design
The Agent struct is immutable. All operations return new agent structs.
Server/OTP integration is handled separately by Jido.AgentServer.
Summary
Callbacks
Called after command processing. Can transform the agent or directives.
Must be pure - no side effects. Return {:ok, agent, directives} to continue.
Called before command processing. Can transform the agent or action.
Must be pure - no side effects. Return {:ok, agent, action} to continue.
Returns signal routes for this agent.
Functions
Creates a new agent from attributes.
Returns the Zoi schema for Agent.
Updates agent state by merging new attributes.
Validates agent state against its schema.
Types
@type action() :: module() | {module(), map()} | Jido.Instruction.t() | [action()]
@type agent_result() :: {:ok, t()} | {:error, Jido.Error.t()}
@type directive() :: Jido.Agent.Directive.t()
Callbacks
@callback on_after_cmd(agent :: t(), action :: term(), directives :: [directive()]) :: {:ok, t(), [directive()]}
Called after command processing. Can transform the agent or directives.
Must be pure - no side effects. Return {:ok, agent, directives} to continue.
Use cases:
- Auto-validate state after changes
- Derive computed fields
- Add invariant checks
Called before command processing. Can transform the agent or action.
Must be pure - no side effects. Return {:ok, agent, action} to continue.
This hook runs once per cmd/2 call, with the action as passed (which may be a list).
It is not a per-instruction hook.
Use cases:
- Mirror action params into agent state (e.g., save last_query before processing)
- Add default params that depend on current state
- Enforce invariants or guards before execution
@callback signal_routes() :: [Jido.Signal.Router.route_spec()]
Returns signal routes for this agent.
Routes map signal types to action modules. AgentServer uses these routes to map incoming signals to actions for execution via cmd/2.
Route Formats
{path, ActionModule}- Simple mapping (priority 0){path, ActionModule, priority}- With priority{path, {ActionModule, %{static: params}}}- With static params{path, match_fn, ActionModule}- With pattern matching{path, match_fn, ActionModule, priority}- Full spec
Examples
def signal_routes do
[
{"user.created", HandleUserCreatedAction},
{"counter.increment", IncrementAction},
{"payment.*", fn s -> s.data.amount > 100 end, LargePaymentAction, 10}
]
end
Functions
Creates a new agent from attributes.
For module-based agents, use MyAgent.new/1 instead.
@spec schema() :: Zoi.schema()
Returns the Zoi schema for Agent.
@spec set(t(), map() | keyword()) :: agent_result()
Updates agent state by merging new attributes.
@spec validate( t(), keyword() ) :: agent_result()
Validates agent state against its schema.