View Source Jido.Agent behaviour (Jido v1.0.0)

Defines an Agent within the Jido system - a compile-time defined entity for managing complex workflows through a sequence of Actions.

Overview

An Agent represents a stateful workflow executor that can plan and execute a series of Actions in a type-safe and composable way. Agents provide a consistent interface for orchestrating complex operations while maintaining state validation, error handling, and extensibility through lifecycle hooks.

Architecture

Key Concepts

  • Agents are defined at compile-time using the use Jido.Agent macro
  • Each Agent instance is created at server with a guaranteed unique ID
  • Agents maintain their own state schema for validation
  • Actions are registered with Agents and executed through a Runner
  • Lifecycle hooks enable customization of validation, planning and execution flows
  • All operations follow a consistent pattern returning {:ok, result} | {:error, reason}

Type Safety & Validation

  • Configuration validated at compile-time via NimbleOptions
  • Server state changes validated against defined schema
  • Action modules checked for behavior implementation
  • Consistent return type enforcement across operations

Features

  • Compile-time configuration validation
  • Server parameter validation via NimbleOptions
  • Comprehensive error handling with recovery hooks
  • Extensible lifecycle callbacks for all operations
  • JSON serialization support for persistence
  • Dynamic Action planning and execution
  • State management with validation and dirty tracking

Usage

Basic Example

defmodule MyAgent do
  use Jido.Agent,
    name: "my_agent",
    description: "Performs a complex workflow",
    category: "processing",
    tags: ["example", "demo"],
    vsn: "1.0.0",
    schema: [
      input: [type: :string, required: true],
      status: [type: :atom, values: [:pending, :running, :complete]]
    ],
    actions: [MyAction1, MyAction2]
end

# Create and configure agent
{:ok, agent} = MyAgent.new()
{:ok, agent} = MyAgent.set(agent, input: "test data")

# Plan and execute actions
{:ok, agent} = MyAgent.plan(agent, MyAction1, %{value: 1})
{:ok, agent} = MyAgent.run(agent)  # Result stored in agent.result

Customizing Behavior

defmodule CustomAgent do
  use Jido.Agent,
    name: "custom_agent",
    schema: [status: [type: :atom]]

  # Add pre-execution validation
  def on_before_run(agent) do
    if agent.state.status == :ready do
      {:ok, agent}
    else
      {:error, "Agent not ready"}
    end
  end

  # Custom error recovery
  def on_error(agent, error) do
    Logger.warning("Agent error", error: error)
    {:ok, %{agent | state: %{status: :error}}}
  end
end

Callbacks

The following optional callbacks can be implemented to customize agent behavior. All callbacks receive the agent struct and return {:ok, agent} | {:error, reason}.

  • on_before_validate_state/1 - Pre-validation processing
  • on_after_validate_state/1 - Post-validation processing
  • on_before_plan/3 - Pre-planning processing with params
  • on_before_run/1 - Pre-execution validation/setup
  • on_after_run/2 - Post-execution processing
  • on_after_directives/2 - Post-directive application
  • on_error/2 - Error handling and recovery

Default implementations pass through the agent unchanged.

Important Notes

Each Agent module defines its own struct type and behavior. Agent functions must be called on matching agent structs:

# Correct usage: agent = MyAgent.new() {:ok, agent} = MyAgent.set(agent, attrs)

# Incorrect usage: agent = MyAgent.new() {:ok, agent} = OtherAgent.set(agent, attrs) # Will fail - type mismatch

Runner Architecture

The Runner executes actions in the agent's pending_instructions queue.

  • Default implementation: Jido.Runner.Simple
  • Custom runners must implement the Jido.Runner behavior
  • Runners handle instruction execution and state management
  • Support for different execution strategies (simple, chain, parallel)

Error Handling

Errors are returned as tagged tuples: {:error, reason}

Common error types:

  • :validation_error - Schema/parameter validation failures
  • :execution_error - Action execution failures
  • :directive_error - Directive application failures

See Jido.Action for implementing compatible Actions.

Type Specifications

  • t() - The Agent struct type
  • instruction() - Single action with params
  • instructions() - List of instructions
  • agent_result() - {:ok, t()} | {:error, term()}

Summary

Callbacks

Called after successful directive application. Allows post-processing of directive results.

Called after successful action execution. Allows post-processing of execution results.

Called after state validation but before saving changes. Allows post-processing of validated state.

Called before planning actions, allows preprocessing of action parameters and potential action routing/transformation.

Called after action planning but before execution. Allows inspection/modification of planned actions.

Called before validating any state changes to the Agent. Allows custom preprocessing of state attributes.

