Jido.Skill behaviour (Jido v2.0.0-rc.1)

View Source

A Skill is a composable capability that can be attached to an agent.

Skills encapsulate:

  • A set of actions the agent can perform
  • State schema for skill-specific data (nested under state_key)
  • Configuration schema for per-agent customization
  • Signal routing rules
  • Optional lifecycle hooks and child processes

Lifecycle

  1. Compile-time: Skill is declared in agent's skills: option
  2. Agent.new/1: mount/2 is called to initialize skill state (pure)
  3. AgentServer.init/1: child_spec/1 processes are started and monitored
  4. Signal processing: handle_signal/2 runs before routing, can override or abort
  5. After cmd/2 (call path): transform_result/3 wraps call results

Example Skill

defmodule MyApp.ChatSkill do
  use Jido.Skill,
    name: "chat",
    state_key: :chat,
    actions: [MyApp.Actions.SendMessage, MyApp.Actions.ListHistory],
    schema: Zoi.object(%{
      messages: Zoi.list(Zoi.any()) |> Zoi.default([]),
      model: Zoi.string() |> Zoi.default("gpt-4")
    }),
    signal_patterns: ["chat.*"]

  @impl Jido.Skill
  def mount(agent, config) do
    # Custom initialization beyond schema defaults
    {:ok, %{initialized_at: DateTime.utc_now()}}
  end

  @impl Jido.Skill
  def router(config) do
    [
      {"chat.send", MyApp.Actions.SendMessage},
      {"chat.history", MyApp.Actions.ListHistory}
    ]
  end
end

Using Skills

defmodule MyAgent do
  use Jido.Agent,
    name: "my_agent",
    skills: [
      MyApp.ChatSkill,
      {MyApp.DatabaseSkill, %{pool_size: 5}}
    ]
end

Configuration Options

  • name - Required. The skill name (letters, numbers, underscores).
  • state_key - Required. Atom key for skill state in agent.
  • actions - Required. List of action modules.
  • description - Optional description.
  • category - Optional category.
  • vsn - Optional version string.
  • schema - Optional Zoi schema for skill state.
  • config_schema - Optional Zoi schema for per-agent config.
  • signal_patterns - List of signal pattern strings (default: []).
  • tags - List of tag strings (default: []).
  • capabilities - List of atoms describing what the skill provides (default: []).
  • requires - List of requirements like {:config, :token}, {:app, :req}, {:skill, :http} (default: []).
  • routes - List of route tuples like {"post", ActionModule} (default: []).
  • schedules - List of schedule tuples like {"*/5 * * * *", ActionModule} (default: []).

Summary

Callbacks

Returns child specification(s) for supervised processes.

Pre-routing hook called before signal routing in AgentServer.

Called when the skill is mounted to an agent during new/1.

Returns the signal router for this skill.

Returns the skill specification with optional per-agent configuration.

Returns sensor subscriptions for this skill.

Transform the agent returned from AgentServer.call/3.

Callbacks

child_spec(config)

(optional)
@callback child_spec(config :: map()) ::
  nil | Supervisor.child_spec() | [Supervisor.child_spec()]

Returns child specification(s) for supervised processes.

Called during AgentServer.init/1. Returned processes are monitored by the AgentServer and tracked in its state.

Parameters

  • config - Per-agent configuration for this skill

Return Values

  • nil - No child processes needed
  • Supervisor.child_spec() - Single child process
  • [Supervisor.child_spec()] - Multiple child processes

Example

def child_spec(config) do
  %{
    id: MyWorker,
    start: {MyWorker, :start_link, [config]}
  }
end

handle_signal(signal, context)

(optional)
@callback handle_signal(signal :: term(), context :: map()) ::
  {:ok, term()} | {:ok, {:override, term()}} | {:error, term()}

Pre-routing hook called before signal routing in AgentServer.

Can inspect, log, or override which action runs for a signal.

Parameters

  • signal - The incoming Jido.Signal struct
  • context - Map with :agent, :agent_module, :skill, :skill_spec, :config

Returns

  • {:ok, nil} or {:ok, :continue} - Continue to normal routing
  • {:ok, {:override, action_spec}} - Bypass router, use this action instead
  • {:error, reason} - Abort signal processing with error

Example

def handle_signal(signal, _context) do
  if signal.type == "admin.override" do
    {:ok, {:override, MyApp.AdminAction}}
  else
    {:ok, :continue}
  end
end

mount(agent, config)

(optional)
@callback mount(agent :: term(), config :: map()) :: {:ok, map() | nil} | {:error, term()}

Called when the skill is mounted to an agent during new/1.

Use this to initialize skill-specific state beyond schema defaults. This is a pure function - no side effects allowed.

Parameters

  • agent - The agent struct (with state from previously mounted skills)
  • config - Per-agent configuration for this skill

Returns

  • {:ok, skill_state} - Map to merge into skill's state slice
  • {:ok, nil} - No additional state (schema defaults only)
  • {:error, reason} - Raises during agent creation

Example

def mount(_agent, config) do
  {:ok, %{initialized_at: DateTime.utc_now(), api_key: config[:api_key]}}
end

router(config)

(optional)
@callback router(config :: map()) :: term()

Returns the signal router for this skill.

The router determines how signals are routed to handlers.

skill_spec(config)

@callback skill_spec(config :: map()) :: Jido.Skill.Spec.t()

Returns the skill specification with optional per-agent configuration.

This is the primary interface for getting skill metadata and configuration.

subscriptions(config, context)

(optional)
@callback subscriptions(config :: map(), context :: map()) :: [
  {module(), keyword() | map()}
]

Returns sensor subscriptions for this skill.

Called during AgentServer.post_init/1 to determine which sensors should be started for this skill. Each sensor is started with the provided configuration.

Parameters

  • config - Per-agent configuration for this skill
  • context - Map containing:
    • :agent_ref - The agent reference (name or PID)
    • :agent_id - The agent's unique identifier
    • :agent_module - The agent module
    • :skill_spec - The skill specification
    • :jido_instance - The Jido instance name

Returns

A list of {sensor_module, sensor_config} tuples where:

  • sensor_module - A module implementing sensor behavior
  • sensor_config - Keyword list or map of sensor configuration

Example

def subscriptions(_config, context) do
  [
    {MyApp.Sensors.FileSensor, [path: "/tmp/watch", target: context.agent_ref]},
    {MyApp.Sensors.TimerSensor, %{interval: 5000, target: context.agent_ref}}
  ]
end

transform_result(action, result, context)

(optional)
@callback transform_result(
  action :: module() | String.t(),
  result :: term(),
  context :: map()
) :: term()

Transform the agent returned from AgentServer.call/3.

Called after signal processing on the synchronous call path only. Does not affect cast/2 or internal state - only the returned agent.

Parameters

  • action - The signal type or action module that was executed
  • result - The agent struct to transform
  • context - Map with :agent, :agent_module, :skill, :skill_spec, :config

Returns

The transformed agent struct (or original if no transformation needed).

Example

def transform_result(_action, agent, _context) do
  # Add metadata to returned agent
  new_state = Map.put(agent.state, :last_call_at, DateTime.utc_now())
  %{agent | state: new_state}
end