Skip to content

Benchmarks

Every performance number fold publishes traces to a runnable instrument in the repo. If a claim doesn’t resolve to something on this page, treat it as a bug and say so.

bench/latency_test.go measures added latency on the proxy path: the same client calls the same upstream — an in-process echo server built from the official MCP Go SDK — directly and through a real gateway; 200 warmup + 2,000 measured sequential calls per side, comparing p50s. The bench CI job runs it on every merge and fails the build if added p50 exceeds 5 ms — deliberately loose for shared GitHub runners; it gates regressions, not records.

tools/perf measures throughput and tail latency for one instance under a concurrency sweep: three processes (driver, the real fold binary, fixture upstream — never sharing a scheduler), where each “connection” is a full official-SDK client session doing sequential calls, retries disabled. Every stage runs direct and through-fold, so the upstream’s own ceiling is visible and the gap between them is the honest cost.

Added latency (the gate’s instrument):

MeasurementValueEnvironment
Added p50 (CI gate)< 5 ms, enforced every mergeShared GitHub Actions runner
Added p50 (typical)~0.20 msApple Silicon, in-process upstream
Gateway p99 (typical)≈ 0.57 msSame run

Throughput (tools/call, namespaced mode, v1.3.0 — Apple M4 Pro, loopback, zero errors across the sweep):

ConnectionsDirect req/sThrough foldfold p50fold p99Retention
812,7968,0380.9 ms2.0 ms63%
6414,5519,2636.2 ms19.0 ms64%
25622,87813,37916.7 ms60.1 ms58%

Passthrough mode measures within noise of namespaced. tools/list through fold runs at ~42,000 req/s, at parity with direct — that’s the list cache absorbing reads inside its TTL, not proxy throughput; quote tools/call. Raw sweep JSON is in the repo.

  • This measures fold’s added work, not your deployment. Loopback transport, a trivial echo upstream — the instruments isolate what the gateway adds (routing, policy, audit accounting, proxying via the official SDK) from everything they can’t control (your network, your upstream’s latency). Your end-to-end numbers are your upstream’s numbers plus roughly the tables’ gap.
  • Retention is the honest throughput framing. The direct column is an in-process SDK server doing near-zero work per call; fold adds a full second hop through the same SDK. Against a real upstream doing real work, the relative overhead shrinks toward the latency gate’s ~0.2 ms.
  • Sessions are the unit of concurrency. Each load-test connection is a full SDK client session, with fold holding a per-client upstream session behind it — the deployment-realistic shape, not an artificial socket storm. One untuned instance, default config.
  • The gate and the typical number are different things. 5 ms is a regression tripwire that has to survive noisy shared runners; ~0.2 ms is what the same instrument reports on quiet hardware. Quote the second, rely on the first.
  • Don’t benchmark the demo. demo.fold.run is rate-limited, containerized on fractional-vCPU hardware, and fronted by Cloudflare — it demonstrates federation, not fold’s ceiling. The harness is one command; run that instead.

In production, the same questions are answered continuously by the fold_request_duration_seconds and fold_upstream_request_duration_seconds histograms — the difference between them is the gateway’s share of every real request.

Terminal window
git clone https://github.com/fold-run/fold && cd fold
make bench # latency gate — what CI runs every merge
make loadtest # throughput sweep (8/64/256 connections)
FOLD_LOAD_MODE=passthrough make loadtest # single-upstream mode

The gate prints p50/p90/p99 for both paths and a one-line BENCH_RESULT; the sweep prints the table above and a headline, with knobs for connections, duration, scenarios, JSON output, and driving an already-running deployment (FOLD_LOAD_FOLD_URL). The bench job on any merge is the public receipt, alongside conformance; full methodology lives in the repo’s docs/benchmarks.md.