# `Squidie.Runtime.DispatchAgent`
[🔗](https://github.com/dark-trench/squidie/blob/main/lib/squidie/runtime/dispatch_agent.ex#L1)

Jido-native dispatch coordination state for one durable dispatch queue.

The agent rebuilds from dispatch-thread journal entries and performs durable
claim appends so the runtime can coordinate leases, retries, and workflow
wakeups from durable facts instead of in-memory state.

# `claim`

```elixir
@type claim() :: %{
  agent: Jido.Agent.t(),
  attempt: Squidie.Runtime.DispatchProtocol.ActionAttempt.t(),
  claim_id: String.t(),
  claim_token: String.t(),
  lease_until: DateTime.t()
}
```

# `lifecycle_update`

```elixir
@type lifecycle_update() :: %{
  :agent =&gt; Jido.Agent.t(),
  :attempt =&gt; Squidie.Runtime.DispatchProtocol.ActionAttempt.t(),
  optional(:lease_until) =&gt; DateTime.t()
}
```

# `queue`

```elixir
@type queue() :: String.t()
```

# `queue_update`

```elixir
@type queue_update() :: %{agent: Jido.Agent.t(), queued?: boolean()}
```

# `schedule_update`

```elixir
@type schedule_update() :: %{agent: Jido.Agent.t(), runnables: [map()]}
```

# `storage_config`

```elixir
@type storage_config() :: Squidie.Runtime.Journal.storage_config()
```

# `actions`

```elixir
@spec actions() :: [module()]
```

Returns the list of actions from all attached plugins.

# `agent_id`

```elixir
@spec agent_id(queue() | atom()) :: String.t()
```

Returns the stable Jido agent id for a dispatch queue.

# `capabilities`

```elixir
@spec capabilities() :: [atom()]
```

Returns the union of all capabilities from all mounted plugin instances.

Capabilities are atoms describing what the agent can do based on its
mounted plugins.

## Example

    MyAgent.capabilities()
    # => [:messaging, :channel_management, :chat, :embeddings]

# `category`

```elixir
@spec category() :: String.t() | nil
```

Returns the agent's category.

# `claim_next`

```elixir
@spec claim_next(storage_config(), Jido.Agent.t(), String.t(), keyword()) ::
  {:ok, claim()} | {:ok, :none} | {:error, term()}
```

Claims the next visible or expired attempt for a dispatch queue agent.

The claim is persisted as an `:attempt_claimed` journal entry with the
agent's current dispatch-thread revision as `:expected_rev`. Concurrent
claimers therefore race at the journal boundary and receive `{:error,
:conflict}` when their projection is stale.

The returned claim contains the raw `claim_token` for the worker process, but
the durable journal stores only its hash. If the append succeeds, the returned
`:attempt` reflects the post-claim projection state.

# `cmd`

```elixir
@spec cmd(Jido.Agent.t(), Jido.Agent.action()) :: Jido.Agent.cmd_result()
```

Execute actions against the agent: `(agent, action) -> {agent, directives}`

This is the core operation. Actions modify state and may perform required
work; directives are runtime-owned external effects.
Execution is delegated to the configured strategy (default: Direct).

## Action Formats

  * `MyAction` - Action module with no params
  * `{MyAction, %{param: 1}}` - Action with params
  * `{MyAction, %{param: 1}, %{context: data}}` - Action with params and context
  * `{MyAction, %{param: 1}, %{}, [timeout: 1000]}` - Action with opts
  * `%Instruction{}` - Full instruction struct
  * `[...]` - List of any of the above (processed in sequence)

## Options

The optional third argument `opts` is a keyword list merged into all instructions:

  * `:timeout` - Maximum time (in ms) for each action to complete
  * `:max_retries` - Maximum retry attempts on failure
  * `:backoff` - Initial backoff time in ms (doubles with each retry)

## Examples

    {agent, directives} = Squidie.Runtime.DispatchAgent.cmd(agent, MyAction)
    {agent, directives} = Squidie.Runtime.DispatchAgent.cmd(agent, {MyAction, %{value: 42}})
    {agent, directives} = Squidie.Runtime.DispatchAgent.cmd(agent, [Action1, Action2])

    # With per-call options (merged into all instructions)
    {agent, directives} = Squidie.Runtime.DispatchAgent.cmd(agent, MyAction, timeout: 5000)

# `cmd`

```elixir
@spec cmd(Jido.Agent.t(), Jido.Agent.action(), keyword()) :: Jido.Agent.cmd_result()
```

# `complete`

```elixir
@spec complete(
  storage_config(),
  Jido.Agent.t(),
  String.t(),
  String.t(),
  String.t(),
  map(),
  keyword()
) :: {:ok, lifecycle_update()} | {:error, term()}
```

Records a durable successful result for a currently claimed attempt.

# `completed_results`

```elixir
@spec completed_results(Jido.Agent.t()) :: [
  Squidie.Runtime.DispatchProtocol.ActionAttempt.t()
]
```

Lists completed dispatch attempts waiting for workflow application.

# `description`

```elixir
@spec description() :: String.t() | nil
```

Returns the agent's description.

# `ensure_run_queued`

```elixir
@spec ensure_run_queued(storage_config(), Jido.Agent.t(), String.t(), keyword()) ::
  {:ok, queue_update()} | {:error, term()}
```

Records that a run belongs to this dispatch queue before runnable attempts are
scheduled.

This queue marker lets recovery discover a started run even if the process
crashes after the run thread is committed and before the first
`:attempt_scheduled` entry is written.

# `expired_claims`

```elixir
@spec expired_claims(Jido.Agent.t(), DateTime.t()) :: [
  Squidie.Runtime.DispatchProtocol.ActionAttempt.t()
]
```

Lists claimed attempts whose leases have expired by the given time.

# `fail`

```elixir
@spec fail(
  storage_config(),
  Jido.Agent.t(),
  String.t(),
  String.t(),
  String.t(),
  map(),
  keyword()
) :: {:ok, lifecycle_update()} | {:error, term()}
```

Records a durable failure for a currently claimed attempt.

`:retry_runnable_key` and `:retry_visible_at` may be provided together to make
a retry attempt visible through the dispatch projection after the given time.

# `heartbeat`

```elixir
@spec heartbeat(
  storage_config(),
  Jido.Agent.t(),
  String.t(),
  String.t(),
  String.t(),
  keyword()
) ::
  {:ok, lifecycle_update()} | {:error, term()}
```

Extends the lease for a currently claimed attempt.

The heartbeat is rejected before writing when the claim token is stale, the
claim has expired, or the dispatch-agent projection is not currently claimed.

# `name`

```elixir
@spec name() :: String.t()
```

Returns the agent's name.

# `new`

```elixir
@spec new(keyword() | map()) :: Jido.Agent.t()
```

Creates a new agent with optional initial state.

The agent is fully initialized including strategy state. For the default
Direct strategy, this is a no-op. For custom strategies, any state
initialization is applied (but directives are only processed by AgentServer).

## Examples

    agent = Squidie.Runtime.DispatchAgent.new()
    agent = Squidie.Runtime.DispatchAgent.new(id: "custom-id")
    agent = Squidie.Runtime.DispatchAgent.new(state: %{counter: 10})

# `plugin_config`

```elixir
@spec plugin_config(module() | {module(), atom()}) :: map() | nil
```

Returns the configuration for a specific plugin.

Accepts either a module or a `{module, as_alias}` tuple for multi-instance plugins.

# `plugin_instances`

```elixir
@spec plugin_instances() :: [Jido.Plugin.Instance.t()]
```

Returns the list of plugin instances attached to this agent.

# `plugin_routes`

```elixir
@spec plugin_routes() :: [{String.t(), module(), integer()}]
```

Returns the expanded and validated plugin routes.

# `plugin_schedules`

```elixir
@spec plugin_schedules() :: [
  Jido.Plugin.Schedules.schedule_spec() | Jido.Agent.Schedules.schedule_spec()
]
```

Returns the expanded plugin and agent schedules.

# `plugin_specs`

```elixir
@spec plugin_specs() :: [Jido.Plugin.Spec.t()]
```

Returns the list of plugin specs attached to this agent.

# `plugin_state`

```elixir
@spec plugin_state(Jido.Agent.t(), module() | {module(), atom()}) :: map() | nil
```

Returns the state slice for a specific plugin.

Accepts either a module or a `{module, as_alias}` tuple for multi-instance plugins.

# `plugins`

```elixir
@spec plugins() :: [module()]
```

Returns the list of plugin modules attached to this agent (deduplicated).

For multi-instance plugins, the module appears once regardless of how many
instances are mounted.

## Example

    MyAgent.plugins()
    # => [MyApp.SlackPlugin, MyApp.OpenAIPlugin]

# `put_checkpoint`

```elixir
@spec put_checkpoint(storage_config(), Jido.Agent.t(), keyword()) ::
  :ok | {:error, term()}
```

Stores the current dispatch projection as a checkpoint for faster rebuilds.

# `rebuild`

```elixir
@spec rebuild(storage_config(), queue() | atom()) ::
  {:ok, Jido.Agent.t()} | {:error, term()}
```

Rebuilds a dispatch agent for one queue from the durable dispatch thread.

# `run_ids`

```elixir
@spec run_ids(Jido.Agent.t()) :: MapSet.t(String.t())
```

Returns every run id known by the dispatch projection.

# `runnable_keys`

```elixir
@spec runnable_keys(Jido.Agent.t()) :: MapSet.t(String.t())
```

Returns every runnable key already known by the dispatch projection.

# `schedule_attempts`

```elixir
@spec schedule_attempts(
  storage_config(),
  Jido.Agent.t(),
  String.t(),
  [map()],
  keyword()
) ::
  {:ok, schedule_update()} | {:error, term()}
```

Appends durable scheduled attempts for planned runnables that are not already
present in the dispatch-agent projection.

The append uses the dispatch thread's current revision as the optimistic fence.
Duplicate callers with stale dispatch projections therefore fail at the journal
boundary, while callers that already see the scheduled attempts return
idempotently without writing.

# `schema`

```elixir
@spec schema() :: Zoi.schema() | keyword()
```

Returns the merged schema (base + plugin schemas).

# `set`

```elixir
@spec set(Jido.Agent.t(), map() | keyword()) :: Jido.Agent.agent_result()
```

Updates the agent's state by merging new attributes.

Uses deep merge semantics - nested maps are merged recursively.

## Examples

    {:ok, agent} = Squidie.Runtime.DispatchAgent.set(agent, %{status: :running})
    {:ok, agent} = Squidie.Runtime.DispatchAgent.set(agent, counter: 5)

# `signal_types`

```elixir
@spec signal_types() :: [String.t()]
```

Returns all expanded route signal types from plugin routes.

These are the fully-prefixed signal types that the agent can handle.

## Example

    MyAgent.signal_types()
    # => ["slack.post", "slack.channels.list", "openai.chat"]

# `strategy`

```elixir
@spec strategy() :: module()
```

Returns the execution strategy module for this agent.

# `strategy_opts`

```elixir
@spec strategy_opts() :: keyword()
```

Returns the strategy options for this agent.

# `strategy_snapshot`

```elixir
@spec strategy_snapshot(Jido.Agent.t()) :: Jido.Agent.Strategy.Snapshot.t()
```

Returns a stable, public view of the strategy's execution state.

Use this instead of inspecting `agent.state.__strategy__` directly.
Returns a `Jido.Agent.Strategy.Snapshot` struct with:
- `status` - Coarse execution status
- `done?` - Whether strategy reached terminal state
- `result` - Main output if any
- `details` - Additional strategy-specific metadata

# `tags`

```elixir
@spec tags() :: [String.t()]
```

Returns the agent's tags.

# `validate`

```elixir
@spec validate(
  Jido.Agent.t(),
  keyword()
) :: Jido.Agent.agent_result()
```

Validates the agent's state against its schema.

## Options
  * `:strict` - When true, only schema-defined fields are kept (default: false)

## Examples

    {:ok, agent} = Squidie.Runtime.DispatchAgent.validate(agent)
    {:ok, agent} = Squidie.Runtime.DispatchAgent.validate(agent, strict: true)

# `visible_attempts`

```elixir
@spec visible_attempts(Jido.Agent.t(), DateTime.t()) :: [
  Squidie.Runtime.DispatchProtocol.ActionAttempt.t()
]
```

Lists attempts whose visibility window has opened and can be claimed.

# `vsn`

```elixir
@spec vsn() :: String.t() | nil
```

Returns the agent's version.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
