WardrikCore (wardrik_core v0.1.0-rc.1)
Copy MarkdownA 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 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.
Summary
Functions
Queues a message. Returns once it's stored.
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 questions about state, from inside a turn.
Calls the context's model once, from inside a turn.
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.
Returns the id of every saved agent.
Restarts the agent on node, with its saved state.
Returns the names of the cluster nodes agents run on, sorted.
Returns why the agent is parked, or nil if it isn't.
Returns each saved agent's id 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.
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.
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 for agent_id, or nil.
Functions
@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- theWardrikCore.Principalwho 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.
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.WardrikCoreunless set.
Any other option is passed to DurableServer.Supervisor.
@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.
@spec context_window(LLMDB.Model.t() | String.t()) :: pos_integer() | nil
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.
@spec evaluate(WardrikCore.Context.t(), String.t(), String.t() | map(), map()) :: {:ok, map(), %{required(atom()) => 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.
@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.
@spec history(GenServer.server()) :: [ReqLLM.Message.t()]
Returns the agent's conversation.
@spec interrupt(GenServer.server()) :: :ok | {:error, :idle}
Stops the running or parked turn. Returns {:error, :idle} if there's none.
@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.
Returns the id of every saved agent.
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.
Returns the names of the cluster nodes agents run on, sorted.
@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.
Returns each saved agent's id mapped to the node running it, or nil if it isn't running.
@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]
@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.
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. SeeWardrikCore.Capability.:middleware- wraps every step. SeeWardrikCore.Middleware.:principal- who the agent acts as. SeeWardrikCore.Principal.:credentials- where tools find a person's credentials. SeeWardrikCore.Credentials.:keys- where code finds API keys. SeeWardrikCore.Keys.:events- where events go. SeeWardrikCore.Events.:metadata- anything of yours, passed to middleware.%{}unless set.:timeout- how long a turn may take, in milliseconds.:infinityunless set.:max_rounds- model calls a turn may make, or:infinity.25unless set.:retries- how failed calls are retried. SeeWardrikCore.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]}
@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 onresume/3.:due- a turn is ready to run, such as one lost in a restart.
@spec stop(GenServer.server()) :: :ok
Stops the agent. It stays saved, and runs again when started.
@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.:infinityunless set.
Events
Its own steps emit nothing to the caller's events. Instead it emits:
[:wardrik_core, :subagent, :started]- with itsprompt.[:wardrik_core, :subagent, :progress]- as it begins each tool call.[:wardrik_core, :subagent, :completed]- with itsmessage.[:wardrik_core, :subagent, :failed]- with itserror.
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.
Returns the running agent for agent_id, or nil.