--- title: 'stackbone.models' description: 'Name a model key in your code and let an operator pick which model it runs on, without a redeploy.' position: 10 --- # `stackbone.models` > `stackbone.models.use(key)` returns the **model id** one named key runs on. Your > code names the key; an operator picks the model in Studio. A pick applies to the > next call, so changing model is a click and not a rebuild. ## Mental model A model id is a deployment decision, not a product decision. `anthropic/claude-haiku-4.5` exists on one provider and not on another, and the operator who configured the [model provider](/docs/home/features/gateway) is the person who knows which ids that provider serves. So your code declares **what the model is for** (a key such as `summariser` or `classifier`) and the operator decides **which model answers**. | Surface | Who decides the keys | Who picks the value | | --------------------------------------------------- | -------------------- | ------------------------------ | | `stackbone.models` | You, in your code. | An operator, in Studio. | | [`stackbone.prompts`](/docs/sdk/platform/prompts) | You, in your code. | An operator, in Studio. | | [`stackbone.settings`](/docs/sdk/platform/settings) | The platform. | An operator, in the dashboard. | It is the same split as the prompt catalogue, one level down: a prompt is the text a model reads, a model selection is which model reads it. Each pick belongs to the workflow or the deep agent whose code is running, so the same `use('chat')` line in two workflows reads two different picks. The surface is agent-local: it reads the agent's own Postgres over the same handle as [`stackbone.database`](/docs/sdk/data/database), and the runtime injects everything the read needs. ## Ask for the model a key runs on ```ts import { stackbone } from '@stackbone/sdk'; async function summarise(article: string) { 'use step'; const model = await stackbone.models.use('summariser', 'openai/gpt-4o-mini'); const completion = await stackbone.ai.chat.completions.create({ model, messages: [{ role: 'user', content: article }], }); if (completion.error) throw new Error(completion.error.code); return completion.data.choices[0]?.message?.content ?? ''; } ``` The second argument is your **code default**: the model this key runs on until somebody picks another one. It is optional. Leave it out when the agent is meant to run in several deployments whose providers serve different ids, and the box decides on its own. Two rules about the key itself: - **Write it as a plain string.** It starts with a lowercase letter and holds letters, digits, `_` and `-` (up to 128 characters), the same format as a prompt key. A key your code builds at run time still resolves, but the build cannot read it, so Studio has no row for it and nobody can pick a model for it. - **One key is one address.** Calling `use('chat')` from three places in the same workflow names one model three times, not three models. The value is read on **every** call and never cached, so a new pick applies to the next run with no rebuild and no deploy. ## The resolution order `use(key, defaultModelId?)` tries four things in order and stops at the first one that answers: | # | Source | Set by | | --- | ------------------------------------------------- | ----------------------------------------------- | | 1 | The model an operator picked for this key | Studio, on the entry's Models block. | | 2 | `defaultModelId`, the default you passed to `use` | You, in your code. | | 3 | The workspace's `defaultModel` | An operator, on the Workspace settings screen. | | 4 | Nothing resolves, so the call throws | Nobody. This is the `no_model_configured` case. | A [deep agent](/docs/sdk/agents/overview) resolves its own model the same way, under the key `model`, with the `model` written in `defineDeepAgent(...)` as step 2. A subagent resolves under `subagent_`, and falls back to the model the agent resolved when it names none of its own. ## When nothing resolves `use(...)` **throws**, unlike [`stackbone.prompts.use(...)`](/docs/sdk/platform/prompts#usekey-vars), which returns an empty string. A prompt that cannot be read degrades to no instruction. A model id has no safe empty value, and quietly running the code default after a failed read would run a model the operator deliberately moved away from. ```ts import { stackbone } from '@stackbone/sdk'; async function classify(text: string) { 'use step'; let model: string; try { model = await stackbone.models.use('classifier'); } catch (error) { // `no_model_configured`: no pick, no default in the call, no workspace default. if ((error as { code?: string }).code === 'no_model_configured') { throw new Error("Pick a model for `classifier` in Studio, on the entry's Models block."); } throw error; } const completion = await stackbone.ai.chat.completions.create({ model, messages: [{ role: 'user', content: text }], }); if (completion.error) throw new Error(completion.error.code); return completion.data.choices[0]?.message?.content ?? ''; } ``` To fix `no_model_configured`, do any one of these: 1. Pick a model for that key in Studio, on the entry's **Models** block. 2. Pass a default in the code: `stackbone.models.use('classifier', 'openai/gpt-4o-mini')`. 3. Set the workspace **Default model** under Settings › Workspace, which covers every key that resolves to nothing. The thrown value is a `ModelsError`, which the SDK exports, and it carries the same `code` / `message` / `meta` shape as every other error on these surfaces. Its `meta` names the owner and the key that had no model. ```ts import { ModelsError } from '@stackbone/sdk'; ``` ## Pick a model in Studio Open the agent or the workflow in Studio's catalogue. Next to its **Prompts** block sits a **Models** block, with one row per model key your code declares: | Row | Where the key comes from | | ----------------- | ----------------------------------------------------- | | `model` | The deep agent's own model. | | `subagent_` | One subagent's model, one row each. | | Your own key | Every `stackbone.models.use(key, …)` the build found. | Each row shows the default written in the code, the model currently picked, and where a reset lands. **Reset** deletes the pick, so the key falls back to the code default, or to the workspace default model when the code names none. A row says `no model configured` when neither side names a model, which is the state that throws at run time. Three things make a row read-only, and the block says which one applies: - The code passes a **built model instance** rather than an id. That instance carries its own provider and credential, so a picked id has nothing to override. - The agent was built with an **SDK older than model selections**, so it resolves its model from its code alone. Upgrade `@stackbone/sdk` and rebuild. - Your seat has no permission to write configuration. The block still shows what runs. Picking or resetting an agent's model rebuilds that agent, so the next turn runs on it. A turn already in flight finishes on the model it started with. A workflow needs no rebuild at all, because `use(...)` reads the pick on every call. A picked id is not checked against the provider's catalogue when it is saved: the provider may be unreachable while an operator edits. An id the provider does not serve fails the run with the provider's own message, and the box never silently falls back to another model. ## Errors `use(...)` throws instead of returning the `{ data, error }` envelope the other surfaces use. | Code | Means | | ------------------------- | ------------------------------------------------------------------------------------------------------- | | `no_model_configured` | No pick, no default passed to `use`, and no workspace `defaultModel`. Do one of the three things above. | | `models_unavailable` | The pick could not be read from the agent's database. | | `database_not_configured` | This agent has no Postgres, so there is nowhere to keep a pick. | | `settings_unavailable` | The workspace settings read failed while looking for `defaultModel`. | A database that predates model selections is not an error: it holds no pick, so the call falls through to your code default. ## What's next - **[Agents](/docs/sdk/agents/overview)**: `model` is optional on `defineDeepAgent`, and an agent and its subagents resolve through this same order. - **[`stackbone.ai`](/docs/sdk/platform/ai)**: the client you hand the resolved id to. - **[`stackbone.prompts`](/docs/sdk/platform/prompts)**: the same code-declares, operator-decides split, for the text instead of the model. - **[`stackbone.settings`](/docs/sdk/platform/settings)**: where `defaultModel`, the last fallback, is read from. - **[Gateway](/docs/home/features/gateway)**: the one endpoint every resolved id is called through.