Called when any error occurs during the agent lifecycle. Provides error handling and recovery strategies.

Functions

Raises an error indicating that Agents cannot be defined at server.

Registers one or more action modules with the agent at server. Registered actions can be used in planning and execution. Action modules must implement the Jido.Action behavior and pass validation checks.

Returns all action modules registered with the agent in registration order. Includes both compile-time and server-registered actions.

Types

agent_result()

@type agent_result() :: {:ok, t()} | {:error, Jido.Error.t()}

instruction()

@type instruction() :: module() | {module(), map()}

instructions()

@type instructions() :: instruction() | [instruction()]

map_result()

@type map_result() :: {:ok, map()} | {:error, Jido.Error.t()}

t()

@type t() :: %Jido.Agent{
  actions: [module()],
  category: String.t() | nil,
  description: String.t() | nil,
  dirty_state?: boolean(),
  id: String.t() | nil,
  name: String.t() | nil,
  pending_instructions: :queue.queue(instruction()) | nil,
  result: term(),
  runner: module() | nil,
  schema: NimbleOptions.schema() | nil,
  state: map(),
  tags: [String.t()] | nil,
  vsn: String.t() | nil
}

Callbacks

on_after_directives(agent, result)

(optional)
@callback on_after_directives(agent :: t(), result :: map()) :: agent_result()

Called after successful directive application. Allows post-processing of directive results.

on_after_run(agent, result)

(optional)
@callback on_after_run(agent :: t(), result :: map()) :: agent_result()

Called after successful action execution. Allows post-processing of execution results.

on_after_validate_state(agent)

(optional)
@callback on_after_validate_state(agent :: t()) :: agent_result()

Called after state validation but before saving changes. Allows post-processing of validated state.

on_before_plan(agent, action, params)

(optional)
@callback on_before_plan(agent :: t(), action :: module(), params :: map()) ::
  agent_result()

Called before planning actions, allows preprocessing of action parameters and potential action routing/transformation.

on_before_run(agent)

(optional)
@callback on_before_run(agent :: t()) :: agent_result()

Called after action planning but before execution. Allows inspection/modification of planned actions.

on_before_validate_state(agent)

(optional)
@callback on_before_validate_state(agent :: t()) :: agent_result()

Called before validating any state changes to the Agent. Allows custom preprocessing of state attributes.

on_error(agent, reason)

(optional)
@callback on_error(agent :: t(), reason :: any()) :: agent_result()

Called when any error occurs during the agent lifecycle. Provides error handling and recovery strategies.

Functions

deregister_action(agent, action_module)

new()

@spec new() :: {:error, Jido.Error.t()}

Raises an error indicating that Agents cannot be defined at server.

This function exists to prevent misuse of the Agent system, as Agents are designed to be defined at compile-time only.

Returns

Always returns {:error, reason} where reason is a config error.

Examples

iex> Jido.Agent.new()
{:error, %Jido.Error{type: :config_error, message: "Agents should not be defined at server"}}

new(id)

@spec new(String.t()) :: {:error, Jido.Error.t()}

register_action(agent, action_modules)

@spec register_action(t(), module() | [module()]) ::
  {:ok, t()} | {:error, Jido.Error.t()}

Registers one or more action modules with the agent at server. Registered actions can be used in planning and execution. Action modules must implement the Jido.Action behavior and pass validation checks.

Action Requirements

Parameters

  • agent - The agent struct to update
  • action_module - Single action module or list of action modules to register

Returns

  • {:ok, updated_agent} - Agent struct with newly registered actions prepended
  • {:error, String.t()} - If action validation fails

Examples

# Register single action
{:ok, agent} = MyAgent.register_action(agent, MyApp.Actions.ProcessFile)

# Register multiple actions
{:ok, agent} = MyAgent.register_action(agent, [Action1, Action2])

Server Considerations

  • Actions persist only for the agent's lifecycle
  • Duplicates are prevented during validation
  • Most recently registered actions take precedence

See Jido.Action for implementing actions and Jido.Util.validate_actions/1 for validation details.

registered_actions(agent)

@spec registered_actions(t()) :: [module()]

Returns all action modules registered with the agent in registration order. Includes both compile-time and server-registered actions.

Parameters

  • agent - The agent struct to inspect

Returns

  • [module()] - Action modules ordered by registration time (newest first)

Examples

agent = MyAgent.new()
MyAgent.registered_actions(agent) #=> [DefaultAction, CoreAction]

{:ok, agent} = MyAgent.register_action(agent, CustomAction)
MyAgent.registered_actions(agent) #=> [CustomAction, DefaultAction, CoreAction]

See register_action/2 for adding actions and plan/3 for using them.