Jido action
Agent Skills documentation and examples for the Jido ecosystem
npx -y skills add agentjido/jido-skills --skill jido-actionAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Create composable, validated Jido Actions using the Action behaviour. Use when defining new actions, working with action schemas, composing action chains, or debugging action execution.
The file declares its own license as Apache-2.0. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
9.3 KB, as published. Nobody here has run it
Building Jido Actions
MANDATORY PREPARATION
Invoke /jido-core first — it contains ecosystem context, conventions, and core design principles. Follow its guidance before proceeding.
Overview
A Jido Action is a discrete, composable unit of functionality. Actions are
defined at compile-time with validated schemas and executed via run/2.
Actions can be:
- Called directly with
Jido.Exec.run/3 - Composed into chains with
Jido.Exec.Chain - Used inside agents via
cmd/2 - Exposed as LLM-callable tools via
to_tool/0
Step 1: Define an Action
defmodule MyApp.Actions.ProcessData do
use Jido.Action,
name: "process_data",
description: "Transforms and validates input data",
category: "processing",
tags: ["data", "transform"],
vsn: "1.0.0",
schema: [
input: [type: :string, required: true, doc: "Raw input data"],
format: [type: :atom, default: :json, doc: "Output format"]
],
output_schema: [
result: [type: :string, required: true],
byte_count: [type: :integer, required: true]
]
@impl true
def run(params, _context) do
result = transform(params.input, params.format)
{:ok, %{result: result, byte_count: byte_size(result)}}
end
defp transform(input, :json), do: Jason.encode!(input)
defp transform(input, :text), do: to_string(input)
end
Configuration Options
| Option | Type | Required | Description |
|---|---|---|---|
name | string | yes | Snake_case identifier |
description | string | no | What the action does |
category | string | no | Grouping category |
tags | list | no | Discovery tags |
vsn | string | no | Version |
schema | keyword | no | NimbleOptions or Zoi input schema |
output_schema | keyword | no | NimbleOptions or Zoi output schema |
Step 2: Implement run/2
The run/2 callback receives validated params and a context map:
@impl true
def run(params, context) do
# params — validated against schema, keys are atoms
# context — map with execution context (e.g., agent state)
{:ok, %{result: "processed"}}
end
Return Values
| Return | Meaning |
|---|---|
{:ok, %{key: value}} | Success with state updates |
{:ok, %{result: v}, extras} | Success with extras (e.g., directives) |
{:error, reason} | Failure |
Validation is open: only fields in the schema are validated. Unspecified fields pass through, enabling action composition without validation conflicts.
Step 3: Schema Validation
Schemas validate input params at runtime. Use NimbleOptions types:
schema: [
name: [type: :string, required: true, doc: "User name"],
age: [type: :integer, default: 0],
role: [type: {:in, [:admin, :user, :guest]}, default: :user],
tags: [type: {:list, :string}, default: []],
metadata: [type: :map, default: %{}]
]
Or use Zoi schemas (recommended for new code):
schema: Zoi.object(%{
name: Zoi.string() |> Zoi.min_length(1),
age: Zoi.integer() |> Zoi.min(0) |> Zoi.default(0),
role: Zoi.enum([:admin, :user, :guest]) |> Zoi.default(:user)
})
Validate params programmatically:
{:ok, validated} = MyApp.Actions.ProcessData.validate_params(%{input: "hello"})
{:ok, validated} = MyApp.Actions.ProcessData.validate_output(%{result: "ok", byte_count: 2})
Step 4: Lifecycle Hooks
Actions support optional callbacks for pre/post processing:
defmodule MyApp.Actions.Enriched do
use Jido.Action,
name: "enriched_action",
schema: [value: [type: :integer, required: true]]
@impl true
def on_before_validate_params(params) do
# Normalize before validation
{:ok, Map.update(params, :value, 0, &abs/1)}
end
@impl true
def on_after_validate_params(params) do
# Post-validation transforms
{:ok, params}
end
@impl true
def run(params, _context) do
{:ok, %{doubled: params.value * 2}}
end
@impl true
def on_after_run(result) do
# Post-processing on the result
result
end
@impl true
def on_error(failed_params, error, _context, _opts) do
# Compensation logic on failure
{:ok, %{compensated: true, original_error: error}}
end
end
| Callback | When | Default |
|---|---|---|
on_before_validate_params/1 | Before param validation | passthrough |
on_after_validate_params/1 | After param validation | passthrough |
on_after_run/1 | After run/2 completes | passthrough |
on_error/4 | On execution failure | passthrough |
Step 5: Execute Actions
Direct Execution with Jido.Exec
# Basic execution
{:ok, result} = Jido.Exec.run(MyApp.Actions.ProcessData, %{input: "hello"})
# With context
{:ok, result} = Jido.Exec.run(MyApp.Actions.ProcessData, %{input: "hello"}, %{user_id: 1})
# Async execution
async_ref = Jido.Exec.run_async(MyApp.Actions.ProcessData, %{input: "hello"})
{:ok, result} = Jido.Exec.await(async_ref)
Action Chains
Execute multiple actions in sequence — output flows into the next action's input:
alias Jido.Exec.Chain
{:ok, result} = Chain.run(
[
{ValidateInput, %{raw: data}},
{TransformData, %{}},
{SaveResult, %{destination: :database}}
],
%{}, # initial context
[] # options
)
Action Closures
Pre-apply params and context for deferred execution:
alias Jido.Exec.Closure
closure = Closure.new(MyAction, %{preset: "value"}, %{env: :prod})
{:ok, result} = Closure.run(closure, %{additional: "param"})
Step 6: Inside Agents
Actions are the building blocks of agent commands:
# In agent signal_routes
def signal_routes(_ctx) do
[
{"data.process", MyApp.Actions.ProcessData},
{"data.validate", {MyApp.Actions.Validate, %{strict: true}}}
]
end
# Direct cmd/2 usage
{agent, directives} = MyAgent.cmd(agent, {MyApp.Actions.ProcessData, %{input: "data"}})
Step 7: AI Tool Integration
Actions can be exposed as LLM-callable tools:
# Convert to tool format
tool_spec = MyApp.Actions.ProcessData.to_tool()
# => %{
# "name" => "process_data",
# "description" => "Transforms and validates input data",
# "parameters" => %{
# "type" => "object",
# "properties" => %{
# "input" => %{"type" => "string", "description" => "Raw input data"},
# "format" => %{"type" => "string", "description" => "Output format"}
# },
# "required" => ["input"]
# }
# }
The schema and doc strings directly map to tool parameter descriptions.
Use descriptive doc values in your schema for best AI tool integration.
Complete Example: Data Pipeline Action
defmodule MyApp.Actions.EnrichUser do
use Jido.Action,
name: "enrich_user",
description: "Enriches a user record with computed fields",
schema: [
user_id: [type: :integer, required: true, doc: "User ID to enrich"],
include_stats: [type: :boolean, default: false, doc: "Include usage statistics"]
],
output_schema: [
user: [type: :map, required: true],
enriched_at: [type: :string, required: true]
]
@impl true
def run(params, context) do
user = context.state.users[params.user_id]
enriched = user
|> Map.put(:full_name, "#{user.first_name} #{user.last_name}")
|> maybe_add_stats(params.include_stats, context)
{:ok, %{user: enriched, enriched_at: DateTime.utc_now() |> to_string()}}
end
defp maybe_add_stats(user, false, _ctx), do: user
defp maybe_add_stats(user, true, ctx) do
Map.put(user, :login_count, length(ctx.state.login_history[user.id] || []))
end
end
DO / DON'T
DO
- Name actions with snake_case. Must contain only letters, numbers, underscores.
- Add
docto schema fields. These become tool descriptions for LLM integration. - Return
{:ok, map}from run/2. Always return a map of state updates. - Use output_schema to validate action outputs for downstream consumers.
- Keep actions focused. One action = one logical operation.
DON'T
- Define actions at runtime.
Jido.Action.new()raises — actions are compile-time only. - Depend on action execution order unless using explicit chains.
- Mutate external state in run/2 inside cmd/2 without understanding the purity boundary — use directives for effects owned by the runtime.
- Ignore error returns. Always pattern match on
{:ok, _}or{:error, _}. - Use overly broad schemas. Validate what you need; let composition handle the rest.