Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.brane.membranelabs.org/llms.txt

Use this file to discover all available pages before exploring further.

Policies are functions. They take a PolicyContext and return a Decision.

Policy Function Shape

Every policy is a plain Python function:
from brane import Decision, PolicyContext

def my_policy(ctx: PolicyContext) -> Decision:
    return Decision(type="allow")
No inheritance, no framework, no configuration file. Just a function that receives context and returns a decision.

Registering Policies

Use @runtime.before_capability or @runtime.after_capability to register:
@runtime.before_capability("execute_sql")
def read_only_sql(ctx):
    if not ctx.arg("query").lower().strip().startswith("select"):
        return Decision(type="deny", reason="Only SELECT queries are allowed")
    return Decision(type="allow")
The decorator registers the function as a Policy targeting "execute_sql" at the before_capability stage. The original function is returned unchanged, so you can call it directly in tests.

Policy Targets

Exact match:
@runtime.before_capability("refund_customer")
def refund_policy(ctx):
    ...
Wildcard:
@runtime.before_capability("*")
def global_policy(ctx):
    if ctx.is_prod and ctx.is_high_risk:
        return Decision(type="deny", reason="High-risk actions blocked in prod")
    return Decision(type="allow")
Wildcard policies run alongside exact-match policies. Both are evaluated; deny wins.

Policy Metadata

Provide a name and version to make decisions traceable:
@runtime.before_capability(
    "refund_customer",
    name="refund_amount_limit",
    version="1.0",
    description="Blocks refunds above the tenant limit",
    priority=10,
)
def refund_limit_policy(ctx):
    amount = ctx.arg("amount_usd", 0)
    tenant_limit = get_tenant_limit(ctx.tenant_id)
    if amount > tenant_limit:
        return Decision(type="deny", reason=f"Amount exceeds limit of ${tenant_limit}")
    return Decision(type="allow")
The name and version are annotated onto the Decision and appear in CapabilityDeniedError.policy_name.

Policy Metadata Fields

  • name: human-readable identifier. Defaults to the function name.
  • version: policy version string.
  • description: what this policy enforces.
  • priority: higher priority runs first. Default is 0.
  • enabled: set False to disable without removing. Default is True.
  • source: where this policy came from.

Multiple Policies On The Same Capability

Multiple policies can target the same capability. They all run. Deny wins: if any policy returns deny, the action is denied regardless of other policies returning allow.
@runtime.before_capability("refund_customer", priority=10)
def refund_amount_limit(ctx):
    if ctx.arg("amount_usd", 0) > 100:
        return Decision(type="deny", reason="Amount too high")
    return Decision(type="allow")

@runtime.before_capability("refund_customer", priority=5)
def refund_scope_check(ctx):
    if not ctx.agent_has_scope("refunds:create"):
        return Decision(type="deny", reason="Missing refunds:create scope")
    return Decision(type="allow")

Testing Policies

Because policies are plain functions, you can test them by constructing a PolicyContext directly:
from brane import AgentAction, Capability, PolicyContext
from my_policies import refund_amount_limit

def test_refund_limit_deny():
    cap = Capability(name="refund_customer", type="tool", risk="high")
    action = AgentAction(
        action_type="tool_call",
        capability=cap,
        input={"customer_id": "cust_1", "amount_usd": 250.0},
    )
    ctx = PolicyContext(action=action, args=action.input)
    decision = refund_amount_limit(ctx)
    assert decision.denied
    assert "Amount too high" in decision.reason

Where Policy Lives

Policy should be a separate control layer: not scattered across tool implementations, not hidden in prompts, not inside ad hoc if-statements in the agent logic. Start with a single policy file per agent. Once policies are explicit, they can be tested, audited, reused, and eventually moved to a central policy system or cloud bundle.
Write explicit deny rules for high-risk capabilities rather than relying on allow-by-default.