Skip to main content

guides/developer/plugins_and_actions_composition.md

# Plugins And Actions Composition

You need a reliable way to compose mountable capabilities (plugins) with executable primitives (actions) without coupling to strategy internals.

After this guide, you can choose the right extension surface for each requirement.

## Pick Your Extension Surface

Jido AI gives you three extension surfaces — **plugins**, **actions**, and **strategies**. Each solves a different problem. Start with the scenario that matches yours.

### Scenario A: You're building a reusable chat capability

You want any agent in your system to gain chat powers by adding one line to its plugin list.

**Reach for a Plugin.**

```elixir
defmodule MyApp.Assistant do
  use Jido.Agent,
    name: "assistant",
    plugins: [
      {Jido.AI.Plugins.Chat, %{default_model: :capable, auto_execute: true}}
    ]
end
```

Plugins give you lifecycle hooks (`mount/2`), capability-level defaults, and stable signal contracts (`chat.message`, `chat.simple`, etc.) that the rest of your app can rely on. When you need the same capability across many agents, plugins are the answer.

### Scenario B: You're adding LLM calls to a background job

You have an Oban worker or a one-off Mix task that needs to call an LLM. You don't need an agent, lifecycle hooks, or signal routing — you just need to run an action and get a result.

**Use an Action directly via `Jido.Exec`.**

```elixir
{:ok, result} =
  Jido.Exec.run(Jido.AI.Actions.Chat.SimpleChat, %{
    model: :fast,
    prompt: "Summarize this support ticket."
  })
```

Actions are the lowest-level primitive. They validate params, call the LLM, and return `{:ok, result}` or `{:error, reason}`. No plugin state, no agent process required.

### Scenario C: You want a custom multi-step reasoning approach

You need chain-of-thought, tree-of-thoughts, or your own bespoke reasoning loop that orchestrates multiple LLM calls with intermediate evaluation.

**Configure a Strategy (and optionally wrap it in a Reasoning plugin).**

```elixir
defmodule MyApp.Analyst do
  use Jido.AI.Agent,
    name: "analyst",
    strategy: {Jido.AI.Reasoning.ReAct.Strategy, [tools: [MyApp.Tools.Search], model: :reasoning]},
    plugins: [
      {Jido.AI.Plugins.Reasoning.ChainOfThought, %{default_model: :reasoning}}
    ]
end
```

Strategies own the multi-step execution loop. Reasoning plugins provide signal routing and defaults on top of strategies so you can trigger them via `reasoning.cot.run` and friends.

### Decision Table

| You need to…                                  | Use a…       | Example                                    |
| --------------------------------------------- | ------------ | ------------------------------------------ |
| Add a reusable capability to multiple agents   | **Plugin**   | `Jido.AI.Plugins.Chat`                     |
| Make a single LLM call in a script or job      | **Action**   | `Jido.Exec.run(SimpleChat, params)`        |
| Orchestrate multi-step reasoning               | **Strategy** | `Jido.AI.Reasoning.ReAct.Strategy`         |
| Expose a capability via stable signal contract | **Plugin**   | `chat.message`, `reasoning.cot.run`        |
| Compose actions without agent lifecycle        | **Action**   | Chain actions with `Jido.Exec`             |
| Customize reasoning defaults per agent         | **Plugin**   | `Jido.AI.Plugins.Reasoning.ChainOfThought` |

---

The rest of this guide covers the contracts and defaults for each surface.

## Public Plugin Surface (v3)

Public capability plugins:

- `Jido.AI.Plugins.Chat`
- `Jido.AI.Plugins.Planning`
- `Jido.AI.Plugins.Reasoning.ChainOfDraft`
- `Jido.AI.Plugins.Reasoning.ChainOfThought`
- `Jido.AI.Plugins.Reasoning.AlgorithmOfThoughts`
- `Jido.AI.Plugins.Reasoning.TreeOfThoughts`
- `Jido.AI.Plugins.Reasoning.GraphOfThoughts`
- `Jido.AI.Plugins.Reasoning.TRM`
- `Jido.AI.Plugins.Reasoning.Adaptive`

Internal runtime plugin:

- `Jido.AI.Plugins.TaskSupervisor` (infrastructure, not a public capability recommendation)

Removed public plugins:

