---
sidebar_position: 2
title: models
---

# `models`

## What it is

At deploy the platform has to decide two things: which model credentials to ask
you for, and whether a missing one should block the install. `models` is the
declaration those decisions are computed from.

Each block says **what kind** of model, **which node** it belongs to, **where**
the key is consumed, and **whether you supply it**. The deploy dialog and the
model disc on the agent's graph are generic renderings of this array — there is
no per-agent special case anywhere.

## Syntax

```js
spec: {
  models: [
    {
      kind: 'llm',                 // 'llm' | 'embedding'
      target: 'agent',             // 'agent' | 'sidecar'
      role: 'review',              // the graph NODE this block belongs to
      label: 'Review',             // the row heading in the deploy dialog
      byok: true,                  // true → a real picker; false → a read-only row
      options: ['claude', 'codex', 'gemini'],
      required: true,              // does a missing key block the deploy?
      note: 'Reads the diff and writes the review.',
    },
    {
      kind: 'llm', target: 'agent', role: 'triage', label: 'Triage',
      byok: false, required: true,
      followsVendorOf: 'review',                       // tracks another block's vendor
      fixedModelByVendor: {                            // …and shows what it then runs
        claude: 'haiku-4.5',
        codex:  'gpt-4o-mini',
        gemini: 'gemini-3-flash',
      },
      note: 'Fixed — a cheap fast-tier model decides how much review depth the change needs.',
    },
  ],
}
```

## Properties

| Name | Type | Required | Allowed values | Default | Update behavior |
|---|---|---|---|---|---|
| `kind` | string | No | `llm`, `embedding` | `llm` | Re-applied on the next deploy |
| `target` | string | No | `agent` (the run container), `sidecar` (a service reads it per request) | `agent` | Re-applied |
| `role` | string | No | a node id in this agent's graph | none | Re-applied |
| `label` | string | No | any | the vendor name | Card only |
| `byok` | boolean | No | `true`, `false` | `true` | Re-applied |
| `provider` | string | No | e.g. `openai` | none | Re-applied |
| `options` | string[] | No | vendor ids for `kind: 'llm'`; **model ids** for `kind: 'embedding'` | `[]` | Re-applied |
| `default` | string | No | one of `options` | none | Re-applied |
| `required` | boolean | No | `true`, `false` | `true` | Re-applied — **this is the deploy gate** |
| `followsVendorOf` | string | No | another block's `role` | none | Re-applied |
| `fixedModelByVendor` | object | No | `{ vendor: modelId }` | none | Re-applied |
| `note` | string | No | one honest sentence | none | Card only |

## The three decisions

**`target` — where the key is consumed.**
`agent` puts it in the run container's environment. `sidecar` routes it to a
service container that reads it **per request**, from the declaring agent's own
encrypted Env bag. One shared service, many isolated tenants.

**`byok` — what the dialog renders.**
`true` gives you a real picker that collects a key. `false` gives you an honest,
read-only line describing what actually runs. It is never a text box that does
nothing.

**`required` — whether a missing key blocks the install.**
`required: false` is how an agent says "I need no AI key". Use it for a block
that names an identity rather than adding a dependency — a deterministic node
that never invokes a model.

## Use cases

| You want | Declare |
|---|---|
| An ordinary bring-your-own-key agent | nothing — it is synthesised for you |
| An agent that runs no model at all | `models: []` |
| A different model per node, chosen by the operator | one block per model-running node, each with its own `role` |
| A cheap fixed tier behind an operator-chosen one | `byok: false` + `followsVendorOf` + `fixedModelByVendor` |
| An embedding model used by a knowledge-base service | `kind: 'embedding', target: 'sidecar', provider: 'openai', options: [<model ids>]` |
| A locked identity badge with no key | `byok: false, required: false` |

### Example — one block per model-running node

The shipped **Product Owner** agent runs three separate judgements, each on its
own node, each with its own picker:

```js
models: [
  { kind: 'llm', target: 'agent', role: 'plan',   label: 'Planner',
    byok: true, options: ['claude','codex','gemini'], required: true,
    note: 'Reads the PRD and the repository, then writes the tickets.' },
  { kind: 'llm', target: 'agent', role: 'review', label: 'Plan reviewer',
    byok: true, options: ['claude','codex','gemini'], required: true,
    note: 'The second opinion that fails a weak plan back to the planner.' },
  { kind: 'llm', target: 'agent', role: 'judge',  label: 'Acceptance judge',
    byok: true, options: ['claude','codex','gemini'], required: true,
    note: 'Judges a pull request against the ticket\'s acceptance criteria.' },
]
```

### Example — an embedding model consumed by a service

```js
models: [{
  kind: 'embedding',
  role: 'kb',
  target: 'sidecar',
  byok: true,
  provider: 'openai',
  options: ['text-embedding-3-small', 'text-embedding-3-large'],
  default: 'text-embedding-3-small',
  required: true,
}]
```

## Gotchas

**`options` means different things per `kind`.** For `kind: 'llm'` they are
vendor ids. For `kind: 'embedding'` they are model ids, and the picker offers
them directly.

**`role` must match a node id exactly.** A block whose `role` names no node
binds to nothing: the pinned tier stops applying, and the node renders a free
picker whose choice the node's own code then ignores. If your node ids live in
a constant, import it — do not retype the string.

**A composite agent collects its children's keys too.** If your agent dispatches
another agent, that child's model blocks are appended to yours automatically,
marked non-blocking and labelled with whose they are. You do not declare them.

**Probe a model id before you declare it.** An id that does not exist makes the
vendor's API reject the call, and the turn comes back empty rather than with an
error.

**Credentials are one per vendor.** The key in the "Codex · OpenAI" slot is the
same key OpenAI embeddings use. At run time the order is: a key supplied for
this agent, then the project's credential for that vendor, then (self-hosted
only) the box's own environment. Nobody should ever paste the same key twice.

## See also

- [Node declarations](./node-declarations.md) — a node's `agent:` pin is what
  *locks* a vendor; `models` describes it.
- [Sidecars](./sidecars.md) — `target: 'sidecar'` needs a service container.
- [Update behavior](./update-behavior.md)
