Repository navigation
[FEATURE] Let an agent carry its own model provider credential #5190
Description
Problem Statement
A multi-agent project runs every agent and delegate in one veryfront serve process, and all of them call the model with the same credential. The openai/ slot resolves OPENAI_API_KEY from the process environment when a model string is resolved (src/provider/model-registry.ts, registerShared("openai", ...)), so two agents that name openai/<model> cannot present different keys.
The per-agent hook cannot change that. resolveModelTransport can return headers, but the OpenAI request builder merges them first and then sets authorization from the slot's key (src/provider/runtime-loader/provider-request-init.ts, createRequestHeaders: new Headers(extraHeaders) followed by headers.set("authorization", ...)). We confirmed on the wire that a hook returning Authorization: Bearer <agent key> still arrives at the endpoint with the process key, while another header from the same hook arrives.
registerModelProvider(name, factory) is not a route either: the factory must return a complete ModelRuntime, and the built-in OpenAI runtime is not exported, so a project cannot ask for "the same provider, a different credential" without writing its own client.
Why it matters: when an OpenAI-compatible gateway sits in front of the model, the bearer is how the gateway identifies the caller for policy decisions, attribution and per-caller token budgets. With one key per process, the gateway sees one caller for every agent in the application, and per-agent controls are not possible without one process per agent.
Proposed Solution
Either of these would do; the first is the smaller change.
- Let
ResolvedModelTransportcarry a credential for the call, for examplecredential?: string, threaded intoLLMProviderConfig.credentialfor the provider created for that call. An agent would then setresolveModelTransport: () => ({ credential: process.env.GATEWAY_KEY_POLICIES }). Alternatively, treat anauthorizationheader that the hook sets explicitly as authoritative instead of overwriting it. - Export a documented way to create a model runtime from a built-in provider with an explicit
credentialandbaseURL, for examplecreateProviderModel("openai", modelId, { credential, baseURL })fromveryfront/provider, soregisterModelProvidercan register one named slot per agent.
In both cases the credential should stay out of prompts, tool metadata and logs, as the existing provider credential does.
Component
Agents
Alternatives Considered
- One process per agent through the agent service runtime. It works but turns a three-agent project into three deployments, and delegation then crosses the network.
- The
mistral/slot shares the OpenAI-compatible code path with its ownMISTRAL_API_KEYandMISTRAL_BASE_URL, so a second agent can be pointed at the same gateway with a second key. That gives at most two identities per process by repurposing a slot named for another vendor, andproviderName: "mistral"changes response handling. - A header set by the hook. It is overwritten, see above.
Example Use Case
As a platform team running Veryfront applications behind an OpenAI-compatible gateway (Envoy AI Gateway with OPA deciding each call by bearer), I want each agent in a delegates tree to present its own credential, so that the gateway can approve models, attribute usage and enforce token budgets per agent rather than per application, without patching Veryfront or splitting the application into one process per agent.
Verification
- I have searched the existing issues to make sure this is not a duplicate (Support first-class secret-manager integration for model provider credential #3870 is about loading provider keys from a secret manager, not about a credential per agent)
- I have provided sufficient context for the team to understand the request
Activity
kwakayama commentedon Oct 16, 2026
Agent Brief
Category: enhancement
Summary:
Let an agent's model transport hook supply the provider credential for a call, so agents in one process can present different credentials to the same built-in provider.
Current behavior:
openaislot insrc/provider/model-registry.tsreads its key and base URL from the process environment when a model string resolves, then callsprovider.createModel(id, { credential, baseURL, ... }).ResolvedModelTransport(src/agent/types.ts) hasmodel,headers,providerOptionsandreasoning. It has no credential field.createRequestHeadersinsrc/provider/runtime-loader/provider-request-init.tsbuilds headers fromextraHeadersand then callsheaders.set(apiKeyHeaderName, apiKey). A hook-suppliedauthorizationheader is therefore overwritten by the slot key. This matches the report. I did not reproduce it on the wire.modelruntime, but the built-in provider runtimes are not exported, so a project cannot build "the same provider with a different credential" without its own client.Desired behavior:
VeryfrontError, using the existing credential validation.credential?: stringonResolvedModelTransportthreaded intoLLMProviderConfig.credential, or (2) an exportedcreateProviderModel(provider, modelId, { credential, baseURL }). Option 1 is the smaller change.Key interfaces:
ResolvedModelTransportandModelTransportResolver(agentresolveModelTransporthook)LLMProviderConfig.credentialand the built-in provider registryAcceptance criteria:
openai/<model>in one process send different bearer credentials, verified with a mocked fetch (withMockFetch)Out of scope:
Verification plan:
deno task test:file src/agent/runtime/model-transport.test.tsdeno task test:file src/agent/runtime/provider-transport.test.tsdeno fmt --check,deno task lint:ci,deno task typecheckdeno task docson the pinned Deno version if the public types change.github/workflows/cicd.ymlcovering unit tests, lint, typecheck and docsHandoff: ready for agent
Thanks @dberesford. I reviewed the report against the current Veryfront Code implementation and reduced it to the Agent Brief above. The remaining work is bounded by the acceptance criteria and verification plan.
Generated by Claude Code