Current section
22 Versions
Jump to
Current section
22 Versions
Compare versions
48
files changed
+5451
additions
-2188
deletions
| @@ -43,7 +43,6 @@ Jido is the core package of the Jido ecosystem. The ecosystem is built around th | |
| 43 43 | | [jido_signal](https://github.com/agentjido/jido_signal) | CloudEvents-based message envelope and supporting utilities for routing and pub/sub messaging | |
| 44 44 | | [jido](https://github.com/agentjido/jido) | Core agent framework with state management, directives, and runtime | |
| 45 45 | | [jido_ai](https://github.com/agentjido/jido_ai) | AI/LLM integration for agents | |
| 46 | - | [jido_coder](https://github.com/agentjido/jido_coder) | AI coding agent with file operations, git integration, and test execution | |
| 47 46 | |
| 48 47 | For demos and examples of what you can build with the Jido Ecosystem, see [https://agentjido.xyz](https://agentjido.xyz). |
| 49 48 | |
| @@ -273,11 +272,13 @@ State operations are internal state transitions handled by the strategy layer du | |
| 273 272 | - [Signals & Routing](guides/signals.md) - Signal-based communication |
| 274 273 | - [Agent Directives](guides/directives.md) - Effect descriptions for the runtime |
| 275 274 | - [Runtime and AgentServer](guides/runtime.md) - Process-based agent execution |
| 275 | + - [Persistence & Storage](guides/storage.md) - Hibernate, thaw, and InstanceManager lifecycle |
| 276 276 | - [Skills](guides/skills.md) - Composable capability bundles |
| 277 277 | - [Strategies](guides/strategies.md) - Execution strategies (Direct, FSM) |
| 278 278 | |
| 279 279 | **Advanced:** |
| 280 280 | - [FSM Strategy Deep Dive](guides/fsm-strategy.livemd) - State machine workflows |
| 281 | + - [Worker Pools](guides/worker-pools.md) - Pre-warmed agent pools for throughput |
| 281 282 | - [Testing Agents](guides/testing.md) - Testing patterns and best practices |
| 282 283 | |
| 283 284 | **API Reference:** [hexdocs.pm/jido](https://hexdocs.pm/jido) |
| @@ -6,25 +6,21 @@ | |
| 6 6 | {<<"GitHub">>,<<"https://github.com/agentjido/jido">>}, |
| 7 7 | {<<"Website">>,<<"https://agentjido.xyz">>}]}. |
| 8 8 | {<<"name">>,<<"jido">>}. |
| 9 | - {<<"version">>,<<"2.0.0-rc.2">>}. |
| 9 | + {<<"version">>,<<"2.0.0-rc.3">>}. |
| 10 10 | {<<"description">>, |
| 11 11 | <<"Pure functional agents and OTP runtime for building autonomous multi-agent workflows in Elixir.">>}. |
| 12 12 | {<<"elixir">>,<<"~> 1.17">>}. |
| 13 13 | {<<"app">>,<<"jido">>}. |
| 14 14 | {<<"files">>, |
| 15 15 | [<<"lib">>,<<"lib/mix">>,<<"lib/mix/tasks">>, |
| 16 | - <<"lib/mix/tasks/jido.install.ex">>,<<"lib/mix/tasks/jido.gen.agent.ex">>, |
| 17 | - <<"lib/mix/tasks/jido.gen.skill.ex">>, |
| 16 | + <<"lib/mix/tasks/jido.gen.plugin.ex">>,<<"lib/mix/tasks/jido.install.ex">>, |
| 17 | + <<"lib/mix/tasks/jido.gen.agent.ex">>, |
| 18 18 | <<"lib/mix/tasks/jido.gen.sensor.ex">>,<<"lib/jido.ex">>,<<"lib/jido">>, |
| 19 | - <<"lib/jido/util.ex">>,<<"lib/jido/sensors">>, |
| 19 | + <<"lib/jido/persist.ex">>,<<"lib/jido/util.ex">>,<<"lib/jido/sensors">>, |
| 20 20 | <<"lib/jido/sensors/bus_sensor.ex">>, |
| 21 21 | <<"lib/jido/sensors/heartbeat_sensor.ex">>,<<"lib/jido/agent_server.ex">>, |
| 22 22 | <<"lib/jido/tracing">>,<<"lib/jido/tracing/trace.ex">>, |
| 23 23 | <<"lib/jido/tracing/context.ex">>,<<"lib/jido/telemetry.ex">>, |
| 24 | - <<"lib/jido/skill">>,<<"lib/jido/skill/spec.ex">>, |
| 25 | - <<"lib/jido/skill/instance.ex">>,<<"lib/jido/skill/config.ex">>, |
| 26 | - <<"lib/jido/skill/manifest.ex">>,<<"lib/jido/skill/routes.ex">>, |
| 27 | - <<"lib/jido/skill/schedules.ex">>,<<"lib/jido/skill/requirements.ex">>, |
| 28 24 | <<"lib/jido/agent_server">>,<<"lib/jido/agent_server/error_policy.ex">>, |
| 29 25 | <<"lib/jido/agent_server/options.ex">>, |
| 30 26 | <<"lib/jido/agent_server/signal_router.ex">>, |
| @@ -42,8 +38,12 @@ | |
| 42 38 | <<"lib/jido/agent_server/signal/cron_tick.ex">>, |
| 43 39 | <<"lib/jido/agent_server/signal/child_exit.ex">>, |
| 44 40 | <<"lib/jido/agent_server/child_info.ex">>, |
| 45 | - <<"lib/jido/agent_server/directive_exec.ex">>,<<"lib/jido/skill.ex">>, |
| 46 | - <<"lib/jido/sensor.ex">>,<<"lib/jido/error.ex">>,<<"lib/jido/agent">>, |
| 41 | + <<"lib/jido/agent_server/directive_exec.ex">>,<<"lib/jido/sensor.ex">>, |
| 42 | + <<"lib/jido/plugin">>,<<"lib/jido/plugin/spec.ex">>, |
| 43 | + <<"lib/jido/plugin/instance.ex">>,<<"lib/jido/plugin/config.ex">>, |
| 44 | + <<"lib/jido/plugin/manifest.ex">>,<<"lib/jido/plugin/routes.ex">>, |
| 45 | + <<"lib/jido/plugin/schedules.ex">>,<<"lib/jido/plugin/requirements.ex">>, |
| 46 | + <<"lib/jido/error.ex">>,<<"lib/jido/thread.ex">>,<<"lib/jido/agent">>, |
| 47 47 | <<"lib/jido/agent/strategy.ex">>,<<"lib/jido/agent/store.ex">>, |
| 48 48 | <<"lib/jido/agent/instance_manager">>, |
| 49 49 | <<"lib/jido/agent/instance_manager/cleanup.ex">>, |
| @@ -57,29 +57,38 @@ | |
| 57 57 | <<"lib/jido/agent/store/file.ex">>,<<"lib/jido/agent/strategy">>, |
| 58 58 | <<"lib/jido/agent/strategy/direct.ex">>, |
| 59 59 | <<"lib/jido/agent/strategy/fsm.ex">>,<<"lib/jido/agent/strategy/state.ex">>, |
| 60 | - <<"lib/jido/discovery.ex">>,<<"lib/jido/observe">>, |
| 61 | - <<"lib/jido/observe/span_ctx.ex">>,<<"lib/jido/observe/noop_tracer.ex">>, |
| 62 | - <<"lib/jido/observe/log.ex">>,<<"lib/jido/observe/tracer.ex">>, |
| 63 | - <<"lib/jido/scheduler.ex">>,<<"lib/jido/igniter">>, |
| 64 | - <<"lib/jido/igniter/helpers.ex">>,<<"lib/jido/igniter/templates.ex">>, |
| 65 | - <<"lib/jido/sensor">>,<<"lib/jido/sensor/spec.ex">>, |
| 66 | - <<"lib/jido/sensor/runtime.ex">>,<<"lib/jido/actions">>, |
| 67 | - <<"lib/jido/actions/control.ex">>,<<"lib/jido/actions/status.ex">>, |
| 68 | - <<"lib/jido/actions/lifecycle.ex">>,<<"lib/jido/actions/scheduling.ex">>, |
| 60 | + <<"lib/jido/discovery.ex">>,<<"lib/jido/storage">>, |
| 61 | + <<"lib/jido/storage/ets.ex">>,<<"lib/jido/storage/file.ex">>, |
| 62 | + <<"lib/jido/observe">>,<<"lib/jido/observe/span_ctx.ex">>, |
| 63 | + <<"lib/jido/observe/noop_tracer.ex">>,<<"lib/jido/observe/log.ex">>, |
| 64 | + <<"lib/jido/observe/tracer.ex">>,<<"lib/jido/scheduler.ex">>, |
| 65 | + <<"lib/jido/igniter">>,<<"lib/jido/igniter/helpers.ex">>, |
| 66 | + <<"lib/jido/igniter/templates.ex">>,<<"lib/jido/sensor">>, |
| 67 | + <<"lib/jido/sensor/spec.ex">>,<<"lib/jido/sensor/runtime.ex">>, |
| 68 | + <<"lib/jido/actions">>,<<"lib/jido/actions/control.ex">>, |
| 69 | + <<"lib/jido/actions/status.ex">>,<<"lib/jido/actions/lifecycle.ex">>, |
| 70 | + <<"lib/jido/actions/scheduling.ex">>,<<"lib/jido/telemetry">>, |
| 71 | + <<"lib/jido/telemetry/formatter.ex">>,<<"lib/jido/telemetry/config.ex">>, |
| 69 72 | <<"lib/jido/application.ex">>,<<"lib/jido/await.ex">>, |
| 70 | - <<"lib/jido/observe.ex">>,<<"lib/jido/agent.ex">>,<<"mix.exs">>, |
| 73 | + <<"lib/jido/observe.ex">>,<<"lib/jido/thread">>, |
| 74 | + <<"lib/jido/thread/store.ex">>,<<"lib/jido/thread/entry.ex">>, |
| 75 | + <<"lib/jido/thread/agent.ex">>,<<"lib/jido/thread/store">>, |
| 76 | + <<"lib/jido/thread/store/adapters">>, |
| 77 | + <<"lib/jido/thread/store/adapters/journal_backed.ex">>, |
| 78 | + <<"lib/jido/thread/store/adapters/in_memory.ex">>,<<"lib/jido/storage.ex">>, |
| 79 | + <<"lib/jido/agent.ex">>,<<"lib/jido/plugin.ex">>,<<"mix.exs">>, |
| 71 80 | <<"README.md">>,<<"LICENSE">>,<<"usage-rules.md">>]}. |
| 72 81 | {<<"licenses">>,[<<"Apache-2.0">>]}. |
| 73 82 | {<<"requirements">>, |
| 74 83 | [[{<<"name">>,<<"jido_action">>}, |
| 75 84 | {<<"app">>,<<"jido_action">>}, |
| 76 85 | {<<"optional">>,false}, |
| 77 | - {<<"requirement">>,<<"~> 2.0.0-rc.2">>}, |
| 86 | + {<<"requirement">>,<<"~> 2.0.0-rc.3">>}, |
| 78 87 | {<<"repository">>,<<"hexpm">>}], |
| 79 88 | [{<<"name">>,<<"jido_signal">>}, |
| 80 89 | {<<"app">>,<<"jido_signal">>}, |
| 81 90 | {<<"optional">>,false}, |
| 82 | - {<<"requirement">>,<<"~> 2.0.0-rc.2">>}, |
| 91 | + {<<"requirement">>,<<"~> 2.0.0-rc.3">>}, |
| 83 92 | {<<"repository">>,<<"hexpm">>}], |
| 84 93 | [{<<"name">>,<<"deep_merge">>}, |
| 85 94 | {<<"app">>,<<"deep_merge">>}, |
| @@ -76,9 +76,15 @@ defmodule Jido do | |
| 76 76 | """ |
| 77 77 | defmacro __using__(opts) do |
| 78 78 | otp_app = Keyword.fetch!(opts, :otp_app) |
| 79 | + storage = Keyword.get(opts, :storage, {Jido.Storage.ETS, [table: :jido_storage]}) |
| 79 80 | |
| 80 81 | quote location: :keep do |
| 81 82 | @otp_app unquote(otp_app) |
| 83 | + @jido_storage Jido.Storage.normalize_storage(unquote(Macro.escape(storage))) |
| 84 | + |
| 85 | + @doc "Returns the storage configuration for this Jido instance." |
| 86 | + @spec __jido_storage__() :: {module(), keyword()} |
| 87 | + def __jido_storage__, do: @jido_storage |
| 82 88 | |
| 83 89 | @doc false |
| 84 90 | def child_spec(init_arg \\ []) do |
| @@ -154,11 +160,83 @@ defmodule Jido do | |
| 154 160 | @doc "Returns the TaskSupervisor name for this Jido instance." |
| 155 161 | @spec task_supervisor_name() :: atom() |
| 156 162 | def task_supervisor_name, do: Jido.task_supervisor_name(__MODULE__) |
| 163 | + |
| 164 | + @doc "Hibernate an agent to storage." |
| 165 | + @spec hibernate(Jido.Agent.t()) :: :ok | {:error, term()} |
| 166 | + def hibernate(agent) do |
| 167 | + Jido.Persist.hibernate(__jido_storage__(), agent) |
| 168 | + end |
| 169 | + |
| 170 | + @doc "Thaw an agent from storage." |
| 171 | + @spec thaw(module(), term()) :: {:ok, Jido.Agent.t()} | :not_found | {:error, term()} |
| 172 | + def thaw(agent_module, key) do |
| 173 | + Jido.Persist.thaw(__jido_storage__(), agent_module, key) |
| 174 | + end |
| 157 175 | end |
| 158 176 | end |
| 159 177 | |
| 160 178 | @type agent_id :: String.t() | atom() |
| 161 179 | |
| 180 | + # Default instance name for scripts/Livebook |
| 181 | + @default_instance Jido.Default |
| 182 | + |
| 183 | + @doc """ |
| 184 | + Returns the default Jido instance name. |
| 185 | + |
| 186 | + Used by `Jido.start/1` for scripts and Livebook quick-start. |
| 187 | + """ |
| 188 | + @spec default_instance() :: atom() |
| 189 | + def default_instance, do: @default_instance |
| 190 | + |
| 191 | + @doc """ |
| 192 | + Start the default Jido instance for scripts and Livebook. |
| 193 | + |
| 194 | + This is an idempotent convenience function - safe to call multiple times |
| 195 | + (returns `{:ok, pid}` even if already started). |
| 196 | + |
| 197 | + ## Examples |
| 198 | + |
| 199 | + # In a script or Livebook |
| 200 | + {:ok, _} = Jido.start() |
| 201 | + {:ok, pid} = Jido.start_agent(Jido.default_instance(), MyAgent) |
| 202 | + |
| 203 | + # With custom options |
| 204 | + {:ok, _} = Jido.start(max_tasks: 2000) |
| 205 | + |
| 206 | + ## Options |
| 207 | + |
| 208 | + Same as `start_link/1`, but `:name` defaults to `Jido.Default`. |
| 209 | + """ |
| 210 | + @spec start(keyword()) :: {:ok, pid()} | {:error, term()} |
| 211 | + def start(opts \\ []) do |
| 212 | + opts = Keyword.put_new(opts, :name, @default_instance) |
| 213 | + |
| 214 | + case start_link(opts) do |
| 215 | + {:ok, pid} -> {:ok, pid} |
| 216 | + {:error, {:already_started, pid}} -> {:ok, pid} |
| 217 | + other -> other |
| 218 | + end |
| 219 | + end |
| 220 | + |
| 221 | + @doc """ |
| 222 | + Stop a Jido instance. |
| 223 | + |
| 224 | + Defaults to stopping the default instance (`Jido.Default`). |
| 225 | + |
| 226 | + ## Examples |
| 227 | + |
| 228 | + Jido.stop() |
| 229 | + Jido.stop(MyApp.Jido) |
| 230 | + |
| 231 | + """ |
| 232 | + @spec stop(atom()) :: :ok |
| 233 | + def stop(name \\ @default_instance) do |
| 234 | + case Process.whereis(name) do |
| 235 | + nil -> :ok |
| 236 | + pid -> Supervisor.stop(pid) |
| 237 | + end |
| 238 | + end |
| 239 | + |
| 162 240 | @doc """ |
| 163 241 | Starts a Jido instance supervisor. |
| 164 242 | |
| @@ -321,6 +399,22 @@ defmodule Jido do | |
| 321 399 | |> Map.get(:active, 0) |
| 322 400 | end |
| 323 401 | |
| 402 | + # --------------------------------------------------------------------------- |
| 403 | + # Persistence |
| 404 | + # --------------------------------------------------------------------------- |
| 405 | + |
| 406 | + @doc "Hibernate an agent using the given Jido instance." |
| 407 | + @spec hibernate(atom(), Jido.Agent.t()) :: :ok | {:error, term()} |
| 408 | + def hibernate(jido_instance, agent) when is_atom(jido_instance) do |
| 409 | + Jido.Persist.hibernate(jido_instance, agent) |
| 410 | + end |
| 411 | + |
| 412 | + @doc "Thaw an agent using the given Jido instance." |
| 413 | + @spec thaw(atom(), module(), term()) :: {:ok, Jido.Agent.t()} | :not_found | {:error, term()} |
| 414 | + def thaw(jido_instance, agent_module, key) when is_atom(jido_instance) do |
| 415 | + Jido.Persist.thaw(jido_instance, agent_module, key) |
| 416 | + end |
| 417 | + |
| 324 418 | # --------------------------------------------------------------------------- |
| 325 419 | # Discovery |
| 326 420 | # --------------------------------------------------------------------------- |
| @@ -331,8 +425,8 @@ defmodule Jido do | |
| 331 425 | @doc "Lists discovered Sensors with optional filtering." |
| 332 426 | defdelegate list_sensors(opts \\ []), to: Jido.Discovery |
| 333 427 | |
| 334 | - @doc "Lists discovered Skills with optional filtering." |
| 335 | - defdelegate list_skills(opts \\ []), to: Jido.Discovery |
| 428 | + @doc "Lists discovered Plugins with optional filtering." |
| 429 | + defdelegate list_plugins(opts \\ []), to: Jido.Discovery |
| 336 430 | |
| 337 431 | @doc "Lists discovered Demos with optional filtering." |
| 338 432 | defdelegate list_demos(opts \\ []), to: Jido.Discovery |
| @@ -343,8 +437,8 @@ defmodule Jido do | |
| 343 437 | @doc "Gets a Sensor by its slug." |
| 344 438 | defdelegate get_sensor_by_slug(slug), to: Jido.Discovery |
| 345 439 | |
| 346 | - @doc "Gets a Skill by its slug." |
| 347 | - defdelegate get_skill_by_slug(slug), to: Jido.Discovery |
| 440 | + @doc "Gets a Plugin by its slug." |
| 441 | + defdelegate get_plugin_by_slug(slug), to: Jido.Discovery |
| 348 442 | |
| 349 443 | @doc "Refreshes the Discovery catalog." |
| 350 444 | defdelegate refresh_discovery(), to: Jido.Discovery, as: :refresh |
| @@ -135,8 +135,8 @@ defmodule Jido.Agent do | |
| 135 135 | alias Jido.Agent.State, as: StateHelper |
| 136 136 | alias Jido.Error |
| 137 137 | alias Jido.Instruction |
| 138 | - alias Jido.Skill.Instance, as: SkillInstance |
| 139 | - alias Jido.Skill.Requirements, as: SkillRequirements |
| 138 | + alias Jido.Plugin.Instance, as: PluginInstance |
| 139 | + alias Jido.Plugin.Requirements, as: PluginRequirements |
| 140 140 | |
| 141 141 | require OK |
| 142 142 | |
| @@ -225,9 +225,9 @@ defmodule Jido.Agent do | |
| 225 225 | "Execution strategy module or {module, opts}. Default: Jido.Agent.Strategy.Direct" |
| 226 226 | ) |
| 227 227 | |> Zoi.default(Jido.Agent.Strategy.Direct), |
| 228 | - skills: |
| 228 | + plugins: |
| 229 229 | Zoi.list(Zoi.any(), |
| 230 | - description: "Skill modules or {module, config} tuples" |
| 230 | + description: "Plugin modules or {module, config} tuples" |
| 231 231 | ) |
| 232 232 | |> Zoi.default([]) |
| 233 233 | }, |
| @@ -293,13 +293,64 @@ defmodule Jido.Agent do | |
| 293 293 | """ |
| 294 294 | @callback signal_routes() :: [Jido.Signal.Router.route_spec()] |
| 295 295 | |
| 296 | - @optional_callbacks [on_before_cmd: 2, on_after_cmd: 3, signal_routes: 0] |
| 296 | + @doc """ |
| 297 | + Serializes the agent for persistence. |
| 298 | + |
| 299 | + Called by `Jido.Persist.hibernate/2` before writing to storage. |
| 300 | + The returned data should NOT include the full Thread - only a pointer. |
| 301 | + |
| 302 | + If not implemented, a default serialization is used that: |
| 303 | + - Excludes `:__thread__` from state |
| 304 | + - Stores thread pointer as `%{id: thread.id, rev: thread.rev}` |
| 305 | + |
| 306 | + ## Parameters |
| 307 | + |
| 308 | + - `agent` - The agent to serialize |
| 309 | + - `ctx` - Context map (may contain jido instance, options) |
| 310 | + |
| 311 | + ## Returns |
| 312 | + |
| 313 | + - `{:ok, serializable_data}` - Data to persist |
| 314 | + - `{:error, reason}` - Serialization failed |
| 315 | + """ |
| 316 | + @callback checkpoint(agent :: t(), ctx :: map()) :: {:ok, map()} | {:error, term()} |
| 317 | + |
| 318 | + @doc """ |
| 319 | + Restores an agent from persisted data. |
| 320 | + |
| 321 | + Called by `Jido.Persist.thaw/3` after loading from storage. |
| 322 | + The Thread is reattached separately by Persist after restore. |
| 323 | + |
| 324 | + If not implemented, a default restoration is used that: |
| 325 | + - Creates a new agent with the persisted id |
| 326 | + - Merges the persisted state |
| 327 | + |
| 328 | + ## Parameters |
| 329 | + |
| 330 | + - `data` - The persisted data (from checkpoint/2) |
| 331 | + - `ctx` - Context map (may contain jido instance, options) |
| 332 | + |
| 333 | + ## Returns |
| 334 | + |
| 335 | + - `{:ok, agent}` - Restored agent (without thread attached) |
| 336 | + - `{:error, reason}` - Restoration failed |
| 337 | + """ |
| 338 | + @callback restore(data :: map(), ctx :: map()) :: {:ok, t()} | {:error, term()} |
| 339 | + |
| 340 | + @optional_callbacks [ |
| 341 | + on_before_cmd: 2, |
| 342 | + on_after_cmd: 3, |
| 343 | + signal_routes: 0, |
| 344 | + checkpoint: 2, |
| 345 | + restore: 2 |
| 346 | + ] |
| 297 347 | |
| 298 348 | # Helper functions that generate quoted code for the __using__ macro. |
| 299 349 | # This approach reduces the size of the main quote block to avoid |
| 300 350 | # "long quote blocks" and "nested too deep" Credo warnings. |
| 301 351 | |
| 302 352 | @doc false |
| 353 | + @spec __quoted_module_setup__() :: Macro.t() |
| 303 354 | def __quoted_module_setup__ do |
| 304 355 | quote location: :keep do |
| 305 356 | @behaviour Jido.Agent |
| @@ -308,13 +359,14 @@ defmodule Jido.Agent do | |
| 308 359 | alias Jido.Agent.State, as: AgentState |
| 309 360 | alias Jido.Agent.Strategy, as: AgentStrategy |
| 310 361 | alias Jido.Instruction |
| 311 | - alias Jido.Skill.Requirements, as: SkillRequirements |
| 362 | + alias Jido.Plugin.Requirements, as: PluginRequirements |
| 312 363 | |
| 313 364 | require OK |
| 314 365 | end |
| 315 366 | end |
| 316 367 | |
| 317 368 | @doc false |
| 369 | + @spec __quoted_basic_accessors__() :: Macro.t() |
| 318 370 | def __quoted_basic_accessors__ do |
| 319 371 | quote location: :keep do |
| 320 372 | @doc "Returns the agent's name." |
| @@ -337,64 +389,65 @@ defmodule Jido.Agent do | |
| 337 389 | @spec vsn() :: String.t() | nil |
| 338 390 | def vsn, do: @validated_opts[:vsn] |
| 339 391 | |
| 340 | - @doc "Returns the merged schema (base + skill schemas)." |
| 392 | + @doc "Returns the merged schema (base + plugin schemas)." |
| 341 393 | @spec schema() :: Zoi.schema() | keyword() |
| 342 394 | def schema, do: @merged_schema |
| 343 395 | end |
| 344 396 | end |
| 345 397 | |
| 346 398 | @doc false |
| 347 | - def __quoted_skill_accessors__ do |
| 348 | - basic_skill_accessors = __quoted_basic_skill_accessors__() |
| 349 | - computed_skill_accessors = __quoted_computed_skill_accessors__() |
| 399 | + @spec __quoted_plugin_accessors__() :: Macro.t() |
| 400 | + def __quoted_plugin_accessors__ do |
| 401 | + basic_plugin_accessors = __quoted_basic_plugin_accessors__() |
| 402 | + computed_plugin_accessors = __quoted_computed_plugin_accessors__() |
| 350 403 | |
| 351 404 | quote location: :keep do |
| 352 | - unquote(basic_skill_accessors) |
| 353 | - unquote(computed_skill_accessors) |
| 405 | + unquote(basic_plugin_accessors) |
| 406 | + unquote(computed_plugin_accessors) |
| 354 407 | end |
| 355 408 | end |
| 356 409 | |
| 357 | - defp __quoted_basic_skill_accessors__ do |
| 410 | + defp __quoted_basic_plugin_accessors__ do |
| 358 411 | quote location: :keep do |
| 359 412 | @doc """ |
| 360 | - Returns the list of skill modules attached to this agent (deduplicated). |
| 413 | + Returns the list of plugin modules attached to this agent (deduplicated). |
| 361 414 | |
| 362 | - For multi-instance skills, the module appears once regardless of how many |
| 415 | + For multi-instance plugins, the module appears once regardless of how many |
| 363 416 | instances are mounted. |
| 364 417 | |
| 365 418 | ## Examples |
| 366 419 | |
| 367 | - iex> #{inspect(__MODULE__)}.skills() |
| 368 | - [SlackSkill, OpenAISkill] |
| 420 | + iex> #{inspect(__MODULE__)}.plugins() |
| 421 | + [SlackPlugin, OpenAIPlugin] |
| 369 422 | """ |
| 370 | - @spec skills() :: [module()] |
| 371 | - def skills do |
| 372 | - @skill_instances |
| 423 | + @spec plugins() :: [module()] |
| 424 | + def plugins do |
| 425 | + @plugin_instances |
| 373 426 | |> Enum.map(& &1.module) |
| 374 427 | |> Enum.uniq() |
| 375 428 | end |
| 376 429 | |
| 377 | - @doc "Returns the list of skill specs attached to this agent." |
| 378 | - @spec skill_specs() :: [Jido.Skill.Spec.t()] |
| 379 | - def skill_specs, do: @skill_specs |
| 430 | + @doc "Returns the list of plugin specs attached to this agent." |
| 431 | + @spec plugin_specs() :: [Jido.Plugin.Spec.t()] |
| 432 | + def plugin_specs, do: @plugin_specs |
| 380 433 | |
| 381 | - @doc "Returns the list of skill instances attached to this agent." |
| 382 | - @spec skill_instances() :: [Jido.Skill.Instance.t()] |
| 383 | - def skill_instances, do: @skill_instances |
| 434 | + @doc "Returns the list of plugin instances attached to this agent." |
| 435 | + @spec plugin_instances() :: [Jido.Plugin.Instance.t()] |
| 436 | + def plugin_instances, do: @plugin_instances |
| 384 437 | |
| 385 | - @doc "Returns the list of actions from all attached skills." |
| 438 | + @doc "Returns the list of actions from all attached plugins." |
| 386 439 | @spec actions() :: [module()] |
| 387 | - def actions, do: @skill_actions |
| 440 | + def actions, do: @plugin_actions |
| 388 441 | end |
| 389 442 | end |
| 390 443 | |
| 391 | - defp __quoted_computed_skill_accessors__ do |
| 444 | + defp __quoted_computed_plugin_accessors__ do |
| 392 445 | quote location: :keep do |
| 393 446 | @doc """ |
| 394 | - Returns the union of all capabilities from all mounted skill instances. |
| 447 | + Returns the union of all capabilities from all mounted plugin instances. |
| 395 448 | |
| 396 449 | Capabilities are atoms describing what the agent can do based on its |
| 397 | - mounted skills. |
| 450 | + mounted plugins. |
| 398 451 | |
| 399 452 | ## Examples |
| 400 453 | |
| @@ -403,13 +456,13 @@ defmodule Jido.Agent do | |
| 403 456 | """ |
| 404 457 | @spec capabilities() :: [atom()] |
| 405 458 | def capabilities do |
| 406 | - @skill_instances |
| 459 | + @plugin_instances |
| 407 460 | |> Enum.flat_map(fn instance -> instance.manifest.capabilities || [] end) |
| 408 461 | |> Enum.uniq() |
| 409 462 | end |
| 410 463 | |
| 411 464 | @doc """ |
| 412 | - Returns all expanded route signal types from skill routes. |
| 465 | + Returns all expanded route signal types from plugin routes. |
| 413 466 | |
| 414 467 | These are the fully-prefixed signal types that the agent can handle. |
| 415 468 | |
| @@ -420,49 +473,50 @@ defmodule Jido.Agent do | |
| 420 473 | """ |
| 421 474 | @spec signal_types() :: [String.t()] |
| 422 475 | def signal_types do |
| 423 | - @validated_skill_routes |
| 476 | + @validated_plugin_routes |
| 424 477 | |> Enum.map(fn {signal_type, _action, _priority} -> signal_type end) |
| 425 478 | end |
| 426 479 | |
| 427 | - @doc "Returns the expanded and validated skill routes." |
| 428 | - @spec skill_routes() :: [{String.t(), module(), integer()}] |
| 429 | - def skill_routes, do: @validated_skill_routes |
| 480 | + @doc "Returns the expanded and validated plugin routes." |
| 481 | + @spec plugin_routes() :: [{String.t(), module(), integer()}] |
| 482 | + def plugin_routes, do: @validated_plugin_routes |
| 430 483 | |
| 431 | - @doc "Returns the expanded skill schedules." |
| 432 | - @spec skill_schedules() :: [Jido.Skill.Schedules.schedule_spec()] |
| 433 | - def skill_schedules, do: @expanded_skill_schedules |
| 484 | + @doc "Returns the expanded plugin schedules." |
| 485 | + @spec plugin_schedules() :: [Jido.Plugin.Schedules.schedule_spec()] |
| 486 | + def plugin_schedules, do: @expanded_plugin_schedules |
| 434 487 | end |
| 435 488 | end |
| 436 489 | |
| 437 490 | @doc false |
| 438 | - def __quoted_skill_config_accessors__ do |
| 439 | - skill_config_public = __quoted_skill_config_public__() |
| 440 | - skill_config_helpers = __quoted_skill_config_helpers__() |
| 441 | - skill_state_public = __quoted_skill_state_public__() |
| 442 | - skill_state_helpers = __quoted_skill_state_helpers__() |
| 491 | + @spec __quoted_plugin_config_accessors__() :: Macro.t() |
| 492 | + def __quoted_plugin_config_accessors__ do |
| 493 | + plugin_config_public = __quoted_plugin_config_public__() |
| 494 | + plugin_config_helpers = __quoted_plugin_config_helpers__() |
| 495 | + plugin_state_public = __quoted_plugin_state_public__() |
| 496 | + plugin_state_helpers = __quoted_plugin_state_helpers__() |
| 443 497 | |
| 444 498 | quote location: :keep do |
| 445 | - unquote(skill_config_public) |
| 446 | - unquote(skill_config_helpers) |
| 447 | - unquote(skill_state_public) |
| 448 | - unquote(skill_state_helpers) |
| 499 | + unquote(plugin_config_public) |
| 500 | + unquote(plugin_config_helpers) |
| 501 | + unquote(plugin_state_public) |
| 502 | + unquote(plugin_state_helpers) |
| 449 503 | end |
| 450 504 | end |
| 451 505 | |
| 452 | - defp __quoted_skill_config_public__ do |
| 506 | + defp __quoted_plugin_config_public__ do |
| 453 507 | quote location: :keep do |
| 454 508 | @doc """ |
| 455 | - Returns the configuration for a specific skill. |
| 509 | + Returns the configuration for a specific plugin. |
| 456 510 | |
| 457 | - Accepts either a module or a `{module, as_alias}` tuple for multi-instance skills. |
| 511 | + Accepts either a module or a `{module, as_alias}` tuple for multi-instance plugins. |
| 458 512 | """ |
| 459 | - @spec skill_config(module() | {module(), atom()}) :: map() | nil |
| 460 | - def skill_config(skill_mod) when is_atom(skill_mod) do |
| 461 | - __find_skill_config_by_module__(skill_mod) |
| 513 | + @spec plugin_config(module() | {module(), atom()}) :: map() | nil |
| 514 | + def plugin_config(plugin_mod) when is_atom(plugin_mod) do |
| 515 | + __find_plugin_config_by_module__(plugin_mod) |
| 462 516 | end |
| 463 517 | |
| 464 | - def skill_config({skill_mod, as_alias}) when is_atom(skill_mod) and is_atom(as_alias) do |
| 465 | - case Enum.find(@skill_instances, &(&1.module == skill_mod and &1.as == as_alias)) do |
| 518 | + def plugin_config({plugin_mod, as_alias}) when is_atom(plugin_mod) and is_atom(as_alias) do |
| 519 | + case Enum.find(@plugin_instances, &(&1.module == plugin_mod and &1.as == as_alias)) do |
| 466 520 | nil -> nil |
| 467 521 | instance -> instance.config |
| 468 522 | end |
| @@ -470,17 +524,17 @@ defmodule Jido.Agent do | |
| 470 524 | end |
| 471 525 | end |
| 472 526 | |
| 473 | - defp __quoted_skill_config_helpers__ do |
| 527 | + defp __quoted_plugin_config_helpers__ do |
| 474 528 | quote location: :keep do |
| 475 | - defp __find_skill_config_by_module__(skill_mod) do |
| 476 | - case Enum.find(@skill_instances, &(&1.module == skill_mod and is_nil(&1.as))) do |
| 477 | - nil -> __find_skill_config_fallback__(skill_mod) |
| 529 | + defp __find_plugin_config_by_module__(plugin_mod) do |
| 530 | + case Enum.find(@plugin_instances, &(&1.module == plugin_mod and is_nil(&1.as))) do |
| 531 | + nil -> __find_plugin_config_fallback__(plugin_mod) |
| 478 532 | instance -> instance.config |
| 479 533 | end |
| 480 534 | end |
| 481 535 | |
| 482 | - defp __find_skill_config_fallback__(skill_mod) do |
| 483 | - case Enum.find(@skill_instances, &(&1.module == skill_mod)) do |
| 536 | + defp __find_plugin_config_fallback__(plugin_mod) do |
| 537 | + case Enum.find(@plugin_instances, &(&1.module == plugin_mod)) do |
| 484 538 | nil -> nil |
| 485 539 | instance -> instance.config |
| 486 540 | end |
| @@ -488,21 +542,21 @@ defmodule Jido.Agent do | |
| 488 542 | end |
| 489 543 | end |
| 490 544 | |
| 491 | - defp __quoted_skill_state_public__ do |
| 545 | + defp __quoted_plugin_state_public__ do |
| 492 546 | quote location: :keep do |
| 493 547 | @doc """ |
| 494 | - Returns the state slice for a specific skill. |
| 548 | + Returns the state slice for a specific plugin. |
| 495 549 | |
| 496 | - Accepts either a module or a `{module, as_alias}` tuple for multi-instance skills. |
| 550 | + Accepts either a module or a `{module, as_alias}` tuple for multi-instance plugins. |
| 497 551 | """ |
| 498 | - @spec skill_state(Agent.t(), module() | {module(), atom()}) :: map() | nil |
| 499 | - def skill_state(agent, skill_mod) when is_atom(skill_mod) do |
| 500 | - __find_skill_state_by_module__(agent, skill_mod) |
| 552 | + @spec plugin_state(Agent.t(), module() | {module(), atom()}) :: map() | nil |
| 553 | + def plugin_state(agent, plugin_mod) when is_atom(plugin_mod) do |
| 554 | + __find_plugin_state_by_module__(agent, plugin_mod) |
| 501 555 | end |
| 502 556 | |
| 503 | - def skill_state(agent, {skill_mod, as_alias}) |
| 504 | - when is_atom(skill_mod) and is_atom(as_alias) do |
| 505 | - case Enum.find(@skill_instances, &(&1.module == skill_mod and &1.as == as_alias)) do |
| 557 | + def plugin_state(agent, {plugin_mod, as_alias}) |
| 558 | + when is_atom(plugin_mod) and is_atom(as_alias) do |
| 559 | + case Enum.find(@plugin_instances, &(&1.module == plugin_mod and &1.as == as_alias)) do |
| 506 560 | nil -> nil |
| 507 561 | instance -> Map.get(agent.state, instance.state_key) |
| 508 562 | end |
| @@ -510,17 +564,17 @@ defmodule Jido.Agent do | |
| 510 564 | end |
| 511 565 | end |
| 512 566 | |
| 513 | - defp __quoted_skill_state_helpers__ do |
| 567 | + defp __quoted_plugin_state_helpers__ do |
| 514 568 | quote location: :keep do |
| 515 | - defp __find_skill_state_by_module__(agent, skill_mod) do |
| 516 | - case Enum.find(@skill_instances, &(&1.module == skill_mod and is_nil(&1.as))) do |
| 517 | - nil -> __find_skill_state_fallback__(agent, skill_mod) |
| 569 | + defp __find_plugin_state_by_module__(agent, plugin_mod) do |
| 570 | + case Enum.find(@plugin_instances, &(&1.module == plugin_mod and is_nil(&1.as))) do |
| 571 | + nil -> __find_plugin_state_fallback__(agent, plugin_mod) |
| 518 572 | instance -> Map.get(agent.state, instance.state_key) |
| 519 573 | end |
| 520 574 | end |
| 521 575 | |
| 522 | - defp __find_skill_state_fallback__(agent, skill_mod) do |
| 523 | - case Enum.find(@skill_instances, &(&1.module == skill_mod)) do |
| 576 | + defp __find_plugin_state_fallback__(agent, plugin_mod) do |
| 577 | + case Enum.find(@plugin_instances, &(&1.module == plugin_mod)) do |
| 524 578 | nil -> nil |
| 525 579 | instance -> Map.get(agent.state, instance.state_key) |
| 526 580 | end |
| @@ -529,6 +583,7 @@ defmodule Jido.Agent do | |
| 529 583 | end |
| 530 584 | |
| 531 585 | @doc false |
| 586 | + @spec __quoted_strategy_accessors__() :: Macro.t() |
| 532 587 | def __quoted_strategy_accessors__ do |
| 533 588 | quote location: :keep do |
| 534 589 | @doc "Returns the execution strategy module for this agent." |
| @@ -552,13 +607,14 @@ defmodule Jido.Agent do | |
| 552 607 | end |
| 553 608 | |
| 554 609 | @doc false |
| 610 | + @spec __quoted_new_function__() :: Macro.t() |
| 555 611 | def __quoted_new_function__ do |
| 556 612 | new_fn = __quoted_new_fn_definition__() |
| 557 | - mount_skills_fn = __quoted_mount_skills_definition__() |
| 613 | + mount_plugins_fn = __quoted_mount_plugins_definition__() |
| 558 614 | |
| 559 615 | quote location: :keep do |
| 560 616 | unquote(new_fn) |
| 561 | - unquote(mount_skills_fn) |
| 617 | + unquote(mount_plugins_fn) |
| 562 618 | end |
| 563 619 | end |
| 564 620 | |
| @@ -595,8 +651,8 @@ defmodule Jido.Agent do | |
| 595 651 | state: initial_state |
| 596 652 | } |
| 597 653 | |
| 598 | - # Run skill mount hooks (pure initialization) |
| 599 | - agent = __mount_skills__(agent) |
| 654 | + # Run plugin mount hooks (pure initialization) |
| 655 | + agent = __mount_plugins__(agent) |
| 600 656 | |
| 601 657 | # Run strategy initialization (directives are dropped here; |
| 602 658 | # AgentServer handles init directives separately) |
| @@ -609,39 +665,39 @@ defmodule Jido.Agent do | |
| 609 665 | # Build initial state from base schema defaults |
| 610 666 | base_defaults = AgentState.defaults_from_schema(@validated_opts[:schema]) |
| 611 667 | |
| 612 | - # Build skill defaults nested under their state_keys |
| 613 | - skill_defaults = |
| 614 | - @skill_specs |
| 668 | + # Build plugin defaults nested under their state_keys |
| 669 | + plugin_defaults = |
| 670 | + @plugin_specs |
| 615 671 | |> Enum.map(fn spec -> |
| 616 | - skill_state_defaults = Jido.Agent.Schema.defaults_from_zoi_schema(spec.schema) |
| 617 | - {spec.state_key, skill_state_defaults} |
| 672 | + plugin_state_defaults = Jido.Agent.Schema.defaults_from_zoi_schema(spec.schema) |
| 673 | + {spec.state_key, plugin_state_defaults} |
| 618 674 | end) |
| 619 675 | |> Map.new() |
| 620 676 | |
| 621 | - # Merge: base defaults + skill defaults + provided state |
| 622 | - schema_defaults = Map.merge(base_defaults, skill_defaults) |
| 677 | + # Merge: base defaults + plugin defaults + provided state |
| 678 | + schema_defaults = Map.merge(base_defaults, plugin_defaults) |
| 623 679 | Map.merge(schema_defaults, opts[:state] || %{}) |
| 624 680 | end |
| 625 681 | end |
| 626 682 | end |
| 627 683 | |
| 628 | - defp __quoted_mount_skills_definition__ do |
| 684 | + defp __quoted_mount_plugins_definition__ do |
| 629 685 | quote location: :keep do |
| 630 | - defp __mount_skills__(agent) do |
| 631 | - Enum.reduce(@skill_specs, agent, fn spec, agent_acc -> |
| 632 | - __mount_single_skill__(agent_acc, spec) |
| 686 | + defp __mount_plugins__(agent) do |
| 687 | + Enum.reduce(@plugin_specs, agent, fn spec, agent_acc -> |
| 688 | + __mount_single_plugin__(agent_acc, spec) |
| 633 689 | end) |
| 634 690 | end |
| 635 691 | |
| 636 | - defp __mount_single_skill__(agent_acc, spec) do |
| 692 | + defp __mount_single_plugin__(agent_acc, spec) do |
| 637 693 | mod = spec.module |
| 638 694 | config = spec.config || %{} |
| 639 695 | |
| 640 696 | case mod.mount(agent_acc, config) do |
| 641 | - {:ok, skill_state} when is_map(skill_state) -> |
| 642 | - current_skill_state = Map.get(agent_acc.state, spec.state_key, %{}) |
| 643 | - merged_skill_state = Map.merge(current_skill_state, skill_state) |
| 644 | - new_state = Map.put(agent_acc.state, spec.state_key, merged_skill_state) |
| 697 | + {:ok, plugin_state} when is_map(plugin_state) -> |
| 698 | + current_plugin_state = Map.get(agent_acc.state, spec.state_key, %{}) |
| 699 | + merged_plugin_state = Map.merge(current_plugin_state, plugin_state) |
| 700 | + new_state = Map.put(agent_acc.state, spec.state_key, merged_plugin_state) |
| 645 701 | %{agent_acc | state: new_state} |
| 646 702 | |
| 647 703 | {:ok, nil} -> |
| @@ -649,8 +705,8 @@ defmodule Jido.Agent do | |
| 649 705 | |
| 650 706 | {:error, reason} -> |
| 651 707 | raise Jido.Error.internal_error( |
| 652 | - "Skill mount failed for #{inspect(mod)}", |
| 653 | - %{skill: mod, reason: reason} |
| 708 | + "Plugin mount failed for #{inspect(mod)}", |
| 709 | + %{plugin: mod, reason: reason} |
| 654 710 | ) |
| 655 711 | end |
| 656 712 | end |
| @@ -658,6 +714,7 @@ defmodule Jido.Agent do | |
| 658 714 | end |
| 659 715 | |
| 660 716 | @doc false |
| 717 | + @spec __quoted_cmd_function__() :: Macro.t() |
| 661 718 | def __quoted_cmd_function__ do |
| 662 719 | quote location: :keep do |
| 663 720 | @doc """ |
| @@ -670,20 +727,36 @@ defmodule Jido.Agent do | |
| 670 727 | |
| 671 728 | * `MyAction` - Action module with no params |
| 672 729 | * `{MyAction, %{param: 1}}` - Action with params |
| 730 | + * `{MyAction, %{param: 1}, %{context: data}}` - Action with params and context |
| 731 | + * `{MyAction, %{param: 1}, %{}, [timeout: 1000]}` - Action with opts |
| 673 732 | * `%Instruction{}` - Full instruction struct |
| 674 733 | * `[...]` - List of any of the above (processed in sequence) |
| 675 734 | |
| 735 | + ## Options |
| 736 | + |
| 737 | + The optional third argument `opts` is a keyword list merged into all instructions: |
| 738 | + |
| 739 | + * `:timeout` - Maximum time (in ms) for each action to complete |
| 740 | + * `:max_retries` - Maximum retry attempts on failure |
| 741 | + * `:backoff` - Initial backoff time in ms (doubles with each retry) |
| 742 | + |
| 676 743 | ## Examples |
| 677 744 | |
| 678 745 | {agent, directives} = #{inspect(__MODULE__)}.cmd(agent, MyAction) |
| 679 746 | {agent, directives} = #{inspect(__MODULE__)}.cmd(agent, {MyAction, %{value: 42}}) |
| 680 747 | {agent, directives} = #{inspect(__MODULE__)}.cmd(agent, [Action1, Action2]) |
| 748 | + |
| 749 | + # With per-call options (merged into all instructions) |
| 750 | + {agent, directives} = #{inspect(__MODULE__)}.cmd(agent, MyAction, timeout: 5000) |
| 681 751 | """ |
| 682 752 | @spec cmd(Agent.t(), Agent.action()) :: Agent.cmd_result() |
| 683 | - def cmd(%Agent{} = agent, action) do |
| 753 | + def cmd(%Agent{} = agent, action), do: cmd(agent, action, []) |
| 754 | + |
| 755 | + @spec cmd(Agent.t(), Agent.action(), keyword()) :: Agent.cmd_result() |
| 756 | + def cmd(%Agent{} = agent, action, opts) when is_list(opts) do |
| 684 757 | {:ok, agent, action} = on_before_cmd(agent, action) |
| 685 758 | |
| 686 | - case Instruction.normalize(action, %{state: agent.state}, []) do |
| 759 | + case Instruction.normalize(action, %{state: agent.state}, opts) do |
| 687 760 | {:ok, instructions} -> |
| 688 761 | ctx = %{agent_module: __MODULE__, strategy_opts: strategy_opts()} |
| 689 762 | strat = strategy() |
| @@ -705,6 +778,7 @@ defmodule Jido.Agent do | |
| 705 778 | end |
| 706 779 | |
| 707 780 | @doc false |
| 781 | + @spec __quoted_utility_functions__() :: Macro.t() |
| 708 782 | def __quoted_utility_functions__ do |
| 709 783 | quote location: :keep do |
| 710 784 | @doc """ |
| @@ -765,22 +839,88 @@ defmodule Jido.Agent do | |
| 765 839 | end |
| 766 840 | |
| 767 841 | @doc false |
| 842 | + @spec __quoted_callbacks__() :: Macro.t() |
| 768 843 | def __quoted_callbacks__ do |
| 844 | + before_after = __quoted_callback_before_after__() |
| 845 | + routes = __quoted_callback_routes__() |
| 846 | + checkpoint = __quoted_callback_checkpoint__() |
| 847 | + restore = __quoted_callback_restore__() |
| 848 | + overridables = __quoted_callback_overridables__() |
| 849 | + helpers = __quoted_callback_helpers__() |
| 850 | + |
| 851 | + quote location: :keep do |
| 852 | + unquote(before_after) |
| 853 | + unquote(routes) |
| 854 | + unquote(checkpoint) |
| 855 | + unquote(restore) |
| 856 | + unquote(overridables) |
| 857 | + unquote(helpers) |
| 858 | + end |
| 859 | + end |
| 860 | + |
| 861 | + defp __quoted_callback_before_after__ do |
| 769 862 | quote location: :keep do |
| 770 863 | # Default callback implementations |
| 771 864 | |
| 865 | + @impl true |
| 772 866 | @spec on_before_cmd(Agent.t(), Agent.action()) :: {:ok, Agent.t(), Agent.action()} |
| 773 867 | def on_before_cmd(agent, action), do: {:ok, agent, action} |
| 774 868 | |
| 869 | + @impl true |
| 775 870 | @spec on_after_cmd(Agent.t(), Agent.action(), [Agent.directive()]) :: |
| 776 871 | {:ok, Agent.t(), [Agent.directive()]} |
| 777 872 | def on_after_cmd(agent, _action, directives), do: {:ok, agent, directives} |
| 873 | + end |
| 874 | + end |
| 778 875 | |
| 876 | + defp __quoted_callback_routes__ do |
| 877 | + quote location: :keep do |
| 878 | + @impl true |
| 779 879 | @spec signal_routes() :: list() |
| 780 880 | def signal_routes, do: [] |
| 881 | + end |
| 882 | + end |
| 781 883 | |
| 884 | + defp __quoted_callback_checkpoint__ do |
| 885 | + quote location: :keep do |
| 886 | + @impl true |
| 887 | + def checkpoint(agent, _ctx) do |
| 888 | + thread = agent.state[:__thread__] |
| 889 | + |
| 890 | + {:ok, |
| 891 | + %{ |
| 892 | + version: 1, |
| 893 | + agent_module: __MODULE__, |
| 894 | + id: agent.id, |
| 895 | + state: Map.delete(agent.state, :__thread__), |
| 896 | + thread: thread && %{id: thread.id, rev: thread.rev} |
| 897 | + }} |
| 898 | + end |
| 899 | + end |
| 900 | + end |
| 901 | + |
| 902 | + defp __quoted_callback_restore__ do |
| 903 | + quote location: :keep do |
| 904 | + @impl true |
| 905 | + def restore(data, _ctx) do |
| 906 | + case new(id: data[:id] || data["id"]) do |
| 907 | + {:ok, agent} -> |
| 908 | + state = data[:state] || data["state"] || %{} |
| 909 | + {:ok, %{agent | state: Map.merge(agent.state, state)}} |
| 910 | + |
| 911 | + error -> |
| 912 | + error |
| 913 | + end |
| 914 | + end |
| 915 | + end |
| 916 | + end |
| 917 | + |
| 918 | + defp __quoted_callback_overridables__ do |
| 919 | + quote location: :keep do |
| 782 920 | defoverridable on_before_cmd: 2, |
| 783 921 | on_after_cmd: 3, |
| 922 | + checkpoint: 2, |
| 923 | + restore: 2, |
| 784 924 | signal_routes: 0, |
| 785 925 | name: 0, |
| 786 926 | description: 0, |
| @@ -790,17 +930,21 @@ defmodule Jido.Agent do | |
| 790 930 | schema: 0, |
| 791 931 | strategy: 0, |
| 792 932 | strategy_opts: 0, |
| 793 | - skills: 0, |
| 794 | - skill_specs: 0, |
| 795 | - skill_instances: 0, |
| 933 | + plugins: 0, |
| 934 | + plugin_specs: 0, |
| 935 | + plugin_instances: 0, |
| 796 936 | actions: 0, |
| 797 937 | capabilities: 0, |
| 798 938 | signal_types: 0, |
| 799 | - skill_config: 1, |
| 800 | - skill_state: 2, |
| 801 | - skill_routes: 0, |
| 802 | - skill_schedules: 0 |
| 939 | + plugin_config: 1, |
| 940 | + plugin_state: 2, |
| 941 | + plugin_routes: 0, |
| 942 | + plugin_schedules: 0 |
| 943 | + end |
| 944 | + end |
| 803 945 | |
| 946 | + defp __quoted_callback_helpers__ do |
| 947 | + quote location: :keep do |
| 804 948 | # Private helper for after hook dispatch |
| 805 949 | defp __do_after_cmd__(agent, msg, directives) do |
| 806 950 | {:ok, agent, directives} = on_after_cmd(agent, msg, directives) |
| @@ -813,8 +957,8 @@ defmodule Jido.Agent do | |
| 813 957 | # Get the quoted blocks from helper functions |
| 814 958 | module_setup = Agent.__quoted_module_setup__() |
| 815 959 | basic_accessors = Agent.__quoted_basic_accessors__() |
| 816 | - skill_accessors = Agent.__quoted_skill_accessors__() |
| 817 | - skill_config_accessors = Agent.__quoted_skill_config_accessors__() |
| 960 | + plugin_accessors = Agent.__quoted_plugin_accessors__() |
| 961 | + plugin_config_accessors = Agent.__quoted_plugin_config_accessors__() |
| 818 962 | strategy_accessors = Agent.__quoted_strategy_accessors__() |
| 819 963 | new_function = Agent.__quoted_new_function__() |
| 820 964 | cmd_function = Agent.__quoted_cmd_function__() |
| @@ -839,62 +983,70 @@ defmodule Jido.Agent do | |
| 839 983 | line: __ENV__.line |
| 840 984 | end) |
| 841 985 | |
| 842 | - # Normalize skills to Instance structs |
| 843 | - @skill_instances Jido.Agent.__normalize_skill_instances__(@validated_opts[:skills] || []) |
| 986 | + # Normalize plugins to Instance structs |
| 987 | + @plugin_instances Jido.Agent.__normalize_plugin_instances__( |
| 988 | + @validated_opts[:plugins] || [] |
| 989 | + ) |
| 844 990 | |
| 845 | - # Build skill specs from instances (for backward compatibility) |
| 846 | - @skill_specs Enum.map(@skill_instances, fn instance -> |
| 847 | - instance.module.skill_spec(instance.config) |
| 848 | - |> Map.put(:state_key, instance.state_key) |
| 849 | - end) |
| 991 | + # Build plugin specs from instances (for backward compatibility) |
| 992 | + @plugin_specs Enum.map(@plugin_instances, fn instance -> |
| 993 | + instance.module.plugin_spec(instance.config) |
| 994 | + |> Map.put(:state_key, instance.state_key) |
| 995 | + end) |
| 850 996 | |
| 851 997 | # Validate unique state_keys (now derived from instances) |
| 852 | - @skill_state_keys Enum.map(@skill_instances, & &1.state_key) |
| 853 | - @duplicate_keys @skill_state_keys -- Enum.uniq(@skill_state_keys) |
| 998 | + @plugin_state_keys Enum.map(@plugin_instances, & &1.state_key) |
| 999 | + @duplicate_keys @plugin_state_keys -- Enum.uniq(@plugin_state_keys) |
| 854 1000 | if @duplicate_keys != [] do |
| 855 1001 | raise CompileError, |
| 856 | - description: "Duplicate skill state_keys: #{inspect(@duplicate_keys)}", |
| 1002 | + description: "Duplicate plugin state_keys: #{inspect(@duplicate_keys)}", |
| 857 1003 | file: __ENV__.file, |
| 858 1004 | line: __ENV__.line |
| 859 1005 | end |
| 860 1006 | |
| 861 1007 | # Validate no collision with base schema keys |
| 862 1008 | @base_schema_keys Jido.Agent.Schema.known_keys(@validated_opts[:schema]) |
| 863 | - @colliding_keys Enum.filter(@skill_state_keys, &(&1 in @base_schema_keys)) |
| 1009 | + @colliding_keys Enum.filter(@plugin_state_keys, &(&1 in @base_schema_keys)) |
| 864 1010 | if @colliding_keys != [] do |
| 865 1011 | raise CompileError, |
| 866 1012 | description: |
| 867 | - "Skill state_keys collide with agent schema: #{inspect(@colliding_keys)}", |
| 1013 | + "Plugin state_keys collide with agent schema: #{inspect(@colliding_keys)}", |
| 868 1014 | file: __ENV__.file, |
| 869 1015 | line: __ENV__.line |
| 870 1016 | end |
| 871 1017 | |
| 872 | - # Merge schemas: base schema + nested skill schemas |
| 873 | - @merged_schema Jido.Agent.Schema.merge_with_skills( |
| 1018 | + # Merge schemas: base schema + nested plugin schemas |
| 1019 | + @merged_schema Jido.Agent.Schema.merge_with_plugins( |
| 874 1020 | @validated_opts[:schema], |
| 875 | - @skill_specs |
| 1021 | + @plugin_specs |
| 876 1022 | ) |
| 877 1023 | |
| 878 | - # Aggregate actions from skills |
| 879 | - @skill_actions @skill_specs |> Enum.flat_map(& &1.actions) |> Enum.uniq() |
| 1024 | + # Aggregate actions from plugins |
| 1025 | + @plugin_actions @plugin_specs |> Enum.flat_map(& &1.actions) |> Enum.uniq() |
| 880 1026 | |
| 881 | - # Expand routes from all skill instances |
| 882 | - @expanded_skill_routes Enum.flat_map(@skill_instances, &Jido.Skill.Routes.expand_routes/1) |
| 1027 | + # Expand routes from all plugin instances |
| 1028 | + @expanded_plugin_routes Enum.flat_map( |
| 1029 | + @plugin_instances, |
| 1030 | + &Jido.Plugin.Routes.expand_routes/1 |
| 1031 | + ) |
| 883 1032 | |
| 884 | - # Expand schedules from all skill instances |
| 885 | - @expanded_skill_schedules Enum.flat_map( |
| 886 | - @skill_instances, |
| 887 | - &Jido.Skill.Schedules.expand_schedules/1 |
| 888 | - ) |
| 1033 | + # Expand schedules from all plugin instances |
| 1034 | + @expanded_plugin_schedules Enum.flat_map( |
| 1035 | + @plugin_instances, |
| 1036 | + &Jido.Plugin.Schedules.expand_schedules/1 |
| 1037 | + ) |
| 889 1038 | |
| 890 1039 | # Generate routes for schedule signal types (low priority) |
| 891 | - @schedule_routes Enum.flat_map(@skill_instances, &Jido.Skill.Schedules.schedule_routes/1) |
| 1040 | + @schedule_routes Enum.flat_map( |
| 1041 | + @plugin_instances, |
| 1042 | + &Jido.Plugin.Schedules.schedule_routes/1 |
| 1043 | + ) |
| 892 1044 | |
| 893 1045 | # Combine routes and schedule routes for conflict detection |
| 894 | - @all_skill_routes @expanded_skill_routes ++ @schedule_routes |
| 1046 | + @all_plugin_routes @expanded_plugin_routes ++ @schedule_routes |
| 895 1047 | |
| 896 | - @skill_routes_result Jido.Skill.Routes.detect_conflicts(@all_skill_routes) |
| 897 | - case @skill_routes_result do |
| 1048 | + @plugin_routes_result Jido.Plugin.Routes.detect_conflicts(@all_plugin_routes) |
| 1049 | + case @plugin_routes_result do |
| 898 1050 | {:error, conflicts} -> |
| 899 1051 | conflict_list = Enum.join(conflicts, "\n - ") |
| 900 1052 | |
| @@ -907,19 +1059,19 @@ defmodule Jido.Agent do | |
| 907 1059 | :ok |
| 908 1060 | end |
| 909 1061 | |
| 910 | - @validated_skill_routes elem(@skill_routes_result, 1) |
| 1062 | + @validated_plugin_routes elem(@plugin_routes_result, 1) |
| 911 1063 | |
| 912 | - # Validate skill requirements at compile time |
| 913 | - @skill_config_map Enum.reduce(@skill_instances, %{}, fn instance, acc -> |
| 914 | - Map.put(acc, instance.state_key, instance.config) |
| 915 | - end) |
| 916 | - @requirements_result Jido.Skill.Requirements.validate_all_requirements( |
| 917 | - @skill_instances, |
| 918 | - @skill_config_map |
| 1064 | + # Validate plugin requirements at compile time |
| 1065 | + @plugin_config_map Enum.reduce(@plugin_instances, %{}, fn instance, acc -> |
| 1066 | + Map.put(acc, instance.state_key, instance.config) |
| 1067 | + end) |
| 1068 | + @requirements_result Jido.Plugin.Requirements.validate_all_requirements( |
| 1069 | + @plugin_instances, |
| 1070 | + @plugin_config_map |
| 919 1071 | ) |
| 920 1072 | case @requirements_result do |
| 921 | - {:error, missing_by_skill} -> |
| 922 | - error_msg = SkillRequirements.format_error(missing_by_skill) |
| 1073 | + {:error, missing_by_plugin} -> |
| 1074 | + error_msg = PluginRequirements.format_error(missing_by_plugin) |
| 923 1075 | |
| 924 1076 | raise CompileError, |
| 925 1077 | description: error_msg, |
| @@ -936,8 +1088,8 @@ defmodule Jido.Agent do | |
| 936 1088 | unquote(module_setup) |
| 937 1089 | unquote(compile_time_setup) |
| 938 1090 | unquote(basic_accessors) |
| 939 | - unquote(skill_accessors) |
| 940 | - unquote(skill_config_accessors) |
| 1091 | + unquote(plugin_accessors) |
| 1092 | + unquote(plugin_config_accessors) |
| 941 1093 | unquote(strategy_accessors) |
| 942 1094 | unquote(new_function) |
| 943 1095 | unquote(cmd_function) |
| @@ -947,36 +1099,37 @@ defmodule Jido.Agent do | |
| 947 1099 | end |
| 948 1100 | |
| 949 1101 | @doc false |
| 950 | - def __normalize_skill_instances__(skills) do |
| 951 | - Enum.map(skills, &__validate_and_create_skill_instance__/1) |
| 1102 | + @spec __normalize_plugin_instances__([module() | {module(), map()}]) :: [PluginInstance.t()] |
| 1103 | + def __normalize_plugin_instances__(plugins) do |
| 1104 | + Enum.map(plugins, &__validate_and_create_plugin_instance__/1) |
| 952 1105 | end |
| 953 1106 | |
| 954 | - defp __validate_and_create_skill_instance__(skill_decl) do |
| 955 | - mod = __extract_skill_module__(skill_decl) |
| 956 | - __validate_skill_module__(mod) |
| 957 | - SkillInstance.new(skill_decl) |
| 1107 | + defp __validate_and_create_plugin_instance__(plugin_decl) do |
| 1108 | + mod = __extract_plugin_module__(plugin_decl) |
| 1109 | + __validate_plugin_module__(mod) |
| 1110 | + PluginInstance.new(plugin_decl) |
| 958 1111 | end |
| 959 1112 | |
| 960 | - defp __extract_skill_module__(m) when is_atom(m), do: m |
| 961 | - defp __extract_skill_module__({m, _}), do: m |
| 1113 | + defp __extract_plugin_module__(m) when is_atom(m), do: m |
| 1114 | + defp __extract_plugin_module__({m, _}), do: m |
| 962 1115 | |
| 963 | - defp __validate_skill_module__(mod) do |
| 1116 | + defp __validate_plugin_module__(mod) do |
| 964 1117 | case Code.ensure_compiled(mod) do |
| 965 | - {:module, _} -> __validate_skill_behaviour__(mod) |
| 966 | - {:error, reason} -> __raise_skill_compile_error__(mod, reason) |
| 1118 | + {:module, _} -> __validate_plugin_behaviour__(mod) |
| 1119 | + {:error, reason} -> __raise_plugin_compile_error__(mod, reason) |
| 967 1120 | end |
| 968 1121 | end |
| 969 1122 | |
| 970 | - defp __validate_skill_behaviour__(mod) do |
| 971 | - unless function_exported?(mod, :skill_spec, 1) do |
| 1123 | + defp __validate_plugin_behaviour__(mod) do |
| 1124 | + unless function_exported?(mod, :plugin_spec, 1) do |
| 972 1125 | raise CompileError, |
| 973 | - description: "#{inspect(mod)} does not implement Jido.Skill (missing skill_spec/1)" |
| 1126 | + description: "#{inspect(mod)} does not implement Jido.Plugin (missing plugin_spec/1)" |
| 974 1127 | end |
| 975 1128 | end |
| 976 1129 | |
| 977 | - defp __raise_skill_compile_error__(mod, reason) do |
| 1130 | + defp __raise_plugin_compile_error__(mod, reason) do |
| 978 1131 | raise CompileError, |
| 979 | - description: "Skill #{inspect(mod)} could not be compiled: #{inspect(reason)}" |
| 1132 | + description: "Plugin #{inspect(mod)} could not be compiled: #{inspect(reason)}" |
| 980 1133 | end |
| 981 1134 | |
| 982 1135 | # Base module functions (for direct use without `use`) |
| @@ -1,20 +1,20 @@ | |
| 1 1 | defmodule Jido.Agent.Schema do |
| 2 2 | @moduledoc false |
| 3 | - # Utilities for merging agent and skill schemas. |
| 3 | + # Utilities for merging agent and plugin schemas. |
| 4 4 | # Handles Zoi schema introspection and merging. |
| 5 5 | |
| 6 | - alias Jido.Skill.Spec |
| 6 | + alias Jido.Plugin.Spec |
| 7 7 | |
| 8 8 | @doc """ |
| 9 | - Merges the agent's base schema with skill schemas. |
| 9 | + Merges the agent's base schema with plugin schemas. |
| 10 10 | |
| 11 | - Each skill's schema is nested under its `state_key`. |
| 12 | - Returns a Zoi object schema with base fields + skill fields. |
| 11 | + Each plugin's schema is nested under its `state_key`. |
| 12 | + Returns a Zoi object schema with base fields + plugin fields. |
| 13 13 | |
| 14 14 | ## Examples |
| 15 15 | |
| 16 16 | base = Zoi.object(%{mode: Zoi.atom()}) |
| 17 | - skills = [%Spec{state_key: :calc, schema: Zoi.object(%{x: Zoi.integer()})}] |
| 17 | + plugins = [%Spec{state_key: :calc, schema: Zoi.object(%{x: Zoi.integer()})}] |
| 18 18 | |
| 19 19 | # Returns: |
| 20 20 | # Zoi.object(%{ |
| @@ -22,28 +22,28 @@ defmodule Jido.Agent.Schema do | |
| 22 22 | # calc: Zoi.object(%{x: Zoi.integer()}) |
| 23 23 | # }) |
| 24 24 | """ |
| 25 | - @spec merge_with_skills(any(), [Spec.t()]) :: any() |
| 26 | - def merge_with_skills(nil, []), do: nil |
| 27 | - def merge_with_skills(base_schema, []), do: base_schema |
| 25 | + @spec merge_with_plugins(any(), [Spec.t()]) :: any() |
| 26 | + def merge_with_plugins(nil, []), do: nil |
| 27 | + def merge_with_plugins(base_schema, []), do: base_schema |
| 28 28 | |
| 29 | - def merge_with_skills(base_schema, skill_specs) when is_list(skill_specs) do |
| 30 | - skill_fields = |
| 31 | - skill_specs |
| 29 | + def merge_with_plugins(base_schema, plugin_specs) when is_list(plugin_specs) do |
| 30 | + plugin_fields = |
| 31 | + plugin_specs |
| 32 32 | |> Enum.filter(& &1.schema) |
| 33 33 | |> Enum.map(fn spec -> {spec.state_key, spec.schema} end) |
| 34 34 | |> Map.new() |
| 35 35 | |
| 36 36 | case base_schema do |
| 37 37 | nil -> |
| 38 | - if map_size(skill_fields) == 0 do |
| 38 | + if map_size(plugin_fields) == 0 do |
| 39 39 | nil |
| 40 40 | else |
| 41 | - Zoi.object(skill_fields) |
| 41 | + Zoi.object(plugin_fields) |
| 42 42 | end |
| 43 43 | |
| 44 44 | base -> |
| 45 45 | base_fields = extract_fields(base) |
| 46 | - Zoi.object(Map.merge(base_fields, skill_fields)) |
| 46 | + Zoi.object(Map.merge(base_fields, plugin_fields)) |
| 47 47 | end |
| 48 48 | end |
Loading more files…