Run agents
Copy MarkdownSetup
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:
| Backend | Keeps agents | Needs |
|---|---|---|
MemoryStore | In memory. Gone when the node stops. | Nothing. For tests and trying things out. |
EKVStore | On disk, and on each node when clustered. | The ekv dependency, and EKV started before WardrikCore. |
ObjectStore | In an S3-compatible bucket. | bucket, access_key_id, secret_access_key, s3_endpoint and default_region. |
MirrorStore | In 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: trueoridempotent: true, the call is repeated. - Otherwise it parks as
:in_doubt. Answer it withresume/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:
| Option | Takes | Default |
|---|---|---|
instructions | the system prompt, sent with every model call and never kept in the history | none |
capabilities | modules that give the agent its tools. See Tools | [] |
middleware | what wraps every call. See Middleware | [] |
principal | who the agent acts as. See Principals | none |
credentials | where tools find a person's credentials. See Credentials | none |
keys | where code finds API keys. See Keys | none |
events | where its events go. See Events | none |
metadata | anything of yours, passed to middleware | %{} |
timeout | how long a turn may take, in milliseconds | :infinity |
max_rounds | model calls a turn may make | 25 |
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 | %{} |
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
| Function | Does |
|---|---|
ask/3 | Queues a message. A running turn ends at its next step, and the next turn answers it |
interrupt/1 | Stops the running or parked turn |
kill/2 | Stops one running tool call. The model gets an error, and the turn goes on |
resume/3 | Resumes a parked turn, with who answered as metadata |
status/1, history/1, parked/1 | What the agent is doing, what it has said, and what it's waiting on |
configure/2 | Changes the model or settings, from the next turn |
stop/1 | Stops the agent until it's started again |
delete/2 | Stops the agent and deletes its saved state |
list/1, whereis/2 | Every saved agent's id, and the running agent for an id |
nodes/1, placements/1 | The cluster's nodes, and the node each saved agent runs on |
move/3 | Restarts an agent on another node, with its saved state |
WardrikCore.status(agent)
#=> :idle
WardrikCore.list()
#=> ["support"]
:ok = WardrikCore.delete("support")