Skip to content
Oris Docs

L2 Policy

L2 LIVE on Base Sepolia

L2 is the rules layer. Every agent payment passes through it before any network hears about the transaction. Six rule primitives compose the spending envelope. The engine evaluates them in memory with atomic counter operations.

What it does

Given an agent_id and a tx_intent, L2 returns an allow verdict plus a Merkle inclusion proof of the policy that authorized the decision. If the policy rejects, the payment never leaves the SDK.

Policies are versioned. Every update produces a new Merkle root, signed by the Oris MPC ring, anchored on OrisL2PolicyRegistry on Base.

Six rule primitives

PrimitiveWhat it checks
max_per_txSingle-transaction ceiling in USD-e6.
max_dailyCumulative spend in the configured timezone day.
max_monthlyCumulative spend in the configured timezone month.
counterparty_whitelistDestination address or domain allowlist.
allowed_categoriesMerchant category restrictions (e.g. cloud_compute).
escalation_thresholdAbove this amount, the payment routes to human approval.

Combine them to draw a tight envelope per agent. A finance team can lock a procurement bot to a single vendor under $50 / day with no escalation needed.

Performance

The engine caches active policies in Redis with atomic Lua-backed counter updates. Latency budget at p95:

StepBudget
Policy cache read< 1 ms
Rule evaluation< 3 ms
Counter atomic update (Lua)< 2 ms
Audit log write< 4 ms
p95 total< 10 ms

The cache is invalidated atomically on every policy update. The next transaction evaluation sees the new policy. There is no propagation delay between dashboard edit and live enforcement.

On-chain commitment

Every policy version is committed to the L2 Merkle tree. The root is signed by the Oris MPC ring and stored in:

OrisL2PolicyRegistry Base Sepolia
0xb43a7d6efdd2df1F11af130133965f00C99dE356

The compliance bundle carries:

  • policy_root — the active root at evaluation time
  • policy_proof — Merkle inclusion proof of the rule that fired

A verifier can independently confirm that the agent was bound by exactly that policy when the payment moved.

Hashing scheme

L2 uses Sparse Merkle Tree with Keccak256 hashing in v1. The migration to Poseidon-2 is queued behind the L4 ZK boundary (v2). Until ZK proofs ship, Keccak256 is the simpler primitive with no on-chain hash function divergence.

SDK example

from oris import OrisClient
client = OrisClient(...)
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,
)
# Evaluate before sending (optional, payments.send() does it inline)
verdict = client.policies.evaluate(
agent_id=agent.id,
to_address="0xA1b2...",
amount=12.50,
category="api_consumption",
)
assert verdict.allow is True
print(verdict.policy_root) # 0x...
print(verdict.policy_proof_len) # in bytes

Policy lifecycle

1. Operator edits in dashboard or API
2. Policy AST signed by operator identity, staged
3. Redis cache invalidated for the agent
4. Next evaluation reads the new policy
5. Merkle root commit batched with other tenants
6. OrisL2PolicyRegistry on-chain commit (every minute)

There is no deploy step, no compile pass, no migration window. Updates take effect on the next transaction.

What flows into the bundle

L2 contributes two fields to every Compliance Bundle:

  • policy_root — the active L2 root at evaluation time.
  • policy_proof — Merkle proof showing the rule that authorized the payment.

A verifier can replay the rule trace deterministically from these two fields.

Where to go next