Skip to content

Traces

Routeweiler emits one structured TraceEvent per HTTP call to a SQLite database. Every call — paid, free (2xx without a 402), or failed — produces exactly one event.

Traces are used for audit, reconciliation, anomaly detection, and debugging.

Enabling traces

Pass a TraceSink to Routeweiler:

from routeweiler import Routeweiler, Funding
from routeweiler.trace.sink_sqlite import TraceSink

async with Routeweiler(
    funding=[Funding.base_usdc(wallet=signer)],
    trace_sink=TraceSink.sqlite("rw.db"),
) as client:
    response = await client.get("https://api.example.com/data")

The rw.db SQLite file is created automatically if it does not exist.

TraceSink.sqlite options

TraceSink.sqlite(
    path="rw.db",
    url_mode="raw",    # "raw" stores the full URL; "drop" omits it for privacy
)

TraceEvent schema

Each event is stored as a JSON blob in the payload column of the trace_events table. Key fields:

Field Type Description
requestId str UUIDv7 — unique identifier for this call.
agentId str | null Value of Routeweiler(agent_id=...).
envelopeId str | null Which budget envelope was drawn (or null if none).
policyHash str SHA-256 fingerprint of the policy at the time of the call.
selectedRail str | null "x402", "l402", "mpp-tempo", "mpp-spt", or null for passthroughs.
payment object | null Payment details (see below). Null for non-402 responses.
outcome.httpStatus int | null Final HTTP status code.
outcome.serviceDelivered bool True when the caller received a 2xx response.
outcome.serviceLatencyMs int Wall-clock ms from first send to final response.
timestampStart ISO 8601 When the request was sent.
timestampEnd ISO 8601 When the response was received.

payment sub-object

Populated only when a 402 was intercepted and payment was attempted.

Field Type Description
proofType "txid" | "preimage" | "spt_id" Type of settlement proof.
proofValue str | null On-chain tx hash, Lightning preimage, or SPT id.
amountNative int Amount in the rail's native base units (wei, sats, cents).
amountNativeCurrency str CAIP-19 asset id, "btc-lightning", or ISO 4217.
amountEnvelope float | null FMV-converted amount in the envelope currency. Null if no envelope.
amountEnvelopeCurrency str | null Envelope currency (e.g. "usd").
fmvQuality str "stablecoin_peg", "coingecko_simple", "fx_leg", or "unavailable".
settlementLatencyMs int ms between payment send and settlement confirmation.

Reading traces

Python

import sqlite3, json

conn = sqlite3.connect("rw.db")
conn.row_factory = sqlite3.Row
rows = conn.execute("SELECT * FROM trace_events ORDER BY timestamp_start DESC").fetchall()

for row in rows:
    payload = json.loads(row["payload"])
    print(
        row["request_id"],
        row["selected_rail"],
        row["http_status"],
        row["service_delivered"],
    )
conn.close()
rows = conn.execute(
    "SELECT * FROM trace_events WHERE selected_rail IS NOT NULL"
).fetchall()

Reconciliation: all calls with USD equivalent

for row in rows:
    p = json.loads(row["payload"])
    payment = p.get("payment")
    if payment:
        print(
            row["request_id"],
            payment["amountEnvelope"],
            payment["amountEnvelopeCurrency"],
            payment["fmvQuality"],
        )

Notes

  • The trace_events and draws tables share the same SQLite database.
  • The schema is created idempotently (CREATE TABLE IF NOT EXISTS) — there is no migration system in Phase 1. Avoid adding columns manually; they will be wiped on the next schema creation.
  • Set url_mode="drop" to omit URLs from traces when the URLs contain sensitive parameters (API keys in path segments, etc.).
  • agent_id and session_id passed to Routeweiler(...) are written into every trace event and used as the sticky-routing key.