WardrikCore (wardrik_core v0.1.0-rc.3)

Copy Markdown

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 guide.

Summary

Types

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

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

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

Functions

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

Runs agents under your supervision tree.

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

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

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

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

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

Returns the agent's conversation.

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

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

Returns the key of every saved agent.

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

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

Returns what the agent waits on, or nil.

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

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

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

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

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

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

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

Returns the running agent, or nil.

Types

agent()

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

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

key()

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

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

price()

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

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

Functions

ask(agent, text, metadata \\ %{})

@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(opts)

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(agent, opts)

@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(model)

@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(instance \\ __MODULE__, agent)

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

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

evaluate(context, args)

@spec evaluate(WardrikCore.Context.t(),
  model: String.t(),
  state: String.t() | map(),
  questions: map()
) ::
  {:ok, map(), %{required(atom()) => 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(context)

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(agent)

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

Returns the agent's conversation.

interrupt(agent)

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

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

kill(agent, call_id)

@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(instance \\ __MODULE__)

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

Returns the key of every saved agent.

move(instance \\ __MODULE__, agent, args)

@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(instance \\ __MODULE__)

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

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

parked(agent)

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

Returns what the agent waits on, or nil.

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

placements(instance \\ __MODULE__)

@spec placements(atom()) :: %{required(key()) => String.t() | nil}

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

price(model)

@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(agent, input, metadata \\ %{})

@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(instance \\ __MODULE__, opts)

@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(agent)

@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(agent)

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

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

subagent(context, prompt, opts \\ [])

@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(instance \\ __MODULE__, agent)

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

Returns the running agent, or nil.

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