fabro/docs/public/reference/user-configuration.mdx
Bryan Helmkamp b9497517a5
feat(llm): support agent profile overrides (#291)
## Summary

Adds catalog-level `agent_profile` overrides so custom providers and
individual models can choose Anthropic, OpenAI, or Gemini agent behavior
independently from their adapter default. The effective precedence is
model override, then provider override, then adapter metadata.

## What Changed

- Added typed provider/model `agent_profile` settings in `fabro-config`
and `fabro-model`, with serde/strum support for `anthropic`, `openai`,
and `gemini`.
- Centralized effective profile resolution in the catalog, including
provider alias canonicalization and a guard against unrelated model
overrides leaking across providers.
- Updated run startup, API sessions, CLI/ACP backends, prompt
project-memory discovery, and standalone agent startup to use the
resolved catalog profile.
- Documented provider-level and model-level `agent_profile`
configuration in the public model and user configuration docs.

No OpenAPI or model-list response shape changes are included.

## Validation

- `cargo nextest run -p fabro-model -p fabro-config -p fabro-workflow -p
fabro-agent` passed: 1826 passed, 125 skipped.
- `cargo +nightly-2026-04-14 fmt --check --all` passed.
- `cargo +nightly-2026-04-14 clippy -p fabro-model -p fabro-config -p
fabro-workflow -p fabro-agent --all-targets -- -D warnings` passed.
- `git diff --check` passed.
- `cargo nextest list -p fabro-dev` confirmed there is no docs-options
reference test target to run.

## Post-Deploy Monitoring & Validation

Watch workflow and agent-session logs for provider/model resolution
errors, unexpected project-memory file selection, or CLI/ACP launch
command mismatches on custom catalog providers. Healthy signal: custom
provider/model runs start normally and use the intended profile-specific
behavior. Failure trigger: repeated `Provider ... is not configured`
errors, missing expected project memory, or profile-specific agent
startup failures after configuring `agent_profile`. Mitigation is to
remove the override from config or revert this PR. Validation window:
first deploy cycle after merge; owner: release/on-call engineer.

---

[![Compound
Engineering](https://img.shields.io/badge/Compound_Engineering-6366f1)](https://github.com/EveryInc/compound-engineering-plugin)
🤖 Generated with GPT-5 via [Codex](https://openai.com/codex)
2026-05-16 17:40:59 -04:00

463 lines
16 KiB
Text

---
title: "Settings Configuration"
description: "Configure CLI and shared machine defaults with settings.toml"
---
Fabro loads machine defaults from `~/.fabro/settings.toml`. The file is optional. If it does not exist, Fabro falls back to built-in defaults.
The CLI and server each read the sections relevant to their own process. On a remote deployment, the CLI machine and the server machine each have their own `settings.toml`.
<Note>
Fabro only reads `settings.toml`. Older `cli.toml`, `user.toml`, and `server.toml` filenames are no longer part of the supported config surface.
</Note>
## File location
The default path is `~/.fabro/settings.toml`.
Use `fabro server start --config /path/to/settings.toml` if the server should read a different file.
## Schema version
Every Fabro config file must declare its schema version with a top-level `_version` key:
```toml title="settings.toml"
_version = 1
```
Files that omit `_version` are treated as version `1`. The legacy top-level `version` key is no longer accepted and raises a targeted rename hint.
## Who reads what
`settings.toml` uses the same schema as `.fabro/project.toml` and `workflow.toml`, but each process only reads the fields it understands. The top-level schema is strictly namespaced — the only allowed domains are `[project]`, `[workflow]`, `[run]`, `[llm]`, `[cli]`, `[server]`, and `[features]`.
| Scope | Examples |
|---|---|
| CLI-only | `[cli.target]`, `[cli.auth]`, `[cli.exec]`, `[cli.output]`, `[cli.updates]`, `[cli.logging]` |
| Shared run defaults | `[run.model]`, `[run.sandbox]`, `[run.checkpoint]`, `[run.inputs]`, `[run.prepare]`, `[run.pull_request]`, `[run.integrations.github.permissions]`, `[run.hooks]`, `[run.agent.mcps]` |
| Shared LLM catalog | `[llm.providers.<id>]`, `[llm.models.<id>]`, model limits, features, controls, and costs |
| Server-only | `[server.listen]`, `[server.api]`, `[server.web]`, `[server.auth]`, `[server.storage]`, `[server.artifacts]`, `[server.slatedb]`, `[server.scheduler]`, `[server.logging]`, `[server.integrations]` |
`[cli.*]` and `[server.*]` stanzas are owner-specific: they are only consumed from `~/.fabro/settings.toml` (plus process-local flags and env overrides). The same stanzas in `.fabro/project.toml` or `workflow.toml` remain schema-valid but runtime-inert.
See [Server Configuration](/administration/server-configuration) for the server-owned sections.
## Precedence
Shared layered domains (`[project]`, `[workflow]`, `[run]`, `[features]`) use this override order:
1. **CLI flags** — always win
2. **Environment overrides** — Fabro-defined override channels
3. **`workflow.toml`** — per-workflow overrides
4. **`.fabro/project.toml`** — project defaults
5. **`~/.fabro/settings.toml`** — machine defaults
6. **Built-in defaults**
Owner-specific domains (`[cli.*]`, `[server.*]`) use a narrower trust boundary — only CLI flags, env overrides, `~/.fabro/settings.toml`, and built-in defaults apply.
## Full example
```toml title="settings.toml"
_version = 1
[cli.target]
type = "http"
url = "https://fabro.example.com/api/v1"
[cli.exec]
prevent_idle_sleep = true
[cli.exec.model]
provider = "anthropic"
name = "claude-opus-4-6"
[cli.exec.agent]
permissions = "read-write"
[cli.output]
format = "text"
verbosity = "normal"
[cli.updates]
check = true
[cli.logging]
level = "info"
[llm.providers.proxy]
display_name = "Acme Gateway"
adapter = "openai_compatible"
agent_profile = "openai"
base_url = "https://llm-gateway.example.com/v1"
credentials = ["env:ACME_GATEWAY_API_KEY"]
aliases = ["gateway"]
[llm.providers.proxy.extra_headers]
x-portkey-api-key = { env = "PORTKEY_API_KEY" }
x-portkey-config = { literal = "@bedrock-prod" }
[llm.models."team-code-large"]
provider = "proxy"
api_id = "provider-wire-model-name"
agent_profile = "anthropic"
display_name = "Team Code Large"
default = true
aliases = ["team-code"]
[llm.models."team-code-large".controls]
reasoning_effort = ["low", "medium", "high"]
speed = ["fast"]
[llm.models."team-code-large".costs]
input_cost_per_mtok = 1.50
output_cost_per_mtok = 8.00
[llm.models."team-code-large".costs.speed.fast]
input_cost_per_mtok = 3.00
output_cost_per_mtok = 16.00
[run.model]
name = "claude-sonnet-4-5"
[run.git.author]
name = "fabro-bot"
email = "fabro-bot@company.com"
[run.pull_request]
enabled = true
[run.integrations.github.permissions]
contents = "write"
pull_requests = "write"
[run.agent.mcps.filesystem]
type = "stdio"
command = ["npx", "-y", "@modelcontextprotocol/server-filesystem", "/workspace"]
startup_timeout = "15s"
tool_timeout = "90s"
[run.agent.mcps.filesystem.env]
NODE_ENV = "production"
[run.agent.mcps.sentry]
type = "http"
url = "https://mcp.sentry.dev/mcp"
[run.agent.mcps.sentry.headers]
Authorization = "Bearer sk-xxx"
```
All fields are optional. Include only the sections and keys you want to override. A single file can still include both CLI and server sections when you run both processes on one machine, but explicit remote targets do not read remote server state from the local machine.
{/* generated:options */}
## `[cli.target]`
Connection info for commands that target a remote Fabro server.
```toml title="settings.toml"
[cli.target]
type = "http"
url = "https://fabro.example.com/api/v1"
```
| Key | Type / values | Default | Description |
|---|---|---|---|
| `type` | `"http"` \| `"unix"` | None | Explicit transport selection. |
| `url` | string | None | Required for `type = "http"`; the API base URL. |
| `path` | string | None | Required for `type = "unix"`; the absolute Unix socket path. |
## `[llm.providers.<id>]`
Define or override an LLM provider. Provider IDs are strings, so custom
providers can be added when they use an adapter Fabro already supports.
```toml title="settings.toml"
[llm.providers.proxy]
display_name = "Acme Gateway"
adapter = "openai_compatible"
agent_profile = "openai"
base_url = "https://llm-gateway.example.com/v1"
credentials = ["env:ACME_GATEWAY_API_KEY"]
priority = 50
enabled = true
aliases = ["gateway"]
[llm.providers.proxy.extra_headers]
x-portkey-api-key = { env = "PORTKEY_API_KEY" }
x-portkey-config = { literal = "@bedrock-prod" }
x-team-secret = { credential = "gateway_team_secret" }
```
| Key | Type / values | Default | Description |
|---|---|---|---|
| `display_name` | string | provider ID | Human-readable provider name. |
| `adapter` | string | inferred for built-ins | Adapter registry key, such as `"anthropic"`, `"openai"`, `"gemini"`, or `"openai_compatible"`. Custom providers normally use `"openai_compatible"`. |
| `agent_profile` | `"anthropic"` \| `"openai"` \| `"gemini"` | adapter default | Agent profile used for project memory, CLI/ACP command selection, and native session routing. |
| `base_url` | string | adapter default | Provider API base URL. Required for most custom OpenAI-compatible providers. |
| `credentials` | array<string> | built-in env refs | Ordered credential refs. Accepted string forms are `credential:<id>` and `env:<NAME>`. Literal secret strings are rejected. |
| `extra_headers` | table | `{}` | Additional headers attached to provider requests. Values must be typed refs: `{ literal = "..." }`, `{ env = "NAME" }`, or `{ credential = "id" }`. |
| `priority` | integer | `0` | Higher-priority configured providers win default selection; ties use canonical provider ID. |
| `enabled` | boolean | `true` | Set `false` to disable a provider after lower-precedence layers define it. |
| `aliases` | array<string> | `[]` | Additional provider names accepted by model routing and fallback config. |
## `[llm.models.<id>]`
Define or override a model in the catalog. The table key is the canonical
model ID Fabro users reference; `api_id` is the model string sent to the
provider API.
```toml title="settings.toml"
[llm.models."team-code-large"]
provider = "proxy"
api_id = "provider-wire-model-name"
agent_profile = "anthropic"
display_name = "Team Code Large"
family = "team-code"
default = true
enabled = true
aliases = ["team-code"]
estimated_output_tps = 80
[llm.models."team-code-large".limits]
context_window = 200000
max_output = 32000
[llm.models."team-code-large".features]
tools = true
vision = false
reasoning = true
reasoning_effort = "levels"
prompt_cache = true
[llm.models."team-code-large".controls]
reasoning_effort = ["low", "medium", "high"]
speed = ["fast"]
[llm.models."team-code-large".costs]
input_cost_per_mtok = 1.50
output_cost_per_mtok = 8.00
cache_input_cost_per_mtok = 0.30
[llm.models."team-code-large".costs.speed.fast]
input_cost_per_mtok = 3.00
output_cost_per_mtok = 16.00
cache_input_cost_per_mtok = 0.60
```
| Key | Type / values | Default | Description |
|---|---|---|---|
| `provider` | string | None | Provider ID this model belongs to. |
| `api_id` | string | model ID | Identifier sent to the provider API. |
| `agent_profile` | `"anthropic"` \| `"openai"` \| `"gemini"` | provider profile | Agent profile override for this model. Model overrides take precedence over provider overrides. |
| `display_name` | string | model ID | Human-readable model name. |
| `family` | string | model ID | Family label used for catalog display and matching. |
| `training` | string | None | Training data cutoff label. |
| `knowledge_cutoff` | string or TOML date | None | Public knowledge cutoff label; TOML dates normalize to `YYYY-MM-DD`. |
| `default` | boolean | `false` | Whether this is the provider default model. |
| `enabled` | boolean | `true` | Set `false` to disable a model after lower-precedence layers define it. |
| `aliases` | array<string> | `[]` | Additional model names accepted by routing and fallback config. |
| `estimated_output_tps` | number | None | Estimated output tokens per second for catalog display and planning. |
## `[llm.models.<id>.limits]`
| Key | Type / values | Default | Description |
|---|---|---|---|
| `context_window` | integer | None | Maximum context window size in tokens. |
| `max_output` | integer | None | Maximum output tokens, if known. |
## `[llm.models.<id>.features]`
| Key | Type / values | Default | Description |
|---|---|---|---|
| `tools` | boolean | `false` | Whether the model supports tool calls. |
| `vision` | boolean | `false` | Whether the model accepts image inputs. |
| `reasoning` | boolean | `false` | Whether the model has reasoning behavior. |
| `reasoning_effort` | `"levels"` \| `"none"` | `"none"` | How Fabro may expose reasoning effort for this model. |
| `prompt_cache` | boolean | `false` | Whether prompt cache pricing/usage applies. |
## `[llm.models.<id>.controls]`
| Key | Type / values | Default | Description |
|---|---|---|---|
| `reasoning_effort` | array<string> | adapter defaults | User-facing reasoning effort values Fabro may send for this model. |
| `speed` | array<string> | `[]` | Additional speeds beyond implicit `standard`; do not list `standard`. |
## `[llm.models.<id>.costs]`
| Key | Type / values | Default | Description |
|---|---|---|---|
| `input_cost_per_mtok` | number | None | Input cost in USD per million tokens. |
| `output_cost_per_mtok` | number | None | Output cost in USD per million tokens. |
| `cache_input_cost_per_mtok` | number | None | Cached input/read cost in USD per million tokens. |
## `[llm.models.<id>.costs.speed.<speed>]`
Per-speed cost overrides use the same keys as `[llm.models.<id>.costs]`.
Each `<speed>` key must be declared in `[llm.models.<id>.controls].speed`.
The `standard` speed is implicit and always uses the base cost table.
## `[cli.updates]`
`[cli.updates]` — upgrade check toggle
```toml title="settings.toml"
[cli.updates]
check = true
```
| Key | Type / values | Default | Description |
|---|---|---|---|
| `check` | boolean | true | Check for new Fabro releases during supported CLI commands. |
## `[cli.output]`
`[cli.output]` — generic CLI output defaults
```toml title="settings.toml"
[cli.output]
format = "text"
verbosity = "verbose"
```
| Key | Type / values | Default | Description |
|---|---|---|---|
| `format` | "text" \| "json" | "text" | Output format for commands that support machine-readable output. |
| `verbosity` | "quiet" \| "normal" \| "verbose" | "normal" | Default output verbosity. |
## `[cli.exec]`
`[cli.exec]` — `fabro exec` defaults
```toml title="settings.toml"
[cli.exec]
prevent_idle_sleep = true
```
| Key | Type / values | Default | Description |
|---|---|---|---|
| `prevent_idle_sleep` | boolean | false | Prevent idle sleep on macOS while an exec run is in flight. |
## `[cli.exec.model]`
```toml title="settings.toml"
[cli.exec.model]
provider = "anthropic"
name = "claude-opus-4-6"
```
| Key | Type / values | Default | Description |
|---|---|---|---|
| `name` | string | None | Model name for `fabro exec`. |
| `provider` | string | None | LLM provider for `fabro exec`. |
## `[cli.exec.agent]`
```toml title="settings.toml"
[cli.exec.agent]
permissions = "read-write"
```
| Key | Type / values | Default | Description |
|---|---|---|---|
| `mcps` | table | None | Agent-scoped MCP entries for `fabro exec`. |
| `permissions` | "read-only" \| "read-write" \| "full" | "read-write" | Tool permission level for `fabro exec`. |
## `[run.model]`
`[run.model]` — provider-neutral default model selection
```toml title="settings.toml"
[run.model]
provider = "anthropic"
name = "claude-sonnet-4-5"
fallbacks = ["openai", "gpt-5.4"]
```
| Key | Type / values | Default | Description |
|---|---|---|---|
| `fallbacks` | array<string> | [] | Ordered list of fallback model references. Supports `...` splice marker<br />at layering time — see [`super::splice_array`]. |
| `name` | string | None | Model name for workflow runs. |
| `provider` | string | None | Provider name for workflow model selection. |
## `[cli.logging]`
`[cli.logging]` — process-owned logging configuration for the CLI
```toml title="settings.toml"
[cli.logging]
level = "info"
```
| Key | Type / values | Default | Description |
|---|---|---|---|
| `level` | "error" \| "warn" \| "info" \| "debug" \| "trace" | "info" | Default CLI log level. |
## `[run.git.author]`
```toml title="settings.toml"
[run.git.author]
name = "fabro-bot"
email = "fabro-bot@company.com"
```
| Key | Type / values | Default | Description |
|---|---|---|---|
| `email` | string | "fabro@local" | Git author email for checkpoint commits. |
| `name` | string | "fabro" | Git author name for checkpoint commits. |
## `[run.pull_request]`
`[run.pull_request]` — provider-neutral PR behavior
```toml title="settings.toml"
[run.pull_request]
enabled = true
```
| Key | Type / values | Default | Description |
|---|---|---|---|
| `auto_merge` | boolean | false | Enable GitHub auto-merge for created pull requests. Implies `draft =<br />false`. |
| `draft` | boolean | true | Open created pull requests as drafts. |
| `enabled` | boolean | false | Automatically create a PR after successful runs. |
| `merge_strategy` | "merge" \| "squash" \| "rebase" | "squash" | Merge method to configure for the pull request. |
## `[run.agent]`
`[run.agent]` — agent knobs only (permissions, MCPs)
```toml title="settings.toml"
[run.agent]
permissions = "read-write"
```
| Key | Type / values | Default | Description |
|---|---|---|---|
| `mcps` | table | None | Agent-scoped MCP server entries, keyed by name. |
| `permissions` | "read-only" \| "read-write" \| "full" | "read-write" | Default tool permission level for workflow agents. |
## `[run.agent.mcps.<name>]`
Configure MCP servers for workflow agents. For `fabro exec`-only MCPs, use `[cli.exec.agent.mcps.<name>]` with the same shape.
```toml title="settings.toml"
[run.agent.mcps.filesystem]
type = "stdio"
command = ["npx", "-y", "@modelcontextprotocol/server-filesystem", "/workspace"]
startup_timeout = "15s"
tool_timeout = "90s"
```
| Key | Type / values | Default | Description |
|---|---|---|---|
| `type` | `"stdio"` \| `"http"` \| `"sandbox"` | None | MCP transport type. |
| `command` | array<string> | None | Command and arguments for `stdio` or `sandbox` transports. |
| `script` | string | None | Shell script alternative to `command` for process-launching transports. |
| `url` | string | None | Remote MCP URL for `http` transport. |
| `port` | integer | None | Sandbox port for `sandbox` transport. |
| `env` | table | `{}` | Additional environment variables for process-launching transports. |
| `headers` | table | `{}` | HTTP headers for `http` transport. |
| `startup_timeout` | duration | `"10s"` | Max duration for startup and MCP handshake. |
| `tool_timeout` | duration | `"60s"` | Max duration for a single MCP tool call. |
See [MCP](/agents/mcp) for transport-specific examples.
{/* /generated:options */}