raxit

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_UNIX socket in a 0700 per-uid runtime directory ($XDG_RUNTIME_DIR/raxit/… or an equivalent fallback) — there is no --addr, no :8080/:8181 to 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 fresh operator token on every start, streams decisions, and exposes no /v1/reload route: raxit apply against a dev daemon 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-token or RAXIT_APPROVALS_TOKEN), which dev doesn'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 running

Fix — pick one:

  • Start serve with the client default explicitly: raxit serve --addr localhost:8181 ...
  • Or pass --addr :8080 to every client command you run against it.

The audit-file split

dev and serve write to different default audit files:

CommandDefault 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.jsonl

Fix: 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.

On this page