- `Jido.AI.Plugins.LLM`
- `Jido.AI.Plugins.ToolCalling`
- `Jido.AI.Plugins.Reasoning`

## Compose In Agent Definition

```elixir
defmodule MyApp.Assistant do
  use Jido.Agent,
    name: "assistant",
    plugins: [
      {Jido.AI.Plugins.Chat, %{default_model: :capable, auto_execute: true}},
      {Jido.AI.Plugins.Planning, %{}},
      {Jido.AI.Plugins.Reasoning.ChainOfThought, %{default_model: :reasoning}}
    ]
end
```

## Plugin Signal Contracts

- `Jido.AI.Plugins.Chat`
  - `chat.message` -> tool-aware chat (`CallWithTools`) with auto-execute defaulting to `true`
  - `chat.simple` -> direct chat generation
  - `chat.complete` -> completion convenience path
  - `chat.embed` -> embedding generation
  - `chat.generate_object` -> schema-constrained structured output
  - `chat.execute_tool` -> direct tool execution by tool name
  - `chat.list_tools` -> tool inventory for the active chat capability
- `Jido.AI.Plugins.Planning`
  - `planning.plan` -> structured plan generation (`Jido.AI.Actions.Planning.Plan`)
  - `planning.decompose` -> goal decomposition (`Jido.AI.Actions.Planning.Decompose`)
  - `planning.prioritize` -> task ordering (`Jido.AI.Actions.Planning.Prioritize`)
- `Jido.AI.Plugins.Reasoning.*`
  - `reasoning.cod.run`
  - `reasoning.cot.run`
  - `reasoning.aot.run`
  - `reasoning.tot.run`
  - `reasoning.got.run`
  - `reasoning.trm.run`
  - `reasoning.adaptive.run`
  - All route to `Jido.AI.Actions.Reasoning.RunStrategy` with fixed strategy identity.

### TaskSupervisor Internal Runtime Contract

`Jido.AI.Plugins.TaskSupervisor` is internal runtime infrastructure enabled by
default via `Jido.AI.PluginStack.default_plugins/1`. It is not part of the
public capability plugin surface.

State key contract:

- Plugin state key is `:__task_supervisor_skill__`
- `mount/2` stores `%{supervisor: pid}` under `agent.state.__task_supervisor_skill__`
- Runtime directives resolve this supervisor via `Jido.AI.Directive.Helpers.get_task_supervisor/1`

Lifecycle and cleanup contract:

- `mount/2` starts an anonymous `Task.Supervisor` linked to the mounting agent process
- Each agent instance receives its own supervisor PID
- When the owning agent process terminates, the linked supervisor terminates automatically

### ModelRouting Runtime Contract

`Jido.AI.Plugins.ModelRouting` is a cross-cutting runtime plugin (enabled by
default in `Jido.AI.Agent`) that assigns model aliases by signal type when the
caller does not explicitly provide one.

Route precedence:

- Exact route keys win first (`"chat.simple"`)
- Wildcard route keys are fallback (`"reasoning.*.run"`)
- Explicit payload model (`:model` or `"model"`) bypasses plugin routing

Wildcard behavior:

- `*` matches exactly one dot-delimited segment
- `"reasoning.*.run"` matches `"reasoning.cot.run"`
- `"reasoning.*.run"` does not match `"reasoning.cot.worker.run"`

Production-style config shape (using `Jido.Agent` so you can mount/configure
this plugin directly):

```elixir
defmodule MyApp.RoutedAssistant do
  use Jido.Agent,
    name: "routed_assistant",
    strategy: {Jido.AI.Reasoning.ReAct.Strategy, [tools: [MyApp.Tools.Search], model: :fast]},
    plugins: [
      {Jido.AI.Plugins.ModelRouting,
       %{
         routes: %{
           "chat.message" => :capable,
           "chat.simple" => :fast,
           "chat.generate_object" => :thinking,
           "reasoning.*.run" => :reasoning
         }
       }}
    ]
end
```

If you are using `Jido.AI.Agent`, `ModelRouting` is already mounted by default.
Do not add a second `ModelRouting` plugin instance (duplicate state key). Use
`Jido.Agent` when you need custom `ModelRouting` plugin config.

### Policy Runtime Contract

