Skip to main content
The stream is the engine’s own progress spine, passed through the gateway verbatim — we do not maintain a second, prettier vocabulary that could drift from reality. This page blesses the stable subset integrators may rely on, and its examples are regenerated from captured live streams (2026-08-27) — where an earlier draft of this page disagreed with the wire, the wire won. Two rules make it safe to build on:
  1. Unknown phases are liveness. Any event whose phase you don’t recognize means “the run is alive.” Render nothing, never error. New phases are additive and ship without a version bump.
  2. Guaranteed fields only. Each event below lists the fields guaranteed present. Other fields may appear and may change without notice.
One honest wart, stated plainly: event payloads use the engine’s camelCase (costUsd, elapsedMs) while REST request/response bodies use snake_case (max_spend_usd, spend_usd). The stream is the engine’s wire format, verbatim — renaming it would mean a translation layer that could lie.

Transport

GET /v1/sessions/{id}/stream is text/event-stream. Each SSE message: id: = the event’s sequence number, data: = one JSON event. Resume with Last-Event-ID; the same numbers page GET …/events?after=. Sequence numbers are global per session, monotonic across turns. Full transport semantics: Ways to watch a run. The first message of a live run is the meta envelope:

The paged replay envelope (GET …/events)

The paged log serves the same event objects, flat, with seq merged in — the event is not nested under a data or event member:
seq is the SSE id: for that event. Rebuild-then-attach (page …/events from 0 to rebuild state, then connect …/stream with Last-Event-ID = the last seq) is the reload/resume recipe.

The stable subset

search-intent — what the parse understood ($0)

Captured live (trimmed):
Guaranteed: phase, query, entity, confidence (a number, 0–1). The wire shape, as measured:
  • constraints is a count (a number). The parsed constraint objects ride predicate — an array of { field, op, value, unit? } objects. Constraint field names are per-query vocabulary (the same query may parse to ingredient/strength one run and vitamin/dosage the next) — treat them as data, not an enum.
  • geo is an object (region, countryCode, …) when present, and may be absent.
  • merchant (a prose merchant descriptor) may appear when the query pins one.
confidence is a number, 0–1, on every event — no surface emits the enum words ("high"/"low") that earlier drafts showed. One known wire inconsistency, stated: POST /v1/sessions/estimate currently returns parsed.confidence as a string-wrapped float ("0.92"). The number form is canonical; the estimate’s string form is a documented divergence that will be fixed as a dated-version change.

search-resolve — the merchant fan-out plan ($0)

Captured live (trimmed):
Guaranteed: phase, merchants — an array of merchant objects, each guaranteed domain (seedUrl and an evidence provenance string may ride along). The fan-out count is merchants.length. (An earlier draft documented merchants as a number; the wire has always carried the array — the array is the contract.)

orchestrate — a routing decision (why money moved)

Captured live (trimmed):
actiondispatched | declined | re-resolved | proposed | stopped (open enum). Guaranteed: phase, action, reason. (The field is action — an earlier draft called it decision; the wire has always said action.) A declined carries by ("pool" | "duplicate-guard"). These events are the audit trail of spend: every dispatch and every decline, with the reason, in the same stream as the rows. During the quiet stretches of a run they may be the only substantive traffic — that’s normal.

search-source — a source delivered (rows ride the event)

Guaranteed: phase, domain, delivered, records. costUsd may be null — cost basis unavailable, which settles at $0. Like every dollar amount on this API, costUsd is the customer’s price, rounded to ≤4 decimals.
delivered decides whether this pass gets a receipt — costUsd does not. A pass with delivered: true produces one receipt on GET …/receipts. A pass with delivered: false produces none, even when its costUsd is non-null and was billed — the common case for a pass stopped by a deadline, an anti-bot wall or a dead transport. Don’t infer a receipt from the presence of a cost: what a receipt covers.
rows is a bounded preview (500 rows / 1.5 MB per source, stated in truncated); the full corpus is at GET …/rows. Stream rows are flat per-source records — see the three row dialects.

cost — the orchestrator’s own model spend

Guaranteed: phase, usd (may be null).

search-done — the run’s terminal (not the turn’s)

stopexhausted | query-budget | spend-budget | agent-settled | stopped (open enum). Guaranteed: phase, stop, records. Two honest details here:
  • unchecked lists merchants that were never dispatched — a fact about us, stated, not hidden. Coverage you didn’t get is named.
  • A record ending at search-done is a turn still judging, not a lost answer. Wait for search-answer and the terminal envelope.

search-adjudication — the judging window is never silent

A completion event with tallies always follows. Guaranteed: phase.

search-answer — the authoritative summary (bounded by construction)

Guaranteed: phase, qualified. Sections shed adaptively by size, each cut named in truncated.cut. Rows the screen couldn’t verify are counted under needsVerification, not silently promoted to qualified. Answer rows are flat like source rows but with renamed fields (price_listed, product_id) — see the dialect table below.

abstain / error — honest endings

Guaranteed: phase, plus reason on an abstain and message on an error. A terminal error additionally carries code — the stable stream-terminal vocabulary; mid-run fail-soft error events may omit it. The session status and the ledger settle accordingly: an abstain is a **0outcome,statedonlysignedreceiptssettle,andtherestofthereservationisreleased.Thesearestatuses,notHTTPerrors(see[Errors](/errors),"FailuresthatareNOTerrors").Aquerywe[declinetospendon](/errors/declinedtospend)endsthiswaytooa201create,anabstainevent,statusabstained,0 outcome, stated** — only signed receipts settle, and the rest of the reservation is released. These are statuses, not HTTP errors (see [Errors](/errors), "Failures that are NOT errors"). A query we [decline to spend on](/errors/declined_to_spend) ends this way too — a `201` create, an `abstain` event, status `abstained`, 0.

session-turn — a turn closed (SSE terminal envelope)

routerun | narrow | ask | reaim | unbound. A reaim record carries proposal: { estimatedUsd, passes, confirmed }. This envelope closes the stream for the turn.

The three row dialects — one session, three spellings

Rows appear on three surfaces, and today they are three different shapes. This is a documented wart, not a contract we defend — a future dated version should converge them — but until then, here is the mapping, as measured on live sessions: Practical guidance: parse prices from price ?? price_listed (both are JSON numbers, with currency alongside); treat all other record keys as per-source schema. url may be an empty string on some rows; no image field is guaranteed to exist.
The two REST surfaces are not interchangeable, and the difference is the audit join. GET …/rows emits source.receipt_id on every row — it names the receipt of the pass that delivered that row, and it is what lets you reconcile a row against the receipts surface. GET …/result.rows[] does not carry it; it carries fulfillment (how the row satisfies the predicate, with evidence) and the adjudication’s duplicateCount / duplicateSources instead.Page …/rows for anything you intend to audit, and treat …/result.rows as a convenience view of the adjudicated subset.

Events not in the subset

harness heartbeats (per-pass phase pulses), search-locate, search-triage, orchestrate-turn/orchestrate-tool envelopes, acquisition/anti-bot cost events, and future phases all flow on the wire. Read them if useful; build on the subset. They can disappear or change shape without notice — the subset cannot.