Skip to content

feat(v1): gate model requests with client.request_gate, and vf-gate - #2969

Closed
RGaonkar26 wants to merge 1 commit into
feat/request-gate-daemonfrom
feat/request-gate
Closed

RGaonkar26 wants to merge 1 commit into
feat/request-gate-daemonfrom
feat/request-gate

Conversation

@RGaonkar26

@RGaonkar26 RGaonkar26 commented Oct 23, 2026 •

Copy link
Copy Markdown

Adds request_gate to client configs. When it is set, every model request through that endpoint waits for a slot of the host-wide gate from #2968. Also adds a vf-gate CLI to inspect and resize gates live.

Why
Several runs or many workers on one host can push a provider past its per-key concurrency limit (for example Prime Inference's 429 Too many concurrent requests for this model (limit: 32)). Main has no way to cap requests per model across processes, or to change a cap during a run.

Change

  • BaseClientConfig.request_gate: RequestGateConfig | None = None. Because it is on the base config, it applies to [client], per-agent clients and judges, and to both eval and train clients.
  • When it is set, build_async_httpx returns a GatedAsyncClient. A request whose JSON body names a model takes a permit before it is sent, and returns it once the response has been read. A stream holds its permit until it is closed or garbage-collected. Requests that name no model pass straight through. A process's first use of a gate, which may start the daemon, runs in a thread.
  • Gate identity is a hash of the endpoint origin, the credential and the model. Anthropic's bare key and OpenAI's Bearer key share one gate.
  • vf-gate (a new script) shows a table of model, cap, in flight, waiting, admitted and wait. It also takes --resize N --model M, --dir and --output json. A resize with no live gate exits non-zero.
  • RequestGateConfig is exported from verifiers.v1.clients. There is a short section in docs/evaluation.md.

Behaviour

  • Off by default. With request_gate unset, build_async_httpx returns the same plain httpx.AsyncClient as before, and verifiers.v1.request_gate is not imported.
  • An unset gate is left out of dumps, so saved configs and traces still load in older verifiers.
  • With a gate, a provider 429 still reaches the harness as it does today (feat(v1): adapt the request gate's limit to 429/529 #2970 adds absorption). clients/eval.py is unchanged.
  • Prime Inference requires X-Prime-Team-ID. Verifiers' clients add it for the Prime Inference host; a gated client whose base_url points elsewhere at the same gateway must send it itself.

Tests

  • Unit: 13 tests in tests/unit/request_gate/test_request_gate.py, against a local fake provider that answers 429 above its limit. They cover one cap shared by 4 processes, a live resize reaching running processes, vf-gate, streams, and a GC-collected stream not deadlocking. Unit suite: 253 passed, 18 skipped.
  • Live (Prime Inference, 2026-10-23):
    • Gate unset: identical to main (plain client, no daemon, gate module not imported, nothing added to the config dump); 8/8 requests succeeded.
    • vf-gate --resize 6 mid-run from cap 2: in flight stayed at 2 or below before the resize and reached 6 after it; 48/48 requests succeeded.
    • A gated agent run (4 rollouts × 3 turns, cap 2) peaked at 2 upstream requests.
  • Not tested live: real gateway 429s (deliberately not provoked on a shared gateway).

Notes

🤖 Generated with Claude Code

Model clients now use the host request gate when their endpoint config sets
`request_gate`. With it unset (the default), `build_async_httpx` returns the
same plain `httpx.AsyncClient` as before and the gate module is never
imported.

- `BaseClientConfig.request_gate: RequestGateConfig | None = None`, so
  `[client.request_gate]`, per-agent `env.<agent>.client.request_gate` and
  judge clients all accept it (eval and train clients alike). An unset gate
  is left out of dumps, so saved configs and traces stay loadable by
  verifiers versions without the field (`extra="forbid"`).
- `build_async_httpx` returns a `GatedAsyncClient` when it is set (from
  `verifiers.v1.request_gate`, imported only then). Every request that names
  a model takes a permit of the gate for its (endpoint
  origin, API key, model) before it is sent and returns it when the response
  is read; a streamed response holds its permit until it is closed or
  collected. Requests that name no model pass straight through. The first
  use of a gate (which may start the daemon) runs off the event loop.
- The gate reads a request's model from its JSON body, for the eval client,
  the train client and judges alike; the clients themselves are unchanged.
- `vf-gate` shows the host's gates (cap, in flight, waiting, admitted, mean
  wait) and `vf-gate --resize N [--model M]` moves the cap of live gates for
  every process using them. `--dir` picks a gate directory, `--output json`.
- A short section in docs/evaluation.md.

Ported from Parker Pettit's verifiers `feat/pipelines`: the client hookup and
`GatedAsyncClient` of 627154b ("Gate concurrent model requests per endpoint,
key and model across the host"), finalizer-safe stream releases from
9af804e, one gate across auth dialects from dad67e0, and the
daemon-backed client of #2946 (c92797b). Operators there
resize through `flow gate resize` and prime-rl's `eval control
resize-requests` (67661d6); `vf-gate` is the verifiers-native equivalent.

Differences from #2946:
- No 429/529 absorption or adaptive limit yet (next branch): a provider 429
  reaches the caller as it does today, and the harness SDK retries it.
- No `ProviderRetryConfig` (96a2e20) and no interception-server changes.
- The first `model_gate` of a process is built in a thread, not on the loop.
- No `MODEL_EXTENSION`: the eval client does not name the model on the
  request, so `clients/eval.py` is untouched (and #2675, which replaces its
  `_request` with the SDK transport, merges cleanly). Parsing the body costs
  well under a millisecond against a model call.
- `vf-gate` is new; `RequestGateConfig` is exported from `verifiers.v1.clients`
  only, not the top-level namespace.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@RGaonkar26

Copy link
Copy Markdown
Author

Closing in favour of Parker's #2946, which is the same request-gate / model-pool work and where it should land. Nothing on verifiers main or our pipeline needs it right now. The branch stays up: it has shared-host hardening (private 0700 dir, peer-uid checks, single-daemon lock, restart window), live tests against the Prime Inference gateway, and the pool poll backoff, all of which apply to #2946 and can be cherry-picked.

@RGaonkar26 RGaonkar26 closed this Oct 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant