# `WardrikCore`

A durable agent runtime. Agents survive a crash, wait for a person, and run every call through
your code.

    children = [
      {WardrikCore.Backends.MemoryStore, name: :agents},
      {WardrikCore, backend: {WardrikCore.Backends.MemoryStore, name: :agents}}
    ]

    Supervisor.start_link(children, strategy: :rest_for_one)

    {:ok, agent} =
      WardrikCore.start_agent(agent_id: "triage", model: "anthropic:claude-haiku-4-5")

    :ok = WardrikCore.ask(agent, "What's on fire?")
    WardrikCore.history(agent)

  * An agent runs one turn at a time. Tools come from `WardrikCore.Capability` modules.
  * Every model call and tool call runs through `WardrikCore.Middleware`.
  * To run more than one instance, give each child a `:name`, and pass it as `instance`.

See the [Run agents](running-agents.html) guide.

# `agent`

```elixir
@type agent() :: String.t() | key()
```

An agent's id, for its own history, or a `t:key/0` for one of its conversations.

# `key`

```elixir
@type key() :: {String.t(), String.t() | nil}
```

An agent's id and its conversation's, `nil` for the agent's own history.

# `price`

```elixir
@type price() :: [
  input: number(),
  output: number(),
  cache_read: number(),
  cache_write: number()
]
```

A model's price in US dollars per million tokens.

# `ask`

```elixir
@spec ask(GenServer.server(), String.t(), map()) :: :ok
```

Queues a message. Returns once it's stored. A running turn ends at its next step.

    :ok = WardrikCore.ask(agent, "Open an issue for the login bug.", %{principal: maria})

## Metadata

  * `:principal` - the `WardrikCore.Principal` who asked. The turn acts for them.
  * `:id` - the message's ID. Generated by default.

# `child_spec`

Runs agents under your supervision tree.

    {WardrikCore, backend: {WardrikCore.Backends.MemoryStore, name: :agents}}

## Options

  * `:backend` - required. The durable_server backend agents are saved in.
  * `:name` - the instance's name. `WardrikCore` by default.

Other options go to `DurableServer.Supervisor`.

# `configure`

```elixir
@spec configure(GenServer.server(), keyword()) :: :ok
```

Changes an agent's model or settings, from its next turn.

Takes `:model` and the options of `start_agent/2`, except `:agent_id` and `:conversation`.

# `context_window`

```elixir
@spec context_window(LLMDB.Model.t() | String.t()) :: pos_integer() | nil
```

Returns `model`'s context window in tokens, from the model catalog, or `nil`.

# `delete`

```elixir
@spec delete(atom(), agent()) :: :ok | {:error, term()}
```

Stops the agent and deletes its saved state. Returns `:ok` if there's no such agent.

# `evaluate`

```elixir
@spec evaluate(WardrikCore.Context.t(),
  model: String.t(),
  state: String.t() | map(),
  questions: map()
) ::
  {:ok, map(), %{required(atom()) =&gt; number()}} | {:error, term()}
```

Asks an evaluation `:model` the `:questions` about `:state`, from inside a turn.

    questions = %{"urgent" => %{type: :boolean, instructions: "Is this urgent?"}}

    {:ok, %{"urgent" => %{"probability" => p}}, measurements} =
      WardrikCore.evaluate(context,
        model: "typesafe:jev-latest",
        state: "The site is down.",
        questions: questions
      )

    context = WardrikCore.Context.measure(context, measurements)

Returns each answer by question name, and what the call used.

# `generate`

```elixir
@spec generate(WardrikCore.Context.t()) :: WardrikCore.Middleware.result()
```

Calls the context's model once, from inside a turn, through the middleware.

    summarizing = %{context | instructions: "Summarise this conversation.", tools: []}
    {:ok, summarized} = WardrikCore.generate(summarizing)
    ReqLLM.Response.text(summarized.response)
    #=> "The user asked about..."

Returns the context with `response` set and the reply added to `messages`. Streams nothing.

# `history`

```elixir
@spec history(GenServer.server()) :: [ReqLLM.Message.t()]
```

Returns the agent's conversation.

# `interrupt`

```elixir
@spec interrupt(GenServer.server()) :: :ok | {:error, :idle}
```

Stops the running or parked turn. Returns `{:error, :idle}` if there's none.

# `kill`

```elixir
@spec kill(GenServer.server(), String.t()) :: :ok | {:error, :not_running}
```

Stops the running tool call `call_id`. The model gets an error, and the turn goes on.

# `list`

```elixir
@spec list(atom()) :: [key()]
```

Returns the key of every saved agent.

# `move`

```elixir
@spec move(atom(), agent(), [{:to, String.t()}]) :: {:ok, pid()} | {:error, term()}
```

Restarts the agent on the node `:to`, ending its turn.

    WardrikCore.move("triage", to: "wardrik@10.0.0.2")

Returns `{:error, :no_such_node}` or `{:error, :not_found}`.

# `nodes`

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

Returns the names of the cluster nodes agents run on, sorted.

# `parked`

