Jido.Action

Hex.pmHex DocsCILicenseWebsiteEcosystemDiscord

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 jido.run.

Overview

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.

Purity and Side Effects

Jido.Action modules are reusable execution units. run/2 may be pure or effectful depending on the job.

Doing HTTP requests, database queries, file system work, or other I/O inside run/2 is acceptable when the step needs that data back immediately to continue the workflow. When an effect should instead be owned by a runtime or integration boundary, hand it off there rather than doing it inline.

When actions are used inside jido, the purity guarantee belongs to the agent or strategy cmd/2 boundary, not necessarily to each action that the runtime executes behind that boundary.

Why Do I Need Actions?

Structured Operations in Elixir's Dynamic World

Elixir excels at building fault-tolerant, concurrent systems, but as applications grow, you often need:

# 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 integration

Jido.Action transforms ad-hoc functions into structured, validated, AI-compatible operations that scale from simple tasks to complex agent workflows.

Key Features

Structured Action Definition

AI Tool Integration

Robust Execution Engine

Workflow Composition

Comprehensive Tool Library

Installation

The fastest way to get started is with Igniter:

mix igniter.install jido_action

This automatically:

Manual Installation

Add jido_action to your list of dependencies in mix.exs:

def deps do
[
{:jido_action, "~> 2.0"}
]
end

Then run:

mix deps.get

Quick Start

1. Define Your First Action

defmodule 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

2. Execute Actions with Jido.Exec

# 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)

Async Contract

See the detailed contract in Execution Engine Guide.

3. Create Workflows with Jido.Instruction

# 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)

4. AI Tool Integration

# 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 calling

Core Components

Jido.Action

The foundational behavior for defining structured, validated actions. Provides:

Jido.Exec

The execution engine for running actions reliably. Features:

Jido.Instruction

The workflow composition system for building complex operations. Enables:

Jido.Plan

DAG-based execution planning for complex workflows. Features:

Bundled Tools

Jido.Action comes with a comprehensive library of pre-built tools organized by category:

Core Utilities (Jido.Tools.Basic)

ToolDescriptionUse Case
SleepPauses execution for specified durationDelays, rate limiting
LogLogs messages with configurable levelsDebugging, monitoring
TodoLogs TODO items as placeholdersDevelopment workflow
RandomSleepRandom delay within specified rangeChaos testing, natural delays
Increment/DecrementNumeric operationsCounters, calculations
NoopNo operation, returns input unchangedPlaceholder actions
TodayReturns current date in specified formatDate operations

Arithmetic Operations (Jido.Tools.Arithmetic)

ToolDescriptionUse Case
AddAdds two numbersMathematical operations
SubtractSubtracts one number from anotherCalculations
MultiplyMultiplies two numbersMath workflows
DivideDivides with zero-division handlingSafe arithmetic
SquareSquares a numberMathematical functions

File System Operations (Jido.Tools.Files)

ToolDescriptionUse Case
WriteFileWrite content to files with optionsFile creation, logging
ReadFileRead file contentsData processing
CopyFileCopy files between locationsBackup, deployment
MoveFileMove/rename filesFile organization
DeleteFileDelete files/directories (recursive)Cleanup operations
MakeDirectoryCreate directories (recursive)Setup operations
ListDirectoryList directory contents with filteringFile discovery

File tools can be scoped with config :jido_action, file_tool_roots: [...] or per-run %{allowed_file_roots: [...]} context. Use roots before exposing these tools to agents or user-influenced requests.

HTTP Operations (Jido.Tools.ReqTool)

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.

HTTP and Lua tools use optional dependencies. Add {:req, "~> 0.6.1"} when using Jido.Tools.ReqTool, and {:lua, "~> 1.0.0-rc"} when using Jido.Tools.LuaEval.

ToolDescriptionUse Case
HTTP ActionsGET, POST, PUT, DELETE requests with Req libraryAPI integration, webhooks
JSON SupportAutomatic JSON parsing and response handlingREST API clients
Custom HeadersConfigurable HTTP headers per actionAuthentication, API keys
Response TransformCustom response transformation via callbacksData mapping, filtering
Action GenerationMacro-based HTTP action creationRapid API client development

Workflow

ToolDescriptionUse Case
WorkflowMulti-step workflow executionComplex processes

Specialized Tools

ToolDescriptionUse Case
Branch/ParallelConditional and parallel executionComplex workflows
Error HandlingCompensation and retry mechanismsFault tolerance

Advanced Features

Error Handling and Compensation

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
end

Lifecycle Hooks

Customize 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
end

Telemetry Integration

Actions 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, %{})

Testing

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
end

Configuration

Configure defaults in your application:

# config/config.exs
config :jido_action,
default_timeout: 10_000,
default_max_retries: 3,
default_backoff: 500,
default_log_level: :info

default_log_level sets Jido's execution log threshold. Per-call log_level options override it, and the application's global Logger configuration still acts as the final backend filter.

If any of these values are invalid at runtime, Jido logs a warning and falls back to the built-in defaults instead of crashing. See Configuration Guide.

Instance Isolation (Multi-Tenant)

For multi-tenant applications, route execution through instance-scoped supervisors:

# Add instance supervisor to your supervision tree
children = [
{Task.Supervisor, name: MyApp.Jido.TaskSupervisor}
]
# Execute with instance isolation
{:ok, result} = Jido.Exec.run(MyAction, params, context, jido: MyApp.Jido)

When jido: MyApp.Jido is provided, all tasks spawn under MyApp.Jido.TaskSupervisor instead of the global supervisor, ensuring complete isolation between tenants.

Contributing

We welcome contributions! Please see our GitHub repository for details.

License

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.