Running the daemon
The managed auto-spawn default, and the advanced explicit-daemon path: raxit dev vs raxit serve, the port trap, and how clients find the daemon.
The default: managed auto-spawn, no daemon to run yourself
Zero-config local usage — Mode 2 (autopatch) or Mode 3 (explicit wrappers) with no
RAXIT_DAEMON_URL set — needs no daemon command at all. The first governed call connects to,
or spawns and attaches to, a local managed engine over a per-project Unix domain socket:
- No port. The managed engine binds only an
AF_UNIXsocket in a0700per-uid runtime directory ($XDG_RUNTIME_DIR/raxit/…or an equivalent fallback) — there is no--addr, no:8080/:8181to reconcile, and nothing else on the host can bind or spoof that socket. - Same-uid, kernel-enforced. Every connection is checked with
SO_PEERCRED; only processes running as your own uid can attach. - Shared automatically. Every process on the host resolving the same project/instance key — a Python app and a Node sidecar, several workers of one app — attaches to the same engine and the same taint/budget/audit view, elected via an advisory lock so only one spawn wins.
- Self-managing lifecycle. The engine idles down after 15 minutes with no calls, pinned alive by any open taint label or budget reservation so it never disappears mid-session; the next governed call after a shutdown simply respawns it.
- Fails closed. Any bootstrap failure (can't resolve/spawn the engine binary, a socket that answers but isn't a raxit engine, a timeout waiting for readiness) raises and the caller's decision degrades to deny — there is no ungoverned in-process fallback.
This is what makes pip install raxit / the TypeScript SDK "just work": no separately-run
daemon, no raxit run required. raxit run (Mode 1) still works exactly as before — it launches
your process with autopatch pre-loaded, and its child process hits the same managed auto-spawn
path from the SDK side.
An explicit RAXIT_DAEMON_URL is always honored first and turns off managed auto-spawn for
that process entirely — set it whenever you want the SDK to attach to a specific engine instead
of managing its own. That's the advanced path below.
Running your own `raxit serve` and NOT setting RAXIT_DAEMON_URL splits your state
If you start raxit serve yourself (TCP, e.g. :8181) and then make a plain SDK call with no
RAXIT_DAEMON_URL set, the SDK does not discover that daemon — it spawns its own second
managed engine over the socket path instead. You now have two engines with two independent
taint/budget/audit views for the same project, which silently defeats cross-call taint tracking.
Set RAXIT_DAEMON_URL=http://localhost:8181 (or whatever --addr you used) so the SDK attaches
to the daemon you started, instead of spawning a second one. This is deliberate: the SDK does
not auto-connect to localhost:8181 by default, because that would reintroduce a
TCP-localhost adoption path — any local process could open that port — which the managed
socket's SO_PEERCRED same-uid check exists specifically to close.
Advanced: running an explicit daemon yourself
Reach for an explicit daemon — and the raxit dev/raxit serve commands below — when you need a
daemon that outlives a single SDK process on purpose: a container's own long-running decision
daemon, a shared daemon multiple hosts/services attach to over TCP, one of the proxy tiers
(mcp-proxy, http-proxy, a2a-gateway, ext-authz), or CI infra that wants a daemon in its
own step. This section covers the parts that trip people up the first time: which daemon command
to use, the default-port mismatch, and where the audit trail actually lands.
raxit dev vs raxit serve
Both are decision daemons that speak the same HTTP API to the same clients (run, apply,
status, plan, approvals, agent) — the difference is what they're for:
raxit dev— local iteration. Prints a freshoperator tokenon every start, streams decisions, and exposes no/v1/reloadroute:raxit applyagainst adevdaemon compiles, tests, and records the new policy locally, then tells you honestly to restart rather than pretending it live-swapped.raxit serve— the daemon a real deployment runs. It refuses to start with the approvals API enabled on a non-loopback listener unless a token is supplied (--approvals-tokenorRAXIT_APPROVALS_TOKEN), whichdevdoesn't need for local loopback use.
$ raxit dev --listen localhost:8282
operator token: bd8744f561f4817b8cc304bb43bcfa393d446150
raxit: WARNING: audit log .raxit/var/dev-audit.jsonl is UNSIGNED ...
✓ Loaded .raxit/security.yaml
✓ Policy compiled in memory
✓ Listening localhost:8282$ RAXIT_APPROVALS_TOKEN=my-token raxit serve --source .raxit/security.yaml --addr localhost:8181
raxit: WARNING: audit log .raxit/var/daemon-audit.jsonl is UNSIGNED ...
raxit serve: decision daemon on localhost:8181 (policy .raxit/security.yaml ve1096c1fd92d9c5f)See dev & serve for the full flag reference.
The port trap
Read this before your first `raxit serve`
raxit serve's own --addr flag defaults to :8080. Every client command that attaches to
a daemon — raxit run, apply, status, plan, approvals, agent — defaults its own
--addr to localhost:8181. raxit dev's --listen flag already defaults to
localhost:8181, matching the clients — the mismatch is specific to serve.
Start serve bare and the default-addr clients won't find it:
$ raxit serve --source .raxit/security.yaml
raxit serve: decision daemon on :8080 ...
$ raxit status
Daemon:
[--] not reachable (localhost:8181) -- daemon may not be runningFix — pick one:
- Start
servewith the client default explicitly:raxit serve --addr localhost:8181 ... - Or pass
--addr :8080to every client command you run against it.
The audit-file split
dev and serve write to different default audit files:
| Command | Default audit path |
|---|---|
raxit dev | .raxit/var/dev-audit.jsonl |
raxit serve | .raxit/var/daemon-audit.jsonl |
Every other command that reads the audit trail (raxit explain, raxit audit tail/verify,
raxit status) defaults its own --audit flag to .raxit/var/dev-audit.jsonl — so querying a
serve-started daemon's decisions with the bare default finds nothing:
$ raxit explain d867c63661454e8497369a08ebaec35d
explain: no record found for id "d867c63661454e8497369a08ebaec35d" in .raxit/var/dev-audit.jsonlFix: pass --audit .raxit/var/daemon-audit.jsonl explicitly for anything you looked up
through a raxit serve daemon. See Common issues for this
and the other first-run stalls.
One daemon per project
A daemon governs a single .raxit workspace and a single agent id. If a daemon governing a
different agent is already bound to the port you're targeting, apply detects the mismatch and
refuses to push to it rather than silently reconfiguring someone else's daemon — run a separate
daemon on its own port per project (--addr) instead of sharing one.
How clients find the daemon
CLI client commands (status, plan, apply, approvals, agent) only ever look at their own
--addr flag (default localhost:8181) — there's no environment override for those. The one
place RAXIT_DAEMON_URL matters is the SDK client your agent process uses: raxit run sets
it in the child process's environment so the Python/TypeScript SDK knows which daemon to attach
to without you wiring it by hand. If you're launching your agent yourself (not via raxit run)
and want it to attach to this explicit daemon rather than spawning its own managed engine, set
RAXIT_DAEMON_URL for it explicitly, matching whichever daemon's --addr/--listen you
started — see the split-state warning above.
Warrants and approvals don't survive a restart
Parked approvals and standing warrants live in the daemon's memory, not on disk — restarting
dev or serve drops both. This is a known limitation, not a bug; see
Limitations for the full warrant-persistence story.

