JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 27
  • Score
    100M100P100Q57545F
  • License Apache-2.0

Reputation-gated spending policies for OWS agent wallets

Package Exports

  • triage-ows
  • triage-ows/dist/index.js

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (triage-ows) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

Readme

Triage x OWS

Reputation-gated spending policies for the agent economy.

Triage is a scoring server and policy engine for the Open Wallet Standard. It evaluates every transaction an AI agent attempts, assigns a trust score based on 5 factors, and enforces dynamic spending limits. Agents start restricted and earn financial autonomy over time.

Triage does not create agents. It governs how much financial autonomy they earn.

OWS Wallet --> triage-policy (stdin/stdout) --> Scoring Server --> APPROVE / DENY
                                                    |
                                            Dashboard + XMTP

Quick Start

# 1. Install
npm install triage-ows hono

# 2. Initialize config
npx triage-ows init

# 3. Start the server + dashboard
npx triage-ows dev

Dashboard at http://localhost:4021. WebSocket at ws://localhost:4021/ws.


How It Works

                    stdin (PolicyContext JSON)
                           |
                    +------v------+
                    | triage-     |    OWS spawns this as a subprocess
                    | policy      |    for every transaction
                    +------+------+
                           |
                    HTTP POST /api/policy/evaluate
                           |
                    +------v------+
                    |   Scoring   |    5-factor trust formula
                    |   Server    |    + risk penalty
                    +------+------+
                           |
              +------------+------------+
              |            |            |
        Record in     Emit WS      XMTP notify
         store        event        (on deny)
              |            |            |
              v            v            v
         Agent DB     Dashboard    Human owner
         (in-mem)     (React)      (can override)
  1. OWS spawns triage-policy as a subprocess for each transaction
  2. The executable reads PolicyContext from stdin and POSTs to the scoring server
  3. The server computes the agent's trust score across 5 factors
  4. Three checks run in order: frozen tier, per-transaction limit, daily limit
  5. Result (allow: true/false) is written to stdout for OWS
  6. Every decision is broadcast via WebSocket to the dashboard
  7. Denials trigger XMTP notifications to the human owner, who can reply "override"

Trust Formula

Trust Score = Identity + OnChain + Behavior + Compliance + Network - Risk
              (0-35)    (0-20)    (0-20)     (0-15)       (0-5)    (0-30)

Clamped to [0, 100]

Identity (0-35)

Additive scoring with a hard cap at 35:

Condition Score Cumulative
Fresh wallet (base) 4 4
Has transaction history 12 12
OWS wallet with approved transactions 20 20
+ Web Bot Auth verified (RFC 9421) +4 24
+ World ID verified (human proof) +11 31-35

An OWS wallet with both Web Bot Auth and World ID: min(35, 20 + 4 + 11) = 35.

On-Chain (0-20)

Sub-factor Max Calculation
Account age 5 min(5, monthsOld * 0.5)
Transaction count 5 min(5, log10(totalRequests) * 2.5)
Counterparty diversity 5 min(5, uniqueCounterparties / 10 * 5)
Balance (funded) 5 5 if on-chain balance > 0, else 0

Balance is checked via Base Sepolia RPC (viem), cached for 10 minutes.

Behavior (0-20)

Sub-factor Max Calculation
Success rate 5 min(5, successfulRequests / totalRequests * 5)
Pacing 5 5 if <5 req/min, 2 if 5-15, 0 if >15
Clean days 5 min(5, consecutiveCleanDays * 0.5)
Counterparty concentration 5 5 if >=5 targets, 2 if >=2, 0 if 1

Compliance (0-15)

Sub-factor Max Calculation
Approval rate 5 min(5, totalApproved / totalDecisions * 5)
Approval streak 5 min(5, consecutiveApprovals * 0.25)
Override frequency 5 5 - min(5, humanOverrides * 1.67)

Fewer human overrides = higher compliance score. Agents that consistently stay within limits score highest.

Network (0-5)

Average trust score of known counterparties, divided by 20. Agents that transact with other trusted agents earn network reputation.

Can be disabled via config: networkScore.enabled: false.

Risk Penalty (0-30, subtracted)