`Jido.AI.Plugins.Policy` is a cross-cutting runtime plugin (enabled by default
in `Jido.AI.Agent`) that hardens request/query inputs and normalizes outbound
result/delta envelopes.

Enforce mode behavior:

- `mode: :enforce` rewrites violating request/query signals to `ai.request.error`
- `mode: :monitor` keeps request/query signals unchanged while still observing
- `block_on_validation_error: true` controls whether validation failures block

Rewrite semantics:

- Enforceable request/query signal types include `chat.*`, `ai.*.query`, and
  `reasoning.*.run`
- Prompt/query fields are validated via `Jido.AI.Validation.validate_prompt/1`
- Violations rewrite to `ai.request.error` with `reason: :policy_violation`

Normalization and sanitization:

- `ai.llm.response` and `ai.tool.result` normalize malformed `data.result` to
  `{:error, %{code: :malformed_result, ...}, []}`
- `ai.llm.delta` strips control bytes from `data.delta` and truncates to
  `max_delta_chars`

Policy hardening config shape (using `Jido.Agent` so you can mount/configure
this plugin directly):

```elixir
defmodule MyApp.PolicyHardenedAssistant do
  use Jido.Agent,
    name: "policy_hardened_assistant",
    strategy: {Jido.AI.Reasoning.ReAct.Strategy, [tools: [MyApp.Tools.Search], model: :fast]},
    plugins: [
      {Jido.AI.Plugins.Policy,
       %{
         mode: :enforce,
         block_on_validation_error: true,
         max_delta_chars: 2_000
       }}
    ]
end
```

If you are using `Jido.AI.Agent`, `Policy` is already mounted by default. Do
not add a second `Policy` plugin instance (duplicate state key). Use
`Jido.Agent` when you need custom `Policy` plugin config.

### Retrieval Runtime Contract

`Jido.AI.Plugins.Retrieval` is an optional cross-cutting runtime plugin that
enriches `chat.message` and `reasoning.*.run` prompts with in-process memory.

Retrieval enrichment lifecycle:

- Read mounted retrieval state (`enabled`, `namespace`, `top_k`, `max_snippet_chars`)
- Skip enrichment when plugin `enabled: false`
- Skip enrichment when payload sets `disable_retrieval: true`
- Resolve query text from `prompt` or `query`
- Recall top-k snippets from namespace memory
- Rewrite prompt with relevant memory block and attach `data.retrieval` metadata

Opt-out behavior:

- Global opt-out: mount plugin with `enabled: false`
- Per-request opt-out: set `disable_retrieval: true` in signal payload

Namespace behavior:

- `namespace` config is used for both enrichment and retrieval action routes
  (`retrieval.upsert`, `retrieval.recall`, `retrieval.clear`)
- If omitted, namespace falls back to agent id, then `"default"`

Retrieval action route contracts:

- `retrieval.upsert` -> `Jido.AI.Actions.Retrieval.UpsertMemory`
  - Required params: `text`
  - Optional params: `id`, `metadata`, `namespace`
  - Returns `%{retrieval: %{namespace, last_upsert}}`
- `retrieval.recall` -> `Jido.AI.Actions.Retrieval.RecallMemory`
  - Required params: `query`
  - Optional params: `top_k` (default `3`), `namespace`
  - Returns `%{retrieval: %{namespace, query, memories, count}}`
- `retrieval.clear` -> `Jido.AI.Actions.Retrieval.ClearMemory`
  - Required params: none
  - Optional params: `namespace`
  - Returns `%{retrieval: %{namespace, cleared}}`

Retrieval plugin config shape:

```elixir
defmodule MyApp.RetrievalEnabledAssistant do
  use Jido.AI.Agent,
    name: "retrieval_enabled_assistant",
    plugins: [
      {Jido.AI.Plugins.Retrieval,
       %{
         enabled: true,
         namespace: "weather_ops",
         top_k: 3,
         max_snippet_chars: 280
       }}
    ]
end

signal =
  Jido.Signal.new!(
    "chat.message",
    %{prompt: "Should I bike to work in Seattle tomorrow?", disable_retrieval: true},
    source: "/cli"
  )
```

### Quota Runtime Contract

`Jido.AI.Plugins.Quota` is an optional cross-cutting runtime plugin that
tracks rolling request/token usage and rejects over-budget requests.

