AshJido

Hex.pmHex DocsCILicenseWebsiteEcosystemDiscord

Bridge Ash Framework resources with Jido agents. Generates Jido.Action modules from Ash actions at compile time.

What This Library Does

What It Does Not Do

Installation

mix igniter.install ash_jido

Or add manually to mix.exs:

def deps do
[
{:ash_jido, "~> 0.2.0"}
]
end

Quick Start

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

Generated modules:

{:ok, user} = MyApp.User.Jido.Register.run(
%{name: "John", email: "john@example.com"},
%{domain: MyApp.Accounts}
)

Query Parameters

Generated Jido read actions include optional query parameters for filtering, sorting, and pagination:

{:ok, users} = MyApp.User.Jido.Read.run(
%{
filter: %{status: %{in: ["active", "pending"]}},
sort: [name: :asc, created_at: :desc],
limit: 20,
offset: 40
},
%{domain: MyApp.Accounts}
)

Available Parameters:

Security: Query parameters use Ash's safe filter_input/sort_input variants, which only allow filtering and sorting on public attributes and honor field policies. Runtime load is disabled unless explicitly allowlisted.

Configuration:

jido do
action :read # query params enabled by default
action :read, query_params?: false # opt out
action :read, allowed_loads: [:profile] # opt into runtime load
action :read, max_page_size: 100 # clamp limit to max
all_actions read_query_params?: true # default for all read actions
all_actions read_allowed_loads: [:profile]
all_actions read_max_page_size: 100 # max page size for all reads
end

Context Requirements

AshJido resolves the Ash domain in this order:

  1. context[:domain]
  2. the resource's static domain: configuration
  3. ArgumentError if neither is available
context = %{
domain: MyApp.Accounts, # required only when the resource has no static domain or you need an override
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)

DSL Options

Individual Actions

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
end

Default generated module names are based on the Ash action name, e.g. action :create generates Resource.Jido.Create even when name: is set. Use module_name: to intentionally choose a different generated module, and provide explicit module_name: values when exposing the same Ash action more than once.

Bulk Exposure

all_actions follows Ash's public API boundary by default: it expands only actions marked public?: true. Explicit action :name entries remain the way to expose a specific private action deliberately, and include_private?: true is available for trusted/internal tool catalogs. Generated schemas also follow Ash's public input boundary by default and omit accepted attributes or action arguments marked public?: false.

jido do
all_actions
all_actions except: [:destroy, :internal]
all_actions only: [:create, :read]
all_actions include_private?: true
all_actions category: "ash.resource"
all_actions tags: ["public-api"]
all_actions vsn: "1.0.0"
all_actions only: [:read], read_load: [:profile]
end

Reactive Signals

The canonical Ash integration path is AshJido.Notifier: add it to the resource and configure publications in jido when you want resource lifecycle events published to a Jido signal bus:

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
end

Generated actions can also emit signals with emit_signals?: true; this is best when a tool run needs runtime dispatch overrides or telemetry signal counters. Both paths build payloads through AshJido.SignalFactory, so signal type/source/subject and signal.extensions["jido_metadata"] are consistent. Generated-action signals include primary key data by default; use signal_include to explicitly widen signal.data. Notifier publications use the configured include mode.

Action Options

OptionTypeDefaultDescription
namestringauto-generatedCustom Jido action name
module_nameatomResource.Jido.ActionCustom module name
descriptionstringfrom Ash actionAction description
categorystringnilCategory for discovery/tool organization
tagslist(string)[]Tags for categorization
vsnstringnilOptional semantic version metadata
output_map?booleantrueConvert structs to public-field maps
include_private?booleanfalseInclude inputs with public?: false in generated schemas for trusted/internal tools
loadtermnilStatic Ash.Query.load/2 for read actions
allowed_loadstermnilAllowlisted runtime load entries for read actions
query_params?booleantrueEnable query parameters (filter, sort, limit, offset, and allowlisted load) for read actions
max_page_sizepos_integernilMaximum limit value for read actions (clamps the limit parameter)
emit_signals?booleanfalseEmit Jido signals from Ash notifications (create/update/destroy)
signal_dispatchtermnilDefault signal dispatch config (can be overridden via context)
signal_typestringderivedOverride emitted signal type
signal_sourcestringderivedOverride emitted signal source
signal_includeatom/list(atom):pkey_onlyData inclusion mode for generated-action signals
telemetry?booleanfalseEmit Jido-namespaced telemetry for generated action execution

all_actions Options

OptionTypeDefaultDescription
onlylist(atom)all public actionsLimit generated actions
exceptlist(atom)[]Exclude actions
include_private?booleanfalseInclude Ash actions and inputs with public?: false for trusted/internal tool catalogs
categorystringash.<action_type>Category added to generated actions
tagslist(string)[]Tags added to all generated actions
vsnstringnilOptional semantic version metadata for generated actions
read_loadtermnilStatic Ash.Query.load/2 for generated read actions
read_query_params?booleantrueEnable query parameters for generated read actions
read_max_page_sizepos_integernilMaximum limit value for generated read actions
emit_signals?booleanfalseEmit Jido signals from generated create/update/destroy actions
signal_dispatchtermnilDefault signal dispatch config for generated actions
signal_typestringderivedOverride emitted signal type
signal_sourcestringderivedOverride emitted signal source
telemetry?booleanfalseEmit Jido-namespaced telemetry for generated action execution

Telemetry

Telemetry is opt-in per action (or via all_actions):

jido do
action :create, telemetry?: true
end

When enabled, generated actions emit:

Metadata includes resource/action/module identity, domain/tenant, actor presence, signaling/read-load flags, and signal delivery counters.

Tool Export Helpers

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)

Sensor Bridge

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)

Default Naming

Action TypePatternExample
:createcreate_<resource>create_user
:read (:read)list_<resources>list_users
:read (:by_id)get_<resource>_by_idget_user_by_id
:updateupdate_<resource>update_user
:destroydelete_<resource>delete_user

Generated schemas are the public tool surface for discovery and validation. Ash authorization, policies, and runtime validation remain the source of truth when an action executes.

Troubleshooting

AshJido: :domain must be provided in context

Update actions require primary key parameter(s): ...

Action X not found in resource

For a full error contract and telemetry interpretation, see Walkthrough: Failure Semantics.

Compatibility

Documentation

Start Here

Walkthroughs: Core

Walkthroughs: Operations

Walkthroughs: Agent Integration

Reference

Real Consumer Integration App

A full AshPostgres-backed consumer harness lives at ash_jido_consumer/.

It exercises real integration scenarios end-to-end:

License

Apache-2.0