Skip to main content

Overview

Sardis policies are natural language spending rules that act as a programmable firewall between AI agents and real money. Every payment must pass through policy validation before execution—if the policy says no, no funds move.
Policy-first architecture: Policy checks run before compliance, before gas estimation, before any blockchain interaction. This fail-fast approach prevents wasted resources on transactions that would be denied.

How Policies Work

When an agent attempts a payment, Sardis evaluates it through a 10-check pipeline. The first failure short-circuits with a denial:

Trust Levels

Sardis maps agent trust to preset spending limits:
Trust LevelPer-TxDailyWeeklyMonthlyTotal
LOW$50$100$500$1,000$5,000
MEDIUM$500$1,000$5,000$10,000$50,000
HIGH$5,000$10,000$50,000$100,000$500,000
UNLIMITEDNo capNo capNo capNo capNo cap
Trust levels automatically map to KYA verification levels: NONE/BASIC → LOW, VERIFIED → MEDIUM, ATTESTED → HIGH.

Creating Policies

Default Policy (Trust-Based)

from sardis_v2_core.spending_policy import create_default_policy, TrustLevel

# Create a LOW trust policy ($50/tx, $100/day)
policy = create_default_policy(
    agent_id="agent_123",
    trust_level=TrustLevel.LOW
)

print(f"Per-tx limit: ${policy.limit_per_tx}")
print(f"Daily limit: ${policy.daily_limit.limit_amount}")

Custom Policy

from sardis_v2_core.spending_policy import SpendingPolicy, TimeWindowLimit, TrustLevel
from decimal import Decimal

policy = SpendingPolicy(
    agent_id="agent_123",
    trust_level=TrustLevel.MEDIUM,
    limit_per_tx=Decimal("200.00"),
    limit_total=Decimal("5000.00"),
    daily_limit=TimeWindowLimit(
        window_type="daily",
        limit_amount=Decimal("500.00")
    ),
    approval_threshold=Decimal("500.00"),  # Human approval above $500
    blocked_merchant_categories=["gambling", "adult"]
)

Natural Language Policies

Sardis can parse natural language into structured policies:
from sardis_v2_core.nl_policy_parser import parse_policy

policy_text = """
Max $100 per transaction, $500 per day.
Block gambling and alcohol.
Only allow payments to OpenAI, Anthropic, and AWS.
Require approval above $200.
"""

policy = parse_policy(policy_text, agent_id="agent_123")
Natural language parsing uses GPT-4 to extract spending rules. The LLM converts human instructions into the SpendingPolicy data structure.

Time Window Limits

Time-based spending caps automatically reset when the window expires:
from sardis_v2_core.spending_policy import TimeWindowLimit
from decimal import Decimal

# Daily limit: $500/day
daily = TimeWindowLimit(
    window_type="daily",
    limit_amount=Decimal("500.00"),
    currency="USDC"
)

# Check remaining budget
remaining = daily.remaining()
print(f"Remaining today: ${remaining}")

# Record a spend
daily.record_spend(Decimal("50.00"))

# Check if a payment can proceed
can_spend, reason = daily.can_spend(Decimal("100.00"))
if not can_spend:
    print(f"Denied: {reason}")  # "time_window_limit"
Resets every 24 hours from the window start time:
daily_limit = TimeWindowLimit(
    window_type="daily",
    limit_amount=Decimal("1000.00")
)
Resets every 7 days:
weekly_limit = TimeWindowLimit(
    window_type="weekly",
    limit_amount=Decimal("5000.00")
)
Resets every 30 days:
monthly_limit = TimeWindowLimit(
    window_type="monthly",
    limit_amount=Decimal("20000.00")
)

Merchant Rules

Control exactly who agents can pay with allowlists, blocklists, and per-merchant caps:

Allowlist (Restrict to Specific Merchants)

from sardis_v2_core.spending_policy import SpendingPolicy

policy = SpendingPolicy(agent_id="agent_123")

# Only allow OpenAI and Anthropic
policy.add_merchant_allow(merchant_id="openai.com")
policy.add_merchant_allow(merchant_id="anthropic.com")

# Payments to other merchants will be denied with "merchant_not_allowlisted"

Blocklist (Block Specific Merchants)

# Block gambling sites
policy.add_merchant_deny(
    category="gambling",
    reason="Company policy: no gambling payments"
)

