Document the catalog overlay without the metadata.fabro namespace

The `[llm]` reference now describes `enabled`, `api_key_url`,
`stands_in_for`, `small_default`, `probe`, `family`, and the cutoffs as
lithos fields, the coding harness under `metadata.agent`, and the secret
names lithos derives for operator-defined providers. Secret-bearing
headers go in `default_headers` as `{{ secrets.NAME }}` tokens. The
integration guides enable a provider with `enabled = true` on its table.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Bryan Helmkamp 2026-09-09 23:26:48 -06:00
parent b82b3dd48e
commit 7ef09968f2
No known key found for this signature in database
10 changed files with 101 additions and 129 deletions

View file

@ -78,9 +78,9 @@ Claude Fable 5 is available as an explicit model but is not the default Anthropi
## Configuring providers and models
Fabro's catalog is the [lithos-llm](https://docs.rs/lithos-llm) built-in catalog with Fabro's policy layer applied. The `[llm]` table in settings is a third layer over both: a lithos catalog overlay that adds providers and models or changes existing entries. Later layers win. Tables merge key by key and every other value replaces. Models are nested under their provider, so two providers can expose the same model id without overwriting each other.
Fabro's catalog is the [lithos-llm](https://docs.rs/lithos-llm) built-in catalog. The `[llm]` table in settings is a second layer over it: a lithos catalog overlay that adds providers and models or changes existing entries. Later layers win. Tables merge key by key and every other value replaces. Models are nested under their provider, so two providers can expose the same model id without overwriting each other.
Provider and model facts use lithos field names: `adapter`, `codec`, `base_url`, `auth`, `limits`, `capabilities`, `pricing`. Fabro policy lives under `metadata.fabro` on the provider or model: credentials, agent profile, `enabled`, default roles, and display metadata. See [Settings Configuration](/reference/user-configuration#llm) for every key.
Provider and model facts use lithos field names: `adapter`, `codec`, `base_url`, `auth`, `enabled`, `limits`, `capabilities`, `pricing`, `small_default`, `probe`, `family`, and the cutoffs. The coding harness a model expects lives under `metadata.agent`, a namespace lithos ships and other agents such as Pebble read too. See [Settings Configuration](/reference/user-configuration#llm) for every key.
```toml title="settings.toml"
[llm.providers.proxy]
@ -92,14 +92,13 @@ auth = { type = "bearer" }
aliases = ["gateway"]
default_model = "team-code-large"
[llm.providers.proxy.metadata.fabro]
agent_profile = "anthropic"
credentials = ["env:ACME_GATEWAY_API_KEY", "vault:ACME_GATEWAY_API_KEY"]
[llm.providers.proxy.metadata.fabro.extra_headers]
[llm.providers.proxy.default_headers]
x-portkey-api-key = "{{ secrets.PORTKEY_API_KEY }}"
x-portkey-config = "@bedrock-prod"
[llm.providers.proxy.metadata.agent]
profile = "anthropic"
[llm.providers.proxy.models."team-code-large"]
display_name = "Team Code Large"
aliases = ["team-code"]
@ -108,21 +107,19 @@ limits = { context_tokens = 200000, max_output_tokens = 32000 }
capabilities = { text = true, tools = true, reasoning = true, caching = true, reasoning_effort = { low = true, medium = true, high = true } }
protocol_options = { reasoning_effort_levels = true }
pricing = { input_usd_micros_per_million = 1500000, output_usd_micros_per_million = 8000000, cached_input_usd_micros_per_million = 300000 }
[llm.providers.proxy.models."team-code-large".metadata.fabro]
family = "team-code"
small_default = true
estimated_output_tps = 80
```
The gateway's API key is `PROXY_API_KEY`: lithos derives the secret name from the provider id (upper case, `-` and `.` as `_`, then `_API_KEY`). Store it with `fabro secret set PROXY_API_KEY ...`.
For [LiteLLM](/integrations/litellm), Fabro ships a disabled provider entry. Enable it in settings and declare the models your proxy exposes:
```toml title="settings.toml"
[llm.providers.litellm]
base_url = "http://localhost:4000/v1"
default_model = "litellm-gpt-5"
[llm.providers.litellm.metadata.fabro]
enabled = true
[llm.providers.litellm.models."litellm-gpt-5"]
@ -138,15 +135,15 @@ capabilities = { text = true, tools = true }
A `provider/model` selector such as `openai/gpt-5.6-sol` pins the provider and names the model by id, alias, or wire id. A bare selector with no provider pin picks the highest-priority ready offering; a separate `provider = "openrouter"` pin selects the OpenRouter offering. Providers with `allow_passthrough = true` also accept `provider/model` selectors for models the catalog does not list.
</Note>
Model roles are separate: the provider's `default_model` controls normal model selection for workflow execution, while `small_default = true` under `metadata.fabro` marks the provider's small utility model for metadata tasks such as generated run titles. If a provider has no small default, Fabro falls back to that provider's default model.
Model roles are separate: the provider's `default_model` controls normal model selection for workflow execution, while `small_default = true` on a model row marks the provider's small utility model for metadata tasks such as generated run titles. If a provider has no small default, Fabro falls back to that provider's default model.
Provider auth has two parts. The lithos `auth` scheme says how a credential is sent: `{ type = "bearer" }`, `{ type = "header", name = "x-api-key" }`, `{ type = "headers" }` for providers that take several secret headers, `{ type = "none" }`, or `{ type = "aws" }`. Fabro's `metadata.fabro.credentials` says where the secret comes from, as ordered `env:<NAME>`, `vault:<NAME>`, or `aws_sigv4` refs; the first that resolves wins. Custom headers for any provider go in `metadata.fabro.extra_headers` as literal text or `{{ secrets.NAME }}` tokens. Put credentials in secrets and reference them with `{{ secrets.NAME }}` instead of a bare literal.
Provider auth has two parts. The lithos `auth` scheme says how a credential is sent: `{ type = "bearer" }`, `{ type = "header", name = "x-api-key" }`, `{ type = "headers" }` for providers that take several secret headers, `{ type = "none" }`, or `{ type = "aws" }`. lithos also says which secret names a provider reads: `OPENAI_API_KEY` for `openai`, `GEMINI_API_KEY` then `GOOGLE_API_KEY` for `gemini`, `MODAL_TOKEN_ID` and `MODAL_TOKEN_SECRET` for `modal`, and `<PROVIDER>_API_KEY` for a provider you define. Fabro looks each name up in the process environment first and the server vault second. Custom headers for any provider go in `default_headers` as literal text or `{{ secrets.NAME }}` tokens; put credentials in secrets and reference them with `{{ secrets.NAME }}` instead of a bare literal.
Workflow runs also add `x-session-id: <run-id>` to every LLM request so compatible gateways can group requests from the same run. An explicitly configured `x-session-id` in provider `extra_headers` takes precedence.
Workflow runs also add `x-session-id: <run-id>` to every LLM request so compatible gateways can group requests from the same run. An explicitly configured `x-session-id` in provider `default_headers` takes precedence.
Provider `metadata.fabro.agent_profile` defaults from `adapter` and controls profile-specific behavior such as which tools the agent registers, project-memory filenames, CLI/ACP command selection, and native session routing. Valid values are `anthropic`, `openai`, `gemini`, `kimi`, and `gpt56`; model-level values override provider-level values.
Provider `metadata.agent.profile` defaults from `adapter` and controls profile-specific behavior such as which tools the agent registers, project-memory filenames, CLI/ACP command selection, and native session routing. Valid values are `anthropic`, `claude-5`, `openai`, `gemini`, `kimi`, `gpt56`, and `gpt6`; model-level values override provider-level values.
Two profiles are selected per model rather than per provider, because they follow the model wherever it is served: `kimi` for Kimi models, and `gpt56` for the GPT-5.6 models (Sol, Terra, Luna). The `gpt56` profile uses Codex's narrow core surface — `shell_command`, `apply_patch`, and `update_plan`, plus optional credential-backed `web_search` — instead of fabro's dedicated file-read, discovery, and `web_fetch` tools. On OpenAI-compatible routes that cannot carry the freeform `apply_patch` grammar, it substitutes the JSON-schema `edit_file` tool. Session features may add their own question, skill, or subagent tools separately.
Three profiles are selected per model rather than per provider, because they follow the model wherever it is served: `claude-5` for Claude 5 models, `kimi` for Kimi models, and `gpt56` for the GPT-5.6 models (Sol, Terra, Luna); `gpt6` for GPT-6 Astra runs on the same harness as `gpt56`. The `gpt56` profile uses Codex's narrow core surface — `shell_command`, `apply_patch`, and `update_plan`, plus optional credential-backed `web_search` — instead of fabro's dedicated file-read, discovery, and `web_fetch` tools. On OpenAI-compatible routes that cannot carry the freeform `apply_patch` grammar, it substitutes the JSON-schema `edit_file` tool. Session features may add their own question, skill, or subagent tools separately.
Costs come from the lithos `pricing` table on each model row. Each token bucket (input, output, reasoning, cache read, cache write) prices at its own rate, with optional long-context and speed tiers. Providers that return an authoritative charge, such as OpenRouter, override the catalog estimate; the billing record says which source it came from.
@ -167,7 +164,7 @@ Fabro ships a built-in [Poolside](/integrations/poolside) provider for Laguna S
Fabro ships an [OpenRouter](/integrations/openrouter) provider definition with a curated model catalog, disabled by default. Enable it in settings and store an API key with `fabro provider login --provider openrouter`:
```toml title="settings.toml"
[llm.providers.openrouter.metadata.fabro]
[llm.providers.openrouter]
enabled = true
```
@ -178,8 +175,6 @@ Fabro ships a [Modal](/integrations/modal) provider definition for Kimi K3, disa
```toml title="settings.toml"
[llm.providers.modal]
base_url = "https://your-endpoint.modal.run/v1"
[llm.providers.modal.metadata.fabro]
enabled = true
```
@ -197,8 +192,6 @@ Fabro ships an [Amazon Bedrock](/integrations/bedrock) provider definition with
```toml title="settings.toml"
[llm.providers.bedrock]
base_url = "https://bedrock-runtime.us-east-1.amazonaws.com"
[llm.providers.bedrock.metadata.fabro]
enabled = true
```
@ -207,7 +200,7 @@ enabled = true
Fabro ships an Ollama provider definition that is disabled by default. Enable it in settings when you want Fabro to route through a local Ollama server:
```toml title="settings.toml"
[llm.providers.ollama.metadata.fabro]
[llm.providers.ollama]
enabled = true
```
@ -219,7 +212,7 @@ When no model or provider is specified, Fabro chooses the default offering on th
| Provider | Default model |
|---|---|
| `anthropic` | `claude-sonnet-4-6` |
| `anthropic` | `claude-sonnet-5` |
| `openai` | `gpt-5.6-sol` |
| `gemini` | `gemini-3.5-flash` |
| `moonshot` | `kimi-k3` |

View file

@ -28,8 +28,6 @@ _version = 1
[llm.providers.bedrock]
base_url = "https://bedrock-runtime.us-east-1.amazonaws.com"
[llm.providers.bedrock.metadata.fabro]
enabled = true
```
@ -39,7 +37,7 @@ The SigV4 signing region is derived from `base_url` — change it to your Region
Two auth modes, tried in order:
**Bedrock API key** (simplest): store the key and Fabro sends it as a bearer token. The key is read from either `AWS_BEARER_TOKEN_BEDROCK` (AWS's canonical name, also honored by the AWS SDKs and CLI) or `BEDROCK_API_KEY` (Fabro's `<PROVIDER>_API_KEY` convention) — use whichever you prefer.
**Bedrock API key** (simplest): store the key and Fabro sends it as a bearer token. The key is read from either `AWS_BEARER_TOKEN_BEDROCK` (AWS's canonical name, also honored by the AWS SDKs and CLI) or `BEDROCK_API_KEY` (the `<PROVIDER>_API_KEY` convention) — use whichever you prefer.
```bash
fabro secret set AWS_BEARER_TOKEN_BEDROCK bedrock-api-key-...
@ -49,22 +47,12 @@ fabro secret set BEDROCK_API_KEY bedrock-api-key-...
Runs read the bearer token from the vault only. Workers start from a cleared environment and the bearer token is not on the inherited allowlist, so exporting it in the server's shell has no effect on runs. `fabro exec` and direct `fabro-llm` SDK usage do read it from process env.
**AWS SigV4** (IAM-scoped): with no API key configured, Fabro signs each request using the AWS default credential chain — environment keys, shared profile, EC2/ECS instance roles, IRSA/web identity, SSO. Expiring session credentials refresh automatically. The catalog declares this as the `aws_sigv4` credential source:
**AWS SigV4** (IAM-scoped): with no API key configured, Fabro signs each request using the AWS default credential chain — environment keys, shared profile, EC2/ECS instance roles, IRSA/web identity, SSO. Expiring session credentials refresh automatically.
```toml
[llm.providers.bedrock.metadata.fabro]
credentials = ["env:AWS_BEARER_TOKEN_BEDROCK", "env:BEDROCK_API_KEY", "vault:AWS_BEARER_TOKEN_BEDROCK", "vault:BEDROCK_API_KEY", "aws_sigv4"]
```
The key resolves from the process environment first (either name), then the server vault (`fabro secret set`), then falls back to SigV4 — so on a server, prefer `secret set`. To select a non-default AWS profile for SigV4, set `AWS_PROFILE` (it, and the rest of the AWS credential-chain variables, are passed through to workflow workers).
The order is fixed by lithos-llm: `AWS_BEARER_TOKEN_BEDROCK`, then `BEDROCK_API_KEY`, then the AWS default chain. Each name is read from the process environment first and the server vault (`fabro secret set`) second — so on a server, prefer `secret set`. To select a non-default AWS profile for SigV4, set `AWS_PROFILE` (it, and the rest of the AWS credential-chain variables, are passed through to workflow workers).
<Warning>
**Bearer-vs-SigV4 precedence.** Because the bearer key is tried before SigV4, setting `AWS_BEARER_TOKEN_BEDROCK` makes the `bedrock` (Converse) provider authenticate with that key too — not just the `bedrock-openai` mantle provider below. If your key is valid only for mantle (it lacks `bedrock:InvokeModel*` on the runtime), every Converse model then fails with *"Authentication failed."* To run Converse models on SigV4 while using a mantle-only bearer key for GPT-5.x, pin the Converse provider to SigV4 explicitly:
```toml
[llm.providers.bedrock.metadata.fabro]
credentials = ["aws_sigv4"]
```
**Bearer-vs-SigV4 precedence.** Because the bearer key is tried before SigV4, setting `AWS_BEARER_TOKEN_BEDROCK` makes the `bedrock` (Converse) provider authenticate with that key too — not just the `bedrock-openai` mantle provider below. If your key is valid only for mantle (it lacks `bedrock:InvokeModel*` on the runtime), every Converse model then fails with *"Authentication failed."* Use a key that covers both surfaces, or keep Converse on SigV4 by leaving both Bedrock secret names unset and enabling only `bedrock`.
</Warning>
## Included models
@ -94,7 +82,7 @@ Not included on this provider: Claude Mythos 5 (Anthropic-Messages-only on `bedr
GPT-5.5 and GPT-5.4 on Bedrock are served only by the `bedrock-mantle` endpoint's OpenAI Responses API — a different surface than Converse. Fabro ships a companion `bedrock-openai` provider for them: the same AWS account and `AWS_BEARER_TOKEN_BEDROCK` key, pointed at the mantle endpoint over the OpenAI dialect.
```toml title="settings.toml"
[llm.providers.bedrock-openai.metadata.fabro]
[llm.providers.bedrock-openai]
enabled = true
# regional: change to https://bedrock-mantle.<region>.api.aws/openai/v1
```

View file

@ -17,7 +17,7 @@ Fabro runs execute through a Fabro server. Add the provider override to the sett
```toml title="settings.toml"
_version = 1
[llm.providers.fireworks.metadata.fabro]
[llm.providers.fireworks]
enabled = true
```

View file

@ -23,8 +23,6 @@ _version = 1
[llm.providers.litellm]
base_url = "http://localhost:4000/v1"
default_model = "litellm-gpt-5"
[llm.providers.litellm.metadata.fabro]
enabled = true
[llm.providers.litellm.models."litellm-gpt-5"]
@ -95,7 +93,7 @@ limits = { context_tokens = 64000, max_output_tokens = 4096 }
capabilities = { text = true, tools = true }
```
The provider's `default_model` names its default. You may also mark one small utility model with `small_default = true` under its `metadata.fabro` table; Fabro uses it for metadata tasks such as generated run titles and falls back to the provider default when it is omitted.
The provider's `default_model` names its default. You may also mark one small utility model with `small_default = true`; Fabro uses it for metadata tasks such as generated run titles and falls back to the provider default when it is omitted.
## Troubleshooting

View file

@ -46,8 +46,6 @@ _version = 1
[llm.providers.modal]
base_url = "https://your-endpoint.modal.run/v1"
[llm.providers.modal.metadata.fabro]
enabled = true
```
@ -122,8 +120,6 @@ For direct SDK use, enable Modal and set its endpoint URL in the `[llm]` overlay
```toml title="settings.toml"
[llm.providers.modal]
base_url = "https://your-endpoint.modal.run/v1"
[llm.providers.modal.metadata.fabro]
enabled = true
```

View file

@ -17,7 +17,7 @@ Fabro runs execute through a Fabro server. Add the provider override to the sett
```toml title="settings.toml"
_version = 1
[llm.providers.openrouter.metadata.fabro]
[llm.providers.openrouter]
enabled = true
```
@ -127,10 +127,10 @@ OpenRouter's [provider routing preferences](https://openrouter.ai/docs/guides/ro
## Attribution headers
Fabro does not send OpenRouter's optional attribution headers (`HTTP-Referer`, `X-Title`) by default, so self-hosted installations stay anonymous on OpenRouter's public app leaderboard. Workflow runs do send `x-session-id: <run-id>` for request grouping; an explicit provider `extra_headers` value for that header takes precedence. To opt in to attribution:
Fabro does not send OpenRouter's optional attribution headers (`HTTP-Referer`, `X-Title`) by default, so self-hosted installations stay anonymous on OpenRouter's public app leaderboard. Workflow runs do send `x-session-id: <run-id>` for request grouping; an explicit provider `default_headers` value for that header takes precedence. To opt in to attribution:
```toml title="settings.toml"
[llm.providers.openrouter.metadata.fabro.extra_headers]
[llm.providers.openrouter.default_headers]
"HTTP-Referer" = "https://your-site.example"
"X-Title" = "Your App"
```

View file

@ -97,7 +97,7 @@ For direct API or SDK requests, disable thinking through `provider_options.pools
Enable OpenRouter and configure its separate API key as described in the [OpenRouter integration](/integrations/openrouter):
```toml title="settings.toml"
[llm.providers.openrouter.metadata.fabro]
[llm.providers.openrouter]
enabled = true
```

View file

@ -192,7 +192,7 @@ pub trait AgentProfile: Send + Sync {
}
```
Profiles are built with `AgentProfileBuilder::new(kind, provider, model, catalog)`. The `AgentProfileKind` values are `anthropic`, `openai`, `gemini`, `kimi`, and `gpt56`; the catalog's `metadata.fabro.agent_profile` picks one per provider or model.
Profiles are built with `AgentProfileBuilder::new(kind, provider, model, catalog)`. The `AgentProfileKind` values are `anthropic`, `claude-5`, `openai`, `gemini`, `kimi`, `gpt56`, and `gpt6`; the catalog's `metadata.agent.profile` picks one per provider or model.
### Events

View file

@ -35,7 +35,7 @@ Files that omit `_version` are treated as version `1`. The legacy top-level `ver
|---|---|
| CLI-only | `[cli.target]`, `[cli.auth]`, `[cli.exec]`, `[cli.output]`, `[cli.updates]`, `[cli.logging]` |
| Server-side run policy | `[run.model]`, `[run.environment]`, `[environments.<slug>]`, `[run.checkpoint]`, `[run.inputs]`, `[run.prepare]`, `[run.pull_request]`, `[run.integrations.github]`, `[run.hooks]`, `[run.agent.mcps]` |
| Shared LLM catalog | `[llm]`, a lithos-llm catalog overlay: `[llm.providers.<id>]`, `[llm.providers.<id>.models.<id>]`, and Fabro policy under `metadata.fabro` |
| Shared LLM catalog | `[llm]`, a lithos-llm catalog overlay: `[llm.providers.<id>]`, `[llm.providers.<id>.models.<id>]`, and the agent harness under `metadata.agent` |
| 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.
@ -96,14 +96,13 @@ auth = { type = "bearer" }
aliases = ["gateway"]
default_model = "team-code-large"
[llm.providers.proxy.metadata.fabro]
agent_profile = "anthropic"
credentials = ["env:ACME_GATEWAY_API_KEY", "vault:ACME_GATEWAY_API_KEY"]
[llm.providers.proxy.metadata.fabro.extra_headers]
[llm.providers.proxy.default_headers]
x-portkey-api-key = "{{ secrets.PORTKEY_API_KEY }}"
x-portkey-config = "@bedrock-prod"
[llm.providers.proxy.metadata.agent]
profile = "anthropic"
[llm.providers.proxy.models."team-code-large"]
display_name = "Team Code Large"
aliases = ["team-code"]
@ -143,14 +142,14 @@ url = "https://fabro.example.com/api/v1"
## `[llm]`
The `[llm]` table is a [lithos-llm](https://docs.rs/lithos-llm) catalog
overlay. Fabro builds its model catalog from three layers: the lithos built-in
providers and models, Fabro's policy layer, and this table. Later layers win;
tables merge key by key and every other value replaces. Fabro does not
interpret the table itself. lithos validates it when the catalog is built, and
rejects unknown provider or model fields.
overlay. Fabro builds its model catalog from two layers: the lithos built-in
providers and models, and this table. Later layers win; tables merge key by
key and every other value replaces. Fabro does not interpret the table itself.
lithos validates it when the catalog is built, and rejects unknown provider or
model fields.
Fabro-specific policy lives under `metadata.fabro` on a provider or model.
lithos carries that namespace verbatim.
Several built-in providers ship with `enabled = false`. Turn one on by setting
`enabled = true` on its provider table.
```toml title="settings.toml"
[llm.providers.proxy]
@ -163,15 +162,13 @@ priority = 50
aliases = ["gateway"]
default_model = "team-code-large"
[llm.providers.proxy.metadata.fabro]
enabled = true
agent_profile = "anthropic"
credentials = ["env:ACME_GATEWAY_API_KEY", "vault:ACME_GATEWAY_API_KEY"]
[llm.providers.proxy.metadata.fabro.extra_headers]
[llm.providers.proxy.default_headers]
x-portkey-api-key = "{{ secrets.PORTKEY_API_KEY }}"
x-portkey-config = "@bedrock-prod"
[llm.providers.proxy.metadata.agent]
profile = "anthropic"
[llm.providers.proxy.models."team-code-large"]
display_name = "Team Code Large"
aliases = ["team-code"]
@ -180,13 +177,17 @@ limits = { context_tokens = 200000, max_output_tokens = 32000 }
capabilities = { text = true, tools = true, reasoning = true, caching = true, reasoning_effort = { low = true, medium = true, high = true } }
protocol_options = { reasoning_effort_levels = true }
pricing = { input_usd_micros_per_million = 1500000, output_usd_micros_per_million = 8000000, cached_input_usd_micros_per_million = 300000 }
[llm.providers.proxy.models."team-code-large".metadata.fabro]
family = "team-code"
small_default = true
estimated_output_tps = 80
```
A provider's API key is the secret lithos names for it: `OPENAI_API_KEY` for
`openai`, `MODAL_TOKEN_ID` and `MODAL_TOKEN_SECRET` for `modal`, and
`<PROVIDER>_API_KEY` (upper case, `-` and `.` as `_`) for a provider you
define, so the gateway above reads `PROXY_API_KEY`. Store it in the server
vault with `fabro secret set`, or export it for `fabro exec` and SDK use.
## `[llm.providers.<id>]`
Define or override an LLM provider. The keys are the lithos provider record.
@ -198,23 +199,24 @@ Define or override an LLM provider. The keys are the lithos provider record.
| `codec` | string | required for new providers | Wire codec: `anthropic-messages`, `openai-responses`, `openai-chat`, `gemini-generate`, or `bedrock-converse`. |
| `base_url` | string | required for new providers | Provider API base URL. The `openai-compatible` adapter appends `/v1/chat/completions` unless the URL already ends in a version segment. |
| `auth` | table | required for new providers | Auth scheme: `{ type = "bearer" }`, `{ type = "header", name = "x-api-key" }`, `{ type = "headers" }`, `{ type = "none" }`, or `{ type = "aws" }`. |
| `enabled` | boolean | `true` | Set `false` to hide a provider from Fabro. `bedrock`, `bedrock-openai`, `fireworks`, `litellm`, `modal`, `ollama`, and `openrouter` ship disabled. |
| `priority` | integer | `0` | Higher-priority ready providers win unqualified model and default selection. |
| `aliases` | array<string> | `[]` | Additional provider names accepted by model routing and fallback config. |
| `default_model` | string | None | The provider's default model id. |
| `allow_passthrough` | boolean | `false` | Whether `provider/model` selectors may name models the catalog does not list. |
| `default_headers` | table | `{}` | Literal headers attached to every request. Secret-bearing headers belong in `metadata.fabro.extra_headers`. |
| `api_key_url` | string | None | Where an operator obtains an API key. Shown by `fabro provider login` and the install flow. |
| `stands_in_for` | string | None | Another provider this one answers for when that provider has no credentials. `openai-codex` stands in for `openai`. |
| `default_headers` | table | `{}` | Headers attached to every request. A value may be literal text or a `{{ secrets.NAME }}` token resolved against the vault. |
## `[llm.providers.<id>.metadata.fabro]`
## `[llm.providers.<id>.metadata.agent]`
Fabro's provider policy. Every key is optional.
Which coding harness the provider's models expect. Pebble reads the same
namespace. Every key is optional; a model row overrides the provider.
| Key | Type / values | Default | Description |
|---|---|---|---|
| `enabled` | boolean | `true` | Set `false` to hide a provider from Fabro. Several built-in providers ship disabled. |
| `agent_profile` | `"anthropic"` \| `"openai"` \| `"gemini"` \| `"kimi"` \| `"gpt56"` | derived from `adapter` | Agent profile for models on this provider. |
| `api_key_url` | string | None | Where an operator obtains an API key. |
| `credentials` | array<string> | `[]` | Ordered credential refs: `vault:<NAME>`, `env:<NAME>`, or `aws_sigv4`. The first that resolves wins. |
| `extra_headers` | table | `{}` | Extra request headers. Values are literal text or `{{ secrets.NAME }}` interpolation strings resolved against the vault. |
| `profile` | `"anthropic"` \| `"claude-5"` \| `"openai"` \| `"gemini"` \| `"kimi"` \| `"gpt56"` \| `"gpt6"` | derived from `adapter` | Agent profile for models on this provider. |
| `reasoning_by_default` | boolean | reasoning models with effort levels: `true` | Whether requests reason when no `reasoning_effort` is supplied. |
## `[llm.providers.<provider>.models.<model-id>]`
@ -232,22 +234,18 @@ aliases. `api_model` is the string sent to the provider and defaults to the id.
| `capabilities` | table | unknown | Per-capability `true`, `false`, or `"unknown"`: `text`, `images`, `audio`, `documents`, `tools`, `reasoning`, `caching`, `cache_routing`, `sampling`, plus `tool_choice = { required, named }`, `response_format = { json_object, json_schema }`, `reasoning_effort = { minimal, low, medium, high, xhigh, max }`, and `speed = { fast, balanced, economical }`. |
| `protocol_options` | table | `{}` | Encoding flags: `reasoning_effort_levels`, `cache_breakpoints`, `system_turns`. |
| `pricing` | table | None | USD micros per million tokens: `input_usd_micros_per_million`, `output_usd_micros_per_million`, `cached_input_usd_micros_per_million`, `cache_write_usd_micros_per_million`, plus optional `long_context` and `speed` tiers. |
## `[llm.providers.<provider>.models.<model-id>.metadata.fabro]`
Fabro's model policy. Every key is optional.
| Key | Type / values | Default | Description |
|---|---|---|---|
| `enabled` | boolean | `true` | Set `false` to hide a model from Fabro. |
| `agent_profile` | profile name | provider profile | Agent profile override for this model. |
| `family` | string | model id | Family label for display and grouping. |
| `training` | string | None | Training data cutoff label. |
| `knowledge_cutoff` | string | None | Public knowledge cutoff label. |
| `training_cutoff` | string | None | Training data cutoff, as the provider states it. |
| `knowledge_cutoff` | string | None | Public knowledge cutoff label, as a person would write it. |
| `estimated_output_tps` | number | None | Estimated output tokens per second. |
| `small_default` | boolean | `false` | Preferred for small utility calls such as generated run titles. |
| `probe` | boolean | `false` | Preferred for provider connectivity probes. |
| `reasoning_by_default` | boolean | reasoning models with effort levels: `true` | Whether requests reason when no `reasoning_effort` is supplied. |
## `[llm.providers.<provider>.models.<model-id>.metadata.agent]`
The same keys as the provider-level `metadata.agent` table, applied to one
model. `profile` here is how a Kimi or GPT-5.6 model keeps its own harness on
a gateway whose other models use the provider default.
## `[cli.updates]`

View file

@ -217,14 +217,14 @@ fn render_manual_llm_catalog(output: &mut String) {
r#"## `[llm]`
The `[llm]` table is a [lithos-llm](https://docs.rs/lithos-llm) catalog
overlay. Fabro builds its model catalog from three layers: the lithos built-in
providers and models, Fabro's policy layer, and this table. Later layers win;
tables merge key by key and every other value replaces. Fabro does not
interpret the table itself. lithos validates it when the catalog is built, and
rejects unknown provider or model fields.
overlay. Fabro builds its model catalog from two layers: the lithos built-in
providers and models, and this table. Later layers win; tables merge key by
key and every other value replaces. Fabro does not interpret the table itself.
lithos validates it when the catalog is built, and rejects unknown provider or
model fields.
Fabro-specific policy lives under `metadata.fabro` on a provider or model.
lithos carries that namespace verbatim.
Several built-in providers ship with `enabled = false`. Turn one on by setting
`enabled = true` on its provider table.
```toml title="settings.toml"
[llm.providers.proxy]
@ -237,15 +237,13 @@ priority = 50
aliases = ["gateway"]
default_model = "team-code-large"
[llm.providers.proxy.metadata.fabro]
enabled = true
agent_profile = "anthropic"
credentials = ["env:ACME_GATEWAY_API_KEY", "vault:ACME_GATEWAY_API_KEY"]
[llm.providers.proxy.metadata.fabro.extra_headers]
[llm.providers.proxy.default_headers]
x-portkey-api-key = "{{ secrets.PORTKEY_API_KEY }}"
x-portkey-config = "@bedrock-prod"
[llm.providers.proxy.metadata.agent]
profile = "anthropic"
[llm.providers.proxy.models."team-code-large"]
display_name = "Team Code Large"
aliases = ["team-code"]
@ -254,13 +252,17 @@ limits = { context_tokens = 200000, max_output_tokens = 32000 }
capabilities = { text = true, tools = true, reasoning = true, caching = true, reasoning_effort = { low = true, medium = true, high = true } }
protocol_options = { reasoning_effort_levels = true }
pricing = { input_usd_micros_per_million = 1500000, output_usd_micros_per_million = 8000000, cached_input_usd_micros_per_million = 300000 }
[llm.providers.proxy.models."team-code-large".metadata.fabro]
family = "team-code"
small_default = true
estimated_output_tps = 80
```
A provider's API key is the secret lithos names for it: `OPENAI_API_KEY` for
`openai`, `MODAL_TOKEN_ID` and `MODAL_TOKEN_SECRET` for `modal`, and
`<PROVIDER>_API_KEY` (upper case, `-` and `.` as `_`) for a provider you
define, so the gateway above reads `PROXY_API_KEY`. Store it in the server
vault with `fabro secret set`, or export it for `fabro exec` and SDK use.
## `[llm.providers.<id>]`
Define or override an LLM provider. The keys are the lithos provider record.
@ -272,23 +274,24 @@ Define or override an LLM provider. The keys are the lithos provider record.
| `codec` | string | required for new providers | Wire codec: `anthropic-messages`, `openai-responses`, `openai-chat`, `gemini-generate`, or `bedrock-converse`. |
| `base_url` | string | required for new providers | Provider API base URL. The `openai-compatible` adapter appends `/v1/chat/completions` unless the URL already ends in a version segment. |
| `auth` | table | required for new providers | Auth scheme: `{ type = "bearer" }`, `{ type = "header", name = "x-api-key" }`, `{ type = "headers" }`, `{ type = "none" }`, or `{ type = "aws" }`. |
| `enabled` | boolean | `true` | Set `false` to hide a provider from Fabro. `bedrock`, `bedrock-openai`, `fireworks`, `litellm`, `modal`, `ollama`, and `openrouter` ship disabled. |
| `priority` | integer | `0` | Higher-priority ready providers win unqualified model and default selection. |
| `aliases` | array<string> | `[]` | Additional provider names accepted by model routing and fallback config. |
| `default_model` | string | None | The provider's default model id. |
| `allow_passthrough` | boolean | `false` | Whether `provider/model` selectors may name models the catalog does not list. |
| `default_headers` | table | `{}` | Literal headers attached to every request. Secret-bearing headers belong in `metadata.fabro.extra_headers`. |
| `api_key_url` | string | None | Where an operator obtains an API key. Shown by `fabro provider login` and the install flow. |
| `stands_in_for` | string | None | Another provider this one answers for when that provider has no credentials. `openai-codex` stands in for `openai`. |
| `default_headers` | table | `{}` | Headers attached to every request. A value may be literal text or a `{{ secrets.NAME }}` token resolved against the vault. |
## `[llm.providers.<id>.metadata.fabro]`
## `[llm.providers.<id>.metadata.agent]`
Fabro's provider policy. Every key is optional.
Which coding harness the provider's models expect. Pebble reads the same
namespace. Every key is optional; a model row overrides the provider.
| Key | Type / values | Default | Description |
|---|---|---|---|
| `enabled` | boolean | `true` | Set `false` to hide a provider from Fabro. Several built-in providers ship disabled. |
| `agent_profile` | `"anthropic"` \| `"openai"` \| `"gemini"` \| `"kimi"` \| `"gpt56"` | derived from `adapter` | Agent profile for models on this provider. |
| `api_key_url` | string | None | Where an operator obtains an API key. |
| `credentials` | array<string> | `[]` | Ordered credential refs: `vault:<NAME>`, `env:<NAME>`, or `aws_sigv4`. The first that resolves wins. |
| `extra_headers` | table | `{}` | Extra request headers. Values are literal text or `{{ secrets.NAME }}` interpolation strings resolved against the vault. |
| `profile` | `"anthropic"` \| `"claude-5"` \| `"openai"` \| `"gemini"` \| `"kimi"` \| `"gpt56"` \| `"gpt6"` | derived from `adapter` | Agent profile for models on this provider. |
| `reasoning_by_default` | boolean | reasoning models with effort levels: `true` | Whether requests reason when no `reasoning_effort` is supplied. |
## `[llm.providers.<provider>.models.<model-id>]`
@ -306,22 +309,18 @@ aliases. `api_model` is the string sent to the provider and defaults to the id.
| `capabilities` | table | unknown | Per-capability `true`, `false`, or `"unknown"`: `text`, `images`, `audio`, `documents`, `tools`, `reasoning`, `caching`, `cache_routing`, `sampling`, plus `tool_choice = { required, named }`, `response_format = { json_object, json_schema }`, `reasoning_effort = { minimal, low, medium, high, xhigh, max }`, and `speed = { fast, balanced, economical }`. |
| `protocol_options` | table | `{}` | Encoding flags: `reasoning_effort_levels`, `cache_breakpoints`, `system_turns`. |
| `pricing` | table | None | USD micros per million tokens: `input_usd_micros_per_million`, `output_usd_micros_per_million`, `cached_input_usd_micros_per_million`, `cache_write_usd_micros_per_million`, plus optional `long_context` and `speed` tiers. |
## `[llm.providers.<provider>.models.<model-id>.metadata.fabro]`
Fabro's model policy. Every key is optional.
| Key | Type / values | Default | Description |
|---|---|---|---|
| `enabled` | boolean | `true` | Set `false` to hide a model from Fabro. |
| `agent_profile` | profile name | provider profile | Agent profile override for this model. |
| `family` | string | model id | Family label for display and grouping. |
| `training` | string | None | Training data cutoff label. |
| `knowledge_cutoff` | string | None | Public knowledge cutoff label. |
| `training_cutoff` | string | None | Training data cutoff, as the provider states it. |
| `knowledge_cutoff` | string | None | Public knowledge cutoff label, as a person would write it. |
| `estimated_output_tps` | number | None | Estimated output tokens per second. |
| `small_default` | boolean | `false` | Preferred for small utility calls such as generated run titles. |
| `probe` | boolean | `false` | Preferred for provider connectivity probes. |
| `reasoning_by_default` | boolean | reasoning models with effort levels: `true` | Whether requests reason when no `reasoning_effort` is supplied. |
## `[llm.providers.<provider>.models.<model-id>.metadata.agent]`
The same keys as the provider-level `metadata.agent` table, applied to one
model. `profile` here is how a Kimi or GPT-5.6 model keeps its own harness on
a gateway whose other models use the provider default.
"#,
);