This walkthrough shows the core AshJido flow:
- Define Ash resources.
- Expose selected actions through the
jidoDSL. - Execute generated
Jido.Actionmodules with runtime context. - Use generated actions directly in a
Jido.AI.Agent.
1. Define a Domain and Resources
defmodule MyApp.Blog do
use Ash.Domain
resources do
resource MyApp.Blog.Author
resource MyApp.Blog.Post
end
end
defmodule MyApp.Blog.Author do
use Ash.Resource,
domain: MyApp.Blog,
data_layer: AshPostgres.DataLayer
postgres do
table "authors"
repo MyApp.Repo
end
attributes do
uuid_primary_key :id
attribute :name, :string, allow_nil?: false
end
actions do
defaults [:read, :destroy]
create :create do
accept [:name]
end
end
enddefmodule MyApp.Blog.Post do
use Ash.Resource,
domain: MyApp.Blog,
extensions: [AshJido],
data_layer: AshPostgres.DataLayer
postgres do
table "posts"
repo MyApp.Repo
end
attributes do
uuid_primary_key :id
attribute :title, :string, allow_nil?: false
attribute :status, :atom, default: :draft
timestamps()
end
relationships do
belongs_to :author, MyApp.Blog.Author, allow_nil?: false, public?: true
end
actions do
defaults [:read, :destroy]
create :create do
accept [:title, :author_id]
end
update :publish do
accept []
change set_attribute(:status, :published)
end
end
jido do
action :create, name: "create_post"
action :read, name: "list_posts", load: [:author]
action :publish, name: "publish_post"
end
end2. Run Generated Actions
alias MyApp.Blog.{Author, Post}
# Create related data with Ash directly
author =
Author
|> Ash.Changeset.for_create(:create, %{name: "Ada"}, domain: MyApp.Blog)
|> Ash.create!(domain: MyApp.Blog)
# Execute generated Jido actions
{:ok, post} =
Post.Jido.Create.run(
%{title: "Ash + Jido", author_id: author.id},
%{domain: MyApp.Blog}
)
{:ok, posts} = Post.Jido.Read.run(%{}, %{domain: MyApp.Blog})
loaded = Enum.find(posts, &(&1[:id] == post[:id]))
# Static load from DSL is applied to read actions
loaded[:author][:name] # => "Ada"
{:ok, published} =
Post.Jido.Publish.run(
%{id: post[:id]},
%{domain: MyApp.Blog}
)
published[:status] # => :published3. Pass Ash Context Through Runtime Context
context[:domain] is optional when the resource has a static domain: configuration.
When present, it overrides the resource domain. Other Ash options are optional and
passed through when present:
context = %{
domain: MyApp.Blog,
actor: current_user,
tenant: "org_123",
scope: %{actor: current_user},
authorize?: true,
tracer: [MyApp.Tracer],
context: %{request_id: "req_123"},
timeout: 15_000
}
Post.Jido.Create.run(params, context)Notes:
- If
actoris omitted, Ash can still resolve actor fromscope. - If
actor: nilis explicitly set, it intentionally overrides actor from scope.
4. Rules to Remember
- Update and destroy generated actions require the resource primary key field or fields in params.
- Destroy generated actions also accept declared Ash destroy action arguments.
loadis static DSL configuration for read actions only.output_map?: true(default) returns maps instead of Ash structs.
5. Use Generated Resource Actions in a Jido.AI.Agent
This is the core integration demo: generated AshJido action modules can be used directly as agent tools.
defmodule MyApp.Blog.PostAgent do
use Jido.AI.Agent,
name: "post_agent",
model: :fast,
tools: [
MyApp.Blog.Post.Jido.Create,
MyApp.Blog.Post.Jido.Read,
MyApp.Blog.Post.Jido.Publish
],
tool_context: %{domain: MyApp.Blog},
system_prompt: "You manage blog posts. Use tools for data operations."
end{:ok, pid} = Jido.AgentServer.start(agent: MyApp.Blog.PostAgent)
{:ok, answer} =
MyApp.Blog.PostAgent.ask_sync(
pid,
"Create a post titled 'Hello' for author #{author_id} and then list posts.",
tool_context: %{
domain: MyApp.Blog,
actor: current_user,
tenant: "org_123"
}
)Important:
- The agent
tools:list should use generated module names (MyApp.Blog.Post.Jido.*). - Pass
domainintool_contextwhen overriding the resource's static domain or when the resource hasdomain: nil. - Add
actor,tenant, and other Ash options intool_contextfor policy and tenancy-aware calls.