Policy schema
One canonical, versioned document defines what an agent may do. The browser workbench validates against it today; the Rust engine and the SDKs will be generated from it.
Representative document
Addresses and limits are illustrative, not deployments or recommended production values. Integers that can exceed 253 are decimal strings. Floats are rejected.
policy_trading_001.json
{
"schema_version": "1.0",
"policy_id": "policy_trading_001",
"version": 7,
"account": {
"chain_id": 4663,
"address": "0x1111111111111111111111111111111111111111"
},
"status": "active",
"limits": {
"max_transaction_value_wei": "100000000000000000",
"max_gas_cost_wei": "5000000000000000",
"hourly_budget_usd_micros": "500000000",
"max_pending_operations": 3,
"max_price_age_seconds": 60
},
"cooldowns": {
"identical_calldata_seconds": 60,
"identical_intent_seconds": 15
},
"contracts": {
"default": "deny",
"allowlist": [
{
"address": "0x2222222222222222222222222222222222222222",
"selectors": [
"0xa9059cbb"
],
"require_verified_code": true
},
{
"address": "0x3333333333333333333333333333333333333333",
"selectors": [
"0x38ed1739"
],
"require_verified_code": true
}
],
"denylist": [
"0x5555555555555555555555555555555555555555"
]
},
"human_approval": {
"usd_threshold_micros": "150000000",
"require_for_unknown_methods": true
},
"failure_mode": "closed"
}The full JSON Schema is in packages/policy-schema/policy.schema.json. Unknown fields are rejected so that a typo can never silently disable a rule.
Field reference
| Field | Type | Constraint | Notes |
|---|---|---|---|
| schema_ | const "1.0" | ||
| policy_ | string | /^[a-z0-9_]{3,64}$/ | |
| version | integer | 1…2147483647 | Assigned on activation. Pending approvals bind to it. |
| account | object | ||
| account. | integer | 1…2147483647 | |
| account. | address | ||
| status | "draft" | "active" | "disabled" | ||
| limits | object | ||
| limits. | uint | Native value per transaction, in wei. | |
| limits. | uint | Estimated total fee per transaction, in wei. | |
| limits. | uint | Rolling 3600-second budget in USD millionths, covering value plus fees. | |
| limits. | integer | 0…1000 | |
| limits. | integer | 1…3600 | Older prices fail closed with RW_STATE_001. |
| cooldowns | object | ||
| cooldowns. | integer | 0…86400 | |
| cooldowns. | integer | 0…86400 | |
| contracts | object | ||
| contracts. | const "deny" | ||
| contracts. | object[] | ≤ 256 items | |
| contracts. | object | ||
| contracts. | address | ||
| contracts. | selector[] | ≤ 64 items | |
| contracts. | boolean | ||
| contracts. | address[] | Explicit deny overrides any allow. | |
| human_ | object | ||
| human_ | uint | Non-negative integer as a decimal string. Never a float. | |
| human_ | boolean | true routes unknown selectors to review; false denies them with RW_METHOD_001. | |
| failure_ | const "closed" |
Units
- Native value and fees are integer wei.
- USD amounts are integer micros (1 USD = 1,000,000). Conversion rounds up, so limits are never under-counted.
- USD enforcement needs a fresh price. A missing price, or one older than
max_price_age_seconds, fails closed withRW_STATE_001. - Exposure is native value plus estimated fee. Token-denominated allocations are specified but not yet in schema 1.0.
Precedence
- An active global emergency stop denies all new protected executions.
- An explicit deny overrides any allow.
- A required human approval cannot be overridden by an ordinary allow rule.
- A missing or invalid policy is never treated as an empty, allow-all policy.
- An unknown method is denied or routed to review, according to
require_for_unknown_methods. - An unavailable or unbounded fee estimate is never treated as zero.
- Activating a new policy version invalidates pending approvals bound to the previous one.
Evaluation order
Checks run in this fixed order and stop at the first failure. The SDK never decides which checks are authoritative. Read-only checks may be reordered for performance; budget reservation and replay protection stay atomic.
| # | Check | Passes when | Fails with |
|---|---|---|---|
| 01 | Policy | A valid, active policy exists for the account. | RW_POLICY_001 |
| 02 | Request schema | Fields are well-formed and within size bounds. | RW_REQUEST_001 |
| 03 | Replay | request_id is new and the deadline has not passed. | RW_REPLAY_001 |
| 04 | Emergency stop and agent | No global stop; the agent is not paused. | RW_PAUSED_001 |
| 05 | Chain and account | Chain ID and sender match the policy. | RW_TARGET_001 |
| 06 | Explicit deny | Target is not denylisted. | RW_POLICY_002 |
| 07 | Target | Target is allowlisted (and code-verified if required). | RW_TARGET_001 |
| 08 | Method | Selector is allowlisted, or routed to review. | RW_METHOD_001 / RW_HUMAN_001 |
| 09 | Value | Native value ≤ per-transaction limit. | RW_BUDGET_001 |
| 10 | Fee | A bounded fee estimate exists and is within the gas limit. | RW_FEE_001 / RW_BUDGET_001 |
| 11 | Price | A price exists and is fresher than max_price_age_seconds. | RW_STATE_001 |
| 12 | Budget | Window usage + this request ≤ hourly budget; reserve atomically. | RW_BUDGET_001 |
| 13 | Pending | Open reservations < max_pending_operations. | RW_RATE_001 |
| 14 | Loop | No identical calldata or equivalent intent within cooldowns. | RW_LOOP_001 |
| 15 | Human threshold | Exposure below the approval threshold, else review. | RW_HUMAN_001 |
After a local ALLOW, the production path also simulates execution, binds the authorization to the operation hash, chain, policy version and expiry, signs inside the enclave, and submits to the bundler. None of these run in the simulator.
Budgets and reservations
Every allowed or pending operation creates a reservation with a unique ID, amount, policy version, expiry and lifecycle state: reserved, pending_approval, released or finalized. The hourly budget counts every non-released reservation created in the trailing 3,600 seconds.
Version migration
schema_version names the document format; version numbers each activated policy. They move independently.
- Policy versions. Every activation creates a new integer version. The previous version is kept and can be loaded back into a draft; nothing is edited in place. Pending approvals evaluated under an older version are invalidated on activation.
- Schema versions. A schema change ships with a new
schema_version. Unknown fields are rejected, so a document written for a newer schema fails validation on an older evaluator instead of being half-applied. - Migrating a document. Convert the old document, validate it against the new schema, run your test transactions against it as a draft, compare the decisions with the active version, then activate it explicitly.
Schema 1.0 is the first version, so no migrations exist yet. Each future schema version will list its changes and a conversion table in the changelog.
Validation errors
Validation returns a list of { path, message } pairs using JSONPath-style paths, for example $.limits.max_transaction_value_wei: must be a non-negative integer string. A policy with any validation error cannot be activated. Try it: edit a limit in the workbench to an invalid value.