# Models & providers (/brain/docs/reference/models)

Brain delegates model inference to the harness runner while keeping provider credentials and transport configuration under host control. When you create a session, you set its model and optional reasoning effort in `config`.

## Model ID format and validation [#model-id-format-and-validation]

Pass `config.model` in `provider/model` format (`1`–`256` characters) when calling `POST /sessions`:

```json
{
  "harness": "pi",
  "config": {
    "model": "openai/gpt-4.1-mini",
    "reasoning_effort": "low",
    "tools": []
  }
}
```

Model validation happens in two steps:

1. **Session creation (`POST /sessions`):** Brain splits `config.model` on the first `/`, verifies that the `provider` prefix exists in your host config's `providers` map, and checks that the configured `api_key_env` environment variable is non-empty. Missing slashes, unknown providers, or missing credentials fail immediately with `400 invalid_request`.
2. **Turn execution (Pi runner startup):** When the first `user.message` starts the Pi runner, the runner looks up `provider` and `model` in its pinned offline [Model catalog](/brain/docs/reference/model-catalog) (`allowModelNetwork: false`, `refreshOnCreate: false`). If the model ID is not in the catalog or the harness falls back to a different model, the turn fails with a `session.error` (`brain_error`) event.

<Callout type="info" title="Explicit credentials only">
Brain authenticates providers with API keys. It passes the resolved key to the runner over stdio in an in-memory credential store (`InMemoryCredentialStore`) and strips ambient host environment variables when spawning child processes.
</Callout>

## Configure providers [#configure-providers]

Declare each provider prefix in the `providers` map of your host configuration file (`brain.json`):

```json
{
  "providers": {
    "openai": { "api_key_env": "OPENAI_API_KEY" },
    "anthropic": { "api_key_env": "ANTHROPIC_API_KEY" }
  }
}
```

Every provider entry requires `api_key_env`, and that environment variable must be set and non-empty when `brain` starts (or when running `brain -check`). If a provider's API key changes in the host environment while Brain is running, Brain recycles any idle runner using the old credential before the next turn.

## Reasoning effort [#reasoning-effort]

Set `config.reasoning_effort` when creating a session to control extended thinking or reasoning tokens:

| Value | Description |
| --- | --- |
| `"off"` | Default when `reasoning_effort` is omitted or `""`. Disables extended reasoning. |
| `"minimal"`, `"low"`, `"medium"`, `"high"`, `"xhigh"`, `"max"` | Requests the corresponding thinking level from the Pi harness. |

- Any value outside this set is rejected at session creation with `400 invalid_request`.
- When the Pi runner opens the session, it checks `session.thinkingLevel === options.config.reasoning_effort`. If the selected model does not support the requested reasoning effort, the runner fails the turn explicitly with `session.error` (`"requested reasoning effort is unsupported by the model"`) rather than silently downgrading it.

## Custom endpoints and proxies [#custom-endpoints-and-proxies]

Each entry in `providers` accepts optional `base_url` and `api` fields to route a catalog provider through an HTTP(S) proxy or OpenAI-compatible gateway:

```json
{
  "providers": {
    "openai": {
      "api_key_env": "OPENAI_API_KEY",
      "base_url": "https://my-proxy.example.com/v1",
      "api": "openai-completions"
    }
  }
}
```

- **`base_url`:** Must be a valid `http` or `https` URL with a host and no embedded user credentials.
- **`api`:** Overrides the transport protocol used by the Pi runtime for that provider (for example, `"openai-completions"` or `"openai-responses"`).
- **Catalog scope:** `base_url` and `api` override transport settings for models that already exist under that provider in the [Model catalog](/brain/docs/reference/model-catalog); they do not register arbitrary new model IDs.

## Prompt caching [#prompt-caching]

Brain and the Pi runner are structured to keep provider prompt prefixes stable across turns, evictions, and checkpoint restores:

- **Stable prompt prefixes:** The system prompt (`config.instructions`) and tool definitions (`config.tools`) stay constant while a turn runs so provider prompt prefixes remain cacheable across model calls.
- **Clean system prompts:** The Pi runner overrides `before_agent_start` so Pi uses your exact `config.instructions` (defaulting to `"You are a helpful assistant. Use the available tools when needed."`) without appending the runner's private local working directory path, which would invalidate prompt caches when a session moves to another server.
- **Native state restoration:** Reloading an evicted session or restoring an external checkpoint opens Pi's native `.jsonl` session file (`SessionManager.open`) instead of rebuilding prompts from flattened event text.
- **No background cache warming:** Upstream background cache-warming timers are disabled (`cacheWarming: "off"`) so idle sessions never make background LLM calls or incur unexpected token charges.