Factor Max Trigger
Frequency spike 10 >15 requests in 60 seconds
Failed transactions 5 min(5, failedRequests * 2)
Inactivity decay 5 min(5, hoursInactive * 0.5)
Spend pressure 5 >85% daily limit + active approvals
Denial streak 5 min(5, consecutiveDenials * 2.5)

Frequency penalty and inactivity decay rate are configurable.


Spending Tiers

Tier Min Score Daily Limit Per-Tx Limit Color
Sovereign 80 $1,000 $500 #fff8e1
Trusted 60 $200 $100 #ffd700
Building 40 $50 $25 #4caf50
Cautious 20 $10 $5 #00bcd4
Restricted 1 $2 $1 #2196f3
Frozen 0 $0 $0 #1a1a4e

All limits are configurable via triage.config.json scoreBands.


API Reference

POST /api/policy/evaluate

Core policy evaluation endpoint. Called by triage-policy for every transaction.

Headers:

  • Content-Type: application/json
  • x-policy-secret: <secret> (optional, if POLICY_SECRET env is set)
  • Signature-Input + Signature (optional, RFC 9421 Web Bot Auth)

Request:

{
  "chain_id": "eip155:84532",
  "wallet_id": "5dccd73e-...",
  "api_key_id": "0d3531c9-...",
  "transaction": {
    "to": "0xRecipient...",
    "value": "1000000000000000",
    "raw_hex": "0x",
    "data": "0x"
  },
  "spending": {
    "daily_total": "0",
    "date": "2026-04-04"
  },
  "timestamp": "2026-04-04T12:00:00Z"
}

Response (200):

{
  "allow": true,
  "trustScore": 82,
  "tier": "Sovereign",
  "dailyLimit": 1000,
  "dailySpent": 150.50,
  "perTxLimit": 500
}

Denial reasons:

  • Agent is frozen (tier check)
  • Exceeds per-transaction limit ($500) (per-tx check)
  • Exceeds daily spending limit ($1000) (daily check)
curl -X POST http://localhost:4021/api/policy/evaluate \
  -H "Content-Type: application/json" \
  -d '{"chain_id":"eip155:84532","wallet_id":"w1","api_key_id":"k1","transaction":{"to":"0xABC","value":"1000000000000000","raw_hex":"0x","data":"0x"},"spending":{"daily_total":"0","date":"2026-04-04"},"timestamp":"2026-04-04T12:00:00Z"}'

POST /api/override/:address

Human override for a denied transaction. Boosts trust score by overrideBoost (default +3). Override expires after 5 minutes.

curl -X POST http://localhost:4021/api/override/0xAgentAddress

Response (200): Full AgentProfile JSON. Response (404): { "error": "Agent not found" } or { "error": "No pending override for this agent" } (including expired overrides).

GET /api/agents

Top 20 agents sorted by trust score.

curl http://localhost:4021/api/agents

GET /api/agents/:address

Single agent profile.

curl http://localhost:4021/api/agents/0xAgentAddress

POST /api/agents/:address/pubkey

Register an agent's Ed25519 public key for Web Bot Auth (RFC 9421).

curl -X POST http://localhost:4021/api/agents/0xAgent/pubkey \
  -H "Content-Type: application/json" \
  -d '{"publicKey":"base64EncodedEd25519PublicKey"}'

GET /api/stats

Aggregate statistics.

curl http://localhost:4021/api/stats

Response:

{
  "totalAgents": 5,
  "totalDecisions": 142,
  "totalApproved": 128,
  "totalDenied": 14
}

POST /api/verify-context

World ID sign request. Returns signing parameters for client-side World ID verification. Returns 501 if WORLD_ID_RP_ID and WORLD_ID_SIGNING_KEY are not configured.

POST /api/verify-human

World ID proof verification. Verifies proof server-side via World API, stores nullifier hash.

curl -X POST http://localhost:4021/api/verify-human \
  -H "Content-Type: application/json" \
  -d '{"address":"0xAgentAddress"}'

POST /api/x402/verify

Verify an x402 payment signature via the facilitator.

