Spending policies
Every agent payment passes through the L2 policy engine before any rail hears about it. Six rule primitives compose the envelope. Evaluation latency is well under ten milliseconds at p95.
The six rules
| Rule | What it checks |
|---|---|
max_per_tx | Single-transaction ceiling in USD. |
max_daily | Cumulative spend in the configured timezone day. |
max_monthly | Cumulative spend in the configured timezone month. |
counterparty_whitelist | Destination address or domain allowlist. |
allowed_categories | Merchant category restrictions. |
escalation_threshold | Above this amount, route to human approval. |
Combine them to draw a precise envelope. A procurement bot can be capped at fifty dollars per transaction, five hundred per day, restricted to a specific vendor list, and escalated above two hundred dollars.
Performance
| Step | p95 |
|---|---|
| Policy cache read | < 1 ms |
| Rule evaluation | < 3 ms |
| Counter atomic update (Lua) | < 2 ms |
| Audit log write | < 4 ms |
| Total | < 10 ms |
The cache is invalidated atomically on every policy update. There is no propagation delay between dashboard edit and live enforcement. Counters are atomic Lua scripts so concurrent payments do not race.
SDK methods
# Create a policypolicy = client.policies.create( agent_id=agent.id, max_per_tx=50.00, max_daily=500.00, max_monthly=5000.00, allowed_categories=["cloud_compute", "api_consumption"], counterparty_whitelist=["0xA1b2...", "0xC3d4..."], escalation_threshold=200.00,)
# Pre-evaluate without sending (optional; payments.send does it inline)verdict = client.policies.evaluate( agent_id=agent.id, to_address="0xA1b2...", amount=12.50, category="api_consumption",)
# Update a ruleclient.policies.update(policy.id, max_daily=750.00)const policy = await client.policies.create({ agentId: agent.id, maxPerTx: 50.00, maxDaily: 500.00, maxMonthly: 5000.00, allowedCategories: ['cloud_compute', 'api_consumption'], counterpartyWhitelist: ['0xA1b2...', '0xC3d4...'], escalationThreshold: 200.00,});
const verdict = await client.policies.evaluate({ agentId: agent.id, toAddress: '0xA1b2...', amount: 12.50, category: 'api_consumption',});
await client.policies.update(policy.id, { maxDaily: 750.00 });Escalation flow
When a payment exceeds escalation_threshold, the engine returns an escalated verdict instead of allow. The payment is held in a pending state until a human approver signs off through the dashboard or the escalation API. Approval signatures are recorded in the audit log.
Versioning
Every update produces a new policy version. The Merkle root commits on-chain to OrisL2PolicyRegistry on Base. Every bundle records the policy version that authorized it, so a verifier can reconstruct the exact rule set in effect at payment time.
Rollback is a forward edit; restore the previous configuration and the next evaluation uses it. The historical chain stays.
Where to go next
- L2 Policy for the cryptographic ground truth behind the rules.
- Policy configuration guide for the write-version-deploy workflow.
- Policies API for the full endpoint reference.
- Activity log to see how policy decisions are recorded.