Policies

The policy vocabulary, the decision engine, and the audit trail behind every automated decision.

Policies are the rules about who may move money and when. They are small, explicit, and versioned in code — not learned, not improvised.

The vocabulary

  • Thresholds — amounts that change who decides. Above 5,000 USDC, humans decide; the agent cannot move funds above policy.
  • Recipient allowlist — trusted recipients (approved, or seen before). New recipients add friction by design.
  • Route allowlist — the corridors automation may use. Anything outside the list is out of scope for autonomous execution.
  • Quorum — how many humans must agree. Team approvals resolve at 2-of-3 across Finance, Operations, and Compliance.
  • Claim window — 90 seconds per transfer, after which the timeout refund path opens.
  • Machine pricing — 0.01 USDC per agent quote, $0.000001 per FX feed.

Where they live

All constants are centralized in the policy module (src/core/services/agent/policies.ts), with the decision limits in packages/domain/src/policy.ts. Tune policy in one file and the agent, the API, and the UI all follow:

export const POLICY_LIMITS = {
  complianceThresholdUsdc: 5_000,
  fxDriftTolerancePct: 2,
  quorum: 2,
  reviewers: ["Finance", "Operations", "Compliance"],
} as const;

The decision engine

evaluatePolicy() is a pure function — no I/O, no model, no randomness. It takes four signals and returns a verdict with human-readable reasons:

SignalReadsPasses when
AmountTransfer size in USDC≤ 5,000 USDC
RecipientTrusted (approved / seen)On the list
FX driftLocked quote vs mid-market≤ 2%
LiquidityCorridor liquidity levelNot low

Verdicts:

  • AUTO_EXECUTE — all signals pass. The transfer proceeds without a human.
  • REQUIRES_REVIEW — one or more signals fail. An approval request is raised with the engine's own reasons; 2-of-3 humans resolve it.
  • BLOCKED — the verdict vocabulary includes hard blocks; today's rules resolve to the two verdicts above, and hard blocks live in the escrow and the live-rail guards.

Wallet limits sit alongside the engine: on the live rail, per-transfer caps and escrow float checks run before any chain call — a transfer that exceeds the testnet float is refused with the real reason, never a silent failure.

One engine, three surfaces — the agent's live sweeps, the policy simulator in the product, and the approval API all call the same function, so decisions can never disagree between surfaces.

The audit trail

Every automated decision is persisted with its signal and decision in the agent action log (GET /api/agent/actions), and every human approval is persisted with its votes and timestamps (GET /api/approvals). Transfers carry their own event trail — from quote to claim or refund — visible on transvia.xyz/transfers.

Nothing is implicit: if money moved, there is a rule that allowed it, a signal that triggered it, and a record that proves it. See Automation Agents for the loop and API for the endpoints.