Run agents

Copy Markdown

Setup

Paste this into a fresh IEx session. It keeps agents in memory, and needs an Anthropic key for the model calls:

Mix.install([{:wardrik_core, "~> 0.1.0-rc.1"}])
System.put_env("ANTHROPIC_API_KEY", "sk-ant-...")

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

{:ok, _} = Supervisor.start_link(children, strategy: :rest_for_one)

Replace "sk-ant-..." with your key. In an app, put the two children in your supervision tree instead.

Start an agent

{:ok, agent} = WardrikCore.start_agent(agent_id: "support", model: "anthropic:claude-haiku-4-5")
:ok = WardrikCore.ask(agent, "What's on fire?")

WardrikCore.history(agent)
#=> [#Message<:user text>, #Message<:assistant text>]

ask/3 returns once the message is stored. The agent answers in its own process.

Where agents are kept

WardrikCore saves agents through a durable_server backend:

BackendKeeps agentsNeeds
MemoryStoreIn memory. Gone when the node stops.Nothing. For tests and trying things out.
EKVStoreOn disk, and on each node when clustered.The ekv dependency, and EKV started before WardrikCore.
ObjectStoreIn an S3-compatible bucket.bucket, access_key_id, secret_access_key, s3_endpoint and default_region.
MirrorStoreIn two backends at once, while you migrate between them.The two backends.

MemoryStore is WardrikCore.Backends.MemoryStore. The rest are under DurableServer.Backends.

To keep agents on disk, add {:ekv, "~> 0.4"} to your deps. Then start EKV first in your supervision tree:

children = [
  {EKV, name: :agents, data_dir: "/var/lib/my_app/agents", cluster_size: 1},
  {WardrikCore, backend: {DurableServer.Backends.EKVStore, name: :agents}}
]

An S3-compatible bucket needs no extra dependency:

children = [
  {WardrikCore,
   backend:
     {DurableServer.Backends.ObjectStore,
      bucket: "my-app-agents",
      access_key_id: System.fetch_env!("AWS_ACCESS_KEY_ID"),
      secret_access_key: System.fetch_env!("AWS_SECRET_ACCESS_KEY"),
      s3_endpoint: "https://s3.amazonaws.com",
      default_region: "us-east-1"}}
]

To write your own backend, such as one for Postgres, implement durable_server's storage behaviour. That behaviour isn't a stable contract yet, so expect changes between durable_server releases.

When a node dies

An agent that crashes comes back by itself, as does one whose node dies, with a backend that keeps agents. A sweep for lost agents restarts it, every :discovery_interval_ms (60_000 unless set), and it picks its message back up. A turn lost while its tools ran resumes from its last checkpoint, without calling the model again. An agent stopped with stop/1 stays stopped.

A tool call lost that way may already have run:

  • If its tool is annotated read_only: true or idempotent: true, the call is repeated.
  • Otherwise it parks as :in_doubt. Answer it with resume/3:
{:tool_calls, [{call, {:park, :in_doubt}}]} = WardrikCore.parked(agent)

# Run it again
WardrikCore.resume(agent, %{call.id => :approve})

# Or tell the model it didn't run
WardrikCore.resume(agent, %{call.id => {:deny, "It may have run already."}})

Options

start_agent/2 also takes:

OptionTakesDefault
instructionsthe system prompt, sent with every model call and never kept in the historynone
capabilitiesmodules that give the agent its tools. See Tools[]
middlewarewhat wraps every call. See Middleware[]
principalwho the agent acts as. See Principalsnone
credentialswhere tools find a person's credentials. See Credentialsnone
keyswhere code finds API keys. See Keysnone
eventswhere its events go. See Eventsnone
metadataanything of yours, passed to middleware%{}
timeouthow long a turn may take, in milliseconds:infinity
max_roundsmodel calls a turn may make25
retrieshow failed calls are retried. See WardrikCore.Retry[]
pricesmodel prices in US dollars per million tokens, by spec. Takes precedence over the model catalog%{}

configure/2 changes them on a running agent, from its next turn:

:ok =
  WardrikCore.configure(agent,
    instructions: "Answer in one sentence.",
    prices: %{"anthropic:claude-haiku-4-5" => [input: 1.0, output: 5.0]}
  )

Control a running agent

FunctionDoes
ask/3Queues a message. A running turn ends at its next step, and the next turn answers it
interrupt/1Stops the running or parked turn
kill/2Stops one running tool call. The model gets an error, and the turn goes on
resume/3Resumes a parked turn, with who answered as metadata
status/1, history/1, parked/1What the agent is doing, what it has said, and what it's waiting on
configure/2Changes the model or settings, from the next turn
stop/1Stops the agent until it's started again
delete/2Stops the agent and deletes its saved state
list/1, whereis/2Every saved agent's id, and the running agent for an id
nodes/1, placements/1The cluster's nodes, and the node each saved agent runs on
move/3Restarts an agent on another node, with its saved state
WardrikCore.status(agent)
#=> :idle

WardrikCore.list()
#=> ["support"]

:ok = WardrikCore.delete("support")