Metadata-Version: 2.4
Name: raxit
Version: 0.0.2
Summary: Zero-dependency Python client for the raxIT runtime governance engine (permit / defer / deny on every tool call)
Project-URL: Homepage, https://raxit.ai
Project-URL: Documentation, https://docs.raxit.ai
Project-URL: Repository, https://github.com/raxitlabs/runtime-security-core
Author: raxIT Labs
License-Expression: Apache-2.0
License-File: LICENSE
Classifier: Intended Audience :: Developers
Classifier: Operating System :: MacOS
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Topic :: Security
Requires-Python: >=3.10
Description-Content-Type: text/markdown

# raxit — thin Python client for the raxIT governance daemon

Govern every agent tool call with permit / defer / deny, before it executes.

```sh
pip install raxit     # zero runtime dependencies (stdlib only); or: uv add raxit
```

Docs: [docs.raxit.ai](https://docs.raxit.ai).

**Platforms:** the launcher resolves/downloads engine binaries for
`darwin`/`linux` only. On Windows, run raxit from **WSL** — there is no
native Windows engine build.

## Zero-code governance (`raxit run`)

The fastest path is no SDK calls at all. `raxit run` boots the daemon on
`localhost:8181`, sets the child environment, and preloads the auto-patcher
(via `sitecustomize`) so every tool call is governed. `raxit init` scans your
repo and generates the `.raxit/` policy workspace first — `raxit run`
fail-closes without it:

```sh
raxit init                     # scan -> generate .raxit/
raxit run -- python agent.py   # inherits the agent id from .raxit/security.yaml
```

Under the hood this sets `RAXIT_AUTOLOAD=1` and puts the raxit
`sitecustomize` dir on `PYTHONPATH`, so the auto-patcher fires at Python
startup and patches the tool-dispatch choke point of each detected
framework:

| Framework | How it's governed |
| --- | --- |
| LangChain | Auto-patched (importable) |
| LangGraph | Auto-patched |
| CrewAI | Auto-patched |
| AutoGen | Auto-patched |
| OpenAI Agents SDK | Auto-patched |
| Pydantic AI | Auto-patched |
| Google ADK | Auto-patched |
| LlamaIndex | Auto-patched |
| Strands Agents | Auto-patched |
| smolagents | Auto-patched |
| Haystack | Auto-patched |
| Agno | Auto-patched |
| Claude Agent SDK | Auto-patched (injects a `PreToolUse` hook) |
| Microsoft Agent Framework | Auto-patched (injects a `FunctionMiddleware`) |
| DSPy | Auto-patched (`Tool.__call__`/`.acall` for `dspy.ReAct`, plus `ToolCalls.ToolCall.execute` for native tool calling — two independent surfaces) |
| CAMEL-AI | Auto-patched (`FunctionTool.__call__`/`.async_call` — the chokepoint every `ChatAgent` tool call, sync or async, normalizes through) |
| Agency Swarm | Auto-patched (inherited — tool dispatch, incl. its own `SendMessage`, flows through OpenAI Agents SDK's already-patched chokepoint; no dedicated patcher) |

A blocked call raises `RaxitDenied`; a `MODIFY` verdict runs the tool on the
engine's transformed arguments; a `permit` runs it unchanged. `raxit.agent`
also exposes `governed` / `governed_tool` as a manual, per-tool escape hatch
when auto-patch doesn't fit.

## Programmatic client

```python
from raxit import RaxitClient

client = RaxitClient("http://localhost:8181", agent_id="my-agent")
d = client.govern(resource="db.read", args={"query": "select 1"})
if d.deferred:
    d = client.govern.approve_wait(d)      # blocks until a human approves
if d.denied:
    raise RuntimeError(f"{d.reason_code}: {d.hint}")
result = run_tool()                        # only runs on permit
client.output.record(result, car=d.car)    # feeds output governance + taint
```

LangChain (no langchain import required by this package):

```python
from raxit.langchain import RaxitCallbackHandler
handler = RaxitCallbackHandler(client)    # raises RaxitDenied on deny
agent.run("...", callbacks=[handler])
```

Fail-closed by default: if the daemon is unreachable, `govern()` returns
`deny` (`deny.daemon_unreachable`). Pass `fail_open=True` for dev only.

### `RAXIT_GOVERNANCE`

Governed by default. `RAXIT_GOVERNANCE=off` is the kill-switch for
contrast/before-after demos — the client then bypasses the daemon entirely
and every call synthesizes `permit`. Only two values are accepted (after
`strip().lower()`): `on` and `off`; unset defaults to `on`. Any other value —
`1`/`true`/`yes`/`0`/`false`/`no`, or a typo — raises `ValueError` at client
construction, before any network call, rather than silently guessing.



## Audit — verify the tamper-evident decision log

`client.audit.verify()` checks the hash-chained Decision Provenance log; pass
`signatures=True` with one or more pinned Ed25519 public keys to also verify
record signatures offline against a trusted key (not the daemon's own
config):

```python
client = RaxitClient("http://localhost:8181", operator_token="<operator token>")
report = client.audit.verify(
    signatures=True,
    require_signed=True,
    pubkeys=["<base64 raw-32-byte Ed25519 pubkey>"],
)
assert report["signatures_verified"] and report["signed_count"] > 0
```

Operator-gated (`Authorization: Bearer <operator_token>`). For fully offline,
adversarial-grade verification use the CLI:
`raxit audit verify --signatures --pubkey <key>`.

## Budget — read the session/principal budget projection

`client.budget.get()` is a strictly read-only projection of the engine's budget
accountant — never a reservation. It reports `used`/`remaining` against each
configured cap (call budget, per-session and per-day token/cost caps):

```python
client = RaxitClient("http://localhost:8181", operator_token="<operator token>")
snap = client.budget.get(agent_id="payments-bot", session_id="sess-1")
for b in snap.budgets:
    print(b.name, b.used, "/", b.limit, b.unit, f"({b.window})")
```

Operator-gated (`Authorization: Bearer <operator_token>`). Fails closed: an
unreachable daemon **raises** `GovernanceError` — it is never read as an empty
or infinite budget. An empty `snap.budgets` means genuinely no cap configured.

## Warrants — inspect the loaded standing grants

`client.warrants.list()` / `client.warrants.get(id)` are a read-only view of the
warrants loaded at `raxit serve` startup. There is no create/revoke — minting
stays the operator-controlled load path.

```python
client = RaxitClient("http://localhost:8181", operator_token="<operator token>")
for w in client.warrants.list():
    print(w.id, w.agent_id, w.tool_pattern, w.status, "expires", w.expires_at)
```

Operator-gated. Fails closed (**raises** `GovernanceError` when unreachable, so
"none" is never confused with "unreachable"). Warrants carry no credentials and
the attested intent-baseline text is never projected.