curl -X POST http://localhost:4021/api/x402/verify \
  -H "Content-Type: application/json" \
  -d '{"paymentHeader":"...","payTo":"0xRecipient","maxAmountRequired":"1000000"}'

GET /api/x402/payment-requirements

Returns payment info for HTTP 402 responses. Requires PAY_TO_ADDRESS env var.

curl http://localhost:4021/api/x402/payment-requirements

WebSocket: /ws

Real-time event stream. Connect to ws://localhost:4021/ws.

Event types:

// Transaction decision
{ type: 'POLICY_DECISION', agent, amount, trustScore, tier, decision: 'APPROVE'|'DENY'|'OVERRIDE', reason, dailyLimit, dailySpent, timestamp }

// Trust score change (on override)
{ type: 'TRUST_CHANGE', agent, oldScore, newScore, oldTier, newTier, reason, timestamp }

// Budget warning (>80% daily limit)
{ type: 'BUDGET_WARNING', agent, spent, limit, percentage, timestamp }

CLI Reference

triage-ows -- Reputation-gated spending policies for OWS agent wallets

Usage:
  triage-ows init            Scaffold triage.config.json + .env.example
  triage-ows dev             Start scoring server + dashboard (dev mode)
  triage-ows register        Register Triage policy with OWS
  triage-ows attach          Attach an OWS agent to Triage governance
  triage-ows seed            Populate demo agents for testing
  triage-ows status          Show server status + agent summary
  triage-ows help            Show this help

Options:
  --port <number>            Server port (default: 4021)
  --config <path>            Config file path (default: ./triage.config.json)

triage-ows init

Creates triage.config.json and .env.example in the current directory. Skips if files already exist.

triage-ows dev

Starts the scoring server and dashboard. Reads config from triage.config.json.

triage-ows dev --port 8080 --config ./custom.config.json

triage-ows register

Registers the Triage policy with OWS. Copies policy-template.json to ~/.ows/policies/triage-trust.json and runs ows policy create.

triage-ows attach

Creates an OWS API key attached to a wallet with the Triage policy.

triage-ows attach --wallet sovereign-agent --key sovereign-key

Runs ows key create --name <key> --wallet <wallet> --policy triage-trust.

triage-ows status

Shows server status and agent summary table.

triage-ows status --port 4021

Library Usage

import {
  // Server
  app, startServer, BASE_SEPOLIA, USDC_BASE_SEPOLIA,

  // Scoring
  computeTrustScore, getSpendingLimits,
  identityScore, onChainScore, behaviorScore,
  complianceScore, networkScore, riskPenalty,

  // Store
  getOrCreateAgent, getAgent, getAllAgents, getTopAgents,
  recordApproval, recordDenial, recordOverride,
  addVerifiedHuman, isVerifiedHuman, setOWSWallet, setWebBotAuthVerified,

  // Config
  loadConfig, getConfig,

  // Events
  emitEvent, startWebSocketServer, attachWebSocketToServer,

  // Web Bot Auth (RFC 9421)
  verifyWebBotAuth, registerAgentPublicKey, parseSignatureInput,

  // Types & Constants
  getTierForScore, SPENDING_TIERS, updateSpendingTiers,
} from 'triage-ows'

import type {
  PolicyContext, PolicyResult, TrustBreakdown, SpendingTier,
  AgentProfile, TriageConfig,
  PolicyDecisionEvent, TrustChangeEvent, BudgetWarningEvent, TriageEvent,
} from 'triage-ows'

Scoring an Agent

const agent = getOrCreateAgent('0xAgentAddress')
const breakdown = computeTrustScore(agent, getAgent)
const { tier, dailyLimit, perTxLimit } = getSpendingLimits(breakdown.total)

console.log(`Score: ${breakdown.total}, Tier: ${tier.name}, Limit: $${dailyLimit}/day`)

Recording Transactions

// Approved transaction
const updated = recordApproval('0xAgent', '0xRecipient', 25.50)

// Denied transaction (stores pending override)
const denied = recordDenial('0xAgent', tx, 500.00)

// Human override (expires after 5 minutes)
const overridden = recordOverride('0xAgent')

Custom Scoring Server