Quota state keys:

- `enabled` toggles budget enforcement
- `scope` selects the quota namespace
- `window_ms` defines rolling budget window duration
- `max_requests` sets per-window request budget (`nil` disables request cap)
- `max_total_tokens` sets per-window token budget (`nil` disables token cap)
- `error_message` sets rejection message for blocked requests

Quota action route contracts:

- `quota.status` -> `Jido.AI.Actions.Quota.GetStatus`
  - Required params: none
  - Optional params: `scope`
  - Returns `%{quota: %{scope, window_ms, usage, limits, remaining, over_budget?}}`
- `quota.reset` -> `Jido.AI.Actions.Quota.Reset`
  - Required params: none
  - Optional params: `scope`
  - Returns `%{quota: %{scope, reset}}`

Scope resolution precedence for quota actions:

1. Explicit action param (`scope`)
2. `context[:plugin_state][:quota][:scope]`
3. `context[:state][:quota][:scope]`
4. `context[:agent][:id]`
5. `"default"`

`GetStatus` context defaults for limits/window:

- `window_ms`: `context[:plugin_state][:quota][:window_ms]` -> `context[:state][:quota][:window_ms]` -> `60_000`
- `max_requests`: `context[:plugin_state][:quota][:max_requests]` -> `context[:state][:quota][:max_requests]` -> `nil`
- `max_total_tokens`: `context[:plugin_state][:quota][:max_total_tokens]` -> `context[:state][:quota][:max_total_tokens]` -> `nil`

Usage accounting contract:

- `ai.usage` increments counters for `requests` and `total_tokens`
- `total_tokens` is preferred, with fallback to `input_tokens + output_tokens`
- Quota status snapshots expose `usage`, `limits`, `remaining`, and `over_budget?`

Budget rejection contract:

- Budgeted signal types include `chat.*`, `ai.*.query`, and `reasoning.*.run`
- When quota is exceeded, requests rewrite to `ai.request.error`
- Rejection payload uses `reason: :quota_exceeded`
- `request_id` is resolved from request correlation fields when present

Quota plugin config shape:

```elixir
defmodule MyApp.QuotaGuardedAssistant do
  use Jido.AI.Agent,
    name: "quota_guarded_assistant",
    plugins: [
      {Jido.AI.Plugins.Quota,
       %{
         enabled: true,
         scope: "assistant_ops",
         window_ms: 60_000,
         max_requests: 50,
         max_total_tokens: 20_000,
         error_message: "quota exceeded for current window"
       }}
    ]
end

signal =
  Jido.Signal.new!(
    "chat.message",
    %{prompt: "Summarize this report in one paragraph.", call_id: "req_123"},
    source: "/cli"
  )

# Expected rewrite shape when over budget:
# %Jido.Signal{
#   type: "ai.request.error",
#   data: %{
#     request_id: "req_123",
#     reason: :quota_exceeded,
#     message: "quota exceeded for current window"
#   }
# }
```

### CoD Plugin Handoff (`reasoning.cod.run`)

```elixir
defmodule MyApp.CoDPluginAgent do
  use Jido.AI.Agent,
    name: "cod_plugin_agent",
    plugins: [
      {Jido.AI.Plugins.Reasoning.ChainOfDraft,
       %{
         default_model: :reasoning,
         timeout: 30_000,
         options: %{llm_timeout_ms: 20_000}
       }}
    ]
end

signal =
  Jido.Signal.new!(
    "reasoning.cod.run",
    %{
      prompt: "Give me a terse plan with one backup.",
      strategy: :cot
    },
    source: "/cli"
  )
```

Execution handoff:

- Route dispatch maps `reasoning.cod.run` to `Jido.AI.Actions.Reasoning.RunStrategy`.
- `Jido.AI.Plugins.Reasoning.ChainOfDraft` overrides payload strategy to `strategy: :cod`.
- `RunStrategy` applies plugin defaults (`default_model`, `timeout`, `options`) from plugin state when omitted by caller params.

### CoT Plugin Handoff (`reasoning.cot.run`)

