---
name: models
description: Use when choosing or changing the model for a canvas, workflow step, or agent — or when the user asks which models Tela supports, which providers are available, or wants to compare models on cost or quality.
---

# Models

## List the catalog, do not answer from memory

The set of models a workspace can use changes, and it is not the same set for every surface. Read it:

```bash
bun --preload ~/.claude/skills/tela-studio/preload.ts -e "
console.log(tela.formatModelCatalog(await tela.listProviders()))
"
```

```bash
bun --preload ~/.claude/skills/tela-studio/preload.ts -e "
console.log((await tela.listProviders({ surface: 'agent' })).map(p => p.provider).join(', '))
"
```

## Two catalogs that do not mix

| | Used by | Id shape |
|---|---|---|
| `llmModels` | canvas versions, workflow `llm-completion` steps | bare — `gpt-5`, `gemini-2.5-flash` |
| `agentModels` | agents | namespaced — `openai/gpt-5.4`, `google/gemini-3-flash-preview` |

The provider sets differ too: the agent catalog carries providers the canvas catalog does not.

So "which providers does Tela support?" has **two answers**, and giving the agent list while the user
is working on a canvas sends them after a model they cannot use. Check before writing an id:

```bash
bun --preload ~/.claude/skills/tela-studio/preload.ts -e "
const { model, availableOnOtherSurface } = await tela.resolveModel('openai/gpt-5.4')
console.log(model ? 'ok' : availableOnOtherSurface
  ? 'that id exists, but only for agents — a canvas cannot use it'
  : 'no such model')
"
```

## Defaults, and when to leave them

`gpt-5` at `temperature: 0` is the default for new work — a sane starting point, not a conclusion.

Leave it when the user asks about cost, speed, or alternatives. That is a request to look at the
catalog, and answering "use gpt-5" to "is there something cheaper?" is not an answer.

## Comparing models

Changing the model is a **two-axis** question: cost and quality. Measuring only cost is the usual
failure, because cost is easy and quality is not.

Never swap the model on a promoted version to try it. Fork a candidate, run the same suite on both,
review the attributes on both, then compare. The full procedure — and the readiness gate that stops
you shipping an unreviewed version — is in `measure.md`.

Two things worth saying to the user when they ask about cost:

- **Cost comes from the usage service**, via `getUsageCost` and `getUsageCostByModel`. The
  `creditsUsed` and `usage.cost` fields on a completion run are execution-time artifacts, not
  reconciled billing — never sum them and call it spend.
- Usage is attributed to the canvas, not the version, so a per-model comparison is done by grouping
  usage events by `model`, not by asking for a version's cost.
- **Usage replicates about a minute late.** After running a candidate, wait before reading its cost —
  `waitForUsageEvents` does that — and never read an empty result as a cheap model.
- **Do not reconstruct the credit formula** to estimate what another model would cost — run it and
  read the usage service. A projection from provider list prices is fine as a rough estimate, as long
  as you say that is what it is.

## Changing the model on a canvas

The model lives in the version's `configuration`, which **replaces** rather than merges — send it
whole or you will wipe `structuredOutput`:

```bash
bun --preload ~/.claude/skills/tela-studio/preload.ts -e "
const { version } = await tela.getCanvas('CANVAS_ID')
const candidate = await tela.createCanvasVersion('CANVAS_ID', {
  title: 'candidate: gemini-2.5-flash',
  configuration: { ...version.configuration, model: 'gemini-2.5-flash' },
  variables: version.variables,
  message: version.message,
})
console.log(candidate.id)
"
```

For an agent, use `updateAgentModel(agentId, model)` with an id from the **agent** catalog.
