Current section

22 Versions

Jump to

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…