Quickstart
Get raxit governing your first agent in minutes.
raxit governs the tool calls your AI agent makes. Every call — read a database, hit a webhook,
run a shell command — goes through raxit first and comes back permit, defer, or deny
before it executes. You author one file, .raxit/security.yaml; three commands take you from
nothing to a governed agent: install, raxit init, raxit run.
1. Install
pip install raxitnpm install @raxitlabs/raxitThe first raxit invocation auto-fetches the engine binary — a pinned, sha256-checked
download for your platform. Nothing else to set up.
Platforms
The engine auto-fetch resolves pinned, sha256-checked binaries for darwin and linux only. On Windows, run raxit from WSL — there is no native Windows engine build.
Version floors
The Python SDK requires Python ≥ 3.10 (requires-python in sdk/python/pyproject.toml).
The TypeScript SDK requires Node ≥ 18 (engines.node in @raxitlabs/raxit's
package.json).
2. Write an agent
A minimal 3-tool LangChain agent, no LLM and no API key required — a scripted plan drives real
BaseTool.invoke() calls, so governance fires for real, not simulated:
from langchain_core.tools import tool
from raxit.autopatch import RaxitDenied
@tool
def read_customer_db(query: str) -> str:
"""Read rows from the customer database."""
return "row1: name=Jane Doe, email=jane@example.com, ssn=123-45-6789"
@tool
def post_to_webhook(url: str, payload: str) -> str:
"""POST a payload to an external webhook URL."""
print(f"[webhook] would POST to {url}: {payload}")
return "ok"
@tool
def calc(expression: str) -> str:
"""Evaluate a simple arithmetic expression, e.g. '2 + 2'."""
# demo-only: stripping __builtins__ is NOT a real sandbox — never eval untrusted input
return str(eval(expression, {"__builtins__": {}}))
PLAN = [
(calc, {"expression": "21 * 2"}),
(read_customer_db, {"query": "select * from customers limit 1"}),
(post_to_webhook, {"url": "https://example.com/hook", "payload": "hi"}),
]
for tool_fn, args in PLAN:
print(f"\n>>> agent calls {tool_fn.name}({args})")
try:
result = tool_fn.invoke(args)
print(f"<<< result: {result}")
except RaxitDenied as exc:
print(f"<<< {exc}")(TypeScript users: the equivalent is a LangChain.js StructuredTool — see
LangChain.js / LangGraph.js for a worked example. The command
shapes below are identical, just node agent.mjs instead of python agent.py.)
3. raxit init
Scans the repo for tool-calling code and writes .raxit/security.yaml:
$ raxit init
✓ Framework detected: unknown
✓ Tools discovered: 3 — calc, post_to_webhook, read_customer_db
✓ .raxit/security.yaml written
✓ Agent id: default-agent (use --agent default-agent, or edit agent: in security.yaml)
✓ Policy compiler ready (Cedar stays internal)
Next steps:
edit .raxit/security.yaml tune the generated rules
raxit plan preview the decision changes
raxit apply make them live (enforce mode)Framework detected: unknown is expected here — a minimal single-file script doesn't carry
enough signal for framework auto-detection, but tool discovery (a separate pass) still found
all three tools by AST. The generated file allow-lists everything it found:
apiVersion: raxit.security/v1
agent: default-agent
default: block
rules:
- effect: allow
resource: calc
- effect: allow
resource: post_to_webhook
- effect: allow
resource: read_customer_db
tests:
- name: calc is permitted
action: calc
expect: permit
- name: unknown_tool is denied (default-deny)
action: unknown_tool
expect: denyNote the agent: default-agent line — you'll need it in a moment.
4. Require approval for one tool
Before the first run, change post_to_webhook's effect from allow to review — this is
the tool that reaches outside the process (an outbound webhook), a natural first thing to gate
on a human:
- effect: review
resource: post_to_webhookPreview, then enforce:
$ raxit plan
raxit plan: 1 change vs running policy e1096c1fd92d (daemon localhost:8181)
~ post_to_webhook (agent default-agent): allow -> review [defer]
0 removed · 2 unchanged
Run `raxit apply` to enforce.
$ raxit apply
✓ Policy compiled + tested (.raxit/security.yaml)
✓ Enforce mode: on
✓ applied. policy e1096c1fd92d -> 49e46c6a8600 (daemon confirmed)5. raxit run — and approve the deferred call
The #1 first-run trap
--agent-id on raxit run must match the agent: field init wrote into
security.yaml (default-agent above). A mismatch doesn't fail loudly by default — the
daemon ends up governing a different principal than the one your agent presents, and calls
resolve against the wrong (or no) policy. Copy the id straight out of the file, or run exactly
as shown below. See troubleshooting if a run doesn't
behave as expected.
Running inside gunicorn / uvicorn / a serverless handler instead of launching the process yourself? See Three ways to run raxit for the in-process autopatch path.
Start the agent. post_to_webhook matches the review rule, so it defers and waits for a human:
$ raxit run --agent-id default-agent -- python agent.py
raxit run: using existing daemon at http://localhost:8181
raxit run: posture — identity=adopted daemon (identity posture owned by that daemon) · credential-broker=off · ambient=present (NOT stripped) · enforce=enforce
[raxit] waiting for human approval (up to 120s)… approval id dt_28930b3bf7da664a
[raxit PENDING] 'post_to_webhook' deferred — waiting for human approval (up to 120s).
Token: dt_28930b3bf7da664a
Approve: raxit approvals approve dt_28930b3bf7da664a
Deny: raxit approvals deny dt_28930b3bf7da664aWhile it waits, approve the pending call from another terminal:
$ raxit approvals approve dt_28930b3bf7da664a --reason "looks fine"
dt_28930b3bf7da664a: approved (approver=operator)The run continues. calc and read_customer_db are plain allow rules, so they ran; and now
that it's approved, post_to_webhook permits and the tool body actually runs:
>>> agent calls calc({'expression': '21 * 2'})
<<< result: 42
>>> agent calls read_customer_db({'query': 'select * from customers limit 1'})
<<< result: row1: name=Jane Doe, email=jane@example.com, ssn=123-45-6789
>>> agent calls post_to_webhook({'url': 'https://example.com/hook', 'payload': 'hi'})
[webhook] would POST to https://example.com/hook: hi
<<< result: ok6. What happens when nobody approves
Run it again, but this time don't approve. After up to 120 seconds (RAXIT_DEFER_TIMEOUT) the
deferred call denies instead:
$ raxit run --agent-id default-agent -- python agent.py
[raxit] waiting for human approval (up to 120s)… approval id dt_1373bb3e0bcf139d
[raxit] approval expired (id dt_1373bb3e0bcf139d)
>>> agent calls post_to_webhook({'url': 'https://example.com/hook', 'payload': 'hi'})
<<< [raxit BLOCKED] 'post_to_webhook' denied (reason: deny.approval_expired). The operation was
not executed. Details: raxit explain d867c63661454e8497369a08ebaec35dpost_to_webhook deferred, waited for a human, and nobody approved it in time — so it denied,
fail-closed. The denial carries a call_id you can hand to
raxit explain d867c63661454e8497369a08ebaec35d for the full decision record, including which
Cedar rule produced it.
That edit → plan → apply → run cycle, plus the human-in-the-loop approval, is the whole workflow. See the security.yaml reference for the full mechanics, including what happens when a policy fails its own tests.
Version note
This walkthrough reflects the current engine, published as raxit v0.0.4 on PyPI/npm —
raxit plan/raxit apply and scanner-extracted tool names in init are both included. If a
command here doesn't match your install, check raxit version.
Next
- Three ways to run raxit — launcher, in-process autopatch, or explicit wrappers — pick by how your process starts.
- The
security.yamlreference — the one file you author: rules, tests, and the plan/apply loop. - Framework coverage — what's auto-patched today, in Python and TypeScript.
- Reason codes — how to read the
permit/defer/denyreason on every decision.

