Jido agent
Agent Skills documentation and examples for the Jido ecosystem
npx -y skills add agentjido/jido-skills --skill jido-agentAssembled 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
Build Jido Agents with schema validation, cmd/2 command pattern, plugins, and directives. Use when creating new agents, defining agent schemas, adding plugins, or working with the AgentServer runtime.
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.5 KB, as published. Nobody here has run it
Building Jido Agents
MANDATORY PREPARATION
Invoke /jido-core first — it contains ecosystem context, conventions, and core design principles. Follow its guidance before proceeding.
Overview
A Jido Agent is an immutable data structure that holds state and processes
commands via cmd/2. Agents are pure functions — no GenServer, no side effects.
The OTP runtime (AgentServer) is a separate concern.
Core API:
new/1— create a new agentset/2— update state directlyvalidate/2— validate state against schemacmd/2— execute actions:(agent, action) -> {agent, directives}
Step 1: Define the Agent Module
defmodule MyApp.TaskManager do
use Jido.Agent,
name: "task_manager",
description: "Manages a list of tasks with priorities",
schema: [
tasks: [type: {:list, :any}, default: []],
status: [type: :atom, default: :idle],
task_count: [type: :integer, default: 0]
]
def signal_routes(_ctx) do
[
{"task.add", MyApp.Actions.AddTask},
{"task.complete", MyApp.Actions.CompleteTask},
{"task.list", MyApp.Actions.ListTasks}
]
end
end
Required Options
| Option | Type | Description |
|---|---|---|
name | String.t() | Snake_case identifier |
description | String.t() | What the agent does |
schema | keyword() | NimbleOptions or Zoi state schema |
Optional Options
| Option | Type | Description |
|---|---|---|
strategy | module/tuple | Execution strategy (default: Strategy.Direct) |
plugins | list | Plugin modules or {module, config} tuples |
signal_routes | list | Compile-time signal-to-action route table |
schedules | list | Cron schedules as {expr, signal_type} |
Step 2: Configure Schema
Use NimbleOptions (legacy) or Zoi (recommended for new code):
# NimbleOptions
schema: [
status: [type: :atom, default: :idle],
counter: [type: :integer, default: 0]
]
# Zoi (recommended)
schema: Zoi.object(%{
status: Zoi.atom() |> Zoi.default(:idle),
counter: Zoi.integer() |> Zoi.default(0)
})
Both are handled transparently by the Agent module.
Step 3: Define Signal Routes
Signal routes map incoming signal types to action modules. AgentServer uses
these to dispatch signals to cmd/2.
def signal_routes(_ctx) do
[
{"user.created", HandleUserCreated}, # simple mapping
{"counter.increment", IncrementAction, 10}, # with priority
{"payment.*", LargePaymentAction}, # wildcard
{"order.placed", {ProcessOrder, %{notify: true}}} # with static params
]
end
Route formats:
{path, ActionModule}— priority 0{path, ActionModule, priority}— explicit priority{path, {ActionModule, %{params}}}— with static params{path, guard_fn, ActionModule, priority}— with guard function
Step 4: Implement cmd/2 Workflow
cmd/2 is the core operation. It accepts actions and returns {agent, directives}.
# Single action
{agent, directives} = MyApp.TaskManager.cmd(agent, MyApp.Actions.AddTask)
# Action with params
{agent, directives} = MyApp.TaskManager.cmd(agent, {MyApp.Actions.AddTask, %{title: "Write docs"}})
# List of actions (processed sequentially)
{agent, directives} = MyApp.TaskManager.cmd(agent, [
{MyApp.Actions.AddTask, %{title: "Task 1"}},
{MyApp.Actions.AddTask, %{title: "Task 2"}}
])
Key invariants:
- The returned
agentis always complete — no "apply directives" step directivesare external effects only — they never modify agent statecmd/2is a pure function — same inputs, same outputs
Step 5: Understand Directives
Directives describe effects for the runtime to interpret. They are bare structs:
| Directive | Purpose |
|---|---|
%Directive.Emit{} | Dispatch a signal via Signal.Dispatch |
%Directive.Error{} | Signal an error |
%Directive.Spawn{} | Spawn a child process |
%Directive.Schedule{} | Schedule a delayed message |
%Directive.RunInstruction{} | Execute instruction at runtime |
%Directive.Stop{} | Stop the agent process |
Emit with dispatch targets:
%Directive.Emit{signal: my_signal} # default
%Directive.Emit{signal: my_signal, dispatch: {:pubsub, topic: "events"}}
%Directive.Emit{signal: my_signal, dispatch: {:pid, target: pid}}
Step 6: Add Plugins
Plugins provide reusable capabilities with isolated state:
defmodule MyApp.TaskManager do
use Jido.Agent,
name: "task_manager",
plugins: [
MyApp.Plugins.AuditLog,
{MyApp.Plugins.RateLimit, %{max_per_minute: 100}}
],
schema: [
tasks: [type: {:list, :any}, default: []]
]
end
Plugins implement Jido.Plugin behaviour with callbacks: plugin_spec/1,
handle_signal/2, transform_result/3, and child_spec/1.
Step 7: Lifecycle Hooks
Agents support two optional pure callbacks:
def on_before_cmd(agent, action) do
# Pre-processing: mirror params, enforce guards
{:ok, agent, action}
end
def on_after_cmd(agent, action, directives) do
# Post-processing: validate state, derive computed fields
{:ok, agent, directives}
end
Step 8: Run with AgentServer
For production, wrap agents in AgentServer — the OTP GenServer runtime:
# Start under DynamicSupervisor
{:ok, pid} = Jido.AgentServer.start(agent: MyApp.TaskManager)
# Start linked
{:ok, pid} = Jido.AgentServer.start_link(
agent: MyApp.TaskManager,
id: "task-mgr-1",
initial_state: %{status: :active}
)
# Send signals
:ok = Jido.AgentServer.call(pid, signal, timeout)
:ok = Jido.AgentServer.cast(pid, signal)
# Get state
{:ok, state} = Jido.AgentServer.state(pid)
Signal flow: Signal → AgentServer → route → Agent.cmd/2 → {agent, directives} → drain loop
Common Patterns
State Machine (FSM Strategy)
Use a strategy to enforce state transitions:
use Jido.Agent,
name: "order_agent",
strategy: {MyApp.OrderFSM, %{initial: :pending}},
schema: [
status: [type: :atom, default: :pending],
items: [type: {:list, :any}, default: []]
]
Parent-Child Hierarchies
Jido supports logical parent-child relationships (not OTP supervision):
{:ok, parent_pid} = Jido.AgentServer.start(agent: ParentAgent)
{:ok, child_pid} = Jido.AgentServer.start(
agent: ChildAgent,
parent: parent_pid,
on_parent_death: :emit_orphan
)
Complete Example: Task Manager Agent
defmodule MyApp.Actions.AddTask do
use Jido.Action,
name: "add_task",
description: "Adds a task to the list",
schema: [
title: [type: :string, required: true],
priority: [type: :atom, default: :normal]
]
@impl true
def run(params, _context) do
task = %{
id: System.unique_integer([:positive]),
title: params.title,
priority: params.priority,
completed: false
}
{:ok, %{
tasks: :append,
_append_tasks: task,
task_count: :increment
}}
end
end
defmodule MyApp.Actions.CompleteTask do
use Jido.Action,
name: "complete_task",
description: "Marks a task as completed",
schema: [
task_id: [type: :integer, required: true]
]
@impl true
def run(params, context) do
tasks = context.state.tasks
updated = Enum.map(tasks, fn
%{id: id} = t when id == params.task_id -> %{t | completed: true}
t -> t
end)
{:ok, %{tasks: updated}}
end
end
defmodule MyApp.TaskManager do
use Jido.Agent,
name: "task_manager",
description: "Manages tasks with priorities",
schema: [
tasks: [type: {:list, :any}, default: []],
status: [type: :atom, default: :idle],
task_count: [type: :integer, default: 0]
]
def signal_routes(_ctx) do
[
{"task.add", MyApp.Actions.AddTask},
{"task.complete", MyApp.Actions.CompleteTask}
]
end
end
DO / DON'T
DO
- Keep agents focused on one domain. One agent = one bounded context.
- Use schema validation for all state. Define every field with types and defaults.
- Test agents as pure functions first. Call
cmd/2on structs before testing with AgentServer. - Return directives for side effects. Let the runtime handle I/O.
- Use
signal_routes/1to map signals to actions declaratively.
DON'T
- Put side effects in actions used inside cmd/2. Use directives to describe effects; let AgentServer execute them.
- Access external state in cmd/2. The agent should only use its own state and action params.
- Skip schema validation. Unvalidated state leads to subtle runtime bugs.
- Use
{:stop, ...}from DirectiveExec for normal completion — use state-based completion instead. - Mutate agent state outside cmd/2 or set/2. Always go through the defined API.