Composable, validated actions for Elixir applications with built-in AI tool integration
Jido.Action is part of the Jido project. Learn more about Jido at agentjido.xyz.
Jido.Action is a framework for building composable, validated actions in Elixir. It provides a standardized way to define discrete units of functionality that can be composed into complex workflows, validated at compile and runtime using NimbleOptions schemas, and seamlessly integrated with AI systems through automatic tool generation.
Whether you're building microservices that need structured operations, implementing agent-based systems, or creating AI-powered applications that require reliable function calling, Jido.Action provides the foundation for robust, traceable, and scalable action-driven architecture.
Structured Operations in Elixir's Dynamic World
Elixir excels at building fault-tolerant, concurrent systems, but as applications grow, you often need:
- Standardized Operation Format: Raw function calls lack structure, validation, and metadata
- AI Tool Integration: Converting functions to LLM-compatible tool definitions manually
- Workflow Composition: Building complex multi-step processes from smaller units
- Parameter Validation: Ensuring inputs are correct before expensive operations
- Error Handling: Consistent error reporting across different operation types
- Runtime Introspection: Understanding what operations are available and how they work
# Traditional Elixir functions
def process_order(order_id, user_id, options) do
# No validation, no metadata, no AI integration
# Error handling is inconsistent
end
# With Jido.Action
defmodule ProcessOrder do
use Jido.Action,
name: "process_order",
description: "Processes a customer order with validation and tracking",
schema: [
order_id: [type: :string, required: true],
user_id: [type: :string, required: true],
priority: [type: {:in, [:low, :normal, :high]}, default: :normal]
]
def run(params, context) do
# Params are pre-validated, action is AI-ready, errors are structured
{:ok, %{status: "processed", order_id: params.order_id}}
end
end
# Use directly or convert to AI tool
ProcessOrder.to_tool() # Ready for LLM integrationJido.Action transforms ad-hoc functions into structured, validated, AI-compatible operations that scale from simple tasks to complex agent workflows.
- Compile-time configuration validation
- Runtime parameter validation with NimbleOptions or Zoi schemas
- Rich metadata including descriptions, categories, and tags
- Automatic JSON serialization support
- Automatic conversion to LLM-compatible tool format
- OpenAI function calling compatible
- Parameter schemas with validation and documentation
- Seamless integration with AI agent frameworks
- Synchronous and asynchronous execution via
Jido.Exec - Automatic retries with exponential backoff
- Timeout handling and cancellation
- Comprehensive error handling and compensation
- Instruction-based workflow definition via
Jido.Instruction - Parameter normalization and context sharing
- Action chaining and conditional execution
- Built-in workflow primitives
- 25+ pre-built actions for common operations
- File system operations, HTTP requests, arithmetic
- Weather APIs, GitHub integration, workflow primitives
- Robot simulation tools for testing and examples
Add jido_action to your list of dependencies in mix.exs:
def deps do
[
{:jido_action, "~> 1.0"}
]
endThen run:
mix deps.getdefmodule MyApp.Actions.GreetUser do
use Jido.Action,
name: "greet_user",
description: "Greets a user with a personalized message",
category: "communication",
tags: ["greeting", "user"],
vsn: "1.0.0",
schema: [
name: [type: :string, required: true, doc: "User's name"],
language: [type: {:in, ["en", "es", "fr"]}, default: "en", doc: "Greeting language"]
]
@impl true
def run(params, _context) do
greeting = case params.language do
"en" -> "Hello"
"es" -> "Hola"
"fr" -> "Bonjour"
end
{:ok, %{message: "#{greeting}, #{params.name}!"}}
end
end# Synchronous execution
{:ok, result} = Jido.Exec.run(MyApp.Actions.GreetUser, %{name: "Alice"})
# => {:ok, %{message: "Hello, Alice!"}}
# With validation error handling
{:error, reason} = Jido.Exec.run(MyApp.Actions.GreetUser, %{invalid: "params"})
# => {:error, %Jido.Action.Error{type: :validation_error, ...}}
# Asynchronous execution
async_ref = Jido.Exec.run_async(MyApp.Actions.GreetUser, %{name: "Bob"})
{:ok, result} = Jido.Exec.await(async_ref)# Define a sequence of actions
instructions = [
MyApp.Actions.ValidateUser,
{MyApp.Actions.GreetUser, %{name: "Alice", language: "es"}},
MyApp.Actions.LogActivity
]
# Normalize with shared context
{:ok, workflow} = Jido.Instruction.normalize(instructions, %{
request_id: "req_123",
tenant_id: "tenant_456"
})
# Execute the workflow
Enum.each(workflow, fn instruction ->
Jido.Exec.run(instruction.action, instruction.params, instruction.context)
end)# Convert action to AI tool format
tool_definition = MyApp.Actions.GreetUser.to_tool()
# Returns LangChain-compatible tool definition:
%{
name: "greet_user",
description: "Greets a user with a personalized message",
function: #Function<...>, # Executes the action
parameters_schema: %{
"type" => "object",
"properties" => %{
"name" => %{"type" => "string", "description" => "User's name"},
"language" => %{
"type" => "string",
"enum" => ["en", "es", "fr"],
"description" => "Greeting language"
}
},
"required" => ["name"]
}
}
# Use with AI frameworks - the function can be called directly
# or convert to OpenAI format for function callingThe foundational behavior for defining structured, validated actions. Provides:
- Compile-time configuration validation
- Parameter and output schemas with validation
- Lifecycle hooks for customization
- Automatic AI tool format generation
- JSON serialization support
The execution engine for running actions reliably. Features:
- Synchronous and asynchronous execution
- Automatic retries with exponential backoff
- Timeout handling and process monitoring
- Comprehensive error handling
- Telemetry integration for monitoring
The workflow composition system for building complex operations. Enables:
- Multiple input formats (modules, tuples, structs)
- Parameter normalization and validation
- Context sharing across actions
- Action allowlist validation
- Flexible workflow definition patterns
DAG-based execution planning for complex workflows. Features:
- Directed acyclic graph of action dependencies
- Parallel execution phases based on dependency analysis
- Builder pattern for constructing plans
- Cycle detection and validation
- Keyword list and programmatic plan construction
Jido.Action comes with a comprehensive library of pre-built tools organized by category:
| Tool | Description | Use Case |
|---|---|---|
Sleep |
Pauses execution for specified duration | Delays, rate limiting |
Log |
Logs messages with configurable levels | Debugging, monitoring |
Todo |
Logs TODO items as placeholders | Development workflow |
RandomSleep |
Random delay within specified range | Chaos testing, natural delays |
Increment/Decrement |
Numeric operations | Counters, calculations |
Noop |
No operation, returns input unchanged | Placeholder actions |
Today |
Returns current date in specified format | Date operations |
| Tool | Description | Use Case |
|---|---|---|
Add |
Adds two numbers | Mathematical operations |
Subtract |
Subtracts one number from another | Calculations |
Multiply |
Multiplies two numbers | Math workflows |
Divide |
Divides with zero-division handling | Safe arithmetic |
Square |
Squares a number | Mathematical functions |
| Tool | Description | Use Case |
|---|---|---|
WriteFile |
Write content to files with options | File creation, logging |
ReadFile |
Read file contents | Data processing |
CopyFile |
Copy files between locations | Backup, deployment |
MoveFile |
Move/rename files | File organization |
DeleteFile |
Delete files/directories (recursive) | Cleanup operations |
MakeDirectory |
Create directories (recursive) | Setup operations |
ListDirectory |
List directory contents with filtering | File discovery |
ReqTool is a specialized action that provides a behavior and macro for creating HTTP request actions using the Req library. It offers a standardized way to build HTTP-based actions with configurable URLs, methods, headers, and response processing.
| Tool | Description | Use Case |
|---|---|---|
| HTTP Actions | GET, POST, PUT, DELETE requests with Req library | API integration, webhooks |
| JSON Support | Automatic JSON parsing and response handling | REST API clients |
| Custom Headers | Configurable HTTP headers per action | Authentication, API keys |
| Response Transform | Custom response transformation via callbacks | Data mapping, filtering |
| Action Generation | Macro-based HTTP action creation | Rapid API client development |
| Tool | Description | Use Case |
|---|---|---|
Weather |
National Weather Service API integration | Weather data, forecasts |
Github.Issues |
GitHub Issues API (create, list, filter) | Issue management |
| Tool | Description | Use Case |
|---|---|---|
Workflow |
Multi-step workflow execution | Complex processes |
Simplebot |
Robot simulation actions | Testing, examples |
| Tool | Description | Use Case |
|---|---|---|
| Branch/Parallel | Conditional and parallel execution | Complex workflows |
| Error Handling | Compensation and retry mechanisms | Fault tolerance |
Actions support sophisticated error handling with optional compensation:
defmodule RobustAction do
use Jido.Action,
name: "robust_action",
compensation: [
enabled: true,
max_retries: 3,
timeout: 5000
]
def run(params, context) do
# Main action logic
{:ok, result}
end
# Called when errors occur if compensation is enabled
def on_error(failed_params, error, context, opts) do
# Perform rollback/cleanup operations
{:ok, %{compensated: true, original_error: error}}
end
endCustomize action behavior with lifecycle hooks:
defmodule CustomAction do
use Jido.Action, name: "custom_action"
def on_before_validate_params(params) do
# Transform params before validation
{:ok, transformed_params}
end
def on_after_validate_params(params) do
# Enrich params after validation
{:ok, enriched_params}
end
def on_after_run({:ok, result}) do
# Post-process successful results
{:ok, enhanced_result}
end
def on_after_run({:error, _} = error), do: error
endActions emit telemetry events for monitoring:
# Attach telemetry handlers
:telemetry.attach("action-handler", [:jido, :action, :stop], fn event, measurements, metadata, config ->
# Handle action completion events
Logger.info("Action completed: #{metadata.action}")
end, %{})Test actions directly or within the execution framework:
defmodule MyActionTest do
use ExUnit.Case
test "action validates parameters" do
assert {:error, _} = MyAction.validate_params(%{invalid: "params"})
assert {:ok, _} = MyAction.validate_params(%{valid: "params"})
end
test "action execution" do
assert {:ok, result} = Jido.Exec.run(MyAction, %{valid: "params"})
assert result.status == "success"
end
test "async action execution" do
async_ref = Jido.Exec.run_async(MyAction, %{valid: "params"})
assert {:ok, result} = Jido.Exec.await(async_ref, 5000)
end
endConfigure defaults in your application:
# config/config.exs
config :jido_action,
default_timeout: 10_000,
default_max_retries: 3,
default_backoff: 500We welcome contributions! Please see our GitHub repository for details.
Copyright 2024-2025 Mike Hostetler
Licensed under the Apache License, Version 2.0. See LICENSE for details.
For information about dependency licenses, see the LICENSE file.
- Documentation: https://hexdocs.pm/jido_action
- GitHub: https://github.com/agentjido/jido_action
- AgentJido: https://agentjido.xyz
- Jido Workbench: https://github.com/agentjido/jido_workbench