smolagents
Governing smolagents agents in Python, zero-code.
Every smolagents tool call — whether from a ToolCallingAgent or a CodeAgent — dispatches
through one method: smolagents.Tool.__call__. raxit's auto-patcher wraps that method, so
every tool call resolves to permit / defer / deny before it executes, with no governance
code in the agent. Python only — smolagents has no JS/TS SDK.
Run it yourself
For your own smolagents agent, install the SDK and launch it under raxit run:
pip install raxit
raxit run -- python your_agent.pyRun the same file without raxit run and it runs ungoverned — governance is a deploy-time
choice, not a code change.
How it works
Tools are ordinary smolagents.Tool subclasses with no governance code. The only "wiring" is
each tool's name, the policy resource it's governed as:
# your_agent.py — imports NOTHING from raxit
from smolagents import Tool
class DbWrite(Tool):
name = "db_write"
description = "Write a row to an internal datastore."
inputs = {"payload": {"type": "string", "description": "row payload"}}
output_type = "string"
def forward(self, payload: str) -> str: ...
agent = ToolCallingAgent(tools=[DbWrite()], model=model)apiVersion: raxit.security/v1
agent: smolagents-bot
default: block
rules:
- effect: allow
resource: fetch_docs
- effect: review # unconditional review == defer.policy
resource: db_write
# shell_exec is intentionally UNLISTED -> default-deny (deny.default_or_forbid).A denied or deferred call raises raxit.autopatch.RaxitDenied from inside Tool.__call__,
carrying the reason code — the tool body never executes. raxit explain <call_id> shows the
full decision record.
Real captured output from a run:
[PASS] autopatch: install() governed the smolagents Tool.__call__ dispatch — patched={'smolagents': ['__call__']}
fetch_docs -> permit -> ran
db_write -> defer -> RaxitDenied raised: reason=defer.policy
shell_exec -> deny -> RaxitDenied raised: reason=deny.default_or_forbid
audit.verify -> ok=True signatures_verified=True signed_count=3 count=3
all checks PASSScope: dotted resource names, and no MODIFY
smolagents.Tool.validate_arguments() requires name to be a valid Python identifier and
rejects dotted names — unlike LangChain's StructuredTool or dspy's Tool(name=...) kwarg.
That's a smolagents constraint, not a raxit one: the auto-patcher governs by tool name,
whatever that name is, so this fixture uses db_write/shell_exec instead of db.write/
shell.exec. Separately, Tool.__call__ receives its input as a single positional scalar, not
a dict, so field-level redaction (MODIFY) isn't exercised at this interception point either —
same limitation our test suite documents for this framework.
What got denied and why
defer.policy—db_writematched thereviewrule; it's suspended for human approval (see Approvals). The demo runs withRAXIT_DEFER_TIMEOUT=0, so the defer surfaces as a raisedRaxitDeniedrather than blocking inline.deny.default_or_forbid—shell_execappears nowhere in the policy, so it falls todefault: block. The tool body never ran, confirmed by the demo's own call log.
Every decision — the permit, the defer, and the deny — lands in one hash-chained,
Ed25519-signed Decision Provenance log, verified green with client.audit.verify(signatures=True).
See Audit and Decisions for the full
permit / defer / deny vocabulary and reason-code catalog.
Related
- LangGraph (Python) — the same docs/ops-bot story, zero-code autopatch at a different interception point.
- DSPy, google-adk — the same story on other Python frameworks.

