Skip to content

refactor(llm): route llm.default reads through resolveCallSiteConfig#30258

Merged
noanflaherty merged 1 commit into
mainfrom
credence/llm-default-resolver-routing
May 11, 2026
Merged

refactor(llm): route llm.default reads through resolveCallSiteConfig#30258
noanflaherty merged 1 commit into
mainfrom
credence/llm-default-resolver-routing

Conversation

@credence-the-bot
Copy link
Copy Markdown
Contributor

@credence-the-bot credence-the-bot Bot commented May 11, 2026

What

Routes ~25 call sites that read config.llm.default.* through resolveCallSiteConfig so they respect the active inference profile, not always the literal llm.default value.

Why

With managed + user profiles (Phase 1.2 #30232), the active profile can differ from llm.default. Direct reads of llm.default.{provider, model, contextWindow, ...} return stale / wrong values when a profile is active.

How

Widen ProvidersConfig.llm from { default: {...} } to LLMConfig. All production callers pass getConfig() which already has llm: LLMConfig. Five test files that construct ProvidersConfig manually were updated to spread LLMSchema.parse({}).

Tier 1 — Dispatch path (behavioral bugs fixed)

  • providers/connection-resolution.ts
  • providers/registry.ts (also widens ProvidersConfig.llm)
  • daemon/conversation.ts (3 sites)
  • daemon/conversation-agent-loop.ts
  • daemon/conversation-store.ts
  • subagent/manager.ts

Tier 2 — Playground / context-window routes

  • runtime/routes/playground/{state,inject-failures,reset-circuit}.ts

Tier 3 — Display / diagnostic

  • daemon/handlers/config-model.ts
  • daemon/conversation-slash.ts
  • runtime/routes/debug-routes.ts
  • usage/attribution.ts
  • workspace/provider-commit-message-generator.ts
  • memory/embedding-backend.ts

Out of scope (separate follow-up)

setModel in handlers/config-model.ts still writes to llm.default via setLlmDefaultField. Only the read side (no-op detection + provider-change detection) is fixed here. Whether the write should target the active profile is a UX call (managed-profile edge case, null-profile case) — design doc + follow-up PR pending.

Also out of scope: providers/registry.ts ollama-init guard now uses the resolved mainAgent provider (strict improvement over the previous llm.default.provider === "ollama" check). Fully scanning all profiles for ollama usage is a bigger change deferred separately.

CI status — pre-existing main breakage

Type Check + Test on Assistant Checks are red, but the failures are pre-existing on main from the OAuth CLI IPC refactor cascade (#30251, #30253, #30255). Same failures reproduce against bare origin/main (3bdf0ed885) — see run 25649496257.

This PR does not introduce new failures:

  • All 6 test files touched or affected by this PR pass on CI (inference-no-mode-boot-e2e, provider-commit-message-generator, provider-registry-ollama, provider-managed-proxy-integration, secret-routes-managed-proxy, satellite-connection-routing).
  • The 18 failing test files on CI are all OAuth/CLI-related (oauth-cli, oauth-providers-routes, all cli/commands/oauth/*, inference-send, routes, credentials-cli, credential-security-invariants, mcp-auth-routes, usage-cli, guard-tests) — none touched here. Each fails identically against bare main.
  • The one Type Check error (oauth-cli.test.ts:440 requirePlatformClient) is identical on bare main.

Recommend admin-merging once main goes green, or admin-merging now with the standard red-CI acceptance.

@credence-the-bot credence-the-bot Bot force-pushed the credence/llm-default-resolver-routing branch from 9ff3106 to 571326b Compare May 11, 2026 04:01
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 potential issues.

View 5 additional findings in Devin Review.

Open in Devin Review


const fastModeEnabled = isAssistantFeatureFlagEnabled("fast-mode", config);
const resolvedSpeed = speedOverride ?? config.llm.default.speed;
const resolvedSpeed = speedOverride ?? resolvedMainAgent.speed;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Incomplete migration: thinking and effort still read from raw llm.default while sibling fields use resolved config

The PR migrates streamThinking (line 438), speed (line 490), and contextWindow (line 497) to read from resolvedMainAgent (i.e. resolveCallSiteConfig("mainAgent", config.llm)), but the agentLoopConfig at assistant/src/daemon/conversation.ts:502-503 still reads thinking and effort from the raw llmDefault (config.llm.default). When a user has an active profile or callSites.mainAgent override that changes thinking.enabled or effort, those overrides are respected for streamThinking and speed but silently ignored for the agent loop's actual thinking/effort wire config. For example, a profile that sets thinking: { enabled: false } would cause this.streamThinking to be false (resolved) but agentLoopConfig.thinking.enabled to remain true (raw default), leading to the model still receiving thinking instructions.

Prompt for agents
In assistant/src/daemon/conversation.ts, after line 437 introduces `const resolvedMainAgent = resolveCallSiteConfig("mainAgent", config.llm)`, lines 502-503 still read `thinking` and `effort` from `llmDefault` (which is `config.llm.default`). These should be migrated to use `resolvedMainAgent` for consistency with `streamThinking`, `speed`, and `contextWindow` which already use `resolvedMainAgent`. Specifically, change `thinking: llmDefault.thinking` to `thinking: resolvedMainAgent.thinking` and `effort: llmDefault.effort` to `effort: resolvedMainAgent.effort` in the `agentLoopConfig` object literal around line 501-503. The `llmDefault` variable on line 491 can then be removed if it has no other usages.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread assistant/src/subagent/manager.ts Outdated
if (!baseProvider) {
throw new Error(
`Subagent: default provider '${appConfig.llm.default.provider}' is not registered`,
`Subagent: default provider '${resolveCallSiteConfig("subagentSpawn", appConfig.llm).provider}' is not registered`,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Subagent error message resolves provider name from wrong call site

The error message at assistant/src/subagent/manager.ts:194 uses resolveCallSiteConfig("subagentSpawn", appConfig.llm).provider to report which provider failed, but the actual provider resolution is done by resolveDefaultProvider(appConfig) which internally uses resolveCallSiteConfig("mainAgent", ...) (assistant/src/providers/connection-resolution.ts:149). If subagentSpawn has a call-site override pointing to a different provider, the error message will report the wrong provider name, making debugging harder.

Suggested change
`Subagent: default provider '${resolveCallSiteConfig("subagentSpawn", appConfig.llm).provider}' is not registered`,
`Subagent: default provider '${resolveCallSiteConfig("mainAgent", appConfig.llm).provider}' is not registered`,
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@credence-the-bot credence-the-bot Bot force-pushed the credence/llm-default-resolver-routing branch from 571326b to 532559e Compare May 11, 2026 04:02
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9ff3106b50

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +149 to +150
const resolved = resolveCallSiteConfig("mainAgent", config.llm);
const connectionName = resolved.provider_connection;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Resolve bootstrap provider per caller, not mainAgent

Changing resolveDefaultProvider to read resolveCallSiteConfig("mainAgent", ...) makes every satellite that bootstraps a base provider (e.g. createApprovalCopyGenerator / createApprovalConversationGenerator in assistant/src/daemon/approval-generators.ts, SubagentManager in assistant/src/subagent/manager.ts) depend on the active main-agent profile even when their own call-site profile has a valid provider_connection. In the scenario where the active main-agent profile is temporarily unresolvable (missing credential/transient auth) but a satellite call site is valid, these paths now bail out early before wrapWithCallSiteRouting can reroute, so the feature fails unnecessarily.

Useful? React with 👍 / 👎.

@credence-the-bot credence-the-bot Bot force-pushed the credence/llm-default-resolver-routing branch from 532559e to 7254070 Compare May 11, 2026 04:12
@credence-the-bot
Copy link
Copy Markdown
Contributor Author

Thanks bots — fixed both Devin findings in the new commit:

  • conversation.ts:502-503thinking + effort now resolve via resolvedMainAgent for consistency with streamThinking/speed/contextWindow.
  • subagent/manager.ts:194 — error message now reports the mainAgent provider name (matches the actual resolution path through resolveDefaultProviderwrapWithCallSiteRouting).

On Codex P2 (connection-resolution.ts:150 — satellites depending on mainAgent resolvability):

This coupling is intentional per the plan and accepted as the design trade-off: resolveDefaultProvider returns the base provider for the active profile (most common case), and wrapWithCallSiteRouting layers per-call-site routing on top. The edge case Codex describes — mainAgent profile temporarily unresolvable while a satellite has a valid provider_connection — does exist, but the fix is structural (decouple bootstrap from mainAgent resolution, or make resolveDefaultProvider return null on satellite-only viability) and out of scope for the read-routing refactor. Filing as a follow-up if it bites in practice.

~30 call sites read `config.llm.default.{provider, model, contextWindow,
speed, thinking, maxTokens, ...}` directly instead of going through
`resolveCallSiteConfig`. With managed + user profiles, the active profile
can differ from `llm.default` and those reads return stale/wrong values.

This routes the reads through the resolver so they pick up the active
profile's effective config.

Interface change: widen `ProvidersConfig.llm` from `{ default: {...} }`
to `LLMConfig`. All production callers pass `getConfig()` which already
satisfies the wider type. Five test files needed manual updates to the
local `ProvidersConfig` construction (spread from `LLMSchema.parse({})`).

Tier 1 (dispatch path):
- providers/connection-resolution.ts
- providers/registry.ts (also widens ProvidersConfig.llm)
- daemon/conversation.ts (3 sites)
- daemon/conversation-agent-loop.ts
- daemon/conversation-store.ts
- subagent/manager.ts

Tier 2 (playground / context-window routes):
- runtime/routes/playground/{state,inject-failures,reset-circuit}.ts

Tier 3 (display / diagnostic):
- daemon/handlers/config-model.ts
- daemon/conversation-slash.ts
- runtime/routes/debug-routes.ts
- usage/attribution.ts
- workspace/provider-commit-message-generator.ts
- memory/embedding-backend.ts

Tests updated to construct full LLMConfig:
- __tests__/provider-managed-proxy-integration.test.ts
- __tests__/inference-no-mode-boot-e2e.test.ts
- __tests__/provider-registry-ollama.test.ts
- __tests__/secret-routes-managed-proxy.test.ts
- providers/__tests__/satellite-connection-routing.test.ts

Out of scope (separate follow-up):
- `setModel` in handlers/config-model.ts still writes to `llm.default` via
  `setLlmDefaultField`. With profiles, this should probably write to the
  active profile instead. Needs a design call.
@credence-the-bot credence-the-bot Bot force-pushed the credence/llm-default-resolver-routing branch from 7254070 to 883125c Compare May 11, 2026 04:25
@noanflaherty noanflaherty merged commit 9543802 into main May 11, 2026
12 checks passed
@noanflaherty noanflaherty deleted the credence/llm-default-resolver-routing branch May 11, 2026 04:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant