raxit

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 raxit

The 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:

agent.py
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: deny

Note 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_webhook

Preview, 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_28930b3bf7da664a

While 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: ok

6. 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 d867c63661454e8497369a08ebaec35d

post_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

On this page