Skip to main content

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.

Metadata

Release files for boundari 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for boundari 0.1.0
File Size Uploaded
boundari-0.1.0.tar.gz 21.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for boundari 0.1.0
File Interpreter ABI Platform
boundari-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 44.5 kB

Release files / boundari-0.1.0.tar.gz

Download URL boundari-0.1.0.tar.gz
Size 21.4 kB
Tags Source
SHA-256 checksum
How to use checksums
3483ebca8c4f37016b660abc25fe723cd24a150ccb6a47617adf1a8c15c6b6a6
BLAKE2b-256 checksum
How to use checksums
f88d6f5fea394768580a8da3ae7989715b212fe56d88fa2d1436753e58101171
Upload date
Uploaded using Trusted Publishing?
What is 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}

Release files / boundari-0.1.0-py3-none-any.whl

Download URL boundari-0.1.0-py3-none-any.whl
Size 23.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d5037356e76d0e2a286054e76d32f875d5b34ab8ed8b770f97f8b67a17596445
BLAKE2b-256 checksum
How to use checksums
502fb5e537a1986bc17297a4da8dc26fd137b1e943ceaff5dbcb5ae61905f7ff
Upload date
Uploaded using Trusted Publishing?
What is 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}

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page