Skip to content
Docs Architecture

Architecture

This guide explains how Agent Passport turns a wallet request into an explainable decision: the service boundary, request lifecycle, Algorand data flow, contracts, caching, persistence, and scaling model. For the scoring rules themselves, see the algorithm and scoring reference.

1. System overview

Agent Passport is a self-hosted HTTP API for evaluating Algorand agents. Read endpoints return trust, delegation, Sybil, reputation, credit, and underwriting evidence. Mutating delegation and reputation routes can write to the configured Algorand applications when an operator wallet is enabled.

System boundaryEvidence moves through one clear evaluation surface
01

Client / agent

HTTP, TypeScript, or Python SDK

GET /scorePOST /delegate
02

Agent Passport API

Validates, authenticates, scores, and explains.

Rate limitsHMAC / x402Metrics60s cache
03

Algorand

algod, indexer, and optional applications.

registry.tealreputation.teal
Read paths remain self-hosted and application-database independent; stateful stores need shared infrastructure when replicas scale out.

Read endpoints do not require an application database: they fetch from the configured Algorand algod and indexer services and use a 60-second in-memory response cache. The service does not silently provision hosted storage, queues, billing, or identity infrastructure.

For stateful endpoints, four per-process stores are NOT shared across pods and must be backed by Redis (or equivalent) when running with REPLICA_COUNT > 1:

  • src/lib/idempotency.ts — Idempotency-Key cache
  • src/lib/security.ts — per-IP rate-limit map
  • src/lib/system-exposure.ts — global + per-wallet exposure ledger
  • src/lib/webhooks.ts — webhook subscriber registry

