Skip to main content

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).

Scope (workflows in this program)

Topics (search / retrieve / get-files / get), Collections (records / context / retrieve), Integrations/Broker (operations / invoke / records), sync, and context retrieval. Dashboard/web read paths are out of scope except where they reuse the same API endpoints being optimized.