import { app, startServer } from 'triage-ows'
import { Hono } from 'hono'

// Add custom routes to the Hono app
app.get('/api/custom', (c) => c.json({ hello: 'world' }))

// Start with custom port
startServer(8080)

Custom Config

import { loadConfig, getConfig, updateSpendingTiers } from 'triage-ows'

// Load from custom path
loadConfig('/path/to/config.json')

// Read config values
const threshold = getConfig().warningThreshold

XMTP Notifications

XMTP uses native bindings (@xmtp/node-sdk). It's loaded lazily to avoid crashes on systems where bindings aren't available. The server handles this automatically — manual import is only needed for advanced library usage.

// XMTP requires native bindings — import lazily
const xmtp = await import('triage-ows/dist/xmtp')
await xmtp.initXMTP()

// Send notifications
await xmtp.notifyDenial(agent, amount, score, tier, limit, spent, reason, txTo)
await xmtp.notifyTrustChange(agent, oldScore, newScore, oldTier, newTier, reason)
await xmtp.notifyBudgetWarning(agent, spent, limit)
await xmtp.notifyAnomaly(agent, pattern, action, riskPenalty, oldScore, newScore)

Types Reference

PolicyContext

The input to every policy evaluation. Sent by OWS to triage-policy via stdin.

Field Type Description
chain_id string Chain identifier (e.g. eip155:84532)
wallet_id string OWS wallet UUID
api_key_id string OWS API key UUID (used as agent identifier)
transaction.to string Recipient address
transaction.value string Transaction value in wei
transaction.raw_hex string Raw transaction hex
transaction.data string Transaction data
spending.daily_total string OWS-reported daily total
spending.date string Date string (ISO)
timestamp string Request timestamp (ISO)
policy_config Record<string, unknown> Optional policy-level config

AgentProfile

Complete agent state tracked by the store.

Field Type Description
address string Agent identifier (api_key_id)
walletId string? OWS wallet UUID
apiKeyId string? OWS API key UUID
isOWSWallet boolean Whether registered as OWS wallet
worldIdVerified boolean World ID human proof verified
webBotAuthVerified boolean RFC 9421 signature verified
nullifierHash string? World ID nullifier hash
trustScore number Current trust score (0-100)
breakdown TrustBreakdown Per-factor score breakdown
tier string Current spending tier name
totalRequests number Lifetime request count
successfulRequests number Lifetime approved count
failedRequests number Lifetime denied count
dailySpent number Today's spending in USD
dailyDate string Date of current daily tracking
consecutiveApprovals number Current approval streak
consecutiveDenials number Current denial streak
totalApproved number Lifetime approved transactions
totalDenied number Lifetime denied transactions
humanOverrides number Total human override count
counterparties string[] Unique transaction targets
pendingOverride object? Pending override data with createdAt timestamp
requestTimestamps number[] Last 100 request timestamps
lastActive number Last activity timestamp
consecutiveCleanDays number Days with only approved transactions
cleanDayDate string Date of last clean day check
createdAt number Agent creation timestamp

TrustBreakdown

Field Type Range Description
identity number 0-35 Identity verification score
onChain number 0-20 On-chain history score
behavior number 0-20 Transaction behavior score
compliance number 0-15 Policy compliance score
network number 0-5 Network trust score
risk number 0-30 Risk penalty (subtracted)
total number 0-100 Final composite score

TriageConfig

Option Type Default Description
scoreBands Array 6 tiers Custom tier definitions (name, min, dailyLimit, perTxLimit, color)
warningThreshold number 0.8 Budget warning threshold (0-1)
xmtp.enabled boolean true Enable XMTP notifications
worldId.enabled boolean true Enable World ID verification
webBotAuth.enabled boolean true Enable RFC 9421 signature verification
networkScore.enabled boolean true Enable network trust scoring
scoring.overrideBoost number 3 Trust score boost per human override
scoring.maxFrequencyPenalty number 10 Max penalty for request frequency spikes
scoring.inactivityDecayRate number 0.5 Trust decay per hour of inactivity
port number 4021 Server port
dashboardEnabled boolean true Serve dashboard static files

