Understanding the `cmd/2` Contract in Jido: Pure Logic for Testable Agents

The cmd/2 contract in Jido is a pure function that transforms an agent state and action into an updated state plus effect directives, enabling deterministic unit testing without external dependencies or process overhead.

The cmd/2 contract is the foundational abstraction that makes Jido agents both powerful and predictable. In the agentjido/jido repository, this pure functional interface separates decision logic from side-effect execution, allowing developers to verify complex agent behaviors in isolation.

What Is the cmd/2 Contract in Jido?

At its core, cmd/2 defines the decision boundary between an agent's internal state management and the runtime's effect execution. Every Jido agent must implement this contract to specify how it responds to actions.

Contract Signature and Types

The formal signature implemented in lib/jido/agent.ex is:

@callback cmd(agent :: Agent.t(), action :: Agent.action()) :: Agent.cmd_result()

This callback returns Agent.cmd_result(), which is defined as {updated_agent, directives}. The contract is enforced by the Jido.Agent.Strategy behaviour, ensuring all strategy implementations return consistent shapes.

Inputs and Outputs

The cmd/2 function accepts two arguments:

  • agent – The current immutable agent struct containing the complete state
  • action – A flexible input that can be:
    • A single action struct
    • A {module, params} tuple
    • An %Instruction{} struct
    • A list containing any combination of the above

The function returns a tuple where:

  • updated_agent – A complete, new agent struct with all state changes applied
  • directives – A list of pure effect descriptors (%Directive.Emit{}, %Directive.Spawn{}, etc.) that describe what side-effects the runtime should perform

The Purity Guarantee and Strategy Delegation

The cmd/2 contract guarantees deterministic purity – given the same agent and action, it always returns the same tuple without performing side-effects or external I/O.

The actual implementation delegates to the agent's strategy via Strategy.cmd/3 after normalizing the inputs. This delegation pattern, defined in lib/jido/agent/strategy.ex【lines 11‑18】, allows different execution strategies (direct, delayed, batched) while maintaining the same pure contract.

Why the cmd/2 Contract Is Critical for Testability

The cmd/2 contract transforms Jido agents into highly testable components by enforcing a strict separation between logic and effects. Here is why this matters:

  • Pure functional core – Because cmd/2 never touches the outside world, unit tests can call it directly with any agent state and action, asserting on the returned {agent, directives} without needing a live AgentServer, message passing, or external services.

  • Deterministic output – The same inputs always yield the same outputs, eliminating flaky tests caused by timing, race conditions, or external state.

  • Separation of concerns via directives – Side-effects are expressed only as pure directive structs. Tests can inspect the list of directives to verify that the correct external actions (emit a signal, spawn a child, schedule a timeout) would be performed, without actually executing them.

  • Strategy-agnostic testing – Because the contract delegates to a strategy (Strategy.cmd/3), you can test both the default direct strategy and any custom strategy by swapping the module in the agent definition. The contract guarantees that every strategy returns the same shape of result, keeping tests consistent.

  • Fast feedback loop – Pure cmd/2 calls execute instantly, so test suites run quickly, encouraging a strong unit-test culture.

The AgentServer uses cmd/2 as its decision-logic entry point while handling the directives at runtime, reinforcing the clean split between pure logic and effect execution【lib/jido/agent_server.ex lines 6‑8】.

Implementing the cmd/2 Contract in Practice

Basic Usage Example

Here is how to invoke cmd/2 on a freshly created agent:


# Define a simple action

defmodule MyAction do
  defstruct []
end

# Call cmd/2 on a freshly created agent

agent = MyAgent.new()
{agent, directives} = MyAgent.cmd(agent, MyAction)

# `directives` will be a list of %Jido.Agent.Directive{} structs

IO.inspect(directives)

Source: Example adapted from lib/jido/agent.ex documentation【lines 15‑18】.

Unit Testing with ExUnit

Because cmd/2 is pure, you can test agent logic without starting processes or mocking external services:

