Configuration Guide
View SourcePrerequisites: Basic Elixir configuration knowledge
Configure Jido Action's runtime behavior, timeouts, and application-specific settings for your environment.
Application Configuration
Basic Setup
Add to your config/config.exs:
import Config
config :jido_action,
# Global timeout in milliseconds (default: 30000)
default_timeout: 30_000,
# Retry configuration
default_max_retries: 1, # Default retry attempts
default_backoff: 250, # Initial backoff in milliseconds (exponential, capped at 30s)
# Execution logging threshold
default_log_level: :info,
# Optional runtime-context propagation across supervised execution
observability: [
context_propagators: [],
context_propagator_failure_mode: :warn
]Environment-Specific Configuration
# config/dev.exs
import Config
config :jido_action,
default_timeout: 60_000, # Longer timeouts in dev
default_log_level: :debug # More verbose execution logs in dev
# config/test.exs
import Config
config :jido_action,
default_timeout: 1_000, # Faster timeouts in tests
default_max_retries: 0, # No retries in tests
default_log_level: :warning # Keep test logs quieter by default
# config/prod.exs
import Config
config :jido_action,
default_timeout: 30_000,
default_max_retries: 3, # More retries in production
default_backoff: 500, # Longer initial backoff
default_log_level: :infoRuntime Configuration
Runtime Config Validation and Fallback
Jido.Exec, Jido.Exec.Async, Jido.Exec.Retry, and Jido.Action.Util validate runtime values for:
:default_timeout:default_max_retries:default_backoff:default_log_level
The timeout and retry settings must be non-negative integers.
default_log_level must be a valid Logger.level().
If a value is invalid (for example -1, :bad, "5000", or :verbose), Jido:
- Logs a warning with the invalid value and config key.
- Uses the internal fallback default for that key.
- Continues execution without crashing.
Example warning behavior:
Application.put_env(:jido_action, :default_timeout, :bad_value)
Application.put_env(:jido_action, :default_log_level, :verbose)
# Exec/Async calls will warn and use fallback defaults.
{:ok, _result} = Jido.Exec.run(MyAction, %{input: "ok"}, %{})Per-Action Configuration
Actions define compensation settings at compile time:
defmodule MyApp.Actions.CriticalOperation do
use Jido.Action,
name: "critical_operation",
description: "An operation with compensation enabled",
compensation: [
enabled: true,
max_retries: 3,
timeout: 10_000
],
schema: [data: [type: :string, required: true]]
def run(params, _context) do
{:ok, %{processed: params.data}}
end
# Called when an error occurs and compensation is enabled
def on_error(failed_params, error, context, opts) do
# Perform rollback logic
{:ok, %{rolled_back: true}}
end
endExecution-Time Configuration
Override settings when executing actions:
# Override timeout and retries for specific execution
{:ok, result} = Jido.Exec.run(
MyApp.Actions.SlowOperation,
%{data: "input"},
%{},
timeout: 60_000, # 60 second timeout
max_retries: 5, # 5 retries
backoff: 500, # 500ms initial backoff (doubles each retry)
log_level: :debug, # Override Jido's execution log threshold for this call
telemetry: :silent # Disable telemetry for this call
)Available execution options:
:timeout- Maximum time in ms for the action to complete (default: 30000):max_retries- Maximum retry attempts on failure (default: 1):backoff- Initial backoff time in ms, doubles with each retry (default: 250, capped at 30s):log_level- Override Jido's execution log threshold for this call. AcceptsLogger.levels(). Global Logger config still applies underneath.:telemetry- Telemetry mode::full(default) or:silent:context_propagators- Context propagator modules captured before supervised task boundaries and reattached inside child processes:context_propagator_failure_mode-:warn(default) to log and skip failures, or:strictto raise
Execution log level precedence is:
opts[:log_level]config :jido_action, default_log_level: ...- built-in
:info
Observability Context Propagation
Jido.Exec can preserve process-local runtime context when execution crosses Task.Supervisor
boundaries for timeout handling, run_async/4, compensation, and async chains.
Configure propagators globally:
config :jido_action,
observability: [
context_propagators: [MyApp.Observability.ContextPropagator],
context_propagator_failure_mode: :warn
]Or override them per execution:
Jido.Exec.run(
MyApp.Actions.ProcessData,
%{data: "input"},
%{},
timeout: 5_000,
context_propagators: [MyApp.Observability.ContextPropagator],
context_propagator_failure_mode: :strict
)Each propagator module must implement:
defmodule MyApp.Observability.ContextPropagator do
@behaviour Jido.Exec.ContextPropagator
def capture(), do: ...
def attach(captured), do: ...
def detach(attached), do: :ok
endThis keeps jido_action observability-agnostic while allowing adapter packages such as
OpenTelemetry bridges to preserve tracing context across supervised execution.
Configuration Access
Access configuration in your actions:
defmodule MyApp.Actions.ConfigAware do
use Jido.Action,
name: "config_aware",
schema: []
def run(params, _context) do
# Get application configuration
timeout = Application.get_env(:jido_action, :default_timeout, 30_000)
api_key = Application.get_env(:my_app, :api_key)
# Use configuration in business logic
case make_api_call(params, api_key, timeout) do
{:ok, result} -> {:ok, result}
{:error, reason} -> {:error, reason}
end
end
endTelemetry Integration
Telemetry Events
Jido Action emits telemetry events under the [:jido, :action] prefix using :telemetry.span/3:
[:jido, :action, :start]- Action execution begins withactionand optionaljido[:jido, :action, :stop]- Action execution completes with bounded summary metadata (action, optionaljido,outcome, and error summary fields when relevant)
Regular action failures are represented as :stop events with metadata.outcome == :error.
[:jido, :action, :exception] is reserved for uncaught exceptions that escape the telemetry span,
which should be rare in normal Jido.Exec flows.
Default execution telemetry intentionally excludes full params, context, result, and
stacktrace payloads to keep metadata low-cardinality and avoid leaking sensitive runtime values.
Custom Telemetry Handlers
Set up telemetry handlers for monitoring:
# In your application.ex
def start(_type, _args) do
# Attach telemetry handlers
:telemetry.attach_many(
"jido-action-handlers",
[
[:jido, :action, :start],
[:jido, :action, :stop]
],
&MyApp.Telemetry.handle_event/4,
%{}
)
# Start your supervision tree
children = [
{Task.Supervisor, name: Jido.Action.TaskSupervisor}
]
Supervisor.start_link(children, strategy: :one_for_one)
endTelemetry Handler Implementation
defmodule MyApp.Telemetry do
require Logger
def handle_event([:jido, :action, :start], _measurements, metadata, _config) do
Logger.debug("Action started",
action: metadata.action,
jido: metadata[:jido]
)
end
def handle_event([:jido, :action, :stop], measurements, metadata, _config) do
duration_ms = System.convert_time_unit(measurements.duration, :native, :millisecond)
case metadata.outcome do
:ok ->
Logger.info("Action completed",
action: metadata.action,
duration_ms: duration_ms,
jido: metadata[:jido],
directive?: metadata[:directive?] == true
)
:error ->
Logger.error("Action failed",
action: metadata.action,
duration_ms: duration_ms,
jido: metadata[:jido],
error_type: metadata[:error_type],
retryable?: metadata[:retryable?] == true,
directive?: metadata[:directive?] == true
)
end
end
endTask Supervisor Setup
Async action execution requires a Task.Supervisor in your supervision tree:
# In your application.ex
def start(_type, _args) do
children = [
{Task.Supervisor, name: Jido.Action.TaskSupervisor}
# ... other children
]
Supervisor.start_link(children, strategy: :one_for_one)
endInstance Isolation (Multi-Tenant)
For multi-tenant applications, create instance-scoped supervisors:
# In your application.ex or dynamic supervisor
def start(_type, _args) do
children = [
# Global supervisor (always required)
{Task.Supervisor, name: Jido.Action.TaskSupervisor},
# Instance-scoped supervisors for tenant isolation
{Task.Supervisor, name: TenantA.Jido.TaskSupervisor},
{Task.Supervisor, name: TenantB.Jido.TaskSupervisor}
]
Supervisor.start_link(children, strategy: :one_for_one)
endExecute actions with instance isolation:
# Routes to TenantA.Jido.TaskSupervisor
{:ok, result} = Jido.Exec.run(MyAction, params, context, jido: TenantA.Jido)
# Routes to TenantB.Jido.TaskSupervisor
{:ok, result} = Jido.Exec.run(MyAction, params, context, jido: TenantB.Jido)Key behaviors:
- When
jido:is absent ornil, uses globalJido.Action.TaskSupervisor - When
jido: MyApp.Jidois provided, usesMyApp.Jido.TaskSupervisor - Raises
ArgumentErrorif instance supervisor is not running (no silent fallback)
Environment Variables
Support runtime configuration via environment variables:
# config/runtime.exs
import Config
config :jido_action,
default_timeout: String.to_integer(System.get_env("JIDO_DEFAULT_TIMEOUT", "30000")),
default_max_retries: String.to_integer(System.get_env("JIDO_MAX_RETRIES", "1")),
default_backoff: String.to_integer(System.get_env("JIDO_DEFAULT_BACKOFF", "250"))
# Application-specific configuration
config :my_app,
database_url: System.get_env("DATABASE_URL"),
api_key: System.get_env("API_KEY")Environment Variable Validation
Validate required environment variables:
defmodule MyApp.Config do
def validate_config! do
required_env_vars = [
"DATABASE_URL",
"API_KEY"
]
missing = Enum.filter(required_env_vars, fn var ->
System.get_env(var) == nil
end)
unless Enum.empty?(missing) do
raise "Missing required environment variables: #{Enum.join(missing, ", ")}"
end
:ok
end
end
# In application.ex
def start(_type, _args) do
MyApp.Config.validate_config!()
# ... rest of startup
endCustom Configuration Modules
Create configuration modules for complex settings:
defmodule MyApp.Config.Actions do
@default_timeout 30_000
@default_retries 1
def get_timeout(action_module) do
case action_module do
MyApp.Actions.SlowOperation -> 60_000
MyApp.Actions.QuickCheck -> 5_000
_ -> Application.get_env(:jido_action, :default_timeout, @default_timeout)
end
end
def get_retries(action_module) do
case action_module do
MyApp.Actions.CriticalOperation -> 5
MyApp.Actions.BestEffort -> 0
_ -> Application.get_env(:jido_action, :default_max_retries, @default_retries)
end
end
endTesting Configuration
Test-Specific Settings
# test/support/test_config.ex
defmodule MyApp.TestConfig do
def setup_test_env do
# Override configuration for tests
Application.put_env(:jido_action, :default_timeout, 1_000)
Application.put_env(:jido_action, :default_max_retries, 0)
# Mock external services
Application.put_env(:my_app, :api_base_url, "http://localhost:4002")
end
end
# In test_helper.exs
MyApp.TestConfig.setup_test_env()Configuration in Tests
defmodule MyApp.Actions.ConfigurableTest do
use ExUnit.Case
setup do
# Save original config
original_timeout = Application.get_env(:jido_action, :default_timeout)
on_exit(fn ->
# Restore original config
Application.put_env(:jido_action, :default_timeout, original_timeout)
end)
%{original_timeout: original_timeout}
end
test "respects custom timeout configuration" do
# Set test-specific timeout
Application.put_env(:jido_action, :default_timeout, 100)
# Test action behavior with short timeout
assert {:error, %Jido.Action.Error.TimeoutError{}} =
Jido.Exec.run(MyApp.Actions.SlowOperation, %{}, %{})
end
endConfiguration Reference
Application Config Keys
| Key | Type | Default | Description |
|---|---|---|---|
:default_timeout | non-negative integer | 30000 | Default action timeout in milliseconds (invalid values warn + fallback) |
:default_max_retries | non-negative integer | 1 | Default number of retry attempts (invalid values warn + fallback) |
:default_backoff | non-negative integer | 250 | Initial backoff time in ms (exponential, invalid values warn + fallback) |
:observability | keyword list | [] | Runtime context propagation config (:context_propagators, :context_propagator_failure_mode) |
Action Compensation Config
Defined at compile time in use Jido.Action:
| Key | Type | Default | Description |
|---|---|---|---|
:enabled | boolean | false | Enable compensation on error |
:max_retries | integer | 1 | Compensation retry attempts |
:timeout | integer | 5000 | Compensation timeout in ms |
Execution Options
Passed to Jido.Exec.run/4:
| Option | Type | Default | Description |
|---|---|---|---|
:timeout | integer | 30000 | Action timeout in milliseconds |
:max_retries | integer | 1 | Number of retry attempts |
:backoff | integer | 250 | Initial backoff in ms |
:log_level | atom | config(:jido_action, :default_log_level) or :info | Jido execution log threshold override |
:telemetry | atom | :full | :full or :silent |
:context_propagators | list(module) | config(:jido_action, :observability)[:context_propagators] | Runtime context propagators captured before supervised execution |
:context_propagator_failure_mode | atom | :warn | :warn to skip failures, :strict to raise |
:jido | atom | nil | Instance name for multi-tenant isolation |
Best Practices
Configuration Organization
- Environment Separation: Different configs for dev/test/prod
- Validation: Validate required configuration at startup
- Defaults: The library provides sensible defaults
Performance
- Timeouts: Set appropriate timeouts for different environments
- Retries: Configure retries based on expected failure rates
- Backoff: Use exponential backoff to avoid thundering herd
Deployment
- Runtime Config: Use
config/runtime.exsfor environment variables - Task Supervisor: Ensure
Jido.Action.TaskSupervisoris in your supervision tree
Next Steps
→ Testing Guide - Testing configurations and environments
→ Error Handling Guide - Error handling patterns
→ Execution Engine Guide - Async execution lifecycle and guarantees
← Error Handling Guide | Next: Testing Guide →