```elixir
defmodule MyApp.CoTPluginAgent do
  use Jido.AI.Agent,
    name: "cot_plugin_agent",
    plugins: [
      {Jido.AI.Plugins.Reasoning.ChainOfThought,
       %{
         default_model: :reasoning,
         timeout: 30_000,
         options: %{llm_timeout_ms: 20_000}
       }}
    ]
end

signal =
  Jido.Signal.new!(
    "reasoning.cot.run",
    %{
      prompt: "Lay out the reasoning steps and one fallback.",
      strategy: :cod
    },
    source: "/cli"
  )
```

Execution handoff:

- Route dispatch maps `reasoning.cot.run` to `Jido.AI.Actions.Reasoning.RunStrategy`.
- `Jido.AI.Plugins.Reasoning.ChainOfThought` overrides payload strategy to `strategy: :cot`.
- `RunStrategy` applies plugin defaults (`default_model`, `timeout`, `options`) from plugin state when omitted by caller params.

### AoT Plugin Handoff (`reasoning.aot.run`)

```elixir
defmodule MyApp.AoTPluginAgent do
  use Jido.AI.Agent,
    name: "aot_plugin_agent",
    plugins: [
      {Jido.AI.Plugins.Reasoning.AlgorithmOfThoughts,
       %{
         default_model: :reasoning,
         timeout: 30_000,
         options: %{profile: :standard, search_style: :dfs, llm_timeout_ms: 20_000}
       }}
    ]
end

signal =
  Jido.Signal.new!(
    "reasoning.aot.run",
    %{
      prompt: "Solve this with algorithmic steps and one fallback.",
      strategy: :cot,
      options: %{profile: :long, require_explicit_answer: true}
    },
    source: "/cli"
  )
```

Execution handoff:

- Route dispatch maps `reasoning.aot.run` to `Jido.AI.Actions.Reasoning.RunStrategy`.
- `Jido.AI.Plugins.Reasoning.AlgorithmOfThoughts` overrides payload strategy to `strategy: :aot`.
- `RunStrategy` applies plugin defaults (`default_model`, `timeout`, `options`) from plugin state when omitted by caller params.

### ToT Plugin Handoff (`reasoning.tot.run`)

```elixir
defmodule MyApp.ToTPluginAgent do
  use Jido.AI.Agent,
    name: "tot_plugin_agent",
    plugins: [
      {Jido.AI.Plugins.Reasoning.TreeOfThoughts,
       %{
         default_model: :reasoning,
         timeout: 30_000,
         options: %{branching_factor: 3, max_depth: 4, traversal_strategy: :best_first}
       }}
    ]
end

signal =
  Jido.Signal.new!(
    "reasoning.tot.run",
    %{
      prompt: "Explore three weather-safe plans with tradeoffs.",
      strategy: :cot,
      options: %{branching_factor: 4, max_depth: 5}
    },
    source: "/cli"
  )
```

Execution handoff:

- Route dispatch maps `reasoning.tot.run` to `Jido.AI.Actions.Reasoning.RunStrategy`.
- `Jido.AI.Plugins.Reasoning.TreeOfThoughts` overrides payload strategy to `strategy: :tot`.
- `RunStrategy` applies plugin defaults (`default_model`, `timeout`, `options`) from plugin state when omitted by caller params.
- ToT option keys include `branching_factor`, `max_depth`, `traversal_strategy`, `generation_prompt`, and `evaluation_prompt`.

### GoT Plugin Handoff (`reasoning.got.run`)

```elixir
defmodule MyApp.GoTPluginAgent do
  use Jido.AI.Agent,
    name: "got_plugin_agent",
    plugins: [
      {Jido.AI.Plugins.Reasoning.GraphOfThoughts,
       %{
         default_model: :reasoning,
         timeout: 30_000,
         options: %{max_nodes: 20, max_depth: 5, aggregation_strategy: :synthesis}
       }}
    ]
end

signal =
  Jido.Signal.new!(
    "reasoning.got.run",
    %{
      prompt: "Compare three weather scenarios and synthesize one recommendation.",
      strategy: :cot,
      options: %{max_nodes: 25, aggregation_strategy: :weighted}
    },
    source: "/cli"
  )
```

Execution handoff:

- Route dispatch maps `reasoning.got.run` to `Jido.AI.Actions.Reasoning.RunStrategy`.
- `Jido.AI.Plugins.Reasoning.GraphOfThoughts` overrides payload strategy to `strategy: :got`.
- `RunStrategy` applies plugin defaults (`default_model`, `timeout`, `options`) from plugin state when omitted by caller params.
- GoT option keys include `max_nodes`, `max_depth`, `aggregation_strategy`, `generation_prompt`, `connection_prompt`, and `aggregation_prompt`.

