Policy-as-code runtime boundaries for Python AI agents
Project description
Boundari
Policy-as-code runtime boundaries for Python AI agents.
Boundari gives AI agents hard runtime boundaries: tool permissions, approval gates, budgets, schemas, redaction, and audit logs without forcing you into a new agent framework.
It is designed to sit around tools from Pydantic AI, OpenAI Agents SDK, LangGraph, CrewAI, or a custom Python loop. Your agent can reason freely, but before it mutates the world, Boundari checks the contract.
Install
pip install boundari
For local development:
uv venv
uv sync --extra dev
uv run pytest
Quickstart
from boundari import Boundary, Budget, ToolPolicy
def send_email(to: str, subject: str, body: str) -> dict[str, str]:
return {"message_id": "msg_123", "status": "sent", "to": to}
boundary = Boundary(
name="support_agent",
budget=Budget(max_tool_calls=20, max_runtime_seconds=180),
tools=[
ToolPolicy("email.send").require_approval(
when="recipient_domain not in trusted_domains"
),
ToolPolicy("docs.search").allow(),
ToolPolicy("shell.run").deny(),
],
trusted_domains=["example.com"],
)
safe_send_email = boundary.wrap_tool("email.send", send_email)
result = safe_send_email(
to="customer@outside.test",
subject="Refund update",
body="We can help with that.",
)
assert result.allowed is False
assert result.reason == "approval_denied"
Boundari returns a structured Decision for denied calls. If you prefer exceptions,
wrap with raise_on_denied=True.
Core Concepts
Boundary is the runtime contract for one agent or workflow.
ToolPolicy says whether a named tool is allowed, denied, schema-validated,
approval-gated, table-scoped, or amount-limited.
RunContext tracks per-run budgets such as tool calls, runtime, tokens, and cost.
Redactor masks sensitive strings before tool outputs are returned to the model.
AuditEvent records allow, deny, approval, and output decisions in a structured way.
Python API
from decimal import Decimal
from pydantic import BaseModel, EmailStr
from boundari import Boundary, Budget, ToolPolicy
class EmailInput(BaseModel):
to: EmailStr
subject: str
body: str
class EmailResult(BaseModel):
message_id: str
status: str
to: EmailStr
boundary = Boundary(
name="customer_support_agent",
budget=Budget(max_tool_calls=12, max_runtime_seconds=120, max_cost_usd=Decimal("0.25")),
tools=[
ToolPolicy("zendesk.search").allow(),
ToolPolicy("zendesk.reply").require_approval(),
ToolPolicy("stripe.refund").require_approval().max_amount("100.00"),
ToolPolicy("email.send").input(EmailInput).output(EmailResult),
ToolPolicy("shell.run").deny(),
],
)
Decorator API
Use boundary_tool to attach Boundari metadata to a plain callable. Framework
adapters can inspect this metadata, and you can also pull the generated policy from
__boundari_tool_policy__.
from boundari import boundary_tool
@boundary_tool(
name="email.send",
risk="high",
scopes=["email:send"],
require_approval=True,
)
async def send_email(to: str, subject: str, body: str) -> dict[str, str]:
return {"message_id": "msg_123", "status": "queued"}
YAML Policies
Create a policy file with:
boundari init
Example boundari.yaml:
agent: sales_ops_agent
budgets:
max_tool_calls: 20
max_runtime_seconds: 180
max_cost_usd: "0.50"
tools:
crm.read_contact:
allow: true
scopes: ["crm:read"]
crm.update_contact:
allow: true
require_approval: true
scopes: ["crm:write"]
email.send:
allow: true
require_approval:
when: "recipient_domain not in trusted_domains"
input_schema: EmailInput
output_schema: EmailResult
shell.run:
allow: false
data:
redact:
- api_key
- credit_card
- email
- phone
trusted_domains:
- example.com
outputs:
require_schema: true
block_if_contains:
- "{{SECRET}}"
policy_tests:
forbidden_tools:
- shell.run
Load it from Python:
from boundari import Boundary
boundary = Boundary.from_file("boundari.yaml")
Human Approval
Approval callbacks receive an ApprovalRequest with the exact tool name, argument
summary, risk, reason, and run id.
from boundari import Boundary
def approve(request):
return request.tool_name != "stripe.refund"
boundary = Boundary.from_file("boundari.yaml", approver=approve)
For local demos, pass console_approver:
from boundari import Boundary, console_approver
boundary = Boundary.from_file("boundari.yaml", approver=console_approver)
FastAPI users can build an approval route with fastapi_approval_router after
installing boundari[web].
Redaction
Boundari redacts common sensitive values from tool outputs before returning them to the agent. Built-in rules include email addresses, phone numbers, credit-card-like numbers, and API keys.
from boundari import Redactor
redactor = Redactor(["email", "api_key"])
assert redactor.redact_text("alice@example.com sk-test-secret") == (
"[REDACTED:email] [REDACTED:api_key]"
)
Raw tool inputs and outputs are not stored by default. JSONLAuditLog stores
structured, redacted audit events.
CLI
boundari init
boundari validate boundari.yaml
boundari test boundari.yaml
boundari replay traces/run_123.jsonl --policy boundari.yaml
boundari explain traces/run_123.jsonl
boundari test fails when a policy is invalid, a configured forbidden tool is
allowed, required output schema markers are missing, or a golden trace violates the
contract.
Golden Traces
A replay trace is JSONL. Each line should include a tool name and optional args.
Use expected_allowed when the trace should assert a specific outcome.
{"tool": "docs.search", "args": {"query": "refund policy"}, "expected_allowed": true}
{"tool": "shell.run", "args": {"command": "rm -rf /"}, "expected_allowed": false}
Framework Adapters
Install optional extras as adapters mature:
pip install "boundari[pydantic-ai]"
pip install "boundari[openai-agents]"
Current adapters keep the core package framework-agnostic and expose lightweight wrappers that preserve the original agent object while carrying the active boundary.
from boundari import Boundary
from boundari.adapters.pydantic_ai import wrap_agent
safe_agent = wrap_agent(agent, boundary=Boundary.from_file("boundari.yaml"))
Development
uv venv
uv sync --extra dev
uv run ruff check .
uv run mypy src
uv run pytest
uv run python -m build
uv run twine check dist/*
Security Positioning
Boundari reduces an agent's blast radius by enforcing deterministic runtime policy. It is not a prompt-injection scanner, hosted security product, model gateway, or secret manager. The core package does not make network calls.
License
MIT License. See LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file boundari-0.1.0.tar.gz.
File metadata
- Download URL: boundari-0.1.0.tar.gz
- Upload date:
- Size: 21.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.17 {"installer":{"name":"uv","version":"0.9.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3483ebca8c4f37016b660abc25fe723cd24a150ccb6a47617adf1a8c15c6b6a6
|
|
| MD5 |
f768dd1cb1ed42b003cfc4a4af7c28e5
|
|
| BLAKE2b-256 |
f88d6f5fea394768580a8da3ae7989715b212fe56d88fa2d1436753e58101171
|
File details
Details for the file boundari-0.1.0-py3-none-any.whl.
File metadata
- Download URL: boundari-0.1.0-py3-none-any.whl
- Upload date:
- Size: 23.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.17 {"installer":{"name":"uv","version":"0.9.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d5037356e76d0e2a286054e76d32f875d5b34ab8ed8b770f97f8b67a17596445
|
|
| MD5 |
abaf1f04580116602beefa13853036da
|
|
| BLAKE2b-256 |
502fb5e537a1986bc17297a4da8dc26fd137b1e943ceaff5dbcb5ae61905f7ff
|