Configuration

triage.config.json

Create with triage-ows init or manually:

{
  "warningThreshold": 0.8,
  "scoreBands": [
    { "name": "Sovereign",  "min": 80, "dailyLimit": 1000, "perTxLimit": 500 },
    { "name": "Trusted",    "min": 60, "dailyLimit": 200,  "perTxLimit": 100 },
    { "name": "Building",   "min": 40, "dailyLimit": 50,   "perTxLimit": 25 },
    { "name": "Cautious",   "min": 20, "dailyLimit": 10,   "perTxLimit": 5 },
    { "name": "Restricted", "min": 1,  "dailyLimit": 2,    "perTxLimit": 1 },
    { "name": "Frozen",     "min": 0,  "dailyLimit": 0,    "perTxLimit": 0 }
  ],
  "scoring": {
    "overrideBoost": 3,
    "maxFrequencyPenalty": 10,
    "inactivityDecayRate": 0.5
  },
  "xmtp": { "enabled": true },
  "worldId": { "enabled": true },
  "webBotAuth": { "enabled": true },
  "networkScore": { "enabled": true },
  "port": 4021,
  "dashboardEnabled": true
}

Config is loaded from (in order):

  1. Path passed to loadConfig(path)
  2. TRIAGE_CONFIG_PATH environment variable
  3. ./triage.config.json in the current working directory
  4. Built-in defaults (if no file found)

Environment Variables

Variable Required Default Description
PORT No 4021 Server port
POLICY_SECRET No - Shared secret for policy endpoint authentication
WORLD_ID_RP_ID No - World ID relying party ID
WORLD_ID_SIGNING_KEY No - World ID signing key (hex)
XMTP_PRIVATE_KEY No - Private key for XMTP notifications
HUMAN_XMTP_ADDRESS No - Wallet address for XMTP DM notifications
PAY_TO_ADDRESS No - Address for x402 payment requirements
BASE_SEPOLIA_RPC_URL No https://sepolia.base.org Base Sepolia RPC endpoint
SCORING_SERVER_URL No http://localhost:4021 Scoring server URL (used by triage-policy)
TRIAGE_CONFIG_PATH No ./triage.config.json Path to config file

Generate a .env.example with triage-ows init.


Override Flow

1. Agent sends transaction
2. Policy evaluation: DENY (exceeds limit)
3. Server records pendingOverride with 5-minute TTL
4. XMTP notification sent to human owner:

   "Transaction denied
    Agent: 0xAgent...
    Amount: $50.00 USDC -> 0xRecipient...
    Trust score: 42 (Building tier)
    Reason: Exceeds per-transaction limit ($25)

    Reply 'override' to approve this transaction."

5. Human replies "override" in XMTP DM
6. Message listener detects reply, POSTs to /api/override/:address
7. Transaction approved, trust score boosted by +3
8. TRUST_CHANGE event emitted to dashboard
9. Override expires after 5 minutes if not acted on

Dashboard

The dashboard is a React SPA served from / on the scoring server.

Panels:

  • Stat Cards -- Total agents, policy decisions (with approval rate), active wallets, daily volume
  • Signing Decisions -- Canvas particle visualization with 3 lanes (APPROVE green, DENY red, OVERRIDE gold). Particle size scales with transaction amount. Shows running $ volume per lane.
  • Agent Budgets -- Top 5 agents with progress bars showing daily spend vs limit. 80% tick marker. Tier labels.
  • Policy Decisions -- Real-time feed of every transaction decision with agent, score, amount, budget usage, and denial reasons
  • Trust Leaderboard -- Top 8 agents ranked by score with tier badges, score bars, daily limits, trend arrows, and World ID / Web Bot Auth verification icons

Security

  • Policy Secret -- Optional shared secret (POLICY_SECRET env) authenticates the policy evaluation endpoint. Both the executable and server must share the same secret.
  • World ID -- Nullifier hashes are verified server-side via the World API. The client never self-reports verification status.
  • Override Timeout -- Pending overrides expire after 5 minutes. Stale overrides cannot be replayed.
  • Input Validation -- Policy evaluation validates JSON structure, handles malformed BigInt values gracefully (falls back to 0).
  • XMTP Graceful Fallback -- XMTP is lazy-loaded. If native bindings are unavailable, the server starts normally with notifications disabled.
  • Web Bot Auth -- Ed25519 signature verification follows RFC 9421. Public keys must be pre-registered via the API.

