# `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)

`ask/3` returns once the message is stored. Agents in memory, as here, are gone when the node
stops. An agent that crashes comes back by itself and picks its message back up. With a backend
that keeps them, such as a disk or an S3 bucket, so does one whose node dies. See
[Run agents](running-agents.html#where-agents-are-kept) for the backends.

## Turns

An agent runs one turn at a time. A message that arrives mid-turn ends the turn at its next
step, and the next turn answers it. Tools come from `WardrikCore.Capability` modules. Every
model call and tool call runs through `WardrikCore.Middleware`.

## Instances

To run more than one instance, give each child a `:name`. Pass that name as the first argument
to the functions that take an `instance`.

# `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, and the next turn answers the message.

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

## Metadata

  * `:principal` - the `WardrikCore.Principal` who asked. The turn that answers acts for them.
    A turn only answers consecutive messages from one principal.
  * `:id` - the message's id. Generated unless set.

# `child_spec`

Runs agents under your supervision tree.

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

Before starting, it loads every module of `wardrik_core`, `req_llm` and each app that depends
on `wardrik_core`. That way a backend that decodes saved agents with `[:safe]` knows every
struct they name.

## Options

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

Any other option is passed 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`.

# `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(), String.t()) :: :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(), String.t(), String.t() | map(), map()) ::
  {:ok, map(), %{required(atom()) =&gt; number()}} | {:error, term()}
```

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

Use it when middleware needs a model's judgement on a call.

    questions = %{"urgent" => %{type: :boolean, instructions: "Is this urgent?"}}
    {:ok, %{"urgent" => %{"probability" => p}}, measurements} =
      WardrikCore.evaluate(context, "typesafe:jev-latest", "The site is down.", questions)

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

Returns each answer under its question's name, and the call's measurements. Pass them to
`WardrikCore.Context.measure/2`.

# `generate`

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

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

Sends the context's instructions, messages and tools. Runs as a `:generate` step through the
middleware, so policy and budgets see it like any other model call. Streams nothing.

Use it when middleware needs the agent's own model, such as to summarise a long history.

    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, the reply added to `messages`, and the call's
measurements added.

# `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 as the call's answer, and the turn goes on.

# `list`

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

Returns the id of every saved agent.

# `move`

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

Restarts the agent on `node`, with its saved state.

Ends the turn it's taking, and abandons anything it's waiting on. Returns
`{:error, :no_such_node}` or `{:error, :not_found}` if the node or the agent doesn't exist.

# `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 why the agent is parked, or `nil` if it isn't.

Parked tool calls return `{:tool_calls, [{call, outcome}]}`. While a resumed turn runs, it's the
calls it parked on that aren't answered yet.

# `placements`

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

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

# `price`

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

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

Returns `nil` if the catalog has no price in dollars. Use the result, or one built from it, in
an agent's `:prices`.

    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})

`metadata` is yours, such as who answered. It's reported on the resumed turn's
`[:wardrik_core, :turn, :start]` event. Returns `{:error, :not_parked}` if the agent isn't
parked.

Parked tool calls may be answered one at a time, as `%{call_id => answer}`. While a resumed turn
runs, a call it parked on and isn't answered yet still takes an answer: the turn resumes with it
once it parks again.

# `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. The agent's id, unique within the instance.
  * `:model` - required. A model spec, as `"anthropic:claude-haiku-4-5"`.
  * `:instructions` - the system prompt. Sent with every model call, 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 credentials. See `WardrikCore.Credentials`.
  * `:keys` - where code finds API keys. See `WardrikCore.Keys`.
  * `:events` - where events go. See `WardrikCore.Events`.
  * `:metadata` - anything of yours, passed to middleware. `%{}` unless set.
  * `:timeout` - how long a turn may take, in milliseconds. `:infinity` unless set.
  * `:max_rounds` - model calls a turn may make, or `:infinity`. `25` unless set.
  * `:retries` - how failed calls are retried. See `WardrikCore.Retry`.
  * `:prices` - model prices in US dollars per million tokens, by spec. Takes precedence over
    the model catalog. `%{}` unless set.

        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` - nothing to do.
  * `:running` - taking a turn.
  * `:waiting` - parked, waiting on `resume/3`.
  * `:due` - a turn is ready to run, such as one lost in a restart.

# `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.

The subagent uses the caller's model, middleware and tools, with `prompt` as its only message.

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

Returns its answer's `text` and the `labels` of what it read. Returns `{:error, reason,
measurements}` if it fails. Either way, pass the measurements to
`WardrikCore.Context.measure/2`.

It can't wait on a person. A call that would park ends it with an error. A subagent that crashes
fails with `{:exit, reason}`.

## Options

  * `:name` - its name. `"subagent"` unless set.
  * `:instructions` - replaces the caller's.
  * `:capabilities` - replaces the caller's.
  * `:except_tools` - names of tools to leave out. `[]` unless set.
  * `:max_rounds` - replaces the caller's.
  * `:timeout` - milliseconds it may run before failing with `:timeout`. `:infinity` unless set.

## Events

Its own steps emit nothing to the caller's events. Instead it emits:

  * `[:wardrik_core, :subagent, :started]` - with its `prompt`.
  * `[:wardrik_core, :subagent, :progress]` - as it begins each tool call.
  * `[:wardrik_core, :subagent, :completed]` - with its `message`.
  * `[:wardrik_core, :subagent, :failed]` - with its `error`.

See `WardrikCore.Telemetry` for their metadata.

While it runs, its context's `subagent` field holds its name. Middleware that counts what each
call measures can use it to skip the subagent's calls, and count the totals the caller adds
instead.

# `whereis`

```elixir
@spec whereis(atom(), String.t()) :: pid() | nil
```

Returns the running agent for `agent_id`, or `nil`.

---

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