F0.1 — Performance budgets & workflow scope
The explicit performance contract for the program. Numbers are targets, measured on the large local fixture (F0.4) via the harness (F9.1), warm cache excluded unless noted. p50/p95 are server-side latency for the API call; CLI/MCP add transport + (de)serialization on top, which is why payload ceilings and connection reuse (F4.2) matter as much as query time.Latency budgets (hot workflows)
* Broker operations/invoke cross the Nango boundary and a live provider; their budget is the
Driftless overhead around the provider call (auth, lookup, audit write), not the provider’s
own latency. Cache operation metadata (F7.2/F7.3) so listing operations does not require a live
round-trip on every invoke.
Payload ceilings (single read response, JSON, uncompressed)
Ceilings are enforced by regression tests (F2.2) and the gates in
gates.md.
Hard backstop (T7): every MCP tool response — static and synthesized —
passes through a byte governor at ToolRegistry.call capped at 50 KB
(DRIFTLESS_MCP_RESPONSE_MAX_BYTES overrides). Under the cap responses are
verbatim; over it the largest lists/strings are shrunk and the response
carries truncated: true + a hint (never a silent cut). The per-read
ceilings above are what well-behaved envelopes should hit; the governor is
the guarantee that an unbounded upstream payload (e.g. a raw provider
operations list) can never flood an agent’s context. Enforced by
apps/mcp/src/tools/response-budget.spec.ts with worst-case fixtures.
Market-data is the one envelope this backstop never trims. Every
typed market-data response (schemaVersion: 'market-data/domain/1')
carries an opaque page cursor bound to its exact filter set and corpus
snapshot; the governor’s usual last-resort shrinking (halving the largest
array/string) would desynchronize that cursor from the page actually
returned. ToolRegistry.governed() special-cases it instead, the same way
it already special-cases the bounded_live record envelope: under the
50 KB cap the envelope passes through verbatim — coverage, provenance
and semantic warnings intact; over the cap it returns an explicit
RESULT_TOO_LARGE rather than a shrunk page, so the caller narrows the
query or lowers limit and retries instead of trusting a nextCursor that
no longer matches what it received. That refusal names the two bounds the
market surface actually takes — the limit and the page cursor — never the
projection/select the record-read surfaces offer and this one does not.
Refusal is the fallback, not the design. suppliers/search — the
fattest market page, and the one whose advertised limit: 50 measured over
the cap — is bounded at the SOURCE, the same INV-011 shape the collection
record envelope uses: the MCP adapter sends its cap minus a 2 KB reserve as
max_bytes on the call body (server-owned, never a tool argument), and the
service assembles the largest page that FITS, measuring the exact wire form
— the model-facing projection, pretty-printed. A page cut for bytes is a
normal page: fewer rows than limit, hasMore: true, and the cursor that
continues at the first row it did not deliver. The safety margin lives at
the MCP layer only; the source measures real bytes rather than estimating
them. The other search operations are not source-bounded yet and still
refuse an over-cap page — with the correction above.
Retrieval quality bars
Measured by the ranking eval harness (F1.6) on a labeled query set:- Recall@10 ≥ 0.90 — the right topic is in the top 10 for known queries.
- MRR ≥ 0.70 — the right topic ranks high, not just present.
- No regression vs the current ranking on the labeled set when search moves to indexed full-text (F1.2) and match-files candidate selection is redesigned (F1.3).
