You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Scoped audit of the providers topic, run by the nightly rotation. Last audited 2026-08-11.
Pages audited: providers/api-key-providers.mdx, providers/subscription-based-providers.mdx, providers/local-models.mdx, providers/custom-providers.mdx.
Platform window: the current provider code on mnfst/manifest main at 061d351f5, plus every merged PR of the last 30 days touching provider connect, model discovery, subscriptions, local runtimes or custom endpoints.
Docs read at bf0ccb2 (repo HEAD is e721746, which adds three backlog lines and no page content). Coherence check GREEN, 48 pages, 58 vars.
Two docs PRs are already open on these pages and are not re-raised here: #91 adds Google Vertex AI to the api-key page, #90 rewrites the custom-provider cost section.
You open Providers, then Subscriptions, and click Connect on Anthropic.
The panel shows a Sign in with Claude button. There is no terminal command anywhere on it.
You sign in, and Anthropic's redirect page shows a code.
You copy the whole string and paste it into the panel.
Paste a token from the command line instead and the panel answers: "That doesn't look like an authorization code. Make sure you copied the full string from the redirect page."
Options:
Rewrite the tab around the browser sign-in, and set the row's Auth flow to OAuth (paste code). Recommended (the flow matches none of the labels the table already uses, so it gets its own).
Rewrite the tab around the browser sign-in, and reuse the existing OAuth (browser) label for the row.
Rewrite the tab only, and leave the row saying Subscription token.
Suggested wording (the tab body is the same under all three options; only the row label differs):
<Tabtitle="Claude">
Click **Sign in with Claude** in the connect panel, then sign in to your
Claude Pro or Max account. Anthropic's redirect page shows a code. Copy the
whole string and paste it back into the panel. The panel refuses a token
generated from a terminal: it wants that full code string. Requests carry
the `anthropic-beta: oauth-2025-04-20` header so Anthropic recognizes the
subscription token.
</Tab>
The draft drops the tab's closing sentence about available models matching your plan. It is not true: a Claude subscription connection is served from a fixed list Manifest carries, not from a live read of your plan.
Doc: providers/subscription-based-providers.mdx:67-68 — "Generate a token with the Claude CLI (claude setup-token) and paste it into Manifest."
Doc: providers/subscription-based-providers.mdx:69-70 — "Available models match what your plan grants in claude.ai."
Code: packages/frontend/src/services/providers.ts:157-164 sets subscriptionAuthMode: 'popup_paste' and supplies no subscriptionCommand. buildProviderDef (:485-501) copies only identity fields from the shared registry and spreads the overlay, so the terminal block gated on provDef.subscriptionCommand (ProviderDetailView.tsx:349-374) never renders.
Code: the shared config still carries subscriptionAuthMode: 'token' and subscriptionCommand: 'claude setup-token' (packages/shared/src/subscription/configs.ts:9-12). Three agents grepped for a reader of getSubscriptionProviderConfig outside packages/shared and tests, and found none.
Render site: packages/frontend/src/components/AnthropicOAuthDetailView.tsx:213-267 — "Sign in with your Claude Pro or Max account…", button Sign in with Claude, divider "Paste the authorization code", "After signing in, Anthropic's redirect page shows a code. Copy the full string and paste it below."
Decisive: AnthropicOAuthDetailView.tsx:102-118 rejects any input without a #, with the quoted message. A sk-ant-oat… token has no #.
The dropped clause: Anthropic subscription discovery is always the curated static list. packages/backend/src/model-discovery/model-discovery.service.ts:214-215 sets useCuratedSubscriptionModels true for an Anthropic subscription regardless of the token: provider.auth_type === 'subscription' && (!apiKey || lowerProvider === 'anthropic'). The comment at :238-240 reads "Anthropic subscription discovery is also static so connecting Claude Code does not spend live /models or /messages calls before the first real request". Already recorded at references/decisions.md:134.
The anthropic-beta: oauth-2025-04-20 sentence stays: packages/backend/src/common/constants/subscription-clients.ts:23-27, cleared on 2026-08-11.
2 · [WRONG] The connect step says Manifest validates your key. It does not: a revoked key connects, shows models, and fails on your first request
You paste a key. The form checks its shape only: the prefix, and a minimum number of characters.
Manifest saves the key. It never asks the provider whether the key is good.
Manifest then asks the provider for its model list. If that call fails, the save still succeeds.
If the model list comes back empty, Manifest fills it from public model data, so you see a full, plausible list either way.
You get a green "connected" toast.
The key is used for the first time on your first routed request. That is where a bad key surfaces, as an authentication error on that request.
Fix: replace the step body.
Suggested wording:
<Steptitle="Paste the key and save">
The form checks the shape of the key before it saves: the prefix and a
minimum length. Manifest then stores the key and asks the provider for its
model list. That fetch can fail without blocking the save, so a wrong or
revoked key still connects, and it can still show a full model list.
Manifest uses the key for the first time on your first routed request, and
a bad key shows up there as an authentication error on that request.
</Step>
Evidence (for the agent)
Doc: providers/api-key-providers.mdx:63-65 — "Manifest validates the key by fetching the provider's model list. That list is what routing sees until you refresh it. If the key is rejected, double-check the prefix below."
Code: the connect handler checks deployment availability (packages/backend/src/routing/provider.controller.ts:116) and region rules (:129-155) only, then swallows discovery failure at :179-183, comment at :182: "Discovery failure is non-fatal — user can retry via "Refresh models"".
Code: a 401/403 never reaches that catch. The fetcher logs and returns [] (packages/backend/src/model-discovery/provider-model-fetcher.service.ts:1097-1101); the surrounding catch returns [] too (:1106-1109).
Code: an empty list on a non-subscription connection falls through to public-catalog-derived lists (packages/backend/src/model-discovery/model-discovery.service.ts:279-303, guard at :281).
Code: the toast is unconditional (packages/frontend/src/components/ProviderKeyForm.tsx:268).
Code: the only rejection the form produces is client-side and shape-only (packages/frontend/src/services/provider-utils.ts:15,17-22,24-29). The server DTO accepts any string (packages/backend/src/routing/dto/routing.dto.ts:71-73).
Journey step 6 rests on the absence of any earlier use: the three checks above are the whole connect path, and the key's next appearance is credential resolution on a routed request (packages/backend/src/routing/routing-core/provider-key.service.ts:119-155). The failure class is auth (packages/shared/src/error-taxonomy.ts:206), rendered "Authentication" (packages/frontend/src/services/formatters.ts:114).
Consumes references/backlog.md line 66.
3 · [WRONG] The custom-provider page says Manifest normalizes the trailing /v1. It does not, and the wrong ending makes model discovery 404
You add a custom provider, pick the OpenAI or the Anthropic format, and paste a base URL.
Manifest builds the model-list URL by appending to what you typed: /models for the OpenAI format, /v1/models for the Anthropic format.
So the OpenAI format needs a base URL that already ends in /v1, and the Anthropic format needs one that does not.
Get it the wrong way round and the probe fails with "The server is up but /v1/models isn't exposed", naming a path Manifest did not call.
Routing itself tolerates either ending, so a base URL that would route fine can still come back with no models.
Entries whose id looks like an embedding, reranker or moderation model are dropped from the result.
Options:
State the rule per format in the base-URL step, and correct both places that name a discovery path. Recommended.
Drop every literal path from the three sentences, and tell readers to try the base URL with and without /v1 when discovery returns nothing.
Keep the paths as they are and add a troubleshooting line for a probe that 404s.
Suggested wording (option 1):
<Steptitle="Enter the base URL">
Paste the base URL of your endpoint, e.g. `https://my-vllm.internal:8000/v1`.
Manifest builds the model-list URL from it, so the ending matters: for the
OpenAI format the base URL must end in `/v1`, for the Anthropic format it
must not.
</Step>
Manifest discovers models against the base URL you gave it: it calls `<base_url>/models` for the OpenAI format, `<base_url>/v1/models` for the Anthropic format, and reads the `data[].id` field of the answer. Routing accepts either ending, so a base URL that routes fine can still come back with no models here. Manifest also leaves out entries whose id looks like an embedding, reranker or moderation model. If your server doesn't expose a model list, you can register models manually from the same panel.
Evidence (for the agent)
Doc: providers/custom-providers.mdx:31 — "Paste the base URL of your endpoint, e.g. https://my-vllm.internal:8000/v1. Manifest normalizes the trailing /v1 automatically."
Doc: providers/custom-providers.mdx:40 — "Manifest calls GET /v1/models against your base URL and lists every model the endpoint reports."
Doc: providers/custom-providers.mdx:46 — "Manifest discovers models by hitting GET <base_url>/v1/models and reading the data[].id field."
Three loci, one edit: the "Probe for models" step at :40 names the same wrong path and needs the same treatment, or it should stop naming a path at all.
Code, read verbatim: packages/backend/src/routing/custom-provider/custom-provider.service.ts:434-439 — trailing slashes are trimmed with a character loop, then const url = apiKind === 'anthropic' ? \${trimmed}/v1/models` : `${trimmed}/models`;. No /v1` strip.
Code: normalization exists only on the forward path. normalizeProviderBaseUrl strips trailing slashes and one trailing /v1 (packages/backend/src/routing/provider-base-url.ts:1-3) before buildCustomEndpoint re-appends the full path (packages/backend/src/routing/proxy/provider-endpoints.ts:589,594,602).
Code: the 404 message is packages/backend/src/routing/custom-provider/probe-error.ts:61-68. The journey quotes its first clause only.
Code: the drop filter is EMBEDDING_MODEL_PATTERN (custom-provider.service.ts:43-44, applied :471-474).
The same probe backs the form's Fetch models button (packages/frontend/src/components/CustomProviderForm.tsx:97-101 → custom-provider.controller.ts:51-60).
The draft puts the per-format rule in the "Enter the base URL" step, which the page places before "Pick the protocol". Whoever applies it may want to swap the two steps.
4 · [MISSING-CASE] "Add the tile and you have a free model to route to" is a cloud promise. On a self-hosted install the tile can ask for a key instead
The Gemini Free card is on the Usage-based page in both Manifest Cloud and a self-hosted install.
On Manifest Cloud, Manifest provisions the credential in the background, and the connection appears on its own.
On a self-hosted install that only happens if whoever runs the instance wired it up for that.
Otherwise the tile falls back to an ordinary API key form and asks you for a key, which the same paragraph has just told you does not exist. The form's own "get a key" link does not lead to a self-serve way to obtain one.
If you ever disconnect the tile, Manifest stops provisioning it, permanently.
A workspace can hold one Gemini Free connection.
Options:
Put the paragraph behind the page's deploy toggle: Cloud keeps today's promise, the self-hosted branch says the tile works only on an instance set up for it. Recommended.
Keep one paragraph, soften the promise to "where your instance supports it", and add that disconnecting it is permanent.
Leave it.
Suggested wording (option 1), using the same toggle markup the Autofix page already uses:
### Gemini Free
Gemini Free is a managed tile.
<divdata-deploy="cloud">
On Manifest Cloud, Manifest provisions the credential for you. There is no key to fetch first: add the tile and you have a free model to route to.
</div>
<divdata-deploy="selfhosted">
The tile is on the Usage-based page of a self-hosted install, but it only provisions a credential when your instance is set up for it. Otherwise it asks you for an API key, like any other provider.
</div>
A workspace can hold one Gemini Free connection. If you disconnect it, Manifest does not provision it again. Connected, the tile behaves like any other provider.
Option 1 also needs the toggle control itself added after the frontmatter. The page has none today.
"When your instance is set up for it" is deliberately vague: what gates it is operator configuration that has no place in user docs.
Evidence (for the agent)
Doc: providers/api-key-providers.mdx:43 — "Gemini Free is a managed tile. Manifest provisions the credential through its own gateway, so there's no key to fetch from Google first: add the tile and you have a free model to route to. It behaves like any other provider once connected."
Code: the deployment gate covers local-only providers and nothing else (packages/backend/src/common/utils/provider-availability.ts:15-17); the registry entry is not localOnly (packages/shared/src/providers.ts:256-268); the card filter excludes only subscription-only and local-only entries (packages/frontend/src/pages/providers/ProviderConnectionsPage.tsx:119-123). Platform PR #2610 (c63d378b5) shipped the tile cloud-only and PR #2623 (b6b5db5a5) deleted that gate.
Code: provisioning is silent and runs on every provider-page load (packages/frontend/src/services/api/providers.ts:119-127). It declines at the if (!autoAvailable) gate, returning connected: false, source: 'none' (packages/backend/src/routing/managed-free-provider/managed-free-provider.service.ts:103-110); eligibility requires operator configuration (packages/backend/src/common/constants/managed-free-providers.ts:70-77).
Code: an inactive row, meaning an explicit past disconnect, permanently stops the minting (managed-free-provider.service.ts:92-101).
Code: the fallback form demands prefix sk- and 10 characters (packages/shared/src/providers.ts:264-266) and renders a "get a key" link whose target is not a self-serve key page (packages/frontend/src/services/provider-api-key-urls.ts:35-40, rendered ProviderKeyForm.tsx:377-388).
Code: one connection per workspace (packages/backend/src/routing/routing-core/provider.service.ts:46,488-496).
Toggle markup: autofix.mdx:9-12 is the control, autofix.mdx:59,77,83 the wrappers; contract documented at scripts/deploy-mode-toggle.js:1-13.
Journey step 2 (cloud provisions silently) is the page's own current claim, kept because option 1 preserves it verbatim. references/decisions.md:107 records that the deployed cloud configuration cannot be determined from either repo, so no run has confirmed it independently.
Consumes references/backlog.md line 19.
Wording ban carried into the draft: the gateway host, the gateway product name, the operator variables and the target of the tile's own "get a key" link stay out of the docs.
5 · [WRONG] The Docker note says Manifest finds the host address for you. It does not for Ollama, on an install path the docs document
For LM Studio and llama.cpp, Manifest works out the container host itself.
For Ollama it does not. Ollama's address comes from a setting whose default points at the container itself.
The bundled Docker Compose file is what saves you. It does two things: it maps the host into the container, and it points Ollama at that host.
Start the container with a plain Docker command instead, which the self-hosted guide shows in four places, and you get neither.
So that reader connects the Ollama tile, gets nothing, and this page has told them Manifest handles it.
On Podman the host has a different name, so the hostname in the sentence is wrong there too.
Options:
Rewrite the Note: Compose handles it, any other container start needs both pieces set by hand, Podman uses a different host name. Recommended.
Scope the Note to the Compose install and say nothing about other install shapes.
Leave the sentence and add a troubleshooting line for "the tile connects but no models appear".
Suggested wording (option 1):
<Note>
With the bundled Docker Compose file, Manifest reaches the host on its own and
Ollama already points at it. If you started the container another way, set both
yourself: map the host in with `--add-host host.docker.internal:host-gateway`,
and set `OLLAMA_HOST` to `http://host.docker.internal:11434`. On Podman the
host is `host.containers.internal`.
</Note>
OLLAMA_HOST is self-hosted material and this page is already scoped self-hosted-only by its own Note. It already has a row in the environment-variables reference, so naming it here creates no halfway state.
Evidence (for the agent)
Doc: providers/local-models.mdx:92-94 — "Inside the Manifest container, the host is reachable as host.docker.internal. Manifest sets this automatically when probing local providers."
Code: LM Studio and llama.cpp resolve the alias at runtime. getContainerHostAlias() returns host.containers.internal on Podman, host.docker.internal on Docker, else localhost (packages/backend/src/common/utils/detect-self-hosted.ts:34-44), surfaced as localLlmHost (packages/backend/src/setup/setup.controller.ts:11-30).
Code: Ollama does not use it. OLLAMA_HOST is process.env['OLLAMA_HOST'] || 'http://localhost:11434' (packages/backend/src/common/constants/ollama.ts:2), read at provider-model-fetcher.service.ts:954, setup.service.ts:71, ollama-sync.service.ts:26, provider-endpoints.ts:517.
Code: the compose file supplies both halves, extra_hosts: - "host.docker.internal:host-gateway" (docker/docker-compose.yml:55-56) and OLLAMA_HOST=${OLLAMA_HOST:-http://host.docker.internal:11434} (:70). Option 1's draft carries both, because on Linux the alias does not resolve without the mapping.
Docs: the docker run install blocks are self-hosted.mdx:87, :100, :114, :193. None passes either half. That page is out of scope here and is parked in references/backlog.md for the self-hosting topic.
Corpus contradiction: reference/environment-variables.mdx:60 already tells the reader "In Docker, set it to http://host.docker.internal:11434", which this Note tells them is unnecessary.
Scope note, not a reversal.references/decisions.md:135 cleared these exact lines on 2026-08-19 with the reason "holds on every documented install path (compose default docker-compose.yml:70 + runtime alias resolution)". That clearance's two citations reach the Compose path and the LM Studio / llama.cpp alias resolution only. The docker run blocks and the Podman host name were never reached by it, and docker run is documented. The clearance is narrowed, not flipped, and no Seb verdict moves, so the reversal protocol owes no revised derivation. Same shape as the precedents at decisions.md:133 and :157.
6 · [WRONG] The regional-providers section names MiniMax, which has no region picker on the API key form
Two providers show a Region control on the API key connect form: Alibaba Cloud and AWS Bedrock.
MiniMax shows none. Its region choice lives on its subscription connect screen.
Send a region for a MiniMax API key connection through the API and Manifest refuses it.
Four of Alibaba Cloud's eight region options also reveal a Base URL field: China (Hong Kong), Germany (Frankfurt), Japan (Tokyo) and Custom endpoint.
That field is required. Leave it empty and the form answers "Base URL is required".
Options:
Name Alibaba Cloud and AWS Bedrock, add the Base URL requirement, and point MiniMax readers at the subscriptions page. Recommended.
Correct the provider list only, and leave the Base URL field undocumented.
Replace the section with "a region selector appears when the provider supports one".
Suggested wording (option 1):
Two providers ask you for a region on the API key form: Alibaba Cloud and AWS Bedrock. Manifest routes to the host that matches your choice. Four of the Alibaba Cloud options (China (Hong Kong), Germany (Frankfurt), Japan (Tokyo) and Custom endpoint) also ask for your own base URL, so have it ready before you connect. MiniMax has a region choice too, but only on its subscription screen: see [Subscription-based providers](/providers/subscription-based-providers).
Evidence (for the agent)
Doc: providers/api-key-providers.mdx:107 — "Some providers serve more than one region. Alibaba Cloud, AWS Bedrock, and MiniMax let you pick a region when you connect them, and Manifest routes to the matching endpoint."
Code: only qwen (packages/frontend/src/services/providers.ts:119) and bedrock (:173) declare apiKeyEndpointRegions; the only other hits for that field are two type declarations and the form's own reader at ProviderKeyForm.tsx:101. The picker renders only when the array is non-empty (packages/frontend/src/components/ProviderKeyForm.tsx:102, render :193-242).
Code: MiniMax's region control is on the device-code screen (packages/frontend/src/components/DeviceCodeDetailView.tsx:396-414).
Code: the server refuses a region for MiniMax on the API key path. getSubscriptionEndpointRegionConfig returns null unless authType === 'subscription' (packages/backend/src/routing/subscription-region.ts:34), so the request reaches the catch-all throw at packages/backend/src/routing/provider.controller.ts:150-153, whose message scopes regions to "MiniMax subscriptions".
Code: the four Alibaba options carrying baseUrlPlaceholder are packages/frontend/src/services/providers.ts:124-146; the field renders at ProviderKeyForm.tsx:210-239 and "Base URL is required" is at :187.
Consumes references/backlog.md line 60.
7 · [WRONG] Two pages say the model list never changes on its own. Opening the model picker refreshes it once a day
But opening a harness's model picker re-runs discovery, with no button pressed, no spinner and no message.
It fires at most once per harness per browser session per calendar day, and only when some connection's list was not already fetched that day.
It covers every connection on that harness, subscriptions included.
So a model can appear, or disappear, without anyone touching Refresh models.
Options:
Say the true shape on both pages: no polling, a refresh when you click the button, and one automatic refresh a day when you open the model picker. Recommended.
Drop only the false clause on each page and keep the Refresh models instruction, without describing the automatic path.
Say the list refreshes on its own from time to time and the button forces it now. A deliberate simplification: no code path polls on a period.
Suggested wording (option 1), two drafts that agree:
On the api-key page, replacing the first two sentences of the Refreshing the model list section:
A connection keeps the model list it discovered when you saved it. Manifest never polls the provider: it refreshes the list when you ask for it, and once a day when you first open the model picker on a harness. When a provider adds, renames, or drops a model, click **Refresh models**: on your harness's **Routing** page, or on the connection itself under **Providers**.
On the subscriptions page, replacing the paragraph after Connect a subscription:
A connection keeps the model list it discovered when you connected it. Manifest never polls your plan: it refreshes the list when you click **Refresh models** on the connection, and once a day when you first open the model picker on a harness. A model your plan just gained shows up after one of those two.
Both must move together. One page says the list is frozen, the other says a new model does not show up on its own, and they are the same fact.
Evidence (for the agent)
Doc: providers/api-key-providers.mdx:75 — "A connection keeps the model list it discovered when you saved it. Nothing re-checks it on a schedule. When a provider adds, renames, or drops a model, click Refresh models: on your harness's Routing page, or on the connection itself under Providers."
Doc: providers/subscription-based-providers.mdx:56 — "A model your plan just gained doesn't show up on its own. Click Refresh models on the connection to pick it up."
Render site: packages/frontend/src/components/ModelPickerModal.tsx:126-146, guard helper wasFetchedToday at :74-83. Conditions: an agentName is set; at least one active non-custom: connection has models_fetched_at that is not today's local date; the sessionStorage key manifest:routing-model-refresh:${agentName} does not already hold today's date. The key is written at :141, before the request at :143, so a failed refresh does not retry.
Journey step 5: it calls refreshModels(agentName) (packages/frontend/src/services/api/routing.ts:349-351), which posts to model.controller.ts:86-91, which calls discoverAllForAgent(tenant_id, { forceRefresh: true }). That loops every active connection of the workspace, so subscriptions are included.
Journey step 3's "no spinner": platform PR #2684 (42a750bb0) touches exactly ModelPickerModal.tsx, its test and a changeset, and deletes the refreshingAll signal that used to spin the per-group icons in this modal.
Onboarding also calls refreshModels, on different triggers, not on the once-a-day gate: when a step change finds the harness's model list empty (packages/frontend/src/pages/Welcome.tsx:157) and after a provider connect completes (:195).
The api-key page's third sentence is TRUE: both locations exist (packages/frontend/src/pages/Routing.tsx:541, packages/frontend/src/pages/providers/ConnectionDetail.tsx:1319).
Consumes references/backlog.md lines 59 and 73.
Scope note.references/decisions.md:135 cleared api-key-providers.mdx:75 on 2026-08-19 citing backend cron paths only. The mechanism here is a frontend one no citation in that clearance reached. The narrowing is already on record at decisions.md:157 as a correction owing no derivation.
8 · [MISSING-CASE] Connecting a provider turns it on for every harness in the workspace, and no providers page says so
You connect a provider while working on one harness.
The connection belongs to the workspace, not to that harness.
Manifest switches it on for every harness the workspace already owns.
Every harness you create afterwards gets it too.
To turn it off for one harness, you open that harness and use its Providers tab.
Manifest refuses to turn it off there while that harness's routing still pins one of the provider's models.
Options:
Add one step to the Add a provider flow saying the connection is workspace-wide, and naming the harness's Providers tab. Recommended.
Same, plus a "Scope of a connection" subsection also covering that reconnecting a disconnected provider re-enables it everywhere while rotating a key does not.
Leave the providers pages silent and let the gateway page keep carrying it from the failure side.
Suggested wording (option 1), a new step after "Paste the key and save":
<Steptitle="Pick which harnesses use it">
The connection belongs to the workspace, not to one harness. Saving it turns
the provider on for every harness in the workspace, and for every harness
you create later. To turn it off for one harness, open that harness and use
its **Providers** tab. Manifest refuses to turn it off there while that
harness's routing still pins one of the provider's models.
</Step>
Evidence (for the agent)
Doc: no page in the corpus says it. grep -rn "workspace" providers/*.mdx returns zero hits. The concept appears only from the failure side, at llm-gateway.mdx:89 ("the provider exists in your workspace but is not enabled for this harness") and errors/M302.mdx:26.
Code: enableProviderForAllAgents (implementation packages/backend/src/routing/routing-core/provider.service.ts:186-199) has exactly four call sites, checked by enclosing function: :441 in upsertProvider (the api-key path this finding is about), :538 in upsertProviderWithLabel (a labelled key), :764 in registerSubscriptionProvider, and :216 in fanOutIfReactivated (the reconnect case, next bullet). So subscriptions fan out the same way. The custom-provider path has no fan-out call.
Code: a new harness inherits every connection, enableAllProvidersForAgent at :174-180, called from packages/backend/src/analytics/controllers/agents.controller.ts:129.
Code: reconnecting a disconnected row fans out again, rotating a live key does not (fanOutIfReactivated, :209-217).
Render site: the harness's Providers tab, route packages/frontend/src/index.tsx:102, tab label packages/frontend/src/pages/AgentDetail.tsx:82, screen packages/frontend/src/pages/AgentProviders.tsx:143-147, refusal toast at :126-132.
Code: the connect page has no harness chooser; it addresses the API with firstAgentName() (packages/frontend/src/pages/providers/ProviderConnectionsPage.tsx:338,935).
Consumes references/backlog.md line 67, extended with the new-harness inheritance and the override location, neither of which that line named.
Out of scope, parked in references/backlog.md this run: llm-gateway.mdx:89 and errors/M302.mdx:26 should end up using the same vocabulary if this ships.
9 · [WRONG] "Every loaded model appears for routing" is not true. Embedding models are dropped before routing sees them
It drops models whose id looks like an embedding model, before anything reaches routing.
There is no setting to turn that off.
So a model you pulled and can see in your runtime can be absent from Manifest, with the probe reported as successful.
Fix: soften the sentence and name the exclusion.
Suggested wording:
<Steptitle="Confirm the server is reachable">
Manifest asks the runtime for its model list on its default port. It leaves
out embedding models. The rest appear for routing.
</Step>
The draft also drops the literal probe path, on purpose. The page lists three runtimes and Manifest does not call the same path for all three: the Ollama tile calls Ollama's own tag API, and the LM Studio and llama.cpp tiles call /v1/models. That mismatch is low harm on its own, so it is not raised separately.
Evidence (for the agent)
Doc: providers/local-models.mdx:56 — "Manifest probes http://localhost:<default-port>/v1/models. If the probe succeeds, every loaded model appears for routing."
Code: the three runtimes on this page use two different filters, which is why the draft claims only what both cover.
Ollama goes through the standard fetcher and UNIVERSAL_NON_CHAT_RE (packages/backend/src/model-discovery/provider-model-fetcher.service.ts:405-406, applied at :485, called on every fetch at :1105). That pattern also catches tts, whisper, dall-e and imagen ids.
LM Studio and llama.cpp go through the custom-provider probe and EMBEDDING_MODEL_PATTERN (packages/backend/src/routing/custom-provider/custom-provider.service.ts:43-44, applied :471-474). That pattern also catches reranker and moderation ids, and does NOT catch tts or whisper.
Embedding ids are the only class both patterns drop, so "It leaves out embedding models" is the strongest universal the page can carry.
Code: the Ollama probe is ${OLLAMA_HOST}/api/tags (provider-model-fetcher.service.ts:953-957), not /v1/models.
Code: no env var or setting disables any of these filters.
Wording ban carried into the draft: the sentence describes the id-pattern filters only. The capability-catalog curation is the finding Seb rejected on 2026-08-20 (issue Docs audit — 2026-08-19 (PR #2733) #81 finding 1) and stays out.
Consumes references/backlog.md line 40. Line 41, the probe path, is cleared into this finding's note rather than raised.
10 · [MISSING-CASE] Kiro's connect screen adds a Region and an optional Start URL, and no page mentions either
Before any device code, the screen shows a Region dropdown, already set to us-east-1, that you can change.
It also offers an optional Start URL, for a plan behind an identity center.
MiniMax's screen asks you to pick a region too, and it accepts a pasted Coding Plan token instead of the device code.
The page describes none of this. It promises three screens and nothing more.
Options:
Extend the "Complete the auth flow" step with the extra values a screen can ask for. Recommended.
Add a Kiro tab to the "How auth works per provider" tabs, alongside the six existing ones.
Leave it.
Suggested wording (option 1):
<Steptitle="Complete the auth flow">
Manifest opens the provider's OAuth page, shows you a device code to enter,
or asks you to paste a token, depending on the provider. Two screens show
more than that. Kiro adds an AWS region, set to us-east-1, and an optional
Start URL for an identity-center plan. MiniMax asks you to pick a region,
and it also takes a pasted Coding Plan token instead of the device code.
Once it's saved, you're routed.
</Step>
Evidence (for the agent)
Doc: providers/subscription-based-providers.mdx:50-52 — "Manifest opens the provider's OAuth page, shows you a device code to enter, or asks you to paste a token, depending on the provider. Once it's saved, you're routed."
Doc: the Kiro row at :22 says only "Device code". grep -rn "Start URL\|Kiro" providers/*.mdx returns two hits: that row, and providers/api-key-providers.mdx:46 inside the subscription-only Note.
Render site, read verbatim: packages/frontend/src/components/DeviceCodeDetailView.tsx:415-431 renders the Kiro Region as a fixed-option Select, :432-452 the Start URL (optional) input with placeholder https://your-domain.awsapps.com/start.
Code: the region is pre-filled, not blank. KIRO_DEFAULT_REGION = 'us-east-1' (:42), seeded into the signal at :97, and KIRO_REGION_OPTIONS (:43 onward) is a closed list of AWS region ids.
Deliberately NOT in the journey: the validator and its message "Enter a valid AWS region, such as us-east-1." (:166-169). The screen offers a closed dropdown, so nothing on it can reach that refusal. It is frontend-only: grep -rn "valid AWS region" packages/ returns that one production hit, and an API caller posting a Kiro region gets the backend's own different message (provider.controller.ts:151-153).
Render site: the MiniMax region select at :396-414, the paste-token alternative at :469-505.
11 · [WRONG] The OpenCode Go tab says every request uses the Anthropic protocol. Most use the OpenAI one
Manifest picks the wire format per model. Most models take the OpenAI format with a bearer token.
The rest take the Anthropic format with x-api-key.
The host in the doc is right. The protocol and the header are the wrong way round for the common case.
Options:
Say Manifest picks the wire format per model, and keep the host. Recommended.
Keep the host and drop the protocol claim entirely.
Document both shapes and say which is the default.
Suggested wording (option 1):
<Tabtitle="OpenCode Go">
Paste the subscription token from your OpenCode account. Requests go to
`opencode.ai/zen/go`. Manifest picks the wire format per model: most models
take the OpenAI format, some take the Anthropic format.
</Tab>
The draft does not say what decides the split, on purpose: what decides it is a third-party catalog lookup, which Seb ruled out of the docs on 2026-08-20.
Evidence (for the agent)
Doc: providers/subscription-based-providers.mdx:90-92 — "Paste the subscription token from your OpenCode account. Requests use the Anthropic protocol against opencode.ai/zen/go, authenticated via the x-api-key header."
Code, read verbatim: packages/backend/src/routing/proxy/provider-endpoints.ts:536-542 — the opencode-go endpoint is buildHeaders: openaiHeaders, buildPath: openaiPath, format: 'openai'. openaiHeaders is Authorization: Bearer (:71-74).
Code: :543-548 — the opencode-go-anthropic variant uses anthropicApiKeyHeaders and /v1/messages, on the same base URL (OPENCODE_GO_BASE, :140).
Code: the switch is per model, packages/backend/src/routing/proxy/provider-client.ts:503-510.
Scope note.references/decisions.md:43 cleared "the OpenCode Go x-api-key variant is a real supported variant" on 2026-08-11, and that stays true: the variant exists and this finding does not dispute it. What this finding disputes is the doc's universal, "every request uses it". The endpoint has been format: 'openai' since it was added, so nothing in the platform moved; a cleared near-finding is not a Seb verdict, so no revised derivation is owed (precedents decisions.md:133, :155, :157).
12 · [WRONG] Three rows of "Where to get a key" send you to a China-mainland console, and Manifest calls those three providers on their international hosts
Doc: the "Where to get a key" column, providers/api-key-providers.mdx:16-37. The three regional cells are :24 (Moonshot), :25 (MiniMax), :26 (Z.ai).
Code, the dashboard map: packages/frontend/src/services/provider-api-key-urls.ts:5-33, rendered as "Get {Provider} API key" at packages/frontend/src/components/ProviderKeyForm.tsx:377-388.
Code, the endpoints an API key connection actually calls, read verbatim: packages/backend/src/routing/proxy/provider-endpoints.ts:331-337 (minimax -> https://api.minimax.io), :365-371 (moonshot -> https://api.moonshot.ai), :406-412 (zai -> https://api.z.ai). The China-mainland hosts exist in this codebase only on the subscription side (packages/backend/src/routing/zai-region.ts:1-4, DeviceCodeDetailView.tsx:410-411).
This finding asserts only that the docs and the product point at different places. It does not assert that a key issued by one console fails on the other host: that is a vendor fact, not verifiable here.
Supersedes references/backlog.md line 68 (the Google row), folded in as a deep-link difference rather than raised on its own.
Questions, not findings
Is the LM Studio / llama.cpp connect screen reachable? Four agents this run independently traced the same dead end: openLocalServer (packages/frontend/src/components/ProviderSelectContent.tsx:199-202) has no caller anywhere in packages/frontend/src, and the only other way to mount the port-probe screen needs a provider=custom:<uuid> deep link that nothing produces. If that holds, clicking Connect on the LM Studio tile never shows the port probe, and the lms server start --bind 0.0.0.0 --port 1234 --cors instructions at providers/local-models.mdx:63-89 describe a screen no user reaches. Nobody loaded the app. This is static reachability, not an observed run. One click on a self-hosted instance settles it, and it decides whether half of that page needs rewriting or nothing at all. Finding 9 does not depend on it. Finding 5 depends on it only for its first journey step, which describes the LM Studio and llama.cpp path; its Ollama half is backend and stands either way.
glm-5.3 on the Z.ai plan. Carried already as question 1 of issue Docs audit — 2026-08-20 (PRs #2740–#2743) #92. No providers page names it, so nothing is owed here. Flagged so the two runs do not diverge.
Cleared this run (for the agent, so no future run re-derives it)
The empty-completion failure of platform PR #2743 as it lands on local runtimes and OpenAI-format custom providers. Verified inside the gate: Ollama (provider-endpoints.ts:516-521), LM Studio and llama.cpp (routed through buildCustomEndpoint, :583-606), and every OpenAI-format custom provider, with no switch to disable it. Not raised: issue Docs audit — 2026-08-20 (PRs #2740–#2743) #92 finding 1 is open on llm-gateway.mdx:95-108 with a scope-free sentence as its recommended option, and a scope-free statement in the section that owns fallback triggers covers these providers by construction. Neither providers page states a fallback trigger, so their silence creates no halfway state. Raising a second locus would be the two-competing-fixes trap recorded on 2026-08-19. Re-check condition: if issue Docs audit — 2026-08-20 (PRs #2740–#2743) #92 finding 1 is rejected outright, this fact returns to the providers topic with no owner. Consumes references/backlog.md line 72.
Subscription model lists shrinking after an upgrade (platform PR #2633 closed the public-catalog fallback for subscriptions, model-discovery.service.ts:248-251,279-303). Not raised: the only way to explain the shrink is to explain what a third-party catalog used to contribute, rejected twice on 2026-08-20.
Minimum key lengths absent from the key-prefix table (api-key-providers.mdx:83-103). The premise holds: no page states a minimum, and the form enforces up to 100 characters for AWS Bedrock. Not raised: the form's own message names the number ("Key is too short (minimum 30 characters)", provider-utils.ts:24-29), which is actionable on its own; adding 24 length values to a reader-aid table is the noise the 2026-08-18 reversal of issue Docs audit — providers (api keys, subscriptions, local, custom) — 2026-08-11 #55 finding 4 rejected; and finding 2 removes the "double-check the prefix below" advice that made this a dead end. Retires references/backlog.md line 69.
local-models.mdx:102 "Network egress | None. Requests never leave the machine." The row's subject is the routed request, and routed requests genuinely never leave the machine. Qualifying it means describing scheduled outbound catalog fetches, which needs the third-party name Seb banned. Retires references/backlog.md line 42.
local-models.mdx:56 naming /v1/models where the Ollama tile calls /api/tags. True (provider-model-fetcher.service.ts:953-957) but harm is nil: Ollama serves /v1/models too, so a reader curling the documented URL gets the right answer. Folded into finding 9's wording note. Retires references/backlog.md line 41.
The Meta model whose display name carries its own training-data caveat (packages/shared/src/providers.ts:55-62). The caveat is inside the display name and reaches the model picker, so it is on screen at the moment of choice. Restating another vendor's training policy is the class rejected on 2026-08-20. Retires references/backlog.md line 15.
An IMAGE candidate: no image in the corpus shows the Refresh models button, though providers/api-key-providers.mdx:75, providers/subscription-based-providers.mdx:38 and :56 all tell the reader to click it, and images/ holds 12 files of which one is a providers shot. NOT raised. The park condition on references/backlog.md line 45 has not fired: the trigger would be the platform giving the user a new reason to press the button, and what changed is a docs finding Seb has not ruled on yet. Four image asks are already open awaiting a capture session (Docs audit — llm gateway (routing, tiers, fallbacks) — 2026-08-09 #53 f5, Docs audit — observability (requests page, analytics) — 2026-08-10 #54 f4, Docs audit — 2026-08-17 (PR #2727) #70 f5, Docs audit — 2026-08-20 (PRs #2740–#2743) #92 f2), and decisions.md:144(e) and :147 both record that a pending capture argues against a new one. Line 45 stays parked, unchanged.
providers/subscription-based-providers.mdx:96-99, the plan-ceiling Note ("If you hit a plan ceiling, requests fall through to your next fallback model, usually a different provider on an API key."). Mechanism: shouldTriggerFallback is status >= 400 with no exclusions (fallback-status-codes.ts:1-3). Not raised, and this run states no conclusion about what a 429 does next: Seb's standing correction of 2026-08-20 (decisions.md:175) governs, and issue Docs audit — llm gateway (routing, tiers, fallbacks) — 2026-08-19 #82 findings 2 and 4 were rejected on this mechanic as "no fact the reader acts on".
custom-providers.mdx:55, the SSRF paragraph. Every clause holds where it counts (url-validation.ts:4-17,147-183, revalidation provider-client.ts:401-411). The one corner it does not cover, self-hosted http:// to a public host still being refused (url-validation.ts:214-218), is scoped away by the sentence's own "inside your own network" clause and produces an explicit error.
Provider-table completeness (api-key-providers.mdx:14-37). 22 rows + Gemini Free + the Vertex row arriving in docs PR docs: Google Vertex AI (issue #85) #91 = 24, which matches the rendered card count: 35 registry ids (packages/frontend/src/services/providers.ts:505-541), minus 3 localOnly (packages/shared/src/providers.ts:371,384,397), minus 8 subscriptionOnly (packages/frontend/src/services/providers.ts:194,217,242,261,288,384,406,447), filter at ProviderConnectionsPage.tsx:119-124.
Every prefix value in the table matches an enforced keyPrefix, and the five providers named at :103 all carry an empty one. The 2026-08-18 reversal holds: no row is proposed for a prefix the form does not enforce.
Subscription auth-flow column, all rows except Anthropic, checked one by one against subscriptionAuthMode in packages/frontend/src/services/providers.ts.
The Kimi model ids at subscription-based-providers.mdx:36-39 are exactly the four in packages/shared/src/subscription/configs.ts:157.
The ChatGPT, GLM and MiniMax tabs all hold against provider-endpoints.ts and the OAuth services.
Internal-leak sweep: 0 hits. All four pages checked against the stage-1 internal-names list (the credential gateway host and product name, the operator variables behind the managed tile, the booking URL that tile links to, the Autofix healer codename and host, the OAuth client ids and their override variables, the vendor-CLI impersonation constants, the SSRF test bypass variable, the dev seed address, the dev onboarding drawer) plus the banned third-party catalog name. Two structural sweeps backed it: zero SCREAMING_SNAKE tokens anywhere in the four pages, and all 57 distinct URLs resolve to public vendor surfaces, app.manifest.build, or the illustrative https://my-vllm.internal:8000/v1. Near-hits classified rather than dismissed: api-key-providers.mdx:43 "its own gateway" is a descriptor, not a name; subscription-based-providers.mdx:68anthropic-beta: oauth-2025-04-20 is a public Anthropic flag; subscription-based-providers.mdx:63chatgpt.com/backend-api/codex/responses is OpenAI's own public backend URL, cleared on 2026-08-11 and 2026-08-12.
Reply with the finding number and the chosen option
(e.g. "3 → option 2", or "3: other idea, let's discuss").
PRs are then made manually with the agent, never by the harness.
Rejected findings: reply "N → reject" and they will never be re-raised.
Scoped audit of the providers topic, run by the nightly rotation. Last audited 2026-08-11.
Pages audited:
providers/api-key-providers.mdx,providers/subscription-based-providers.mdx,providers/local-models.mdx,providers/custom-providers.mdx.Platform window: the current provider code on
mnfst/manifestmain at061d351f5, plus every merged PR of the last 30 days touching provider connect, model discovery, subscriptions, local runtimes or custom endpoints.Docs read at
bf0ccb2(repo HEAD ise721746, which adds three backlog lines and no page content). Coherence check GREEN, 48 pages, 58 vars.Two docs PRs are already open on these pages and are not re-raised here: #91 adds Google Vertex AI to the api-key page, #90 rewrites the custom-provider cost section.
12 findings: WRONG x8, MISSING-CASE x3, OBSOLETE x1.
Findings
1 · [OBSOLETE] The Claude tab tells you to generate a token in a terminal. The connect panel is a browser sign-in and refuses that token
Page: https://manifest.build/docs/providers/subscription-based-providers (the Claude tab, and the Anthropic row of the supported-subscriptions table)
What actually happens:
Options:
OAuth (paste code). Recommended (the flow matches none of the labels the table already uses, so it gets its own).OAuth (browser)label for the row.Subscription token.Suggested wording (the tab body is the same under all three options; only the row label differs):
The draft drops the tab's closing sentence about available models matching your plan. It is not true: a Claude subscription connection is served from a fixed list Manifest carries, not from a live read of your plan.
Evidence (for the agent)
providers/subscription-based-providers.mdx:17— "| Anthropic | Claude Pro/Max | Subscription token |"providers/subscription-based-providers.mdx:67-68— "Generate a token with the Claude CLI (claude setup-token) and paste it into Manifest."providers/subscription-based-providers.mdx:69-70— "Available models match what your plan grants in claude.ai."packages/frontend/src/services/providers.ts:157-164setssubscriptionAuthMode: 'popup_paste'and supplies nosubscriptionCommand.buildProviderDef(:485-501) copies only identity fields from the shared registry and spreads the overlay, so the terminal block gated onprovDef.subscriptionCommand(ProviderDetailView.tsx:349-374) never renders.subscriptionAuthMode: 'token'andsubscriptionCommand: 'claude setup-token'(packages/shared/src/subscription/configs.ts:9-12). Three agents grepped for a reader ofgetSubscriptionProviderConfigoutsidepackages/sharedand tests, and found none.packages/frontend/src/components/AnthropicOAuthDetailView.tsx:213-267— "Sign in with your Claude Pro or Max account…", button Sign in with Claude, divider "Paste the authorization code", "After signing in, Anthropic's redirect page shows a code. Copy the full string and paste it below."AnthropicOAuthDetailView.tsx:102-118rejects any input without a#, with the quoted message. Ask-ant-oat…token has no#.packages/backend/src/model-discovery/model-discovery.service.ts:214-215setsuseCuratedSubscriptionModelstrue for an Anthropic subscription regardless of the token:provider.auth_type === 'subscription' && (!apiKey || lowerProvider === 'anthropic'). The comment at:238-240reads "Anthropic subscription discovery is also static so connecting Claude Code does not spend live /models or /messages calls before the first real request". Already recorded atreferences/decisions.md:134.anthropic-beta: oauth-2025-04-20sentence stays:packages/backend/src/common/constants/subscription-clients.ts:23-27, cleared on 2026-08-11.2 · [WRONG] The connect step says Manifest validates your key. It does not: a revoked key connects, shows models, and fails on your first request
Page: https://manifest.build/docs/providers/api-key-providers (Add a provider, the "Paste the key and save" step)
What actually happens:
Fix: replace the step body.
Suggested wording:
Evidence (for the agent)
providers/api-key-providers.mdx:63-65— "Manifest validates the key by fetching the provider's model list. That list is what routing sees until you refresh it. If the key is rejected, double-check the prefix below."packages/backend/src/routing/provider.controller.ts:116) and region rules (:129-155) only, then swallows discovery failure at:179-183, comment at:182: "Discovery failure is non-fatal — user can retry via "Refresh models"".[](packages/backend/src/model-discovery/provider-model-fetcher.service.ts:1097-1101); the surrounding catch returns[]too (:1106-1109).packages/backend/src/model-discovery/model-discovery.service.ts:279-303, guard at:281).packages/frontend/src/components/ProviderKeyForm.tsx:268).packages/frontend/src/services/provider-utils.ts:15,17-22,24-29). The server DTO accepts any string (packages/backend/src/routing/dto/routing.dto.ts:71-73).packages/backend/src/routing/routing-core/provider-key.service.ts:119-155). The failure class isauth(packages/shared/src/error-taxonomy.ts:206), rendered "Authentication" (packages/frontend/src/services/formatters.ts:114).references/backlog.mdline 66.3 · [WRONG] The custom-provider page says Manifest normalizes the trailing
/v1. It does not, and the wrong ending makes model discovery 404Page: https://manifest.build/docs/providers/custom-providers (Enter the base URL, Probe for models, Model discovery)
What actually happens:
/modelsfor the OpenAI format,/v1/modelsfor the Anthropic format./v1, and the Anthropic format needs one that does not.Options:
/v1when discovery returns nothing.Suggested wording (option 1):
Evidence (for the agent)
providers/custom-providers.mdx:31— "Paste the base URL of your endpoint, e.g.https://my-vllm.internal:8000/v1. Manifest normalizes the trailing/v1automatically."providers/custom-providers.mdx:40— "Manifest callsGET /v1/modelsagainst your base URL and lists every model the endpoint reports."providers/custom-providers.mdx:46— "Manifest discovers models by hittingGET <base_url>/v1/modelsand reading thedata[].idfield.":40names the same wrong path and needs the same treatment, or it should stop naming a path at all.packages/backend/src/routing/custom-provider/custom-provider.service.ts:434-439— trailing slashes are trimmed with a character loop, thenconst url = apiKind === 'anthropic' ? \${trimmed}/v1/models` : `${trimmed}/models`;. No/v1` strip.normalizeProviderBaseUrlstrips trailing slashes and one trailing/v1(packages/backend/src/routing/provider-base-url.ts:1-3) beforebuildCustomEndpointre-appends the full path (packages/backend/src/routing/proxy/provider-endpoints.ts:589,594,602).packages/backend/src/routing/custom-provider/probe-error.ts:61-68. The journey quotes its first clause only.EMBEDDING_MODEL_PATTERN(custom-provider.service.ts:43-44, applied:471-474).packages/frontend/src/components/CustomProviderForm.tsx:97-101→custom-provider.controller.ts:51-60).:65on this page and touches nothing else here.4 · [MISSING-CASE] "Add the tile and you have a free model to route to" is a cloud promise. On a self-hosted install the tile can ask for a key instead
Page: https://manifest.build/docs/providers/api-key-providers (the Gemini Free section)
What actually happens:
Options:
Suggested wording (option 1), using the same toggle markup the Autofix page already uses:
Option 1 also needs the toggle control itself added after the frontmatter. The page has none today.
"When your instance is set up for it" is deliberately vague: what gates it is operator configuration that has no place in user docs.
Evidence (for the agent)
providers/api-key-providers.mdx:43— "Gemini Free is a managed tile. Manifest provisions the credential through its own gateway, so there's no key to fetch from Google first: add the tile and you have a free model to route to. It behaves like any other provider once connected."packages/backend/src/common/utils/provider-availability.ts:15-17); the registry entry is notlocalOnly(packages/shared/src/providers.ts:256-268); the card filter excludes only subscription-only and local-only entries (packages/frontend/src/pages/providers/ProviderConnectionsPage.tsx:119-123). Platform PR #2610 (c63d378b5) shipped the tile cloud-only and PR #2623 (b6b5db5a5) deleted that gate.packages/frontend/src/services/api/providers.ts:119-127). It declines at theif (!autoAvailable)gate, returningconnected: false, source: 'none'(packages/backend/src/routing/managed-free-provider/managed-free-provider.service.ts:103-110); eligibility requires operator configuration (packages/backend/src/common/constants/managed-free-providers.ts:70-77).managed-free-provider.service.ts:92-101).sk-and 10 characters (packages/shared/src/providers.ts:264-266) and renders a "get a key" link whose target is not a self-serve key page (packages/frontend/src/services/provider-api-key-urls.ts:35-40, renderedProviderKeyForm.tsx:377-388).packages/backend/src/routing/routing-core/provider.service.ts:46,488-496).autofix.mdx:9-12is the control,autofix.mdx:59,77,83the wrappers; contract documented atscripts/deploy-mode-toggle.js:1-13.references/decisions.md:107records that the deployed cloud configuration cannot be determined from either repo, so no run has confirmed it independently.references/backlog.mdline 19.5 · [WRONG] The Docker note says Manifest finds the host address for you. It does not for Ollama, on an install path the docs document
Page: https://manifest.build/docs/providers/local-models (the Note at the end of Running Manifest in Docker)
What actually happens:
Options:
Suggested wording (option 1):
OLLAMA_HOSTis self-hosted material and this page is already scoped self-hosted-only by its own Note. It already has a row in the environment-variables reference, so naming it here creates no halfway state.Evidence (for the agent)
providers/local-models.mdx:92-94— "Inside the Manifest container, the host is reachable ashost.docker.internal. Manifest sets this automatically when probing local providers."getContainerHostAlias()returnshost.containers.internalon Podman,host.docker.internalon Docker, elselocalhost(packages/backend/src/common/utils/detect-self-hosted.ts:34-44), surfaced aslocalLlmHost(packages/backend/src/setup/setup.controller.ts:11-30).OLLAMA_HOSTisprocess.env['OLLAMA_HOST'] || 'http://localhost:11434'(packages/backend/src/common/constants/ollama.ts:2), read atprovider-model-fetcher.service.ts:954,setup.service.ts:71,ollama-sync.service.ts:26,provider-endpoints.ts:517.extra_hosts: - "host.docker.internal:host-gateway"(docker/docker-compose.yml:55-56) andOLLAMA_HOST=${OLLAMA_HOST:-http://host.docker.internal:11434}(:70). Option 1's draft carries both, because on Linux the alias does not resolve without the mapping.docker runinstall blocks areself-hosted.mdx:87,:100,:114,:193. None passes either half. That page is out of scope here and is parked inreferences/backlog.mdfor the self-hosting topic.reference/environment-variables.mdx:60already tells the reader "In Docker, set it tohttp://host.docker.internal:11434", which this Note tells them is unnecessary.references/decisions.md:135cleared these exact lines on 2026-08-19 with the reason "holds on every documented install path (compose default docker-compose.yml:70 + runtime alias resolution)". That clearance's two citations reach the Compose path and the LM Studio / llama.cpp alias resolution only. Thedocker runblocks and the Podman host name were never reached by it, anddocker runis documented. The clearance is narrowed, not flipped, and no Seb verdict moves, so the reversal protocol owes norevisedderivation. Same shape as the precedents atdecisions.md:133and:157.6 · [WRONG] The regional-providers section names MiniMax, which has no region picker on the API key form
Page: https://manifest.build/docs/providers/api-key-providers (Regional providers)
What actually happens:
Options:
Suggested wording (option 1):
Evidence (for the agent)
providers/api-key-providers.mdx:107— "Some providers serve more than one region. Alibaba Cloud, AWS Bedrock, and MiniMax let you pick a region when you connect them, and Manifest routes to the matching endpoint."qwen(packages/frontend/src/services/providers.ts:119) andbedrock(:173) declareapiKeyEndpointRegions; the only other hits for that field are two type declarations and the form's own reader atProviderKeyForm.tsx:101. The picker renders only when the array is non-empty (packages/frontend/src/components/ProviderKeyForm.tsx:102, render:193-242).packages/frontend/src/components/DeviceCodeDetailView.tsx:396-414).getSubscriptionEndpointRegionConfigreturns null unlessauthType === 'subscription'(packages/backend/src/routing/subscription-region.ts:34), so the request reaches the catch-all throw atpackages/backend/src/routing/provider.controller.ts:150-153, whose message scopes regions to "MiniMax subscriptions".baseUrlPlaceholderarepackages/frontend/src/services/providers.ts:124-146; the field renders atProviderKeyForm.tsx:210-239and "Base URL is required" is at:187.references/backlog.mdline 60.7 · [WRONG] Two pages say the model list never changes on its own. Opening the model picker refreshes it once a day
Page: https://manifest.build/docs/providers/api-key-providers (Refreshing the model list) and https://manifest.build/docs/providers/subscription-based-providers (the line after Connect a subscription)
What actually happens:
Options:
Suggested wording (option 1), two drafts that agree:
On the api-key page, replacing the first two sentences of the Refreshing the model list section:
On the subscriptions page, replacing the paragraph after Connect a subscription:
Both must move together. One page says the list is frozen, the other says a new model does not show up on its own, and they are the same fact.
Evidence (for the agent)
providers/api-key-providers.mdx:75— "A connection keeps the model list it discovered when you saved it. Nothing re-checks it on a schedule. When a provider adds, renames, or drops a model, click Refresh models: on your harness's Routing page, or on the connection itself under Providers."providers/subscription-based-providers.mdx:56— "A model your plan just gained doesn't show up on its own. Click Refresh models on the connection to pick it up."packages/frontend/src/components/ModelPickerModal.tsx:126-146, guard helperwasFetchedTodayat:74-83. Conditions: anagentNameis set; at least one active non-custom:connection hasmodels_fetched_atthat is not today's local date; the sessionStorage keymanifest:routing-model-refresh:${agentName}does not already hold today's date. The key is written at:141, before the request at:143, so a failed refresh does not retry.refreshModels(agentName)(packages/frontend/src/services/api/routing.ts:349-351), which posts tomodel.controller.ts:86-91, which callsdiscoverAllForAgent(tenant_id, { forceRefresh: true }). That loops every active connection of the workspace, so subscriptions are included.42a750bb0) touches exactlyModelPickerModal.tsx, its test and a changeset, and deletes therefreshingAllsignal that used to spin the per-group icons in this modal.refreshModels, on different triggers, not on the once-a-day gate: when a step change finds the harness's model list empty (packages/frontend/src/pages/Welcome.tsx:157) and after a provider connect completes (:195).packages/frontend/src/pages/Routing.tsx:541,packages/frontend/src/pages/providers/ConnectionDetail.tsx:1319).references/backlog.mdlines 59 and 73.references/decisions.md:135clearedapi-key-providers.mdx:75on 2026-08-19 citing backend cron paths only. The mechanism here is a frontend one no citation in that clearance reached. The narrowing is already on record atdecisions.md:157as a correction owing no derivation.8 · [MISSING-CASE] Connecting a provider turns it on for every harness in the workspace, and no providers page says so
Page: https://manifest.build/docs/providers/api-key-providers (Add a provider)
What actually happens:
Options:
Suggested wording (option 1), a new step after "Paste the key and save":
Evidence (for the agent)
grep -rn "workspace" providers/*.mdxreturns zero hits. The concept appears only from the failure side, atllm-gateway.mdx:89("the provider exists in your workspace but is not enabled for this harness") anderrors/M302.mdx:26.enableProviderForAllAgents(implementationpackages/backend/src/routing/routing-core/provider.service.ts:186-199) has exactly four call sites, checked by enclosing function::441inupsertProvider(the api-key path this finding is about),:538inupsertProviderWithLabel(a labelled key),:764inregisterSubscriptionProvider, and:216infanOutIfReactivated(the reconnect case, next bullet). So subscriptions fan out the same way. The custom-provider path has no fan-out call.enableAllProvidersForAgentat:174-180, called frompackages/backend/src/analytics/controllers/agents.controller.ts:129.fanOutIfReactivated,:209-217).packages/frontend/src/index.tsx:102, tab labelpackages/frontend/src/pages/AgentDetail.tsx:82, screenpackages/frontend/src/pages/AgentProviders.tsx:143-147, refusal toast at:126-132.firstAgentName()(packages/frontend/src/pages/providers/ProviderConnectionsPage.tsx:338,935).references/backlog.mdline 67, extended with the new-harness inheritance and the override location, neither of which that line named.references/backlog.mdthis run:llm-gateway.mdx:89anderrors/M302.mdx:26should end up using the same vocabulary if this ships.9 · [WRONG] "Every loaded model appears for routing" is not true. Embedding models are dropped before routing sees them
Page: https://manifest.build/docs/providers/local-models (Connect to Manifest, "Confirm the server is reachable")
What actually happens:
Fix: soften the sentence and name the exclusion.
Suggested wording:
The draft also drops the literal probe path, on purpose. The page lists three runtimes and Manifest does not call the same path for all three: the Ollama tile calls Ollama's own tag API, and the LM Studio and llama.cpp tiles call
/v1/models. That mismatch is low harm on its own, so it is not raised separately.Evidence (for the agent)
providers/local-models.mdx:56— "Manifest probeshttp://localhost:<default-port>/v1/models. If the probe succeeds, every loaded model appears for routing."UNIVERSAL_NON_CHAT_RE(packages/backend/src/model-discovery/provider-model-fetcher.service.ts:405-406, applied at:485, called on every fetch at:1105). That pattern also catches tts, whisper, dall-e and imagen ids.EMBEDDING_MODEL_PATTERN(packages/backend/src/routing/custom-provider/custom-provider.service.ts:43-44, applied:471-474). That pattern also catches reranker and moderation ids, and does NOT catch tts or whisper.${OLLAMA_HOST}/api/tags(provider-model-fetcher.service.ts:953-957), not/v1/models.references/backlog.mdline 40. Line 41, the probe path, is cleared into this finding's note rather than raised.10 · [MISSING-CASE] Kiro's connect screen adds a Region and an optional Start URL, and no page mentions either
Page: https://manifest.build/docs/providers/subscription-based-providers (Connect a subscription, "Complete the auth flow")
What actually happens:
Options:
Suggested wording (option 1):
Evidence (for the agent)
providers/subscription-based-providers.mdx:50-52— "Manifest opens the provider's OAuth page, shows you a device code to enter, or asks you to paste a token, depending on the provider. Once it's saved, you're routed.":22says only "Device code".grep -rn "Start URL\|Kiro" providers/*.mdxreturns two hits: that row, andproviders/api-key-providers.mdx:46inside the subscription-only Note.packages/frontend/src/components/DeviceCodeDetailView.tsx:415-431renders the Kiro Region as a fixed-optionSelect,:432-452theStart URL (optional)input with placeholderhttps://your-domain.awsapps.com/start.KIRO_DEFAULT_REGION = 'us-east-1'(:42), seeded into the signal at:97, andKIRO_REGION_OPTIONS(:43onward) is a closed list of AWS region ids.:166-169). The screen offers a closed dropdown, so nothing on it can reach that refusal. It is frontend-only:grep -rn "valid AWS region" packages/returns that one production hit, and an API caller posting a Kiro region gets the backend's own different message (provider.controller.ts:151-153).:396-414, the paste-token alternative at:469-505.11 · [WRONG] The OpenCode Go tab says every request uses the Anthropic protocol. Most use the OpenAI one
Page: https://manifest.build/docs/providers/subscription-based-providers (the OpenCode Go tab)
What actually happens:
opencode.ai/zen/go.x-api-key.Options:
Suggested wording (option 1):
The draft does not say what decides the split, on purpose: what decides it is a third-party catalog lookup, which Seb ruled out of the docs on 2026-08-20.
Evidence (for the agent)
providers/subscription-based-providers.mdx:90-92— "Paste the subscription token from your OpenCode account. Requests use the Anthropic protocol againstopencode.ai/zen/go, authenticated via thex-api-keyheader."packages/backend/src/routing/proxy/provider-endpoints.ts:536-542— theopencode-goendpoint isbuildHeaders: openaiHeaders,buildPath: openaiPath,format: 'openai'.openaiHeadersisAuthorization: Bearer(:71-74).:543-548— theopencode-go-anthropicvariant usesanthropicApiKeyHeadersand/v1/messages, on the same base URL (OPENCODE_GO_BASE,:140).packages/backend/src/routing/proxy/provider-client.ts:503-510.references/decisions.md:43cleared "the OpenCode Go x-api-key variant is a real supported variant" on 2026-08-11, and that stays true: the variant exists and this finding does not dispute it. What this finding disputes is the doc's universal, "every request uses it". The endpoint has beenformat: 'openai'since it was added, so nothing in the platform moved; a cleared near-finding is not a Seb verdict, so norevisedderivation is owed (precedentsdecisions.md:133,:155,:157).12 · [WRONG] Three rows of "Where to get a key" send you to a China-mainland console, and Manifest calls those three providers on their international hosts
Page: https://manifest.build/docs/providers/api-key-providers (Supported providers)
What actually happens:
api.moonshot.ai,api.minimax.io,api.z.ai.Options:
Suggested wording (option 1 takes the whole list, option 2 the first three). Docs value first.
The three regional ones:
https://platform.moonshot.cn->https://platform.moonshot.ai/https://platform.minimaxi.com->https://platform.minimax.io/user-center/basic-information/interface-keyhttps://bigmodel.cn->https://z.ai/manage-apikey/apikey-listThe deep-link differences, option 1 only:
https://platform.openai.com->https://platform.openai.com/api-keyshttps://console.anthropic.com->https://console.anthropic.com/settings/keyshttps://ai.google.dev->https://aistudio.google.com/apikeyhttps://x.ai/api->https://docs.x.ai/docs/api-referencehttps://platform.deepseek.com->https://platform.deepseek.com/api_keyshttps://console.mistral.ai->https://console.mistral.ai/api-keys/https://console.groq.com->https://console.groq.com/keyshttps://fireworks.ai->https://app.fireworks.ai/api-keyshttps://build.nvidia.com->https://build.nvidia.com/settings/api-keyshttps://aws.amazon.com/bedrock->https://docs.aws.amazon.com/bedrock/latest/userguide/api-keys-generate.htmlhttps://bailian.console.aliyun.com->https://www.alibabacloud.com/help/en/model-studio/developer-reference/get-api-keyEvidence (for the agent)
providers/api-key-providers.mdx:16-37. The three regional cells are:24(Moonshot),:25(MiniMax),:26(Z.ai).packages/frontend/src/services/provider-api-key-urls.ts:5-33, rendered as "Get {Provider} API key" atpackages/frontend/src/components/ProviderKeyForm.tsx:377-388.packages/backend/src/routing/proxy/provider-endpoints.ts:331-337(minimax->https://api.minimax.io),:365-371(moonshot->https://api.moonshot.ai),:406-412(zai->https://api.z.ai). The China-mainland hosts exist in this codebase only on the subscription side (packages/backend/src/routing/zai-region.ts:1-4,DeviceCodeDetailView.tsx:410-411).references/backlog.mdline 68 (the Google row), folded in as a deep-link difference rather than raised on its own.Questions, not findings
Is the LM Studio / llama.cpp connect screen reachable? Four agents this run independently traced the same dead end:
openLocalServer(packages/frontend/src/components/ProviderSelectContent.tsx:199-202) has no caller anywhere inpackages/frontend/src, and the only other way to mount the port-probe screen needs aprovider=custom:<uuid>deep link that nothing produces. If that holds, clicking Connect on the LM Studio tile never shows the port probe, and thelms server start --bind 0.0.0.0 --port 1234 --corsinstructions atproviders/local-models.mdx:63-89describe a screen no user reaches. Nobody loaded the app. This is static reachability, not an observed run. One click on a self-hosted instance settles it, and it decides whether half of that page needs rewriting or nothing at all. Finding 9 does not depend on it. Finding 5 depends on it only for its first journey step, which describes the LM Studio and llama.cpp path; its Ollama half is backend and stands either way.glm-5.3on the Z.ai plan. Carried already as question 1 of issue Docs audit — 2026-08-20 (PRs #2740–#2743) #92. No providers page names it, so nothing is owed here. Flagged so the two runs do not diverge.Cleared this run (for the agent, so no future run re-derives it)
provider-endpoints.ts:516-521), LM Studio and llama.cpp (routed throughbuildCustomEndpoint,:583-606), and every OpenAI-format custom provider, with no switch to disable it. Not raised: issue Docs audit — 2026-08-20 (PRs #2740–#2743) #92 finding 1 is open onllm-gateway.mdx:95-108with a scope-free sentence as its recommended option, and a scope-free statement in the section that owns fallback triggers covers these providers by construction. Neither providers page states a fallback trigger, so their silence creates no halfway state. Raising a second locus would be the two-competing-fixes trap recorded on 2026-08-19. Re-check condition: if issue Docs audit — 2026-08-20 (PRs #2740–#2743) #92 finding 1 is rejected outright, this fact returns to the providers topic with no owner. Consumesreferences/backlog.mdline 72.model-discovery.service.ts:248-251,279-303). Not raised: the only way to explain the shrink is to explain what a third-party catalog used to contribute, rejected twice on 2026-08-20.api-key-providers.mdx:83-103). The premise holds: no page states a minimum, and the form enforces up to 100 characters for AWS Bedrock. Not raised: the form's own message names the number ("Key is too short (minimum 30 characters)",provider-utils.ts:24-29), which is actionable on its own; adding 24 length values to a reader-aid table is the noise the 2026-08-18 reversal of issue Docs audit — providers (api keys, subscriptions, local, custom) — 2026-08-11 #55 finding 4 rejected; and finding 2 removes the "double-check the prefix below" advice that made this a dead end. Retiresreferences/backlog.mdline 69.local-models.mdx:102"Network egress | None. Requests never leave the machine." The row's subject is the routed request, and routed requests genuinely never leave the machine. Qualifying it means describing scheduled outbound catalog fetches, which needs the third-party name Seb banned. Retiresreferences/backlog.mdline 42.local-models.mdx:56naming/v1/modelswhere the Ollama tile calls/api/tags. True (provider-model-fetcher.service.ts:953-957) but harm is nil: Ollama serves/v1/modelstoo, so a reader curling the documented URL gets the right answer. Folded into finding 9's wording note. Retiresreferences/backlog.mdline 41.packages/shared/src/providers.ts:55-62). The caveat is inside the display name and reaches the model picker, so it is on screen at the moment of choice. Restating another vendor's training policy is the class rejected on 2026-08-20. Retiresreferences/backlog.mdline 15.providers/api-key-providers.mdx:75,providers/subscription-based-providers.mdx:38and:56all tell the reader to click it, andimages/holds 12 files of which one is a providers shot. NOT raised. The park condition onreferences/backlog.mdline 45 has not fired: the trigger would be the platform giving the user a new reason to press the button, and what changed is a docs finding Seb has not ruled on yet. Four image asks are already open awaiting a capture session (Docs audit — llm gateway (routing, tiers, fallbacks) — 2026-08-09 #53 f5, Docs audit — observability (requests page, analytics) — 2026-08-10 #54 f4, Docs audit — 2026-08-17 (PR #2727) #70 f5, Docs audit — 2026-08-20 (PRs #2740–#2743) #92 f2), anddecisions.md:144(e)and:147both record that a pending capture argues against a new one. Line 45 stays parked, unchanged.providers/subscription-based-providers.mdx:96-99, the plan-ceiling Note ("If you hit a plan ceiling, requests fall through to your next fallback model, usually a different provider on an API key."). Mechanism:shouldTriggerFallbackisstatus >= 400with no exclusions (fallback-status-codes.ts:1-3). Not raised, and this run states no conclusion about what a 429 does next: Seb's standing correction of 2026-08-20 (decisions.md:175) governs, and issue Docs audit — llm gateway (routing, tiers, fallbacks) — 2026-08-19 #82 findings 2 and 4 were rejected on this mechanic as "no fact the reader acts on".custom-providers.mdx:55, the SSRF paragraph. Every clause holds where it counts (url-validation.ts:4-17,147-183, revalidationprovider-client.ts:401-411). The one corner it does not cover, self-hostedhttp://to a public host still being refused (url-validation.ts:214-218), is scoped away by the sentence's own "inside your own network" clause and produces an explicit error.api-key-providers.mdx:14-37). 22 rows + Gemini Free + the Vertex row arriving in docs PR docs: Google Vertex AI (issue #85) #91 = 24, which matches the rendered card count: 35 registry ids (packages/frontend/src/services/providers.ts:505-541), minus 3localOnly(packages/shared/src/providers.ts:371,384,397), minus 8subscriptionOnly(packages/frontend/src/services/providers.ts:194,217,242,261,288,384,406,447), filter atProviderConnectionsPage.tsx:119-124.keyPrefix, and the five providers named at:103all carry an empty one. The 2026-08-18 reversal holds: no row is proposed for a prefix the form does not enforce.subscriptionAuthModeinpackages/frontend/src/services/providers.ts.subscription-based-providers.mdx:36-39are exactly the four inpackages/shared/src/subscription/configs.ts:157.provider-endpoints.tsand the OAuth services.app.manifest.build, or the illustrativehttps://my-vllm.internal:8000/v1. Near-hits classified rather than dismissed:api-key-providers.mdx:43"its own gateway" is a descriptor, not a name;subscription-based-providers.mdx:68anthropic-beta: oauth-2025-04-20is a public Anthropic flag;subscription-based-providers.mdx:63chatgpt.com/backend-api/codex/responsesis OpenAI's own public backend URL, cleared on 2026-08-11 and 2026-08-12.Reply with the finding number and the chosen option
(e.g. "3 → option 2", or "3: other idea, let's discuss").
PRs are then made manually with the agent, never by the harness.
Rejected findings: reply "N → reject" and they will never be re-raised.