# Block a specific merchant temporarily
from datetime import datetime, timedelta

policy.add_merchant_deny(
    merchant_id="suspicious-site.com",
    reason="Under investigation",
    expires_at=datetime.now() + timedelta(days=7)
)

Per-Merchant Caps

# Limit OpenAI to $500/tx, $2000/day
policy.add_merchant_allow(
    merchant_id="openai.com",
    max_per_tx=Decimal("500.00"),
    reason="API spending cap"
)

Category-Based Rules

# Block entire merchant categories
policy.block_merchant_category("gambling")
policy.block_merchant_category("alcohol")
policy.block_merchant_category("adult")

# Unblock a category
policy.unblock_merchant_category("alcohol")

Spending Scopes

Restrict agents to specific spending categories:
from sardis_v2_core.spending_policy import SpendingScope

# Only allow compute and data purchases
policy.allowed_scopes = [
    SpendingScope.COMPUTE,
    SpendingScope.DATA
]

# Available scopes:
# - SpendingScope.ALL (default)
# - SpendingScope.RETAIL
# - SpendingScope.DIGITAL
# - SpendingScope.SERVICES
# - SpendingScope.COMPUTE
# - SpendingScope.DATA
# - SpendingScope.AGENT_TO_AGENT

MCC Code Blocking

Block payments based on Merchant Category Codes (MCCs):
# Block high-risk MCC categories
policy.blocked_merchant_categories = [
    "gambling",      # MCC 7995, 7802, etc.
    "adult",         # MCC 5967
    "weapons",       # MCC 5091
    "cryptocurrency" # MCC 6051
]

# Check MCC during payment
approved, reason = await policy.evaluate(
    wallet=wallet,
    amount=Decimal("50.00"),
    fee=Decimal("0.50"),
    chain="base",
    token=TokenType.USDC,
    mcc_code="7995"  # Online gambling
)

if not approved:
    print(reason)  # "merchant_category_blocked:gambling"

Policy Evaluation

Async Evaluation (Production)

Use evaluate() for full policy enforcement with DB-backed spend tracking:
from sardis_v2_core.spending_policy import SpendingPolicy
from sardis_v2_core.tokens import TokenType
from decimal import Decimal

policy = SpendingPolicy(agent_id="agent_123")

approved, reason = await policy.evaluate(
    wallet=wallet,
    amount=Decimal("50.00"),
    fee=Decimal("0.50"),
    chain="base",
    token=TokenType.USDC,
    merchant_id="openai.com",
    merchant_category="saas",
    mcc_code="5734",
    scope=SpendingScope.DIGITAL,
    rpc_client=rpc_client,          # For balance check
    policy_store=policy_store,      # For DB-backed limits
    drift_score=Decimal("0.2")      # Goal drift detection
)

if approved:
    if reason == "requires_approval":
        # Route to human for approval
        await send_approval_request(policy, amount)
    else:
        # Execute payment
        await execute_payment(wallet, amount)
else:
    print(f"Denied: {reason}")

Sync Validation (Testing/Pre-Flight)

Use validate_payment() for quick local checks without DB or RPC calls:
approved, reason = policy.validate_payment(
    amount=Decimal("50.00"),
    fee=Decimal("0.50"),
    merchant_id="openai.com",
    scope=SpendingScope.DIGITAL
)
validate_payment() uses in-memory state only. For production, always use evaluate() with a policy_store to prevent race conditions.

Goal Drift Detection

Block payments when an agent drifts too far from its stated purpose:
policy.max_drift_score = Decimal("0.5")  # Max 50% drift allowed

# During payment evaluation
approved, reason = await policy.evaluate(
    wallet=wallet,
    amount=Decimal("25.00"),
    fee=Decimal("0.50"),
    chain="base",
    token=TokenType.USDC,
    drift_score=Decimal("0.7")  # 70% drift - exceeds max
)

if not approved:
    print(reason)  # "goal_drift_exceeded"
Drift scores are calculated by comparing the agent’s current actions to its manifest. A score of 0.0 = perfect alignment, 1.0 = completely off-task.

Approval Thresholds

Require human approval for large transactions while auto-approving small ones:
policy.approval_threshold = Decimal("500.00")