A boot-time warning is logged for each store when REPLICA_COUNT > 1. The on-disk JSON files (data/*.json) only protect against restart loss, not against pod fan-out — each replica independently enforces its cap and serves its subscriber list. Scale reads by adding pods; scale state by adding Redis.

HTTP parsing stays at the application edge. Shared wallet and amount rules live in src/http/validation.ts, while domain modules remain independent of Express request and response objects. New routes should validate and normalize at this boundary, call a domain function, and then map its result to HTTP.

2. Request lifecycle

A request passes through ordered platform and security stages before its route handler. The stack validates input, assigns a request ID, applies CORS and rate limits, enforces deadlines and optional payment/authentication, and then dispatches to the capability. Operational routes are kept separate from business logic so health, readiness, metrics, and version metadata remain safe to expose to deployment tooling.

#MiddlewarePurposeHeaders added
1app.set('trust proxy', TRUST_PROXY_HOPS)Honour X-Forwarded-For from N trusted proxy hops; 0 (default) trusts nothing, 1 trusts one hop, 2+ trusts N hops for CloudFront → ALB → app style chains—
2helmet()HSTS, X-Content-Type-Options, X-Frame-Options, CSPStrict-Transport-Security, etc.
3requestIdMiddlewareUUID per request; reads X-Request-ID if validX-Request-ID
4requestLoggingMiddlewareOne JSON log line per request—
5corsMiddleware({ origin })CORS with * or allow-listAccess-Control-*
6rateLimiter({ windowMs, max })600 req/min/IP; bypasses for ops + trusted IPsX-RateLimit-*
7express.json({ limit: '100kb' })JSON body parser, hard 100 KB cap—
8metricsMiddlewareRecords http_request_duration_seconds—
9requestDeadlineMiddlewareSets res.locals.deadlineAt from REQUEST_TIMEOUT_MS—
10x402MiddlewareWhen enabled, requires x-payment on premium endpointsx402 spec
11settlementVerificationMiddlewareAsync verify of payment on-chain—
12hmacAuth (if HMAC_SECRET set)HMAC-SHA256 auth on mutating endpoints—
13idempotencyMiddlewareIdempotency-Key handling for mutating callsidempotency-key, idempotent-replay
14Route handlerPer-endpoint logicper-endpoint

Operational endpoints (/health, /ready, /health/deep, /metrics, /registry/status) are exempt from rate limiting (step 6) and from payment (step 10).

3. Cache and idempotency stores

Response cache

src/lib/cache.ts — TTLCache<unknown>(maxEntries: 500, ttlMs: 60_000). Caches /score and /passport for 60 s. Invalidated on /delegate, /revoke, /reputation/record for the affected wallet.

Idempotency store

src/lib/idempotency.ts — in-memory Map<key, { bodyHash, status, body, expiresAt }>. Default 24 h TTL, 10 000 entry cap, 5-minute sweeper. Body hash uses canonical JSON (sorted keys) so {"a":1,"b":2} and {"b":2,"a":1} produce the same hash.

For multi-replica deployments, back this with Redis. The current implementation is per-process.

Rate-limit and system-exposure JSON files

Two single-purpose data/*.json files persist across restarts:

  • data/rate-limit.json — per-IP request counts (resets after 60 s)
  • data/system-exposure.json — cumulative approved credit per wallet

Both use the shared JSON-file persistence helper (src/lib/json-store.ts) with a write-queue mutex to prevent concurrent-write races. Multi-replica needs Redis for both.

4. Smart contracts

Two TEAL v10 contracts under contracts/:

registry.teal — delegation registry

  • App ID: REGISTRY_APP_ID env var
  • Global state: admin (operator address)
  • Box storage: one box per (sponsor, agent) pair, value = amount in microALGO units plus the last-update timestamp
  • Methods:
  • add_delegation(sponsor, agent, amount) — creates a box
  • revoke_delegation(sponsor, agent) — deletes the box
  • update_admin(new_admin) — rotates the operator key
  • Update permission: only the admin address

The current transaction format passes the agent and sponsor as application accounts (Accounts[0] and Accounts[1]) and includes the deterministic box reference in the application call. Existing deployments created with the older payment-transfer format must be redeployed (or migrated with a planned contract upgrade) before enabling the updated service.

reputation.teal — on-chain reputation events

  • App ID: REPUTATION_APP_ID env var
  • Global state: admin, event_count, per-wallet event counter
  • Local state: per-account event log
  • Methods:
  • record_event(wallet, event_type, amount, counterparty) — appends
  • update_admin(new_admin) — rotates the operator key
  • Update permission: only the admin address

Deploying the contracts: npm run deploy-registry and npm run deploy-reputation (uses DEPLOYER_MNEMONIC). The runtime operator wallet (OPERATOR_MNEMONIC) is a separate key with on-chain permission.

5. Data flow (request → Algorand → response)

Request lifecycleOne request, parallel chain evidence, explainable response
  1. 01
    Request

    GET /score?wallet=…

  2. 02
    Gateway

    Validate, identify, rate-limit

  3. 03
    Chain evidence

    Round, account, transactions

  4. 04
    Decision

    Five signals computed in process

  5. 05
    Response

    Score, risk, evidence, checksum

parallel reads1 status + 1 account lookup + optional transaction reads200 JSON

Trust-score generation fans out 5 sub-score calls (age, activity, volume, velocity, compliance), each hitting algod once. The composite is computed in-process. Total: 1 status() + 1 accountInformation() + 0-25 txlist/txinfo for delegation, all in parallel where independent.

6. Module reference

FilePurposePublic exports
src/trust-score.tsComposite trust scorescoreWallet, scoreWalletFresh
src/delegation.tsSponsor graph BFSscoreDelegation, scoreDelegationFresh
src/sybil.ts12 sybil signalsdetectSybil, detectSybilFresh
src/reputation.tsEvent log + scorecomputeReputation, recordEvent
src/credit.tsCapacity estimateestimateCredit, estimateCreditWithTrust
src/underwriting.tsDecision engineunderwrite
src/passport.tsFull documentgeneratePassport
src/trust-graph.tsGraph analyticsanalyzeTrustGraph, simulateSponsorLoss
src/counterparty.tsBuyer risk checkcheckCounterparty
src/registry.tsOn-chain delegatedelegate, revoke
src/lib/cache.tsLRU TTL cacheTTLCache
src/lib/idempotency.tsIdempotency-KeyidempotencyMiddleware
src/lib/security.tsCORS, rate limit, HMACcorsMiddleware, rateLimiter, hmacAuth
src/lib/metrics.tsPrometheusmetricsMiddleware
src/lib/x402.tsx402 paymentx402Middleware
src/lib/hmac-auth.tsHMAC authhmacAuth
src/lib/operator-wallet.tsOperator wallet initinitOperatorWallet
src/lib/system-exposure.ts$100k capaddSystemExposure, capToSystemCapacity
src/lib/webhooks.tsSubscriber registryaddSubscriber, fireWebhook
src/lib/sanctions.tsDeny-list providergetSanctionsProvider, checkSanctions
src/lib/timeout.tswithTimeoutwithTimeout
src/lib/json-store.tsJSON-file persistencequeueJsonWrite, readJsonFile
src/lib/request-deadline.tsPer-request deadlinerequestDeadlineMiddleware
src/lib/graph.tsSybil graph signals(private)
src/lib/build-info.tsVersion metadatabuildInfo, packageVersion

7. Scaling characteristics

ResourceBoundNotes
MemoryO(active wallets in 60s window × cache entry size)TTLCache caps at 500 entries
Algorand RPC1 round-trip per sub-score + 1 for statusIndexed per wallet; cache mitigates
Multi-replicaEach replica is independentIdempotency + rate-limit + exposure need Redis for cross-replica consistency
Cold start~1s to import + 0 external callsNo DB to warm
Graceful shutdown10s forced exit after SIGTERMmetricsCollectors, idempotencySweeper, rateLimitTimer all stop

For horizontal scaling:

  1. Add a load balancer that forwards X-Forwarded-For and set TRUST_PROXY_HOPS to the number of trusted hops. The default of 0 is fail-closed (does not honour the spoofable header); set to 1 for a single reverse proxy (ALB, nginx), to 2 for CloudFront → ALB → app, and to 2 or 3 for k8s ingress → sidecar → app depending on the topology.
  2. Set REPLICA_COUNT to the planned replica fan-out. The service will emit a boot-time warning for each per-process store (rate-limit, idempotency, system-exposure, webhook subscribers) so you remember to back them with Redis. The src/lib/json-store.ts interface is designed to be drop-in replaceable.
  3. Configure your orchestrator's readinessProbe to /ready and livenessProbe to /health.