Jido.Agent.Strategy behaviour (Jido v2.0.0-rc.0)
View SourceBehaviour for agent execution strategies.
A Strategy decides how to execute actions in cmd/2. The default strategy
(Direct) simply executes actions immediately. Advanced strategies can
implement behavior trees, LLM chains of thought, or other execution patterns.
Core Contract
Strategies implement these callbacks:
cmd(agent, action, context) :: {agent, directives}
init(agent, context) :: {agent, directives}
tick(agent, context) :: {agent, directives}
snapshot(agent, context) :: Strategy.Snapshot.t()The cmd/3 callback is required. Others are optional with default implementations.
Lifecycle
init/2- Called by AgentServer afterMyAgent.new/1and before the firstcmd/2. Use this to initialize strategy-specific state.cmd/3- Called byMyAgent.cmd/2to execute actions.tick/2- Called by AgentServer when a strategy has scheduled a tick (via{:schedule, ms, :strategy_tick}). Use for multi-step execution.
Snapshot Interface
To avoid agents inspecting strategy internals, strategies expose their state
through snapshot/2 which returns a Strategy.Snapshot struct:
snap = MyStrategy.snapshot(agent, ctx)
if snap.done?, do: snap.resultThis provides a stable interface regardless of internal implementation.
Usage
Set strategy at compile time:
defmodule MyAgent do
use Jido.Agent,
name: "my_agent",
strategy: Jido.Agent.Strategy.Direct # default
end
# Or with options:
defmodule MyBTAgent do
use Jido.Agent,
name: "bt_agent",
strategy: {MyBehaviorTreeStrategy, max_depth: 5}
endBuilt-in Strategies
Jido.Agent.Strategy.Direct- Execute actions immediately (default)
Custom Strategies
Use the module and implement the required cmd/3 callback:
defmodule MyCustomStrategy do
use Jido.Agent.Strategy
@impl true
def cmd(agent, action, ctx) do
# Custom execution logic
# Must return {updated_agent, directives}
end
# Optionally override init/2, tick/2, snapshot/2
endStrategy state should live inside agent.state under the reserved key
:__strategy__. Use Jido.Agent.Strategy.State helpers to manage it.
Summary
Callbacks
Returns the schema/spec for a strategy action.
Execute instructions against the agent.
Initialize strategy-specific state for a freshly created Agent.
Declares signal routes handled by this strategy.
Returns a stable snapshot of the strategy state.
Tick-based continuation for multi-step or long-running strategies.
Functions
Default snapshot implementation using Strategy.State helpers.
Normalizes instruction params using the strategy's action_spec.
Types
@type action_spec() :: %{ optional(:schema) => Zoi.schema() | keyword(), optional(:doc) => String.t(), optional(:name) => String.t() }
@type status() :: :idle | :running | :waiting | :success | :failure
Callbacks
@callback action_spec(action :: term()) :: action_spec() | nil
Returns the schema/spec for a strategy action.
When a strategy handles internal actions (like :react_start), this callback
provides the schema for parameter normalization. If the action has a schema,
params will be coerced/normalized before the strategy receives them.
Return nil for actions that don't need normalization.
Parameters
action- The action atom or module
Returns
action_spec()- Map with optional:schema,:doc,:namekeysnil- If no normalization needed
@callback cmd( agent :: Jido.Agent.t(), instructions :: [Jido.Instruction.t()], ctx :: context() ) :: {Jido.Agent.t(), [Jido.Agent.directive()]}
Execute instructions against the agent.
Called by MyAgent.cmd/2 after normalization. Receives a list of
already-normalized Instruction structs. Must return the updated agent
and any external directives.
Parameters
agent- The current agent structinstructions- List of normalizedInstructionstructscontext- Execution context with:agent_moduleand:strategy_opts
Returns
{updated_agent, directives}- The new agent state and external effects
@callback init(agent :: Jido.Agent.t(), ctx :: context()) :: {Jido.Agent.t(), [Jido.Agent.directive()]}
Initialize strategy-specific state for a freshly created Agent.
Called in two contexts:
- By
MyAgent.new/1to initialize strategy state (directives are dropped) - By
AgentServerduring startup to capture and process init directives
Since this may be called twice, implementations should be idempotent for state changes. The second call should return the same state (or recognize it's already initialized) while still emitting any desired directives.
Default implementation is a no-op.
Parameters
agent- The agent struct (may already have strategy state fromnew/1)context- Execution context with:agent_moduleand:strategy_opts
Returns
{updated_agent, directives}- The agent with initialized strategy state
@callback signal_routes(ctx :: context()) :: [ {String.t(), signal_target()} | {String.t(), signal_target(), integer()} | {String.t(), (Jido.Signal.t() -> boolean()), signal_target()} | {String.t(), (Jido.Signal.t() -> boolean()), signal_target(), integer()} ]
Declares signal routes handled by this strategy.
Returns a list of route specs that map signal types to strategy commands. AgentServer consults this to route incoming signals to the appropriate actions.
Route Targets
{:strategy_cmd, action}- Callcmd(agent, [{action, signal.data}]){:strategy_tick}- Schedule a strategy tick{:custom, term}- Custom handling (for extension)
Parameters
context- Execution context with:agent_moduleand:strategy_opts
Returns
List of route specs:
{signal_type, target}- Route with default priority{signal_type, target, priority}- Route with custom priority{signal_type, match_fn, target}- Route with pattern matching{signal_type, match_fn, target, priority}- Full route spec
Examples
def signal_routes(_ctx) do
[
{"react.user_query", {:strategy_cmd, :react_start}},
{"ai.llm_result", {:strategy_cmd, :react_llm_result}},
{"ai.tool_result", {:strategy_cmd, :react_tool_result}}
]
end
@callback snapshot(agent :: Jido.Agent.t(), ctx :: context()) :: Jido.Agent.Strategy.Snapshot.t()
Returns a stable snapshot of the strategy state.
Strategies should map any internal fields (status enums, final answers, etc.)
into a Strategy.Snapshot struct. Callers must not depend on internal
__strategy__ state shape.
Default implementation uses Strategy.State helpers.
Parameters
agent- The current agent structcontext- Execution context
Returns
Strategy.Snapshot.t()- Snapshot of strategy state
@callback tick(agent :: Jido.Agent.t(), ctx :: context()) :: {Jido.Agent.t(), [Jido.Agent.directive()]}
Tick-based continuation for multi-step or long-running strategies.
Called by AgentServer when a strategy has indicated it wants to be ticked
(via a schedule directive like {:schedule, ms, :strategy_tick}).
Default implementation is a no-op.
Parameters
agent- The current agent structcontext- Execution context with:agent_moduleand:strategy_opts
Returns
{updated_agent, directives}- The new agent state and external effects
Functions
@spec default_snapshot(Jido.Agent.t()) :: Jido.Agent.Strategy.Snapshot.t()
Default snapshot implementation using Strategy.State helpers.
Reads status from state and determines terminal status.
@spec normalize_instruction(module(), Jido.Instruction.t(), context()) :: Jido.Instruction.t()
Normalizes instruction params using the strategy's action_spec.
If the strategy implements action_spec/1 and returns a schema for the
action, params are normalized (string keys → atoms, type coercion via Zoi).
Otherwise, only basic string-to-atom key conversion is performed.
Parameters
strategy_mod- The strategy moduleinstruction- The instruction to normalizectx- Execution context
Returns
- Updated instruction with normalized params