### TRM Plugin Handoff (`reasoning.trm.run`)

```elixir
defmodule MyApp.TRMPluginAgent do
  use Jido.AI.Agent,
    name: "trm_plugin_agent",
    plugins: [
      {Jido.AI.Plugins.Reasoning.TRM,
       %{
         default_model: :reasoning,
         timeout: 30_000,
         options: %{max_supervision_steps: 6, act_threshold: 0.92}
       }}
    ]
end

signal =
  Jido.Signal.new!(
    "reasoning.trm.run",
    %{
      prompt: "Recursively improve this emergency plan and stop when confidence is high.",
      strategy: :cot,
      options: %{max_supervision_steps: 7, act_threshold: 0.95}
    },
    source: "/cli"
  )
```

Execution handoff:

- Route dispatch maps `reasoning.trm.run` to `Jido.AI.Actions.Reasoning.RunStrategy`.
- `Jido.AI.Plugins.Reasoning.TRM` overrides payload strategy to `strategy: :trm`.
- `RunStrategy` applies plugin defaults (`default_model`, `timeout`, `options`) from plugin state when omitted by caller params.
- TRM option keys include `max_supervision_steps` and `act_threshold`.

### Adaptive Plugin Handoff (`reasoning.adaptive.run`)

```elixir
defmodule MyApp.AdaptivePluginAgent do
  use Jido.AI.Agent,
    name: "adaptive_plugin_agent",
    plugins: [
      {Jido.AI.Plugins.Reasoning.Adaptive,
       %{
         default_model: :reasoning,
         timeout: 30_000,
         options: %{
           default_strategy: :react,
           available_strategies: [:cod, :cot, :react, :tot, :got, :trm, :aot],
           complexity_thresholds: %{simple: 0.3, complex: 0.7}
         }
       }}
    ]
end

signal =
  Jido.Signal.new!(
    "reasoning.adaptive.run",
    %{
      prompt: "Pick the best strategy and produce a weather-safe commute with one backup.",
      strategy: :cot,
      options: %{default_strategy: :tot}
    },
    source: "/cli"
  )
```

Execution handoff:

- Route dispatch maps `reasoning.adaptive.run` to `Jido.AI.Actions.Reasoning.RunStrategy`.
- `Jido.AI.Plugins.Reasoning.Adaptive` overrides payload strategy to `strategy: :adaptive`.
- `RunStrategy` applies plugin defaults (`default_model`, `timeout`, `options`) from plugin state when omitted by caller params.
- Adaptive option keys include `default_strategy`, `available_strategies`, and `complexity_thresholds`.

## Chat Plugin Defaults Contract

`Jido.AI.Plugins.Chat` mounts the following defaults unless overridden in plugin config:

- `default_model: :capable`
- `default_max_tokens: 4096`
- `default_temperature: 0.7`
- `default_system_prompt: nil`
- `auto_execute: true`
- `max_turns: 10`
- `tool_policy: :allow_all`
- `tools: %{}` (normalized from configured tool modules)
- `available_tools: []`

## Tool Registry Precedence Contract

Tool-calling actions (`CallWithTools`, `ExecuteTool`, `ListTools`) resolve tool maps with this precedence:

1. `context[:tools]`
2. `context[:tool_calling][:tools]`
3. `context[:chat][:tools]`
4. `context[:state][:tool_calling][:tools]`
5. `context[:state][:chat][:tools]`
6. `context[:agent][:state][:tool_calling][:tools]`
7. `context[:agent][:state][:chat][:tools]`
8. `context[:plugin_state][:tool_calling][:tools]`
9. `context[:plugin_state][:chat][:tools]`

First non-`nil` tool map wins. This keeps direct `Jido.Exec` action calls and plugin-routed calls deterministic.

`ListTools` security filtering defaults:

- sensitive names are excluded by default
- `include_sensitive: true` disables sensitive-name filtering
- `allowed_tools: [...]` applies an allowlist after sensitive filtering unless `include_sensitive: true` is set

## Planning Plugin Defaults Contract

