Skip to main content

Policy Engine

The Policy Engine evaluates declarative policies before every agent action. It enforces default deny — if no policy explicitly allows an action, it’s rejected.

Design Principles

  • Default deny — new agents can’t do anything until policies are assigned
  • Declarative — JSON-based policy definitions, no custom DSL to learn
  • Composable — multiple policies evaluated in order; ALL must pass
  • Fast — < 5ms evaluation, cached in Redis

Policy Types

Spending Limit

Controls how much value an agent can transact:
{
  "id": "spending-limit-1",
  "type": "spending-limit",
  "enabled": true,
  "config": {
    "maxPerTransaction": "100000000000000000",
    "maxPerDay": "500000000000000000",
    "maxPerWeek": "2000000000000000000"
  }
}
Values are in wei (for EVM) or lamports (for Solana). The engine tracks cumulative spend per agent over rolling time windows.

Approved Addresses

Whitelist of addresses an agent can interact with:
{
  "id": "approved-addrs-1",
  "type": "approved-addresses",
  "enabled": true,
  "config": {
    "addresses": [
      "0x1111111254EEB25477B68fb85Ed929f73A960582",
      "0xDef1C0ded9bec7F1a1670819833240f027b25EfF"
    ]
  }
}
Approved addresses work as a whitelist. If enabled, only transactions to listed addresses are allowed. Disable this policy to allow transactions to any address.

Auto-Approve Threshold

Transactions below this value are auto-approved; above requires manual approval:
{
  "id": "auto-approve-1",
  "type": "auto-approve-threshold",
  "enabled": true,
  "config": {
    "maxValue": "50000000000000000"
  }
}
When a transaction exceeds the threshold but passes other policies, it enters the approval queue. A tenant admin must explicitly approve or reject it.

Rate Limit

Controls how many transactions an agent can submit:
{
  "id": "rate-limit-1",
  "type": "rate-limit",
  "enabled": true,
  "config": {
    "maxPerMinute": 10,
    "maxPerHour": 100
  }
}

Time Window

Restricts when an agent can transact:
{
  "id": "time-window-1",
  "type": "time-window",
  "enabled": true,
  "config": {
    "allowedHours": { "start": 8, "end": 22 },
    "timezone": "UTC"
  }
}

Allowed Chains

Restricts which blockchain networks an agent can use:
{
  "id": "allowed-chains-1",
  "type": "allowed-chains",
  "enabled": true,
  "config": {
    "chainIds": [8453, 1]
  }
}

Evaluation Flow

Incoming Request

    ├─ Load applicable policies (cached, 30s TTL)

    ├─ Evaluate in order:
    │   1. allowed-chains    → DENY/ALLOW
    │   2. approved-addresses → DENY/ALLOW
    │   3. spending-limit    → DENY/ALLOW
    │   4. rate-limit        → DENY/ALLOW
    │   5. time-window       → DENY/ALLOW
    │   6. auto-approve      → AUTO/QUEUE

    │   ALL must pass. First DENY short-circuits.

    ├── ALLOW → Execute action, log result
    ├── DENY  → Return 403, log denial
    └── QUEUE → Hold for manual approval, notify via webhook

Policy Results

Every policy evaluation returns detailed results:
{
  "approved": false,
  "requiresManualApproval": true,
  "results": [
    { "type": "spending-limit", "passed": true, "reason": "Within daily limit" },
    { "type": "approved-addresses", "passed": true, "reason": "Address whitelisted" },
    { "type": "auto-approve-threshold", "passed": false, "reason": "Value 0.1 ETH exceeds auto-approve threshold of 0.05 ETH" }
  ]
}
These results are stored with every transaction record and returned in API responses, making it easy to debug why a transaction was denied or queued.

Approval Queue

Transactions that pass all policies except auto-approve enter the approval queue:
// List pending approvals
const pending = await fetch(
  `https://api.steward.fi/vault/${agentId}/pending`,
  { headers: { Authorization: `Bearer ${agentToken}` } }
);

// Approve (requires tenant-level auth)
await fetch(
  `https://api.steward.fi/vault/${agentId}/approve/${txId}`,
  {
    method: "POST",
    headers: { "X-Steward-Key": "tenant-key" },
  }
);

// Reject
await fetch(
  `https://api.steward.fi/vault/${agentId}/reject/${txId}`,
  {
    method: "POST",
    headers: { "X-Steward-Key": "tenant-key" },
  }
);
Webhooks are dispatched for approval_required, tx_signed, tx_rejected, and tx_failed events. Configure your webhook URL via the Tenants API.