Three ways to run raxit
The three ways to put raxit in front of your agent — launcher, in-process autopatch, and explicit wrappers — and how to pick one.
There are three ways to put raxit in front of your agent. All three talk to the same local
engine, read the same .raxit/security.yaml, return the same permit / defer /
deny decisions, and write the same audit trail. They compose — you can launch most of an
app zero-code and still wrap one high-risk tool explicitly. Pick by how your process starts,
not by how much governance you want.
Mode 1 — Zero-code launcher
You run your agent through raxit run. It loads autopatch before your code and governs every
supported framework with no change to your agent.
raxit run --agent-id default-agent -- python agent.py
# TypeScript:
raxit run --agent-id default-agent -- node app.mjs`--agent-id` must match `security.yaml`
The --agent-id you pass must equal the agent: field raxit init wrote into
security.yaml. A mismatch governs a different principal than the one your agent presents, and
calls resolve against the wrong (or no) policy — see
Quickstart for the full trap.
Best when you control the launch command: local development, containers (make raxit run
the entrypoint), and CI jobs. This is the mode the Quickstart uses.
Mode 2 — In-process autopatch
When you don't launch the process by hand — a gunicorn or uvicorn worker, a serverless
handler, a notebook, an existing entrypoint you can't wrap — call the same hook raxit run
triggers, yourself, as early as possible in your entrypoint:
import raxit.autopatch
raxit.autopatch.install() # patch every detected framework, before your agent code runsinstall() returns {framework: [methods patched]} and fails closed (raises
ChokepointDriftError) if a framework is importable but the dispatch path raxit hooks has moved — it will
not silently leave that framework ungoverned. Configure it with environment variables the same
way the launcher does:
RAXIT_DAEMON_URL— an explicit engine endpoint to call. Set this to point at a remote or already-running engine (a shared team daemon, a container sidecar, CI infra). It is always honored first when set.RAXIT_AGENT_ID— the principal to present; must matchagent:insecurity.yaml, exactly as--agent-iddoes in Mode 1.
TypeScript processes you don't launch by hand use the equivalent preload — set
NODE_OPTIONS=--import @raxitlabs/raxit/autopatch-preload on the worker (raxit run wires the
same preload for you in Mode 1).
Zero setup: no port, no `raxit run`, no daemon to start yourself
If RAXIT_DAEMON_URL is unset, the SDK is fully self-sufficient: the first governed call
connects to — or, on a cold start, spawns and attaches to — a local managed engine over a
per-project Unix domain socket. There is no port to pick, no raxit dev/raxit serve to run
in another terminal, and no daemon lifecycle to manage. pip install raxit (or the TypeScript
equivalent) plus one install() call is the whole story for local development, a gunicorn/
uvicorn worker, a serverless handler, or a notebook.
The managed engine binds the socket in a 0700 per-uid runtime directory, verifies same-uid
peer credentials (SO_PEERCRED) on connect, and idles itself down after 15 minutes — pinned
alive by any open taint/budget state so it never disappears mid-session. Every process on the
same host resolving the same project/instance key shares that one engine (and its taint view),
so a Python app and a Node sidecar attach to the same decision authority. See
Running the daemon for the socket/lifecycle details and the
advanced explicit-daemon path.
Set RAXIT_DAEMON_URL only when you deliberately want the SDK to attach to a specific
engine instead of managing its own — pointing at a shared team daemon, a container's own
raxit serve, or a proxy tier. An explicit RAXIT_DAEMON_URL is always honored first and
disables the managed auto-spawn entirely for that process.
A framework-scoped install only covers that framework's dispatch
raxit.autopatch.install("langgraph") patches that framework's tool-dispatch path only.
A call that bypasses it — for example invoking a tool object directly instead of routing it
through the framework — is not intercepted. install() with no argument patches every
framework it detects, but the same rule holds: only calls that flow through a patched
dispatch path are governed. See
Known ungoverned paths for the exact bypasses and how
to close them.
Best for: gunicorn / uvicorn workers, serverless handlers, notebooks, and any long-lived entrypoint where you can add one import but can't change the launch command.
Mode 3 — Explicit wrappers
When you want governance on a specific call and want it to be visible in the code, wrap the tool yourself. This is a first-class mode, not a fallback.
from raxit import RaxitClient
from raxit.agent import governed_tool
client = RaxitClient()
@governed_tool("send_email", client)
def send_email(to: str, body: str) -> str:
...import { RaxitClient, governedTool } from "@raxitlabs/raxit";
const client = new RaxitClient();
const sendEmail = governedTool("send_email", client, async (args) => {
// ...tool body...
});RaxitClient is the low-level client the demos drive directly when they need a
decision without a framework in the loop.
Reach for explicit wrappers when:
- your framework has no autopatch support — wrap the tools you care about directly;
- you run a zero-tolerance setup where every governed call must be explicit in the source, by policy, rather than attached at startup;
- you hit an integration point autopatch can't carry a MODIFY through (e.g. the Vercel AI SDK) — the wrapper supports every decision type, including argument rewrites, on every framework. See Frameworks for which integrations need this.
Experimental: daemon-side proxies
The three modes above enforce in your process. The engine binary also ships
mcp-proxy, http-proxy, a2a-gateway, and ext-authz subcommands — enforcement points that
sit outside your process (in front of an MCP server, an HTTP egress path, an agent-to-agent
hop, or as an Envoy external-authz check). These are experimental and carry no stability
contract; flags and behavior can change without notice, and they are not part of the supported
run paths. See limitations for what is and isn't guaranteed.
Which mode should I use?
| Your deployment shape | Recommended mode |
|---|---|
| Local dev / a script you launch | Mode 1 — raxit run |
| Container image | Mode 1 — make raxit run the entrypoint |
| CI job | Mode 1 — raxit run around the test command |
| gunicorn / uvicorn worker | Mode 2 — install() in the worker entrypoint |
| Serverless handler | Mode 2 — install() at cold start / module load |
| Notebook | Mode 2 — install() in the first cell |
| Framework raxit doesn't autopatch | Mode 3 — governed_tool / governedTool() |
| One high-risk tool in an otherwise zero-code app | Mode 1 or 2, plus Mode 3 on that tool |
| Every governed call must be explicit in source | Mode 3 — wrap each tool |
They stack: a container launched with raxit run (Mode 1) can still wrap its one credential-
minting tool with governed_tool (Mode 3). Same daemon, same policy, same audit trail.
Related
- Quickstart — the launcher path end to end.
- Running the daemon — the managed auto-spawn default, and how to point Modes 2 and 3 at an explicit daemon instead.
- Frameworks — which frameworks autopatch, and which need Mode 3.
- Known ungoverned paths — the calls autopatch does not intercept, and how to close them.