# Payment under threshold: auto-approved
approved, reason = await policy.evaluate(
    wallet=wallet,
    amount=Decimal("100.00"),
    fee=Decimal("1.00"),
    chain="base",
    token=TokenType.USDC
)
print(approved, reason)  # (True, "OK")

# Payment over threshold: requires approval
approved, reason = await policy.evaluate(
    wallet=wallet,
    amount=Decimal("600.00"),
    fee=Decimal("1.00"),
    chain="base",
    token=TokenType.USDC
)
print(approved, reason)  # (True, "requires_approval")

Code Examples

Example 1: E-Commerce Bot Policy

ecommerce_policy.py
from sardis_v2_core.spending_policy import (
    SpendingPolicy,
    TimeWindowLimit,
    TrustLevel,
    SpendingScope
)
from decimal import Decimal

# Create policy for shopping agent
policy = SpendingPolicy(
    agent_id="ecommerce_bot_001",
    trust_level=TrustLevel.MEDIUM,
    limit_per_tx=Decimal("200.00"),
    limit_total=Decimal("10000.00"),
    approval_threshold=Decimal("500.00")
)

# Daily budget: $1000
policy.daily_limit = TimeWindowLimit(
    window_type="daily",
    limit_amount=Decimal("1000.00")
)

# Only allow retail purchases
policy.allowed_scopes = [SpendingScope.RETAIL]

# Allowlist approved merchants
for merchant in ["amazon.com", "shopify.com", "stripe.com"]:
    policy.add_merchant_allow(merchant_id=merchant)

# Block gambling and adult content
policy.block_merchant_category("gambling")
policy.block_merchant_category("adult")

print(f"Policy created: {policy.policy_id}")

Example 2: API Credits Policy

api_credits_policy.py
from sardis_v2_core.spending_policy import (
    create_default_policy,
    TrustLevel
)
from decimal import Decimal

# Create HIGH trust policy for API agent
policy = create_default_policy(
    agent_id="api_agent_001",
    trust_level=TrustLevel.HIGH
)

# Allowlist AI providers with per-merchant caps
policy.add_merchant_allow(
    merchant_id="openai.com",
    max_per_tx=Decimal("500.00"),
    reason="GPT-4 API credits"
)

policy.add_merchant_allow(
    merchant_id="anthropic.com",
    max_per_tx=Decimal("500.00"),
    reason="Claude API credits"
)

policy.add_merchant_allow(
    merchant_id="aws.com",
    max_per_tx=Decimal("1000.00"),
    reason="AWS infrastructure"
)

# Scope: compute only
from sardis_v2_core.spending_policy import SpendingScope
policy.allowed_scopes = [SpendingScope.COMPUTE]

Policy Data Model

@dataclass
class SpendingPolicy:
    policy_id: str
    agent_id: str
    trust_level: TrustLevel
    limit_per_tx: Decimal
    limit_total: Decimal
    spent_total: Decimal
    daily_limit: Optional[TimeWindowLimit]
    weekly_limit: Optional[TimeWindowLimit]
    monthly_limit: Optional[TimeWindowLimit]
    merchant_rules: list[MerchantRule]
    allowed_scopes: list[SpendingScope]
    blocked_merchant_categories: list[str]
    approval_threshold: Optional[Decimal]
    max_drift_score: Optional[Decimal]
    created_at: datetime
    updated_at: datetime

Reason Codes

When a policy denies a payment, it returns a reason code:
CodeDescription
per_transaction_limitSingle payment exceeds per-tx cap
total_limit_exceededLifetime spending cap reached
daily_limit_exceededDaily window exhausted
weekly_limit_exceededWeekly window exhausted
monthly_limit_exceededMonthly window exhausted
merchant_deniedMerchant on blocklist
merchant_not_allowlistedMerchant not on allowlist
merchant_cap_exceededPer-merchant cap exceeded
merchant_category_blockedMCC category blocked
scope_not_allowedWrong spending category
insufficient_balanceWallet can’t cover payment
goal_drift_exceededAgent off-task
requires_approvalNeeds human sign-off

Next Steps

Agents

Create AI agents with policy-enforced wallets

Payments

Execute payments through the policy firewall

Compliance

KYC/AML and sanctions screening

Wallets

Non-custodial MPC wallets

Build docs developers (and LLMs) love