Why agent swaps need an evidence trail
Agent execution tooling matured fast: an AI agent can plan a swap, build the transaction, and submit it without a human touching a keyboard. The evidence half moved slower. If a position sours later, an agent pipeline that kept no records cannot answer the only question that matters in the post-mortem: what was observed, modeled, and not evaluated at the moment of signing — and under which methodology version?
A preflight evidence record answers it with a document instead of a memory. Every keyed check_swap call persists a routescore.preflight_record.v0 record and returns three additive fields on the response: record_id (retrieve the record), evidence_bundle_id (the stored evidence bundle, minted with the response so the id is part of the hashed evidence), and record_output_hash (a SHA-256 content hash a third party can re-derive offline). Existing integrations keep working — the fields are additive and back-compatible.
- record_id — fetch the full record via GET /api/public/v1/records/{record_id} or the get_preflight_record MCP tool.
- evidence_bundle_id — names the stored evidence bundle; minted with the response, inside it, so it is part of the hashed content.
- record_output_hash — a canonical-JSON SHA-256 hash of the record, re-derivable offline with no Routescore account.
- Both evaluated answers persist: a 200 (clear / caution) and a 422 (unsupported) are both recorded; schema-invalid 400s never are.
What a record contains — and what it never contains
The record is a response-superset, never a fork: the full check response nests verbatim under its response field — no figure renamed, softened, or re-summarized. Around it the record adds only what an audit needs: an identity-redacted echo of the evaluated request, a declared actor type (human, agent, multisig, or unknown — declared by the caller, never verified, and anything else degrades to unknown rather than a guess), a reserved policy-reference slot, and the integrity block with the hash and its re-derivation recipe.
Two boundaries are structural. Records are identity-free by construction: they never store wallet addresses, ENS names, or emails — an actor label that looks like one is dropped before recording, not masked. And records are read-only evidence, never signing material: a response carrying calldata, transaction payloads, or signatures is rejected outright rather than stored, so a preflight record can never be replayed into an execution.
How a third party re-verifies the hash
The hash identifies the evidence content, not the storage row: record_id, recorded_at, and the integrity block itself are excluded, so re-recording identical evidence yields the identical hash. It is a content hash, not a signature — and it uses the same single recipe as every other Routescore artifact hash, so there is exactly one verification procedure to learn.
The recipe, stated inside every record: remove record_id, recorded_at, and integrity; serialize the remainder as canonical JSON — ECMAScript JSON.stringify applied to the recursively key-sorted value (arrays kept in order, no whitespace, ECMAScript number rendering and string escaping, no Unicode normalization); SHA-256 the UTF-8 string; prefix sha256:. If the result matches integrity.output_hash, the document is exactly what the check produced at decision time — and if any field were edited afterwards, the recomputed hash would no longer match. Non-JavaScript implementations must reproduce ECMAScript serialization semantics (number rendering is the usual divergence) or use the Node reference snippet in the records guide, which is normative. An auditor, a counterparty, or a future you can run it offline.
Owner-scoped retrieval, failure honesty, and outcome labels
Retrieval is keyed and owner-scoped: a key only ever sees records belonging to its own account, and a record id from another account is indistinguishable from a missing one — both return 404. GET /api/public/v1/records lists newest-first summaries with cursor pagination; GET /api/public/v1/records/{record_id} returns the pristine record document plus its stored evidence bundle; the get_preflight_record MCP tool gives an agent the same read.
Failure is stated, never papered over: if the durable write fails, the check response still returns in full — verdict and trust envelope unaffected — with the three linkage fields null plus a record_persistence_failed caveat, so a missing record is always visible as missing. And records never rewrite history: each summary carries an outcome_label_id that starts null and is set only after an after-the-fact outcome label reconciles the check with independently observed settlement data. Labeling never happens at decision time, and the original record is never edited.