Skip to content

feat(adapters): per-experiment endpoint routing - #45

Merged
gjtorikian merged 4 commits into
mainfrom
feat/agent-endpoint-overrides
Nov 10, 2026
Merged

gjtorikian merged 4 commits into
mainfrom
feat/agent-endpoint-overrides

Conversation

@gjtorikian

Copy link
Copy Markdown
Collaborator

Summary

  • An adapter's endpoint overrides are per sweep: every codex arm reads the same OPENAI_BASE_URL, so two codex arms could not reach two endpoints in one run. The benchmark now drives OpenAI's model and third-party models (DeepSeek, Kimi, Gemini) through Cloudflare AI Gateway's unified API, which is a different base URL and token than the provider-native route, so the route has to be per experiment.
  • agent.endpoint on AgentSpec names the host variables an experiment's key, base URL and headers come from (apiKeyEnv, baseUrlEnv, headersEnv), plus wireApi (responses | chat) for codex's custom provider. Unset fields keep the vendor defaults, so existing experiments are unchanged.
  • Naming variables rather than values keeps every existing guarantee: the key and headers variables must be secret-named (redacted like a key), the base URL variable must not be (its value is identity and joins the egress allowlist), and the preflight key probe follows the experiment's variables, so a bad gateway token still fails before any container starts. defineExperiment refuses anything else, including wireApi on a non-codex adapter.
  • Codex renders the names into its custom model provider (env_key, wire_api) and the login shim pipes the named key variable. Claude Code only reads the vendor names, so a shell shim re-exports custom ones under them inside the container; with vendor names the argv is byte-identical to before.
  • agent.provider now reaches the adapter identity instead of being hard-coded to the vendor, so a codex-driven deepseek/… arm is recorded as what it is.

An adapter's endpoint overrides are per sweep: every codex arm reads
the same OPENAI_BASE_URL, so two codex arms could not reach two
endpoints in one run. A benchmark that drives OpenAI's model and
third-party models through a gateway's unified API needs exactly that,
a different base URL and token than the provider-native route, per
experiment, without the adapter growing a vendor table.

`agent.endpoint` names the host variables an experiment's key, base
URL and headers come from; unset fields keep the vendor defaults.
Naming variables rather than values keeps every existing guarantee:
the key variable must be secret-named so it is redacted, the base URL
variable must not be so its value stays identity and joins the egress
allowlist, and the key probe follows the experiment's variables so a
bad gateway token still fails before any container. Codex renders the
names into its custom provider; Claude Code only reads the vendor
names, so a shell shim re-exports custom ones under them in the
container, and the default argv is untouched.
@greptile-apps

greptile-apps Bot commented Nov 10, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 4/5

[Medium impact] The PR changes provider routing for configured Codex and Claude Code experiments.

Fix Claude’s bearer-auth probe before merging so valid gateway tokens do not stop the run.

Findings

  1. P1 Valid gateway tokens get rejected ▶
Fix with agent prompt
### Issue 1
src/adapters/claude.ts:112-114
If a gateway requires bearer auth, Claude’s `keyProbe` rejects a valid custom key unless the user also puts an `Authorization` header in `headersEnv`. The probe sends the key only as `x-api-key`, while the new wrapper also sets `ANTHROPIC_AUTH_TOKEN`. A 401/403 from the probe then stops the whole run before Claude starts.

Make the probe send the same bearer credential as the custom-key wrapper, while keeping vendor-default behavior unchanged.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

Adds per-experiment endpoint routing through agent.endpoint.

  • Each experiment can route its agent through its own provider endpoint.
  • Run records include declared endpoints when comparison groups check agent identity.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A["agent.endpoint"] --> B["Resolve and check variable names"]
  B --> C["Read host values"]
  C --> D["Probe credentials"]
  C --> E["Record base URL and allow its host"]
  C --> F["Forward values into attempt container"]
  F --> G["Codex provider config"]
  F --> H["Claude environment wrapper"]
Loading

Reviews (4) · Last reviewed commit: "Merge branch 'main' into feat/agent-endp..." · Reviewed by Greptile

Comment thread src/runner/execute.ts Resolved
Comment thread src/experiments/types.ts Resolved
Comment thread src/adapters/claude.ts Resolved
Gateways differ on which credential header they read. A provider-native
Anthropic route takes `x-api-key`, which Claude Code sends from
ANTHROPIC_API_KEY; a gateway's unified API takes `Authorization:
Bearer`, which Claude Code only sends from ANTHROPIC_AUTH_TOKEN. The shim
that re-exports a custom key variable now sets both, so one experiment
works against either route without a second knob. The vendor route still
sees only the vendor variable.
Three gaps the review found in per-experiment endpoint routing:

The manifest recorded only adapter, provider, model, reasoning and CLI
version, and comparison groups diff those fields, so two arms that
differed only in wire or in the variables they read compared as one
configuration. The declared endpoint is now an agent identity component
and part of the group diff; an experiment without one records nothing
new and keeps its prior identity.

Key probes were deduplicated by adapter name, so of two codex arms on
different gateways only the first token was checked and a rejected
second token started containers. Probes now deduplicate by adapter plus
the host variables it reads.

A declared name could be another role's vendor variable — a key read
from ANTHROPIC_CUSTOM_HEADERS and the headers from ANTHROPIC_API_KEY —
which the Claude shim would read and then overwrite. Such names are
refused, as are two roles resolving to one name.
@gjtorikian
gjtorikian merged commit 59a3a8e into main Nov 10, 2026
13 checks passed
@gjtorikian
gjtorikian deleted the feat/agent-endpoint-overrides branch November 10, 2026 21:06
Comment thread src/adapters/claude.ts
Comment on lines +112 to 114
...parseHeaderLines(env[this.endpoint.headersEnv]),
"x-api-key": env[this.endpoint.apiKeyEnv] ?? "",
"anthropic-version": "2023-06-01",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Valid gateway tokens get rejected

If a gateway requires bearer auth, Claude’s keyProbe rejects a valid custom key unless the user also puts an Authorization header in headersEnv. The probe sends the key only as x-api-key, while the new wrapper also sets ANTHROPIC_AUTH_TOKEN. A 401/403 from the probe then stops the whole run before Claude starts.

Make the probe send the same bearer credential as the custom-key wrapper, while keeping vendor-default behavior unchanged.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/adapters/claude.ts
Line: 112-114

Comment:
**Valid gateway tokens get rejected**

If a gateway requires bearer auth, Claude’s `keyProbe` rejects a valid custom key unless the user also puts an `Authorization` header in `headersEnv`. The probe sends the key only as `x-api-key`, while the new wrapper also sets `ANTHROPIC_AUTH_TOKEN`. A 401/403 from the probe then stops the whole run before Claude starts.

Make the probe send the same bearer credential as the custom-key wrapper, while keeping vendor-default behavior unchanged.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant