Actions Overview
View SourceActions are the fundamental building blocks of agent behavior in Jido. Each Action represents a discrete, composable unit of functionality that can be executed as part of a workflow. This guide explores how to create, use, and compose Actions effectively.
Core Concepts
Actions in Jido follow several key design principles:
- Self-Contained: Each Action encapsulates a specific piece of functionality with clear inputs and outputs
- Composable: Actions can be chained together to create complex workflows
- Validated: Input parameters are validated using schemas
- Context-Aware: Actions receive execution context for enhanced flexibility
- Error-Resilient: Built-in error handling and compensation mechanisms
Defining Actions
Here's a simple Action that adds two numbers:
defmodule MyApp.Actions.Add do
use Jido.Action,
name: "add",
description: "Adds two numbers together",
schema: [
value: [type: :number, required: true, doc: "First number"],
amount: [type: :number, required: true, doc: "Second number"]
]
@impl true
def run(%{value: value, amount: amount}, _context) do
{:ok, %{result: value + amount}}
end
end
Key Components
- Module Definition: Actions are Elixir modules that use the
Jido.Action
behavior - Configuration:
name
: Unique identifier for the Actiondescription
: Human-readable descriptionschema
: Parameter validation rules using NimbleOptions
- Run Implementation: The
run/2
callback defines the Action's core logic
Action Lifecycle
When an Action is executed, it goes through several phases:
- Parameter Validation: Input is checked against the schema
- Execution: The
run/2
callback is invoked - Result Processing: Output is normalized and returned
- Error Handling: Failures are caught and processed
Example Workflow
defmodule MyApp.Actions.ProcessOrder do
use Jido.Action,
name: "process_order",
description: "Processes a customer order",
schema: [
order_id: [type: :string, required: true],
customer_id: [type: :string, required: true]
]
@impl true
def run(params, context) do
with {:ok, order} <- fetch_order(params.order_id),
{:ok, processed} <- apply_processing(order, context) do
{:ok, %{
order_id: params.order_id,
status: "processed",
result: processed
}}
else
{:error, reason} -> {:error, "Failed to process order: #{reason}"}
end
end
end
Error Handling
Actions support multiple error handling patterns:
1. Basic Error Return
def run(%{value: 0}, _context) do
{:error, "Cannot process zero value"}
end
2. Compensation Logic
defmodule MyApp.Actions.RiskyOperation do
use Jido.Action,
name: "risky_operation",
compensation: [enabled: true]
def on_error(failed_params, error, _context, _opts) do
# Compensation logic here
{:ok, %{compensated: true, original_error: error}}
end
end
3. Error Context
{:error, %Jido.Error{
type: :validation_error,
message: "Invalid input",
details: %{field: :value, reason: "must be positive"}
}}
Composing Actions
Actions can be composed in several ways:
1. Sequential Chaining
alias MyApp.Actions.{ValidateOrder, ProcessOrder, NotifyCustomer}
instructions = [
ValidateOrder,
{ProcessOrder, %{priority: "high"}},
NotifyCustomer
]
Jido.Workflow.run_chain(instructions, %{order_id: "123"})
2. Conditional Execution
def process_with_notification(params) do
base_instructions = [ValidateOrder, ProcessOrder]
instructions =
if params.notify do
base_instructions ++ [NotifyCustomer]
else
base_instructions
end
Jido.Workflow.run_chain(instructions, params)
end
Built-in Actions
Jido provides several built-in Action modules:
Basic Operations
alias Jido.Actions.Basic
# Sleep for a duration
Basic.Sleep.run(%{duration_ms: 1000}, %{})
# Log a message
Basic.Log.run(%{level: :info, message: "Processing"}, %{})
File Operations
alias Jido.Actions.Files
# Write to a file
Files.WriteFile.run(%{
path: "output.txt",
content: "Hello",
create_dirs: true
}, %{})
Arithmetic Operations
alias Jido.Actions.Arithmetic
# Add numbers
Arithmetic.Add.run(%{value: 5, amount: 3}, %{})
# Multiply numbers
Arithmetic.Multiply.run(%{value: 4, amount: 2}, %{})
Testing Actions
Actions can be tested using standard ExUnit tests:
defmodule MyApp.Actions.AddTest do
use ExUnit.Case
test "adds two numbers" do
params = %{value: 5, amount: 3}
assert {:ok, %{result: 8}} = MyApp.Actions.Add.run(params, %{})
end
test "validates required parameters" do
params = %{value: 5}
assert {:error, _} = MyApp.Actions.Add.run(params, %{})
end
end
Best Practices
- Keep Actions Focused: Each Action should do one thing well
- Validate Inputs: Use comprehensive schemas to catch issues early
- Handle Errors: Implement proper error handling and compensation
- Provide Context: Use the context parameter for shared state
- Document Well: Include clear descriptions and parameter documentation
Next Steps
- Learn about Action Configuration including timeout settings
- Learn about Action Workflows
- Explore Testing Actions
- Understand Action Directives
- See Actions as Tools