```elixir
@spec parked(GenServer.server()) :: term() | nil
```

Returns what the agent waits on, or `nil`.

Parked tool calls return `{:tool_calls, [{call, outcome}]}`.

# `placements`

```elixir
@spec placements(atom()) :: %{required(key()) =&gt; String.t() | nil}
```

Returns each saved agent's key mapped to the node running it, or `nil` if it isn't running.

# `price`

```elixir
@spec price(LLMDB.Model.t() | String.t()) :: price() | nil
```

Returns `model`'s price from the model catalog, in US dollars per million tokens, or `nil`.

    WardrikCore.price("anthropic:claude-haiku-4-5")
    #=> [input: 1.0, output: 5.0, cache_read: 0.1, cache_write: 1.25]

# `resume`

```elixir
@spec resume(GenServer.server(), term(), map()) :: :ok | {:error, :not_parked}
```

Resumes a parked turn, passing `input` to whatever parked it.

    WardrikCore.resume(agent, %{"call_1" => :approve}, %{by: maria})

  * Answer parked tool calls by ID, one at a time or together.
  * `metadata` is yours. It appears on the turn's `[:wardrik_core, :turn, :start]` event.
  * Returns `{:error, :not_parked}` if the agent isn't waiting.

# `start_agent`

```elixir
@spec start_agent(atom(), keyword()) :: {:ok, pid()} | {:error, term()}
```

Starts an agent, or loads it if it was saved before.

    {:ok, agent} =
      WardrikCore.start_agent(
        agent_id: "support",
        model: "anthropic:claude-haiku-4-5",
        instructions: "Answer customers' questions about their orders.",
        capabilities: [{MyApp.Orders, []}]
      )

## Options

  * `:agent_id` - required. Your ID for the agent.
  * `:model` - required. A model spec, as `"anthropic:claude-haiku-4-5"`.
  * `:conversation` - your ID for one of the agent's conversations, with its own history.
    `nil`, the main history, by default.
  * `:instructions` - the system prompt. Never kept in the history.
  * `:capabilities` - modules that give the agent its tools. See `WardrikCore.Capability`.
  * `:middleware` - wraps every step. See `WardrikCore.Middleware`.
  * `:principal` - who the agent acts as. See `WardrikCore.Principal`.
  * `:credentials` - where tools find a person's tokens. See `WardrikCore.Credentials`.
  * `:keys` - where code finds API keys. See `WardrikCore.Keys`.
  * `:events` - where events go. See `WardrikCore.Events`.
  * `:metadata` - your own data, passed to middleware. `%{}` by default.
  * `:timeout` - how long a turn may take, in milliseconds. `:infinity` by default.
  * `:max_rounds` - model calls per turn, or `:infinity`. `25` by default.
  * `:retries` - see `WardrikCore.Retry`.
  * `:model_timeouts` - how long a model call waits, as `ReqLLM` takes them. Agents run
    unattended and a model may think for minutes, so a call waits up to five minutes for an
    answer, and as long on a stream gone quiet:
    `[receive_timeout: 300_000, stream_idle_timeout: 300_000]` by default. A turn's `:timeout`
    bounds the whole.
  * `:prices` - US dollars per million tokens, by model spec, over the model catalog's.
    `%{}` by default.

        prices: %{"zai_coding_plan:glm-5.1" => [input: 1.54, output: 4.84]}

# `status`

```elixir
@spec status(GenServer.server()) :: :idle | :running | :waiting | :due
```

Returns what the agent is doing: `:idle`, `:running`, `:waiting` on `resume/3`, or `:due` to
run a turn.

# `stop`

```elixir
@spec stop(GenServer.server()) :: :ok
```

Stops the agent. It stays saved, and runs again when started.

# `subagent`

```elixir
@spec subagent(WardrikCore.Context.t(), String.t(), keyword()) ::
  {:ok, %{text: String.t(), labels: [String.t()]}, map()}
  | {:error, term(), map()}
```

Runs `prompt` as one turn of a subagent, from inside a tool or middleware.

    {:ok, %{text: text, labels: labels}, measurements} =
      WardrikCore.subagent(context, "Find this week's releases.", name: "researcher")

  * Uses the caller's model, middleware and tools.
  * Returns `{:error, reason, measurements}` if it fails. Pass the measurements to
    `WardrikCore.Context.measure/2`.
  * It can't wait for a person. A call that would park fails it.
  * It emits `[:wardrik_core, :subagent, _]` events in place of its own steps'. See
    `WardrikCore.Telemetry`.

## Options

  * `:name` - `"subagent"` by default. Set as the context's `subagent` while it runs.
  * `:instructions`, `:capabilities`, `:max_rounds` - replace the caller's.
  * `:except_tools` - tool names to leave out. `[]` by default.
  * `:timeout` - milliseconds before it fails with `:timeout`. `:infinity` by default.

# `whereis`

```elixir
@spec whereis(atom(), agent()) :: pid() | nil
```

Returns the running agent, or `nil`.

    WardrikCore.whereis("support")
    WardrikCore.whereis({"support", "C1"})

---

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