| D-M2-36-1 |
2026-10-25 |
The measuring point on the adapter side of both targets is a Go fake adapter without SDK in services/server/perf/internal/fakeadapter (row "Fake adapter (load)" of 09 section 16.1), one per agent, connected through the gateway test client wstest as consumer perf with the agent's API token and a DPoP proof of harness.Client.Proof (the harness GatewayConn sends no consumer name). Only the gateway transport is measured. The receive time of a frame is taken by a reader goroutine as soon as wstest hands the frame over, before the adapter decodes it; wstest validates every frame against the contract first, so the receive time can only be later than the socket read, never earlier. Place: perf/internal/fakeadapter/transport.go, adapter.go. |
02 17 names a fake adapter as the measuring point, and the fake agent of 09 16.1 runs on the Python SDK, whose interpreter and process boundary would lie inside the measured interval; the gateway is the transport of every adapter that runs permanently (09 4.2), the REST poll depends on the adapter's own cadence and the webhook transport has no owner yet. |
| D-M2-36-2 |
2026-10-25 |
Both tests run under perf.sustained_messages_per_s human messages with the default rate limits of the measured server, with the named constants of perf/internal/agentpath/constants.go (TriggerEvery 10, BudgetShare 0.5, ChannelsPerAgent, ThreadsPerChannel, ViewersPerRun, StepEvery, CallsPerRun, Warmup, Window, Drain, MinSamples, ConsumerName; values in perf/README.md). agentpath.NewPlan derives agents, patch runs per agent and every person count from the defaults of the keys (server schema), from 02, from the table of 04 for agent.run_update_min_interval (an adapter throttle with no server key) and from the table of 09 for adapter.invocation_token_leeway and adapter.jwks_refresh_min_interval. The world is seeded through the public API of a seeding process with raised ratelimit.* (perfkit.StartSeeding), then the process restarts with the default configuration (perfkit.Measure); perf/internal/agentworld is untagged and builds on test/harness directly, because perfkit carries the build tag perf. Place: perf/internal/agentpath/plan.go, perf/internal/agentworld. |
02 17 names no load for these targets; the agent path shares the process, the pool and event_seq with chat, so the chat load belongs to the measurement, and a tenth of triggering messages keeps the trigger path a real share without replacing chat. |
| D-M2-36-3 |
2026-10-25 |
perf.invocation_delivery_p99: start is occurred_at of the received agent.invocation envelope (the transaction of the triggering message, which writes the event, so at or before the commit: an upper bound of "commit to receipt"); end is the receive time of D-M2-36-1; the sample is end minus start corrected by report.ClockOffset (perfkit.ClockOffset); one sample per trigger scheduled in the window, matched by message_id; the value is the nearest-rank p99. Place: perf/internal/agentpath/latency.go, perf/invocation. |
04 2.2 and 15.3 step 1; both points can only make a sample larger. |
| D-M2-36-4 |
2026-10-25 |
perf.run_patch_fanout_p99: start is the scheduled send time of the patch that produced version v (a lagging writer keeps it, so a slow server cannot hide behind a slow generator); end is the receipt of the first run.updated that lifts the viewer's local version to v or above under the client rule of 04 11.3 point 4; one sample per version and viewer for versions scheduled in the window (many viewers of one live box are many samples, RK-32); both times on the test process clock, no skew correction. Place: perf/internal/agentpath/latency.go (Viewer), perf/internal/fakeadapter/patches.go, perf/run_patch. |
The interval contains the gateway op, the stored patch, the coalescing window sync.run_patch_coalesce_interval and the frame write, the path a person sees. |
| D-M2-36-5 |
2026-10-25 |
A value counts only when the path behaved correctly while it was taken: besides the threshold a test fails on an answer other than 201 (among them 429 rate_limited) or a trigger without trigger_state received, a trigger without exactly one agent.invocation, an invocation of an unknown message, a redelivered envelope, a token the reference verifier rejects, any other status of an adapter call, an error or close frame on any connection, a run that does not end completed (read-only query on runs through the admin pool), a gap, a late snapshot, a viewer whose version differs from getRun after Drain, a version of the window that did not reach every viewer, and fewer than MinSamples samples. Both targets join report.Required. Place: perf/invocation, perf/run_patch, perf/internal/report/results.go. |
ADR 0063 point 5: a fast but wrong path must not end green; a late snapshot means a viewer queue overflowed and patches were dropped, which the target does not describe. |
| D-M2-36-6 |
2026-10-25 |
No change to the perf recipe: it already runs go test -p 1, and PERF_TIMEOUT (quality.perf.timeout) is the limit per test binary, which each new package keeps with its seeding plus Warmup + Window + Drain. On the build host the two new tests ran alone (go test -tags perf -run) through the machine-wide test slots instead of the whole make perf, because the host is shared by several build teams; the milestone-closing make perf on the reference host stays the owner operation of PLAN 4 item 3. |
Two load tests on one host measure each other; a whole make perf on a shared host slows every team and measures their load as well. |