defmodule MyAgentTest do
  use ExUnit.Case

  test "cmd/2 returns updated state and correct directive" do
    agent = MyAgent.new(state: %{counter: 0})

    {agent, directives} = MyAgent.cmd(agent, MyAction)

    # State is updated immutably

    assert agent.state.counter == 0

    # Verify that a specific directive was produced

    assert [%Jido.Agent.Directive.Emit{signal: :my_signal}] = directives
  end
end

This test verifies both the state transformation and the side-effect intent without executing actual side-effects.

Custom Strategy Implementation

You can implement custom strategies while maintaining the cmd/2 contract. Here is a delayed execution strategy:

defmodule MyDelayedStrategy do
  use Jido.Agent.Strategy

  @impl true
  def cmd(agent, instructions, ctx) do
    # Example: schedule each instruction to run after a delay

    directives =
      Enum.map(instructions, fn instr ->
        %Jido.Agent.Directive.Schedule{
          delay_ms: 100,
          action: instr
        }
      end)

    {agent, directives}
  end
end

defmodule MyDelayedAgent do
  use Jido.Agent,
    name: "delayed",
    strategy: MyDelayedStrategy
end

The custom strategy still returns {agent, directives} as required by the cmd/2 contract, allowing the same unit-testing approach to be reused regardless of execution semantics.

Key Source Files

File Role
lib/jido/agent.ex Defines the cmd/2 public API, its invariants, and default callbacks【lines 13‑23】.
lib/jido/agent/strategy.ex Declares the Strategy behaviour; cmd/3 is the callback that implements the contract for each strategy【lines 11‑18】.
lib/jido/agent_server.ex Shows how the runtime (AgentServer) invokes cmd/2 as pure decision logic and then executes the returned directives【lines 6‑8】.
test/support/test_agents.ex Contains test helpers that illustrate unit-testing of agents via cmd/2.

These files together establish the pure contract, its implementation hooks, and the runtime execution path, making Jido agents highly testable and composable.

Summary

  • The cmd/2 contract in Jido is a pure function that transforms an agent state and action into an updated state plus effect directives.
  • It enforces deterministic, side-effect-free logic that enables direct unit testing without process overhead or external dependencies.
  • Side-effects are expressed as pure directive structs (%Directive.Emit{}, %Directive.Spawn{}, etc.), allowing tests to verify intent without execution.
  • The contract delegates to strategies via Strategy.cmd/3, enabling custom execution semantics while maintaining testability.
  • Key implementations reside in lib/jido/agent.ex, lib/jido/agent/strategy.ex, and lib/jido/agent_server.ex.

Frequently Asked Questions

What makes cmd/2 different from other agent methods?

Unlike callback methods that might interact with GenServer state or perform I/O directly, cmd/2 is a pure function that strictly separates decision logic from execution. It accepts an immutable agent struct and returns a new struct plus directives, never modifying global state or triggering side-effects during the call. This purity distinguishes it from traditional actor-model methods that often mix logic and effects.

How does cmd/2 enable testing without external dependencies?

Because cmd/2 is deterministic and side-effect-free, tests can invoke it directly with specific agent states and actions, then assert on the returned tuple of {updated_agent, directives}. There is no need to start an AgentServer, mock external services, or manage process state. Tests verify that the correct directives (like %Directive.Emit{} or %Directive.Spawn{}) are produced, confirming the agent's intent without executing actual side-effects.

Can I implement custom logic while maintaining the cmd/2 contract?

Yes, through strategies. The cmd/2 contract delegates to Strategy.cmd/3, allowing you to implement custom execution semantics—such as delayed processing, batching, or transactional boundaries—while still returning the required {agent, directives} tuple. As long as your strategy implementation remains pure and returns the correct shape, it maintains the testability benefits of the core contract.

Where does the actual side-effect execution happen if cmd/2 is pure?

Side-effects are executed by the runtime layer, specifically the AgentServer in lib/jido/agent_server.ex. The server calls cmd/2 to obtain the pure decision result, then interprets the returned directives (such as %Directive.Emit{} or %Directive.Schedule{}) to perform the actual I/O, message passing, or process spawning. This architecture ensures that your business logic remains pure and testable while the runtime handles effectful operations.

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 →