Jido Execution Strategies: Direct vs FSM and When to Use Each

Jido provides two built-in execution strategies—Direct for immediate action execution and FSM for state-machine workflows—plus support for custom strategies via the Jido.Agent.Strategy behaviour.

The agentjido/jido repository separates pure decision logic from runtime side-effects through pluggable execution strategies. When you define an agent, you select a strategy module that determines how actions are dispatched, how state transitions occur, and how execution progress is tracked.

Built-in Execution Strategies

Jido ships with two production-ready strategies in lib/jido/agent/strategy/. Each implements the callbacks defined in lib/jido/agent/strategy.ex: init/2, cmd/3, tick/2, and snapshot/2.

Direct Strategy

Jido.Agent.Strategy.Direct is the default execution strategy. It runs every normalized instruction immediately and sequentially via Jido.Exec.run/1, merges results into the agent's state, applies any StateOps, and returns only external directives.

Use Direct when:

  • You need simple, one-shot actions where results apply right away
  • You don't require multi-step orchestration, retry loops, or explicit state-machine transitions
  • You want optional thread-level tracing via the thread?: true option

When thread?: true is passed (or a thread already exists), the strategy records instruction start/end checkpoints in the thread managed by Jido.Thread.Agent.

FSM Strategy

Jido.Agent.Strategy.FSM provides a finite-state-machine execution model. It maintains an internal Machine struct (tracking status, processed count, last result, error, and transition map) inside agent.state.__strategy__.

Use FSM when:

  • Your workflow requires explicit states (e.g., "idle → processing → completed/failed")
  • You need to enforce allowed state transitions, handle retries, or batch-process instruction collections
  • You want observable progress via Strategy.Snapshot for supervisors or monitoring tools

The FSM dispatches instructions one-by-one, transitions states after each result, and can auto-transition back to the initial state. Thread checkpoints are added automatically when thread?: true.

Creating Custom Execution Strategies

You can implement domain-specific orchestration by creating a module that use Jido.Agent.Strategy and implements at least cmd/3. Store custom data in agent.state.__strategy__ via Jido.Agent.Strategy.State helpers, and expose tailored snapshots via snapshot/2.

Common use cases for custom strategies include:

  • LLM chain-of-thought loops
  • Behavior trees
  • Tool-calling cycles
  • External scheduler integration
  • Event-driven execution loops

How Execution Strategies Work

The strategy architecture follows a strict separation between pure logic and side-effects:

  1. Agent definitionuse Jido.Agent injects a strategy field selected via the strategy: option
  2. Command normalizationMyAgent.cmd/2 normalizes incoming actions into Instruction lists, then delegates to strategy.cmd/3
  3. Strategy execution – The strategy module runs instructions, manages state transitions, and emits directives
  4. Snapshot exposuresnapshot/2 returns a stable Strategy.Snapshot struct without leaking internal representation
  5. Thread tracking – Optional execution threads live under agent.state.__thread__ when enabled

Code Examples

Basic Direct Strategy

defmodule SimpleAgent do
  use Jido.Agent,
    name: "simple",
    strategy: Jido.Agent.Strategy.Direct
end

# Execute an action immediately

{:ok, agent} = SimpleAgent.new()
{agent, directives} = SimpleAgent.cmd(agent, MyAction, foo: "bar")

Direct Strategy with Thread Tracking

defmodule TracedAgent do
  use Jido.Agent,
    name: "traced",
    strategy: {Jido.Agent.Strategy.Direct, thread?: true}
end

{:ok, agent} = TracedAgent.new()
{agent, _} = TracedAgent.cmd(agent, MyAction, opts: 1)

# Inspect the thread via Jido.Thread.Agent.fetch/1

FSM Strategy for Workflow States

defmodule OrderProcessor do
  use Jido.Agent,
    name: "order",
    strategy: {
      Jido.Agent.Strategy.FSM,
      initial_state: "idle",
      transitions: %{
        "idle"       => ["processing"],
        "processing" => ["completed", "failed"]
      }
    }
end

{:ok, agent} = OrderProcessor.new()

# Transition from idle to processing

{agent, dirs} = OrderProcessor.cmd(agent, StartOrder, order_id: 123)

Custom LLM Chain-of-Thought Strategy

defmodule ChainOfThoughtStrategy do
  use Jido.Agent.Strategy

  @impl true
  def init(agent, _ctx), do: {agent, []}

  @impl true
  def cmd(agent, instructions, _ctx) do
    Enum.reduce(instructions, {agent, []}, fn instr, {ag, dirs} ->
      {:ok, result} = MyLLM.run(instr.params.prompt)
      ag = Jido.Agent.Strategy.State.update(ag, fn s -> 
        Map.put(s, :thoughts, [result | Map.get(s, :thoughts, [])]) 
      end)
      {ag, dirs}
    end)
  end
end

defmodule ThoughtfulAgent do
  use Jido.Agent,
    name: "thoughtful",
    strategy: ChainOfThoughtStrategy
end

Summary

  • Direct strategy (lib/jido/agent/strategy/direct.ex) executes instructions immediately and sequentially, ideal for simple one-shot actions with optional thread tracking.
  • FSM strategy (lib/jido/agent/strategy/fsm.ex) manages finite-state-machine workflows with explicit transitions, perfect for multi-step processes requiring state enforcement and observability.
  • Custom strategies implement the Jido.Agent.Strategy behaviour to handle domain-specific execution patterns like LLM chains or behavior trees.
  • All strategies use agent.state.__strategy__ for internal state via Jido.Agent.Strategy.State helpers and expose snapshots via snapshot/2.

Frequently Asked Questions

What is the default execution strategy in Jido?

Jido.Agent.Strategy.Direct is the default strategy when you don't specify a strategy: option in your agent definition. It executes all instructions immediately using Jido.Exec.run/1 and applies results directly to the agent state.

When should I use the FSM strategy instead of Direct?

Use the FSM strategy when your workflow requires explicit state management, such as enforcing valid transitions between "pending" and "completed" states, handling retries, or batch-processing collections where each item moves through a defined lifecycle. The Direct strategy cannot enforce state transitions or pause between steps.

How do I enable execution tracing with strategies?

Pass thread?: true in the strategy options tuple when defining your agent: strategy: {Jido.Agent.Strategy.Direct, thread?: true}. Both Direct and FSM strategies support this option, which records instruction start/end checkpoints in agent.state.__thread__ via Jido.Thread.Agent.

Can I combine different strategies in the same application?

Yes. Strategy selection happens at the individual agent level via the strategy: option in use Jido.Agent. You can have some agents using Direct for simple tasks while others use FSM for complex workflows, or implement custom strategies for specific domains like LLM orchestration.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →