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()
Paid calls only¶
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_eventsanddrawstables 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_idandsession_idpassed toRouteweiler(...)are written into every trace event and used as the sticky-routing key.