Bridge Ash Framework resources with Jido agents. Generates Jido.Action modules from Ash actions at compile time.
- Adds a
jidoDSL section to Ash resources - Generates
Jido.Actionmodules at compile time for selected actions - Maps Ash argument types to NimbleOptions schemas
- Runs actions via Ash with provided
domain,actor, andtenant - Converts Ash errors to
Jido.Action.Error(Splode-based) errors - Publishes
Jido.Signalevents from Ash action notifications
- Auto-discover domains or resources (domain is explicit and required)
- Add pagination or query-layer magic
- Bypass Ash authorization, policies, or data layers
mix igniter.install ash_jidoOr add manually to mix.exs:
def deps do
[
{:ash_jido, "~> 0.1.0"}
]
enddefmodule MyApp.User do
use Ash.Resource,
domain: MyApp.Accounts,
extensions: [AshJido]
actions do
create :register
read :by_id
update :profile
end
jido do
action :register, name: "create_user"
action :by_id, name: "get_user"
action :profile
end
endGenerated modules:
{:ok, user} = MyApp.User.Jido.Register.run(
%{name: "John", email: "john@example.com"},
%{domain: MyApp.Accounts}
)The domain is required in context. An ArgumentError is raised if missing.
context = %{
domain: MyApp.Accounts, # REQUIRED
actor: current_user, # optional: for authorization
tenant: "org_123", # optional: for multi-tenancy
authorize?: true, # optional: explicit authorization mode
tracer: [MyApp.Tracer], # optional: Ash tracer modules
scope: MyApp.Scope.for(user), # optional: Ash scope
context: %{request_id: "1"}, # optional: Ash action context
timeout: 15_000, # optional: Ash operation timeout
signal_dispatch: {:pid, target: self()} # optional: override signal dispatch
}
MyApp.User.Jido.Create.run(params, context)jido do
action :create
action :read, name: "list_users", description: "List all users", load: [:profile]
action :update, category: "ash.update", tags: ["user-management"], vsn: "1.0.0"
action :special, output_map?: false # preserve Ash structs
endjido do
all_actions
all_actions except: [:destroy, :internal]
all_actions only: [:create, :read]
all_actions category: "ash.resource"
all_actions tags: ["public-api"]
all_actions vsn: "1.0.0"
all_actions only: [:read], read_load: [:profile]
endAdd AshJido.Notifier to the resource and configure publications in jido:
defmodule MyApp.Post do
use Ash.Resource,
domain: MyApp.Blog,
extensions: [AshJido],
notifiers: [AshJido.Notifier]
jido do
signal_bus MyApp.SignalBus
signal_prefix "blog"
publish :create, "blog.post.created",
include: [:id, :title],
metadata: [:actor, :tenant]
publish_all :update, include: :changes_only
end
endPublished signals include Ash metadata in signal.extensions["jido_metadata"].
| Option | Type | Default | Description |
|---|---|---|---|
name |
string | auto-generated | Custom Jido action name |
module_name |
atom | Resource.Jido.Action |
Custom module name |
description |
string | from Ash action | Action description |
category |
string | nil |
Category for discovery/tool organization |
tags |
list(string) | [] |
Tags for categorization |
vsn |
string | nil |
Optional semantic version metadata |
output_map? |
boolean | true |
Convert structs to maps |
load |
term | nil |
Static Ash.Query.load/2 for read actions |
emit_signals? |
boolean | false |
Emit Jido signals from Ash notifications (create/update/destroy) |
signal_dispatch |
term | nil |
Default signal dispatch config (can be overridden via context) |
signal_type |
string | derived | Override emitted signal type |
signal_source |
string | derived | Override emitted signal source |
telemetry? |
boolean | false |
Emit Jido-namespaced telemetry for generated action execution |
| Option | Type | Default | Description |
|---|---|---|---|
only |
list(atom) | all actions | Limit generated actions |
except |
list(atom) | [] |
Exclude actions |
category |
string | ash.<action_type> |
Category added to generated actions |
tags |
list(string) | [] |
Tags added to all generated actions |
vsn |
string | nil |
Optional semantic version metadata for generated actions |
read_load |
term | nil |
Static Ash.Query.load/2 for generated read actions |
emit_signals? |
boolean | false |
Emit Jido signals from generated create/update/destroy actions |
signal_dispatch |
term | nil |
Default signal dispatch config for generated actions |
signal_type |
string | derived | Override emitted signal type |
signal_source |
string | derived | Override emitted signal source |
telemetry? |
boolean | false |
Emit Jido-namespaced telemetry for generated action execution |
Telemetry is opt-in per action (or via all_actions):
jido do
action :create, telemetry?: true
endWhen enabled, generated actions emit:
[:jido, :action, :ash_jido, :start][:jido, :action, :ash_jido, :stop][:jido, :action, :ash_jido, :exception]
Metadata includes resource/action/module identity, domain/tenant, actor presence, signaling/read-load flags, and signal delivery counters.
Use AshJido.Tools to list generated actions and export LLM-friendly tool maps:
# Generated action modules for a resource
AshJido.Tools.actions(MyApp.Accounts.User)
# Generated action modules for all resources in a domain
AshJido.Tools.actions(MyApp.Accounts)
# Tool payloads (name/description/schema/function) for agent/LLM integrations
AshJido.Tools.tools(MyApp.Accounts.User)AshJido.SensorDispatchBridge keeps the dispatch-first signal model while adding optional sensor runtime forwarding:
# Accepts %Jido.Signal{}, {:signal, %Jido.Signal{}}, and {:signal, {:ok, %Jido.Signal{}}}
:ok = AshJido.SensorDispatchBridge.forward(signal_message, sensor_runtime)
# Batch forwarding with per-message errors
%{forwarded: count, errors: errors} =
AshJido.SensorDispatchBridge.forward_many(messages, sensor_runtime)
# Ignore non-signal mailbox noise safely
:ok | :ignored | {:error, :runtime_unavailable} =
AshJido.SensorDispatchBridge.forward_or_ignore(message, sensor_runtime)| Action Type | Pattern | Example |
|---|---|---|
:create |
create_<resource> |
create_user |
:read (:read) |
list_<resources> |
list_users |
:read (:by_id) |
get_<resource>_by_id |
get_user_by_id |
:update |
update_<resource> |
update_user |
:destroy |
delete_<resource> |
delete_user |
AshJido: :domain must be provided in context
- Pass
%{domain: MyApp.Domain}as the second argument torun/2
Update actions require an 'id' parameter
- Include
idin params for:updateand:destroyactions
Action X not found in resource
- Check
jido action :...entries match defined Ash actions
For a full error contract and telemetry interpretation, see Walkthrough: Failure Semantics.
- Elixir: ~> 1.18
- OTP: 27 or 28
- Ash: ~> 3.12
- Jido: ~> 2.0
Start Here
- Getting Started — comprehensive usage
- Interactive Demo — try in Livebook
Walkthroughs: Core
- Resource to Action — define resources and run generated actions
- Policy, Scope, and Authorization — policy-aware actor, scope, tenant patterns
- AshPostgres Consumer Harness — real DB-backed integration scenarios
Walkthroughs: Operations
- Signals, Telemetry, and Sensors — notification signals and observability
- Failure Semantics — deterministic errors and telemetry outcomes
Walkthroughs: Agent Integration
- Tools and AI Integration — action metadata and tool export
- Agent Tool Wiring — domain tool catalogs and safe execution wrappers
Reference
- Usage Rules — AI/LLM patterns
A full AshPostgres-backed consumer harness lives at ash_jido_consumer/.
It exercises real integration scenarios end-to-end:
- context passthrough + policy behavior
- relationship-aware reads (
load) - notifications to signals (
emit_signals?) - Jido telemetry emission (
telemetry?)
MIT