Skip to content

Connecting a model

This page: how the generated app runs an agent, and what the model actually receives. Full variable list: Configuration.

Two live modes, and one for CI

ModeHowModelUse for
No key (default)nothing to configurenone — the composer is inert and says sofirst run, screenshots, everything that is not the copilot
Livea provider key in .enva real provideractual agent work
MockMODEL_PROVIDER=mocka scripted LanguageModelV2e2e and CI, with no credentials

The mock is not a simulation of the pipeline — it is the pipeline, with the model replaced by a script. Host protocol, per-request catalog composition, client-tool suspension, oRPC execution and reconciliation are all the real ones.

Setting a key

bash
# .env — set one. There is no provider switch: which key you set IS the choice.
ANTHROPIC_API_KEY=sk-ant-...
# OPENROUTER_API_KEY=sk-or-...

Restart, and the picker in the composer fills with that provider's models. Setting both offers both.

To change which ids are offered:

bash
ANTHROPIC_MODELS=claude-sonnet-4-5,claude-haiku-4-5
DEFAULT_MODEL=anthropic/claude-sonnet-4-5    # must be one of the allowed ids

OpenRouter ids carry a gateway prefix

Mastra's router splits a model string on the first slash, so the vendor segment has to survive it. The app prefixes the gateway for you (openrouter/anthropic/claude-sonnet-4.5), which is also why OpenRouter's own meta-models need it twice: openrouter/openrouter/auto. Never strip a leading openrouter/. The model must support tool calling; one that does not fails with "No endpoints found that support tool use".

What the model actually gets

Per protocol step, the composed catalog is:

  • the governed domain:* tools your identity may see, scoped by route (app/agent/host/scope.ts), and
  • the view:* capabilities currently mounted in the tab —

each with a description, a JSON Schema input, an effect and a confirmation requirement. Live availability is not in that block: it rides in a compact system message after the conversation, so the tool definitions stay byte-identical across a turn and the provider's prompt cache survives.

Instructions live in server/mastra.ts. They improve planning and enforce nothing — delete every line and availability, schema surgery, approvals and server authorization are unchanged, because all four are runtime code.

Run limits are host code, not prompt text:

LimitValueWhere
Model steps per protocol request5RUN_LIMITS.maxStepsPerRequest (server/mastra.ts)
Inactivity between stream chunks45 sRUN_LIMITS.modelTimeoutMs
Protocol steps per turn8MAX_STEPS_PER_TURN (app/agent/host/transport-client.ts)
Turn deadline180 sTURN_DEADLINE_MS
Identical consecutive failures3LOOP_LIMITS.maxIdenticalFailures (app/agent/host/loop-guard.ts)
Any consecutive failures4LOOP_LIMITS.maxConsecutiveFailures

Try it

Restart, press ⌘J, and ask:

Which invoices are overdue, and by how much?

The copilot reads the ageing ladder on the server, narrows the table in your browser, reads the rows back, and answers from what it read. The table moves because the agent called the same setter the toolbar calls — it has no privileged channel into the UI, and the URL updates so the view it produced is one you can share.

Then ask it to issue a draft. It will not: a model-initiated issue-invoice comes back as an approval card, and nothing moves until you decide. That is ADR-0010 in one interaction.

When it does not work

You seeMeaning
MODEL_NOT_CONFIGUREDNo provider key is set, so there is no model to run
MODEL_TIMEOUTNo chunk for 45 s — usually an upstream stall
MODEL_ERRORThe provider rejected the request. A model without tool-calling support is the common cause
RUN_LIMIT_EXCEEDEDA limit above was hit; the message says which
NO_SUCH_TOOLThe model invented a tool name. Answered with retry: "no" — the catalog for that step is the complete set that exists
CATALOG_TOO_LARGEMore capabilities than the protocol's named limits allow. Scope the route

All of these are typed host frames, not exceptions; see Error codes.

Built on Agent Surface, oRPC Agent, Mastra and assistant-ui.