Implemented — deny only; approval_required is planned
Refund Limit Policy
Allow small refunds automatically. Block large refunds until approval is implemented.
Problem
A support agent can issue refunds to customers. You want to:
- Allow refunds up to $100 automatically — the agent can proceed without human review
- Block refunds above $100 — these require a human to approve (approval workflow planned)
- Never allow refunds in non-production environments
Complete Example
from brane import CapabilityDeniedError, Decision, Effect, Runtime
# Tenant configuration (in practice, fetch from your config/DB)
TENANT_LIMITS = {
"tenant_acme": 100.0,
"tenant_globex": 250.0,
"tenant_initech": 50.0,
}
DEFAULT_LIMIT = 100.0
# 1. Create runtime
runtime = Runtime(
agent_id="support-agent",
environment="prod",
tenant_id="tenant_acme",
)
# 2. Register the refund capability
@runtime.capability(
name="refund_customer",
type="tool",
risk="high",
effect=Effect(
type="financial",
reversible=False,
external=True,
description="Issues a payment refund",
),
data_namespace="billing.refunds",
owner="payments-team",
)
def refund_customer(customer_id: str, amount_usd: float, reason: str = ""):
# Real implementation calls your payments API
return {"refunded": True, "customer_id": customer_id, "amount": amount_usd}
# 3. Policy: only allow refunds in production
@runtime.before_capability(
"refund_customer",
name="refund_require_prod",
version="1.0",
priority=200,
)
def require_prod_for_refunds(ctx):
if not ctx.is_prod:
return Decision(
type="deny",
reason="Refunds can only be issued in the production environment",
)
return Decision(type="allow")
# 4. Policy: enforce per-tenant refund limit
@runtime.before_capability(
"refund_customer",
name="refund_amount_limit",
version="1.0",
priority=100,
)
def refund_amount_limit(ctx):
amount = ctx.arg("amount_usd", 0)
limit = TENANT_LIMITS.get(ctx.tenant_id, DEFAULT_LIMIT)
if amount <= 0:
return Decision(type="deny", reason="Refund amount must be positive")
if amount > limit:
return Decision(
type="deny",
# Future: type="approval_required" — pause until a human approves
reason=(
f"Refund of ${amount:.2f} exceeds the ${limit:.2f} limit "
f"for tenant {ctx.tenant_id}. Human approval is required."
),
)
return Decision(type="allow")
# 5. Use the capability
try:
# Allowed — within limit
result = refund_customer("cust_123", 75.00, reason="Duplicate charge")
print(result)
# {"refunded": True, "customer_id": "cust_123", "amount": 75.0}
# Denied — exceeds limit
refund_customer("cust_456", 349.00, reason="Defective product")
except CapabilityDeniedError as e:
print(f"Blocked: {e.reason}")
# Output: Blocked: Refund of $349.00 exceeds the $100.00 limit for tenant tenant_acme. Human approval is required.
print(f"Action ID: {e.action_id}")How It Works
Two policies protect the refund_customer capability, running in priority order:
- require_prod_for_refunds (priority 200, runs first) — blocks the refund entirely if the environment is not prod. Prevents staging or dev agents from issuing real refunds.
- refund_amount_limit (priority 100, runs second) — checks the refund amount against the tenant-specific limit. Blocks amounts over the limit.
Both policies must allow for the refund to proceed. Either can deny.
Adding a Third Policy: Scope Check
@runtime.before_capability(
"refund_customer",
name="refund_scope_check",
version="1.0",
priority=150,
)
def require_refund_scope(ctx):
if not ctx.agent_has_scope("refunds:create"):
return Decision(
type="deny",
reason="Agent requires refunds:create scope to issue refunds",
)
return Decision(type="allow")Future: Approval Instead of Deny
Once approval_required decisions are implemented, the limit policy can be updated to pause rather than block:
if amount > limit:
return Decision(
type="approval_required",
reason=f"Refund of ${amount:.2f} requires human approval",
approval={
"approver_group": "finance-ops",
"expiration_minutes": 60,
"context": {
"customer_id": ctx.arg("customer_id"),
"amount": amount,
"tenant": ctx.tenant_id,
},
},
)The runtime will pause the action, send the approval request, and resume when a human approves or deny automatically when they reject or the request expires.
Production Notes
- The policy runs before the refund API call. If the policy denies, no refund is issued and no payment provider API is called.
- Store tenant limits in your configuration or database, not hardcoded. The policy function is plain Python — fetch from wherever makes sense for your stack.
- Use
action_idfromCapabilityDeniedErrorto correlate the denial with your own request logs. - When audit sinks are available, every allowed and denied refund attempt will produce an audit event — creating a complete refund trail for compliance.