Features

  • 5-factor trust scoring -- Identity, On-Chain, Behavior, Compliance, Network minus Risk
  • 6 spending tiers -- Sovereign through Frozen with configurable limits
  • Real-time dashboard -- Canvas particle flow, live feed, leaderboard, budget tracking
  • XMTP notifications -- Denial alerts, budget warnings, trust changes via DM
  • Human override -- Reply "override" in XMTP to approve denied transactions (+3 trust boost)
  • Override timeout -- 5-minute expiry on pending overrides
  • Web Bot Auth -- RFC 9421 HTTP Message Signatures for cryptographic agent identity
  • World ID -- Human verification via World ID proof-of-personhood
  • x402 payments -- Payment verification via x402 facilitator
  • ETH price oracle -- Live CoinGecko price feed with 5-minute cache and $2,500 fallback
  • On-chain balance -- Base Sepolia balance lookup via viem with 10-minute cache
  • Configurable -- triage.config.json for tiers, thresholds, feature flags, scoring tuning
  • CLI -- init, dev, register, attach, seed, status commands
  • Policy secret -- Optional shared secret for endpoint authentication
  • Budget warnings -- Configurable threshold (default 80%) with XMTP alerts
  • WebSocket events -- Real-time POLICY_DECISION, TRUST_CHANGE, BUDGET_WARNING streams

Technologies

Technology Role
Hono HTTP framework
viem On-chain queries (Base Sepolia)
XMTP Agent-to-human messaging (@xmtp/node-sdk)
World ID Human proof-of-personhood
x402 Payment verification protocol
ws WebSocket server
React Dashboard UI
Tailwind CSS Dashboard styling
CoinGecko ETH/USD price feed
Node.js crypto Ed25519 signature verification (RFC 9421)

OWS Integration

Register the Policy

# Registers triage-trust policy with OWS
triage-ows register

This copies policy-template.json to ~/.ows/policies/triage-trust.json and runs ows policy create.

Attach an Agent

# Create an OWS API key with Triage governance
triage-ows attach --wallet my-agent --key my-agent-key

This runs ows key create --name <key> --wallet <wallet> --policy triage-trust.

Policy Template

{
  "id": "triage-trust",
  "name": "Triage Reputation-Gated Policy",
  "version": 1,
  "rules": [
    { "type": "allowed_chains", "chain_ids": ["eip155:84532"] }
  ],
  "executable": "./node_modules/.bin/triage-policy",
  "config": {
    "scoring_server": "http://localhost:4021"
  },
  "action": "deny"
}

The action: "deny" means transactions are blocked by default if the policy executable fails or times out.


Demo & Simulation

Seed Demo Agents

npm run seed

Creates 5 agents at different trust levels: Sovereign (81), Trusted (72), Building (53), Cautious (23), Restricted (~17).

Run Live Simulation

# Start server in one terminal
triage-ows dev

# Run simulation in another
npm run simulate

The simulator fires policy evaluations every 2-3 seconds with randomized amounts, occasionally triggering denials and new agent registrations.


What's Next

  • Persistence -- SQLite or Postgres backend for agent state
  • Multi-chain -- Extend beyond Base Sepolia to mainnet and other chains
  • Full network scoring -- Graph-based trust propagation across agent networks
  • Trust portability -- Export/import agent trust scores across Triage instances
  • Policy marketplace -- Share and compose policy configurations
  • Anomaly detection -- ML-based pattern detection triggering notifyAnomaly
  • Rate limiting -- Per-agent request rate limits on the API

Future Integrations

  • AgentKit — Coinbase AgentKit agent verification as an identity signal. AgentKit-created agents would receive an identity boost similar to Web Bot Auth, recognizing that the agent was created through a governed framework.
  • ERC-8004 — External reputation feed integration for standardized agent identity tokens.

License

MIT