Skip to main content
The built-in agent uses an AI SDK model instance your config constructs and can name a separate model for visual judgments. Custom executors own their model calls and may run without a model. The runner has no default model, no gateway of its own, and reads no model or key variable. The line in e2e.config.ts that builds the instance decides who serves it and how it authenticates.

Pick a gateway or a provider

A gateway reaches every vendor’s models with one key. A provider package calls one vendor directly. e2e init writes the one you chose.
e2e.config.ts
gateway() ships with ai, so no extra package. It reads AI_GATEWAY_API_KEY.
The model id is whatever the package accepts. Gateways take vendor/model; a direct provider takes the vendor’s bare id; a local server takes the name it serves the model under.

Where the key comes from

The provider package reads its key, not the runner. Every AI SDK provider accepts apiKey at construction and otherwise reads its own environment variable on the first request.
Three consequences:
  • The key is read late. Constructing gateway('...') needs no variable. Deterministic suites, e2e list, and config loading all work without a key. Only a step that calls a model needs the provider’s key.
  • There is no E2E_* key variable. The runner never sees the key, so it has nothing to name. Set the variable the package you imported documents.
  • The runner never logs it. The key lives in your config module and the provider’s request headers. It does not enter the report, the trace, or automatically inherited environment of the app process. Explicitly putting a key in command.env forwards it to that process.

Several models in one config

Each model slot is its own instance, so they can come from different packages:
e2e.config.ts
With no configured executor, model drives agent.act and the judgments, pixels included: the act loop shows it screenshots, so pick one that accepts images and grounds a point in them well. The provider needs its key when the model is first used. An agents.<name>.model that names a different model than the one passed to createAgent({ model }) is INVALID_CONFIG. A configured executor, including createAgent(...), handles agent.act and agent.assert through runStep. That assertion path rejects vision and screenshot options. An executor may implement both methods without a model. agent.waitFor and agent.extract still use the built-in judgment tier and its model routing; see Custom executors. To switch models per run without editing the config, read a variable of your own:
Agent options fragment

Models that refuse forced tool calls

The built-in agent.act loop asks the model for a tool call on every turn. Some models reject that request shape. Anthropic’s Claude Fable 5.1 answers HTTP 400 to any forced tool choice. The runner recognizes the refusal, retries with the choice left to the model and a tools-only rule in its instructions, and keeps that mode for the rest of the process. The refused request does not count as a model call.

What fails, and where

A missing model is checked once per run, so a suite without one reports one run-level error rather than one blocked step per test. A missing key is not checked ahead of time: the runner does not know which variable to look for, so the provider reports it on the first call.

Agents and personas

Several agents in one suite.

Config reference

The model slot and every other key.