WardrikDaemon
Copy MarkdownWardrik as a daemon: agents from files, config.yaml, an HTTP API, and the wardrik command.
Try it
curl -fsSL https://github.com/wardrik-dev/wardrik_daemon/releases/download/v0.1.0-rc.1/install.sh | WARDRIK_VERSION=0.1.0-rc.1 sh
wardrik init --org acme
init asks what it needs and says what it does:
- the model provider, those whose key is set here first, and the model:
claude-haiku-4-5for Anthropic. - the provider's key, not shown as you type, unless the environment or the env file sets it.
- three files, keeping any already there:
~/.config/wardrik/config.yaml- the deployment's settings, with the default model.~/.config/wardrik/agents/assistant.md- the front door.~/.config/wardrik/env- keys the daemon reads. Only you can read it.
- the daemon, as a systemd user service where there's one, or else with
wardrik start. - the key, sealed in the vault, unless the env file sets it.
- the front door, asked one short question.
Without a terminal, flags answer:
wardrik init --provider anthropic --model claude-sonnet-5 --key-from-env MY_KEY --no-service --no-ask
--yes runs the daemon as a systemd service. To keep the key yourself, put it in the env file
before init, and it's read from there:
echo 'ANTHROPIC_API_KEY=sk-ant-...' >> ~/.config/wardrik/env
The front door gets web_search where BRAVE_API_KEY is set as you run init. Elsewhere
config.yaml says how to turn it on.
The daemon runs in the background. Every other command reaches it over its HTTP API, at
127.0.0.1:4747 unless config.yaml says otherwise.
A key sealed in the vault is read as each call is made, so a new key needs no restart. Add --org
to seal an org's own key.
echo "$KEY" | wardrik vault put ANTHROPIC_API_KEY
Tell the front door an outcome, as "tell me when https://elixir-lang.org/atom.xml has a new post".
It proposes the work as the calls that do it: a worker it defines, and the triggers that start it.
Approve them with wardrik approval approve, and the work runs on its own. The front door's body
is its instructions, then a playbook to edit as you like.
The installer checks the release against its SHA256SUMS, puts it in ~/.local/lib/wardrik
beside the one before it, and wardrik in ~/.local/bin. Without WARDRIK_VERSION it installs
the newest release, pre-releases included. The release runs on Linux, x86_64 or aarch64, with
glibc 2.35 or later. Elsewhere, run the image:
docker run -d --name wardrik -p 127.0.0.1:4747:4747 \
-v wardrik-config:/config -v wardrik:/data ghcr.io/wardrik-dev/wardrik:0.1.0-rc.1
docker exec -it wardrik wardrik init
docker build . builds the image, and docker build --target tarball --output dist . the
tarball.
In a checkout of this repository, mix release builds the same release. Without one,
mix wardrik serve, mix wardrik ls and the rest run the same commands.
Agents
An agent is a Markdown file in agents/. The frontmatter is its definition, and the body is its
instructions:
---
description: Watches the incident queue and says what needs an engineer.
model: anthropic:claude-sonnet-5
tools: [linear/*]
policy: careful
triggers:
- {name: daily, every: "0 9 * * *", timezone: America/Los_Angeles, prompt: What's new?}
channels: [slack]
---
You watch the incident queue. Summarise anything new, and say which issues need an engineer.Its ID is the deployment's org and the file's name, as acme/triage. With user: jon, it's
acme/@jon/triage. Keys the file leaves out come from config.yaml's defaults.
wardrik reload reads the files again:
- a changed file's agent uses its new definition from its next turn.
- a removed file's agent is forgotten.
- a file that doesn't read is logged, and
wardrik doctorsays why.
The API can't forget an agent a file declares.
The daemon offers three tools of its own:
run_lua- Lua in a sealed sandbox, for arithmetic, reshaping data and the current time. See WardrikLua.web_search- searches through Brave. Needsbrave.api_key. See WardrikBrave.deep_research- splits a topic into angles, and has a subagent with the agent's own tools research each at once. See WardrikResearch.
With files in its tools, an agent keeps its own tree of files in the store. context: [MEMORY.md] reads files into every turn: that's its memory. See
WardrikWorkspace.
Policies are YAML files in policies/, by name: policies/careful.yaml is policy: careful.
config.yaml
org: acme
base_url: https://agents.acme.example # where people and services reach it
defaults:
model: anthropic:claude-haiku-4-5
mcp:
github:
url: https://api.githubcopilot.com/mcp/ # its full endpoint, used as given
credential: github
only: [list_commits, get_commit]
oauth:
providers:
github:
issuer: https://github.com/login/oauth
resource: https://api.githubcopilot.com/mcp/
client_id: ${GITHUB_CLIENT_ID}
client_secret: ${GITHUB_CLIENT_SECRET}
scopes: [repo]
flow: device
channels:
slack:
type: slack
token: ${SLACK_BOT_TOKEN}
app_token: ${SLACK_APP_TOKEN} # Socket Mode; or signing_secret, over HTTP
approvers: ["@maria"]
brave:
api_key: ${BRAVE_API_KEY} # web_search; or seal it: wardrik vault put BRAVE_API_KEY
notify:
ops: {channel: slack, room: "#ops", approvers: ["@maria"]}
audit: {webhook: {url: https://hooks.example.com/wardrik, secret: "${WEBHOOK_SECRET}"}}
http:
port: 4747
ip: 127.0.0.1
storage:
postgres:
url: ${DATABASE_URL} # postgres://user:pass@host/db; ?ssl=true for TLSdefaults is what every agent gets where its definition says nothing. That holds however it was
defined: from a file, over the HTTP API, or by the front door.
${NAME} is replaced with the environment variable NAME, one the env file sets, or the
deployment's secret NAME, which wardrik vault put NAME seals. vault, http and storage
are read before the vault opens, so they take the environment alone.
An unknown key is an error, and the daemon says where. tools, tokens, cluster and ssh
aren't supported yet, and are skipped with a warning.
The vault's key is vault.keys, or WARDRIK_VAULT_KEY, or vault_key in the data home, made the
first time the daemon starts.
models
Prices for models the catalog has no dollar price for, so budgets and spending count them. Give
input and output, and optionally cache_read and cache_write, in dollars per million
tokens. Or price it as another model with priced_as. Either takes a markup, as 0.1 for 10%
more.
models:
zai_coding_plan:glm-5.1: {priced_as: zai:glm-5.1}
openrouter:qwen3-coder: {input: 0.3, output: 1.2}workspace
workspace.shared names the file trees agents beyond their owners may mount. Each lists, by
resource pattern, the agents that may read it and those that may write it:
workspace:
shared:
acme/runbooks:
read: ["acme/*"]
write: [acme/agent/ops, "acme/agent/ops/*"]notify
notify names the targets agents' notices go to: a room of a channel, or a webhook. An agent file
routes notices to a target, or straight to a room:
notify: {to: ["slack:#updates"], on: [finished, failed]}storage
storage keeps everything in Postgres. Point it at the primary. The daemon creates its table as
it starts, one node at a time. Without storage, everything is kept on the node's disk.
orgs
orgs declares the orgs beside the deployment's own org. Each lists the servers, channels and
notify targets its agents use, by their names in those sections, and the policy in policies/ it's
held to:
org: platform
orgs:
"*": # every org
mcp: [search]
policy: base
payments:
mcp: [stripe, github]
channels: [payments-slack]
notify: [audit]
policy: payments-strict
growth:
mcp: [github]- Every agent of an org is held to
"*"'s policy, its org's policy, and its own definition's. The strictest decides. A definition can narrow them, never widen them. - A policy's budget is the org's: its agents spend from it together. Each org spends
"*"'s budget apart from the others. A budget per turn stays each agent's own. - An org whose policy isn't in
policies/runs none of its agents. - A server, channel or target no entry lists is the deployment's own org's.
- A channel serves one org, or every org if
"*"lists it. - Any other org is refused: no agent, secret, server or token is kept for it.
Each org's agents reach a server with their own credential. Here, payments and growth each seal
their own github:
wardrik vault put github --org payments
The wardrik command
wardrik help lists every command, and wardrik help COMMAND says more about one. Every command
takes --json.
| Command | Does |
|---|---|
init, doctor, version, completion SHELL | sets up the front door and starts the daemon, checks what the daemon reads, prints the version, writes shell completion |
serve, start, stop, pid, logs | runs the daemon in the foreground or background, stops it, prints its process id, follows its log |
service install, service uninstall | runs it as a systemd user service, or stops doing so |
status, reload, audit | says where it runs, reads the agent files again, shows what people changed |
agent ls (ls), agent show AGENT | lists agents, shows one |
agent ask AGENT MESSAGE (ask), agent follow AGENT (follow) | asks and prints the answer as it comes, follows what an agent does |
agent history, agent cancel, agent rm, agent calls, agent kill, agent move | an agent's conversation, stopping its turn, removing it, its running tool calls, stopping one, moving it to another node |
approval ls, approval approve AGENT CALL, approval deny AGENT CALL | the calls agents wait on a person for |
trigger ls, trigger set, trigger rm | an agent's triggers |
server ls, server add, server rm | MCP servers an owner adds |
connection ls, connection connect, connection disconnect | accounts an owner connects |
token create, token ls, token revoke | API tokens |
vault put, vault ls, vault rm | secrets: the deployment's, or an org's or a person's with --org or --user |
The command acts with a token the daemon writes to cli_token in the data home each time it starts.
WARDRIK_URL and WARDRIK_TOKEN point it at another daemon.
Directories
| Variable | Default | Holds |
|---|---|---|
WARDRIK_CONFIG_DIR | ~/.config/wardrik | config.yaml, agents/, policies/ and env |
WARDRIK_DATA_DIR | ~/.local/share/wardrik/store | the store |
WARDRIK_DATA_HOME | ~/.local/share/wardrik | vault_key, cli_token and the node's cookie |
WARDRIK_STATE_DIR | ~/.local/state/wardrik | logs |
WARDRIK_ENV_FILE | env in the config directory | variables the daemon sets where its environment doesn't |