`Jido.AI.Plugins.Planning` mounts the following defaults unless overridden in plugin config:

- `default_model: :planning`
- `default_max_tokens: 4096`
- `default_temperature: 0.7`

Planning actions consume these plugin defaults when the caller omits those params.
Action-specific fields remain action-owned:

- `Plan`: `goal`, optional `constraints`/`resources`, optional `max_steps`
- `Decompose`: `goal`, optional `max_depth`, optional `context`
- `Prioritize`: `tasks`, optional `criteria`, optional `context`

Planning selection guidance:

- Use `Plan` when you need a sequential execution plan from one goal.
- Use `Decompose` when the goal is too large and should be split into hierarchical sub-goals.
- Use `Prioritize` when you already have a task list and need ranked execution order.

## Model Routing Plugin Defaults Contract

`Jido.AI.Plugins.ModelRouting` mounts default routes unless overridden by plugin
config:

- `"chat.message" => :capable`
- `"chat.simple" => :fast`
- `"chat.complete" => :fast`
- `"chat.embed" => :embedding`
- `"chat.generate_object" => :thinking`
- `"reasoning.*.run" => :reasoning`

Exact routes take precedence over wildcard routes. Wildcards use a
single-segment `*` matcher between dots.

## Reasoning CoT Plugin Defaults Contract

`Jido.AI.Plugins.Reasoning.ChainOfThought` mounts the following defaults unless overridden in plugin config:

- `strategy: :cot`
- `default_model: :reasoning`
- `timeout: 30_000`
- `options: %{}`

## Reasoning AoT Plugin Defaults Contract

`Jido.AI.Plugins.Reasoning.AlgorithmOfThoughts` mounts the following defaults unless overridden in plugin config:

- `strategy: :aot`
- `default_model: :reasoning`
- `timeout: 30_000`
- `options: %{}`

## Reasoning ToT Plugin Defaults Contract

`Jido.AI.Plugins.Reasoning.TreeOfThoughts` mounts the following defaults unless overridden in plugin config:

- `strategy: :tot`
- `default_model: :reasoning`
- `timeout: 30_000`
- `options: %{}`

## Reasoning GoT Plugin Defaults Contract

`Jido.AI.Plugins.Reasoning.GraphOfThoughts` mounts the following defaults unless overridden in plugin config:

- `strategy: :got`
- `default_model: :reasoning`
- `timeout: 30_000`
- `options: %{}`

## Reasoning TRM Plugin Defaults Contract

`Jido.AI.Plugins.Reasoning.TRM` mounts the following defaults unless overridden in plugin config:

- `strategy: :trm`
- `default_model: :reasoning`
- `timeout: 30_000`
- `options: %{}`

## Reasoning Adaptive Plugin Defaults Contract

`Jido.AI.Plugins.Reasoning.Adaptive` mounts the following defaults unless overridden in plugin config:

- `strategy: :adaptive`
- `default_model: :reasoning`
- `timeout: 30_000`
- `options: %{}`

## Action Context Contract (Plugin -> Action)

When plugin-routed actions execute, the action context contract includes:

- `state`
- `agent`
- `plugin_state`
- `provided_params`

Actions should read defaults from explicit params first, then context/plugin state fallback.

## Strategy Runtime Compatibility

All built-in reasoning strategies now support module-action fallback execution for non-strategy commands. This means plugin-routed `Jido.Action` modules execute on strategy agents instead of silently no-oping.

## When To Use Plugins vs Actions

Use plugins when:

- capability should be mountable and reusable across many agents
- you need lifecycle hooks and capability-level defaults
- you want stable signal contracts for app/runtime integration

Use actions directly when:

- you are building one-off pipelines or background jobs
- you need low-level composition via `Jido.Exec`
- you do not need plugin lifecycle behavior

## Failure Mode: Defaults Not Being Applied

Symptom:

- execution succeeds but model/tool defaults are ignored

Fix:

- verify plugin state keys and `mount/2` shape match plugin schema and action fallback readers
- ensure caller does not override defaults with empty explicit params

## Next

- [Actions Catalog](actions_catalog.md)
- [Package Overview (Production Map)](../user/package_overview.md)
- [Migration Guide: Plugins And Signals (v2 -> v3)](../user/migration_plugins_and_signals_v3.md)