### Run AshLua Installer Source: https://github.com/ash-project/ash_lua/blob/main/README.md Run the mix igniter.install command to install ash_lua. ```shell mix igniter.install ash_lua ``` -------------------------------- ### Discovering API Surface with AshLua.Docs Source: https://github.com/ash-project/ash_lua/blob/main/documentation/tutorials/getting-started-with-ash-lua.md Shows how to use AshLua.Docs to list callables, get documentation for specific callables, list types, get documentation for specific types, and generate a full documentation page. Specify the otp_app for context. ```elixir AshLua.Docs.list_callables(otp_app: :my_app) # => ["accounts.user.create", "accounts.user.read", ...] ``` ```elixir {:ok, md} = AshLua.Docs.callable_doc([otp_app: :my_app], "accounts.user.create") ``` ```elixir AshLua.Docs.list_types(otp_app: :my_app) # => ["accounts.user", ...] ``` ```elixir {:ok, md} = AshLua.Docs.type_doc([otp_app: :my_app], "accounts.user") ``` ```elixir # Or get one rendered page covering everything: AshLua.Docs.full_doc(otp_app: :my_app) ``` -------------------------------- ### LLM Workflow: Querying Overdue Todos with AshLua Source: https://github.com/ash-project/ash_lua/blob/main/documentation/topics/example-use-cases.md This example shows an LLM interacting with AshLua tools to discover API surface and execute a composed Lua script for a complex query. The LLM first uses `ash_lua_docs` to find relevant actions and types, then uses `ash_lua_eval` to execute a Lua script that counts overdue high-priority todos and retrieves a sample of the oldest ones. All actions are executed within the user's context, ensuring security and authorization. ```text LLM → ash_lua_docs({ search = "overdue todo" }) ← - `work.todo.read` (operation) — list todos with filtering - `work.todo` (record type) — record type ... LLM → ash_lua_docs({ name = "work.todo.read" }) ← # `work.todo.read` ... (the markdown ash_lua generates today) LLM → ash_lua_docs({ name = "work.todo" }) ← # Record type `work.todo` — fields include `priority`, `due_date`, `created_at`, ... LLM → ash_lua_eval({ script = """ local overdue = assert(work.todo.read({ filter = { priority = "high", completed = false, due_date = { less_than = today() } }, operation = "count" })) local sample = assert(work.todo.read({ filter = { priority = "high", completed = false, due_date = { less_than = today() } }, sort = "created_at", limit = 5, fields = { "created_at" } })) return overdue, sample """ }) ← { result = { 12, [<5 todo records>] }, error = nil } ``` -------------------------------- ### Lua Script: Count Overdue Items Source: https://github.com/ash-project/ash_lua/blob/main/documentation/topics/example-use-cases.md A Lua script example that counts overdue items assigned to the current user. It uses the `work.todo.read` operation with specific filters and requests a 'count' operation. ```lua return assert(work.todo.read({ filter = { assigned_to_id = my_id, completed = false, due_date = { less_than = today() } }, operation = "count" })) ``` -------------------------------- ### Discovering the API Surface with AshLua.Docs Source: https://github.com/ash-project/ash_lua/blob/main/documentation/tutorials/getting-started-with-ash-lua.md This section explains how to use `AshLua.Docs` to generate markdown documentation for your API, which can be used for reading or for tools like MCP's `search_docs` and `get_docs`. ```APIDOC ## Discovering the API surface `AshLua.Docs` produces per-operation and per-record-type markdown straight from your domains — useful both as a reading aid and as a feed for an MCP `search_docs` / `get_docs` tool (e.g. `ash_ai`). ```elixir AshLua.Docs.list_callables(otp_app: :my_app) # => ["accounts.user.create", "accounts.user.read", ...] {:ok, md} = AshLua.Docs.callable_doc([otp_app: :my_app], "accounts.user.create") AshLua.Docs.list_types(otp_app: :my_app) # => ["accounts.user", ...] {:ok, md} = AshLua.Docs.type_doc([otp_app: :my_app], "accounts.user") # Or get one rendered page covering everything: AshLua.Docs.full_doc(otp_app: :my_app) ``` The rendered pages deliberately avoid implementation vocabulary — they describe operations as `get` / `list` / `create` / `update` / `delete` / `call`, and treat stored, computed, and summary fields uniformly as "fields". ``` -------------------------------- ### Expose Domain and Resource with AshLua Extensions Source: https://github.com/ash-project/ash_lua/blob/main/documentation/tutorials/getting-started-with-ash-lua.md Include `AshLua.Domain` and `AshLua.Resource` extensions in your Ash domain and resource definitions to make them available to Lua scripts. ```elixir defmodule MyApp.Accounts do use Ash.Domain, otp_app: :my_app, extensions: [AshLua.Domain] resources do resource MyApp.Accounts.User end end defmodule MyApp.Accounts.User do use Ash.Resource, domain: MyApp.Accounts, extensions: [AshLua.Resource] # ... attributes / actions ... end ``` -------------------------------- ### Performing CRUD Operations in Ash Lua Source: https://github.com/ash-project/ash_lua/blob/main/documentation/tutorials/getting-started-with-ash-lua.md Demonstrates how to create, update, and delete records using the posts.post module. Ensure the input fields and primary keys are correctly provided for each operation. ```lua local post = assert(posts.post.create({ input = { title = "Hello", body = "World" }, fields = { "id", "title" } })) local updated = assert(posts.post.update({ input = { id = post.id, title = "Hello again" }, fields = { "title" } })) assert(posts.post.destroy({ input = { id = post.id } })) ``` -------------------------------- ### LLM Interaction with Ash AI Tools Source: https://github.com/ash-project/ash_lua/blob/main/documentation/how_to/integrate-with-ash-ai.md Demonstrates a typical interaction flow where the LLM uses `ash_lua_docs` to inspect available actions and then `ash_lua_eval` to execute a Lua script for data retrieval and calculation. ```text LLM → ash_lua_docs({ search = "overdue todo" }) ← # Search results for `overdue todo` - `work.todo.read` (operation) — list todos with filtering - `work.todo` (record type) — record type ... LLM → ash_lua_docs({ name = "work.todo.read" }) ← # `work.todo.read` … (the per-operation page) LLM → ash_lua_docs({ name = "work.todo" }) ← # Record type `work.todo` … (fields, filterable predicates, …) LLM → ash_lua_eval({ script = """ local overdue = assert(work.todo.read({ filter = { priority = \"high\", completed = false, due_date = { less_than = today() } }, operation = \"count\" })) local sample = assert(work.todo.read({ filter = { priority = \"high\", completed = false, due_date = { less_than = today() } }, sort = \"created_at\", limit = 5, fields = { \"created_at\" } })) return overdue, sample """ }) ← { 12, [<5 records>] } ``` -------------------------------- ### Define Agent Surface with AshLua.EvalActions Source: https://github.com/ash-project/ash_lua/blob/main/documentation/how_to/integrate-with-ash-ai.md Define a resource with the `AshLua.EvalActions` extension to expose specific Ash actions for a given resource. This sets up the agent surface for LLM interaction. ```elixir defmodule MyApp.Agents.MCPActions do use Ash.Resource, domain: MyApp.Agents, extensions: [AshLua.EvalActions] eval_actions do resource MyApp.Posts.Post, actions: [:read, :get_statistics] resource MyApp.Posts.Comment, actions: [:read] resource MyApp.Accounts.User, actions: [:read, :create] end end ``` -------------------------------- ### Define Ash AI Tools in Domain Source: https://github.com/ash-project/ash_lua/blob/main/documentation/how_to/integrate-with-ash-ai.md Expose Ash actions as MCP tools within your domain's `tools do ... end` block. The LLM will see these under the declared tool names. ```elixir defmodule MyApp.Agents do use Ash.Domain, otp_app: :my_app, extensions: [AshAi] resources do resource MyApp.Agents.MCPActions end tools do tool :ash_lua_docs, MyApp.Agents.MCPActions, :docs tool :ash_lua_eval, MyApp.Agents.MCPActions, :eval end end ``` -------------------------------- ### Configure Resource Exposure to Lua Source: https://github.com/ash-project/ash_lua/blob/main/documentation/dsls/DSL-AshLua.Resource.md Use this block to configure the name under which the resource will be exposed in Lua and whether it should be exposed at all. Defaults to true. ```lua lua do name "todo" expose? true end ``` -------------------------------- ### Add AshLua Dependency Source: https://github.com/ash-project/ash_lua/blob/main/documentation/tutorials/getting-started-with-ash-lua.md Add the AshLua dependency to your project's `deps.ex` file. ```elixir def deps do [ {:ash_lua, "~> 0.1.0"} ] end ``` -------------------------------- ### Query Lists with Filtering and Sorting in Ash Lua Source: https://github.com/ash-project/ash_lua/blob/main/documentation/tutorials/getting-started-with-ash-lua.md List-style read operations accept reserved keys like `filter`, `sort`, `limit`, and `offset` to narrow down and control the returned results. Use `page` for cursor-based pagination. ```lua local results = assert(posts.post.read({ filter = { published = true }, -- narrow the result set sort = "-created_at", -- `-` prefix for descending limit = 10, offset = 20, fields = { "title", { author = { "name" } } }, })) ``` -------------------------------- ### Configure Eval Actions DSL Source: https://github.com/ash-project/ash_lua/blob/main/documentation/dsls/DSL-AshLua.EvalActions.md Sets up the scope of resources and actions available to the synthesized :eval and :docs actions. This DSL block defines the Lua surface by listing specific resources and the actions that can be invoked on them. ```elixir eval_actions do resource MyApp.Posts.Post, actions: [:read, :get_statistics] resource MyApp.Posts.Comment, actions: [:read] end ``` -------------------------------- ### Evaluate a Lua Script with AshLua Source: https://github.com/ash-project/ash_lua/blob/main/documentation/tutorials/getting-started-with-ash-lua.md Use `AshLua.eval!/2` to execute a Lua script. It accepts an OTP app, and optionally an actor, tenant, or context. The script can interact with Ash domains. ```elixir { [user_id], _lua } = AshLua.eval!( """ local user, err = accounts.user.create({ input = { name = "Zach", email = "z@example.com" }, fields = { "id" } }) assert(err == nil) return user.id """, otp_app: :my_app, actor: current_user ) ``` -------------------------------- ### Configure Custom Action Names for AshLua.EvalActions Source: https://github.com/ash-project/ash_lua/blob/main/documentation/how_to/integrate-with-ash-ai.md Customize the default action names (`:eval` and `:docs`) for the `AshLua.EvalActions` extension. This is useful for avoiding naming conflicts or when multiple agent surfaces are needed. ```elixir eval_actions do eval_action_name :run_lua docs_action_name :describe_lua resource MyApp.Posts.Post, actions: [:read] end ``` -------------------------------- ### Ash Lua Mutations Source: https://github.com/ash-project/ash_lua/blob/main/documentation/tutorials/getting-started-with-ash-lua.md Demonstrates how to perform create, update, and delete mutations using the Ash Lua API. Mutations for create, update, and delete operations function similarly to read operations, with action fields and arguments nested under the 'input' key. Update and delete operations also require the primary key within the 'input'. ```APIDOC ## Mutations Create / update / delete behave like read, except action fields and arguments go under the `input` key. Update and delete take the primary key there too: ```lua local post = assert(posts.post.create({ input = { title = "Hello", body = "World" }, fields = { "id", "title" } })) local updated = assert(posts.post.update({ input = { id = post.id, title = "Hello again" }, fields = { "title" } })) assert(posts.post.destroy({ input = { id = post.id } })) ``` Generic actions (defined with `action :name, type do ... end`) take their declared arguments and return whatever the action returns — a scalar, a map, a list, whatever. `fields` is honored when the return type is a record or a structured value. ``` -------------------------------- ### Configure Domain Name for Lua Source: https://github.com/ash-project/ash_lua/blob/main/documentation/dsls/DSL-AshLua.Domain.md Set the Lua table name under which the domain's resources will be exposed. Defaults to the snake_case of the domain module's last segment. ```elixir lua do name "accounts" end ``` -------------------------------- ### Define Eval Actions for MCPActions Source: https://github.com/ash-project/ash_lua/blob/main/documentation/dsls/DSL-AshLua.EvalActions.md Configures the Lua surface for synthesized :eval and :docs actions by specifying which resources and actions are accessible. This ensures that the agent operates within a defined scope and respects authorization. ```elixir defmodule MyApp.Agents.MCPActions do use Ash.Resource, extensions: [AshLua.EvalActions] eval_actions do resource MyApp.Posts.Post, actions: [:read, :get_statistics] resource MyApp.Posts.Comment, actions: [:read] end end ``` -------------------------------- ### Define Agent Scope with Multiple Actions Source: https://github.com/ash-project/ash_lua/blob/main/documentation/how_to/integrate-with-ash-ai.md Configure an agent with broader permissions, including read, create, and reassign actions for specific resources. This allows agents to manage resources more comprehensively. ```elixir defmodule MyApp.Agents.SupportMCP do use Ash.Resource, domain: MyApp.Agents, extensions: [AshLua.EvalActions] eval_actions do resource MyApp.Support.Ticket, actions: [:read, :create, :reassign] resource MyApp.Accounts.User, actions: [:read] end end ``` -------------------------------- ### Define a Tile Resource in Elixir Source: https://github.com/ash-project/ash_lua/blob/main/documentation/topics/example-use-cases.md Defines a resource for persisting dashboard tile configurations, including name, script, and owner. This serves as the data model for user-defined scripts. ```elixir defmodule MyApp.Dashboards.Tile do use Ash.Resource, domain: MyApp.Dashboards attributes do uuid_primary_key :id attribute :name, :string, allow_nil?: false, public?: true attribute :script, :string, allow_nil?: false, public?: true end # ... owner relationship, actions, policies ... end ``` -------------------------------- ### Add ash_lua to Dependencies Source: https://github.com/ash-project/ash_lua/blob/main/README.md Add ash_lua to your project's dependencies in the mix.exs file. ```elixir def deps do [ {:ash_lua, ">~ 0.1.6"} ] end ``` -------------------------------- ### Lua Script: Top 5 Best-Selling Products Source: https://github.com/ash-project/ash_lua/blob/main/documentation/topics/example-use-cases.md A Lua script to find the top 5 best-selling products this week. It filters products sold within the last 7 days, sorts them by units sold in descending order, limits the result to 5, and specifies the 'name' and 'units_sold' fields. ```lua return assert(catalog.product.read({ filter = { sold_at = { greater_than = ago(7, "day") } }, sort = "-units_sold", limit = 5, fields = { "name", "units_sold" } })) ``` -------------------------------- ### Render a Tile with AshLua Eval in Elixir Source: https://github.com/ash-project/ash_lua/blob/main/documentation/topics/example-use-cases.md Renders a dashboard tile by evaluating its Lua script using `AshLua.eval!`. It sets the actor and tenant context for the script execution and handles potential Lua runtime or compiler exceptions. ```elixir defmodule MyAppWeb.TileLive do use MyAppWeb, :live_view def render_tile(tile, current_user) do case AshLua.eval!(tile.script, otp_app: :my_app, actor: current_user, tenant: current_user.org_id ) do {[value], _lua} -> {:ok, value} end rescue e in [Lua.RuntimeException, Lua.CompilerException] -> {:error, Exception.message(e)} end end ``` -------------------------------- ### Define Lua Namespace for Actions Source: https://github.com/ash-project/ash_lua/blob/main/documentation/dsls/DSL-AshLua.Domain.md Defines a public Lua namespace for exposing Ash actions. Dotted strings create nested Lua tables. ```elixir namespace "pages" do action :list, MyApp.StorefrontPage, :list_for_storefront end ``` -------------------------------- ### Selecting Fields for Records Source: https://github.com/ash-project/ash_lua/blob/main/documentation/tutorials/getting-started-with-ash-lua.md Operations that return records can be customized to return only specific fields by using the `fields` argument. This allows for efficient data retrieval by specifying stored, computed, summary, or linked fields. ```APIDOC ## accounts.user.read ### Description Reads a single user record, allowing for custom field selection. ### Method `read` (within `accounts.user`) ### Parameters #### Request Body - **fields** (table) - Optional - A table specifying which fields to return. Can include stored fields, computed fields, summary fields, and selections for linked records or structured values. ### Request Example ```lua local user = assert(accounts.user.read({ fields = { "id", "name", "email" } })) ``` ### Response #### Success Response (200) Returns a table representing the user record with the specified fields. #### Response Example ```json { "id": "user-id", "name": "John Doe", "email": "john.doe@example.com" } ``` ### Field Selection Details `fields` accepts a tree structure for complex selections: ```lua fields = { "id", "title", -- stored fields "title_upper", -- computed fields "comment_count", -- summary fields { author = { "id", "name" } }, -- linked one-record { comments = { "id", "body" } }, -- linked many-records { title_prefixed = { args = { prefix = ">> " } } }, -- computed with input { metadata = { "priority", "category" } }, -- structured-value sub-selection { coordinates = { "latitude" } }, -- tuple sub-selection { content = { text = { "body" } } }, -- one-of (union) member sub-selection } ``` Passing an empty sub-selection like `{ author = {} }` defaults to the primary key only. Unknown fields or arguments will result in an `(nil, err)` with `code = "invalid_fields"`. ``` -------------------------------- ### Define Custom Ash AI Tool Names Source: https://github.com/ash-project/ash_lua/blob/main/documentation/how_to/integrate-with-ash-ai.md If you've customized synthesized action names using `eval_action_name` or `docs_action_name`, pass these custom names to the `tool` declaration. ```elixir tools do tool :read_lua_surface, MyApp.Agents.MCPActions, :describe_lua tool :run_lua, MyApp.Agents.MCPActions, :run_lua end ``` -------------------------------- ### Summarize Result Sets with Operations in Ash Lua Source: https://github.com/ash-project/ash_lua/blob/main/documentation/tutorials/getting-started-with-ash-lua.md Use the `operation` key to summarize a result set without retrieving individual records. Supported operations include `count`, `exists`, and aggregate functions like `sum`, `avg`, `min`, `max` on specific fields. ```lua local total = assert(posts.post.read({ operation = "count" })) ``` ```lua local any = assert(posts.post.read({ operation = "exists" })) ``` ```lua local avg_rating = assert(posts.comment.read({ operation = { "avg", "rating" } })) ``` ```lua local high_sum = assert(posts.comment.read({ filter = { rating = { greater_than_or_equal = 5 } }, operation = { "sum", "rating" } })) ``` -------------------------------- ### Expose Resource Actions to Lua Surface Source: https://github.com/ash-project/ash_lua/blob/main/documentation/dsls/DSL-AshLua.EvalActions.md Exposes a specific set of actions on a given resource to the Lua surface. This is used within the `eval_actions` block to control agent access. ```elixir resource MyApp.Posts.Post, actions: [:read, :create] ``` -------------------------------- ### Lua API: Handling Results and Errors Source: https://github.com/ash-project/ash_lua/blob/main/documentation/tutorials/getting-started-with-ash-lua.md Lua operations return a result and an error. Check the error value to handle failures, or use `assert` to raise errors directly. ```lua local user, err = accounts.user.create({ input = { name = "Zach" } }) if err then -- err is a table: { message = "...", errors = { { code = "...", fields = {...}, ... }, ... } } print("create failed:", err.message) else print("created user:", user.id) end ``` ```lua local user = assert(accounts.user.create({ input = { name = "Zach" } })) ``` -------------------------------- ### Customize Lua Names for Domain and Resource Source: https://github.com/ash-project/ash_lua/blob/main/documentation/tutorials/getting-started-with-ash-lua.md Override the default Lua table names for your domain and resource using the `lua do ... end` block. This allows you to control how they are accessed from Lua scripts. ```elixir defmodule MyApp.Accounts do use Ash.Domain, otp_app: :my_app, extensions: [AshLua.Domain] lua do name "accounts" end # ... end defmodule MyApp.Accounts.User do use Ash.Resource, domain: MyApp.Accounts, extensions: [AshLua.Resource] lua do name "user" end # ... end ``` -------------------------------- ### Define Nested Lua Namespace for Actions Source: https://github.com/ash-project/ash_lua/blob/main/documentation/dsls/DSL-AshLua.Domain.md Defines a public Lua namespace with nested tables for exposing Ash actions. Dotted strings are split into nested Lua tables. ```elixir namespace "storefronts.pages" do action :list, MyApp.StorefrontPage, :list_for_storefront end ``` -------------------------------- ### Select Specific Fields in Ash Lua Source: https://github.com/ash-project/ash_lua/blob/main/documentation/tutorials/getting-started-with-ash-lua.md Pass a `fields` selection to opt into more than just the primary key when reading records. The `fields` argument accepts a tree structure for complex selections. ```lua local user = assert(accounts.user.read({ fields = { "id", "name", "email" } })) ``` ```lua fields = { "id", "title", -- stored fields "title_upper", -- computed fields "comment_count", -- summary fields { author = { "id", "name" } }, -- linked one-record { comments = { "id", "body" } }, -- linked many-records { title_prefixed = { args = { prefix = ">> " } } }, -- computed with input { metadata = { "priority", "category" } }, -- structured-value sub-selection { coordinates = { "latitude" } }, -- tuple sub-selection { content = { text = { "body" } } }, -- one-of (union) member sub-selection } ``` -------------------------------- ### Lua Script: Average Rating of Last 10 Reviews Source: https://github.com/ash-project/ash_lua/blob/main/documentation/topics/example-use-cases.md This Lua script calculates the average rating of the user's last 10 reviews. It filters reviews by the current user, sorts them by creation date, limits to 10, and applies an average aggregation to the 'rating' field. ```lua return assert(reviews.review.read({ filter = { reviewer_id = my_id }, sort = "-created_at", limit = 10, operation = { "avg", "rating" } })) ``` -------------------------------- ### Return structure for :eval action Source: https://github.com/ash-project/ash_lua/blob/main/documentation/how_to/integrate-with-ash-ai.md The :eval action returns a map containing the result, error, and print output from a Lua script. 'result' holds the encoded Lua value on success, 'error' holds details on failure, and 'print_output' collects printed lines. ```elixir %{ result: , error: <%{ class, errors: [...] } or nil>, print_output: [, ...] } ``` -------------------------------- ### Querying Lists of Records Source: https://github.com/ash-project/ash_lua/blob/main/documentation/tutorials/getting-started-with-ash-lua.md List-style read operations support filtering, sorting, limiting results, and pagination. Operations can also be used to summarize result sets without retrieving individual records. ```APIDOC ## posts.post.read (List Querying) ### Description Reads a list of post records, supporting filtering, sorting, limiting, and pagination. Can also perform aggregate operations. ### Method `read` (within `posts.post`) ### Parameters #### Request Body - **filter** (table) - Optional - A table to narrow down the result set based on specific criteria. - **sort** (string) - Optional - A string specifying the sort order. Prefix with `-` for descending order (e.g., `"-created_at"`). - **limit** (number) - Optional - The maximum number of records to return. - **offset** (number) - Optional - The number of records to skip from the beginning of the result set (used with `limit`). - **page** (table) - Optional - A table for cursor-based pagination. Accepts `limit` (number) and `after` (string cursor) or `before` (string cursor). - **fields** (table) - Optional - A table specifying which fields to return for each record in the list (see `accounts.user.read` for details). - **operation** (string | table) - Optional - Specifies an aggregate operation to perform on the result set instead of returning records. Supported values: `"count"`, `"exists"`, or a table like `{ "sum" | "avg" | "min" | "max" | "count", "" }`. ### Request Example (Filtering, Sorting, Limiting) ```lua local results = assert(posts.post.read({ filter = { published = true }, sort = "-created_at", limit = 10, offset = 20, fields = { "title", { author = { "name" } } }, })) ``` ### Request Example (Cursor Pagination) ```lua local results = assert(posts.post.read({ page = { limit = 10, after = "cursor-string" }, })) ``` ### Request Example (Aggregate Operations) ```lua -- Count all posts local total = assert(posts.post.read({ operation = "count" })) -- Check if any post exists local any = assert(posts.post.read({ operation = "exists" })) -- Calculate average rating of comments local avg_rating = assert(posts.comment.read({ operation = { "avg", "rating" } })) -- Calculate sum of ratings for comments with rating >= 5 local high_sum = assert(posts.comment.read({ filter = { rating = { greater_than_or_equal = 5 } }, operation = { "sum", "rating" } })) ``` ### Response #### Success Response (200) - **results** (table) - A list of records matching the query. - **count** (number) - The total number of records matching the query (may be approximate if `limit` is used). - **limit** (number) - The limit applied to the results. - **more** (boolean) - Indicates if there are more records available beyond the current result set. - **offset** (number) - The offset used for pagination (if applicable). - **before** (string) - Cursor for the previous page (if using cursor pagination). - **after** (string) - Cursor for the next page (if using cursor pagination). If an `operation` is specified, the response will be the result of that operation (e.g., a number for count, sum, avg). ``` -------------------------------- ### Define Read-Only Agent Scope with Ash AI Source: https://github.com/ash-project/ash_lua/blob/main/documentation/how_to/integrate-with-ash-ai.md Configure an agent to only have read access to specific resources. This is useful for agents that should not perform any destructive operations. ```elixir defmodule MyApp.Agents.ReadOnlyMCP do use Ash.Resource, domain: MyApp.Agents, extensions: [AshLua.EvalActions] eval_actions do resource MyApp.Posts.Post, actions: [:read] resource MyApp.Posts.Comment, actions: [:read] end end ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.