From 84fb5138fcd7f64eaee06f3c78fff487844d0d81 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Fri, 10 Jul 2026 11:26:16 -0700 Subject: [PATCH] FN-7793: add fallback-lane thinking-level settings foundation Adds the schema/storage foundation for per-fallback-lane thinking-level settings, without any runtime or UI consumption yet. - Add global `fallbackThinkingLevel` default settings key. - Add workflow-declared `planningFallbackThinkingLevel` and `validatorFallbackThinkingLevel` companion settings (enum-validated against THINKING_LEVELS) alongside their existing fallback provider/model pairs. - Add project-scoped `titleSummarizerFallbackThinkingLevel` default settings key. - Extend `GlobalSettings`/`ProjectSettings` TypeScript interfaces with the new optional `ThinkingLevel` fields and FNXC documentation comments. - Update settings-schema `MovedProjectSettingsKey` type to include the new workflow-moved thinking-level keys. - Update `docs/settings-reference.md` and `docs/storage.md` to document the new keys and correct workflow-vs-project scope for the pre-existing planning/validator fallback provider/model keys. - Extend `effective-settings-overlay` and `settings-parity` unit tests to cover the new keys' scope and inheritance behavior. - Add changeset `.changeset/fallback-thinking-levels.md` (minor). Files changed: .changeset/fallback-thinking-levels.md | 7 +++++++ docs/settings-reference.md | 4 ++++ docs/storage.md | 12 +++++++---- .../__tests__/effective-settings-overlay.test.ts | 6 ++++++ .../core/src/__tests__/settings-parity.test.ts | 10 ++++++++++ packages/core/src/builtin-workflow-settings.ts | 23 +++++++++++++++++++++- packages/core/src/settings-schema.ts | 10 +++++++++- packages/core/src/types.ts | 12 +++++++++++ 8 files changed, 78 insertions(+), 6 deletions(-) Fusion-Task-Id: FN-7793 Fusion-Task-Lineage: 0e4bac3e-01d2-4d17-9f0a-af87e8a66eee Co-authored-by: Fusion (runfusion.ai) --- .changeset/fallback-thinking-levels.md | 7 ++++++ docs/settings-reference.md | 4 ++++ docs/storage.md | 12 ++++++---- .../effective-settings-overlay.test.ts | 6 +++++ .../src/__tests__/settings-parity.test.ts | 10 ++++++++ .../core/src/builtin-workflow-settings.ts | 23 ++++++++++++++++++- packages/core/src/settings-schema.ts | 10 +++++++- packages/core/src/types.ts | 12 ++++++++++ 8 files changed, 78 insertions(+), 6 deletions(-) create mode 100644 .changeset/fallback-thinking-levels.md diff --git a/.changeset/fallback-thinking-levels.md b/.changeset/fallback-thinking-levels.md new file mode 100644 index 0000000000..4e1b02074b --- /dev/null +++ b/.changeset/fallback-thinking-levels.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": minor +--- + +summary: Add persisted thinking-level settings for every fallback model lane. +category: feature +dev: New optional ThinkingLevel keys — global `fallbackThinkingLevel`, workflow `planningFallbackThinkingLevel`/`validatorFallbackThinkingLevel`, project `titleSummarizerFallbackThinkingLevel`. Schema foundation only; no runtime/UI consumption yet. diff --git a/docs/settings-reference.md b/docs/settings-reference.md index b473f5e3c8..3cfc75ebf5 100644 --- a/docs/settings-reference.md +++ b/docs/settings-reference.md @@ -64,6 +64,7 @@ Defaults from `DEFAULT_GLOBAL_SETTINGS`; key scope from `GLOBAL_SETTINGS_KEYS`. | `modelPricingSource` | `string` | `undefined` | Source label/URL for the current pricing override set, currently the LiteLLM model pricing JSON when fetched through the dashboard. | | `fallbackProvider` | `string` | `undefined` | Fallback provider when the selected/default model hits transient provider failures or model-compatibility/auth-tier rejections. Dashboard chat also offers this fallback for explicit user-selected models, but the engine only swaps for retryable provider/model-selection failures. | | `fallbackModelId` | `string` | `undefined` | Fallback model ID (must pair with `fallbackProvider`). | +| `fallbackThinkingLevel` | `ThinkingLevel` | `undefined` | Optional global fallback-lane thinking override for the `fallbackProvider`/`fallbackModelId` pair. Inherits `defaultThinkingLevel` when unset. | | `defaultThinkingLevel` | `"off" \| "minimal" \| "low" \| "medium" \| "high" \| "xhigh"` | `undefined` | Default reasoning effort for AI sessions. `xhigh` requests maximum reasoning effort; Claude CLI adapters map it to `high` for non-Opus models and `max` for Opus models. If a provider/runtime rejects simultaneous `thinking` and `reasoning_effort` parameters, Fusion retries without the explicit thinking override instead of failing the run. | | `ntfyEnabled` | `boolean` | `false` | Enable ntfy push notifications. | | `failureNotificationMode` | `"sticky-only" \| "terminal-only" \| "all"` | `"sticky-only"` | Failure notification behavior. `sticky-only` defers failed-task notifications by `failureNotificationDelayMs` and suppresses transient self-recoveries. `terminal-only` suppresses while auto-retry is still active and only dispatches when `paused === true` or `column === "in-review"` with `status === "failed"`. `all` restores legacy immediate failure notifications. | @@ -529,6 +530,7 @@ Default notes: | `planningModelId` | `string` | `undefined` | Model ID for planning agents. | | `planningFallbackProvider` | `string` | `undefined` | Fallback provider for planning. | | `planningFallbackModelId` | `string` | `undefined` | Fallback model ID for planning. | +| `planningFallbackThinkingLevel` | `ThinkingLevel` | `undefined` | Optional workflow planning-fallback thinking override. Inherits the planning/default thinking level when unset. | | `defaultProviderOverride` | `string` | `undefined` | Project-level override for global default provider baseline. | | `defaultModelIdOverride` | `string` | `undefined` | Project-level override for global default model baseline. | | `defaultThinkingLevelOverride` | `ThinkingLevel` | `undefined` | Optional project default-lane thinking override used when a task does not set `thinkingLevel`; inherits `defaultThinkingLevel` when unset. | @@ -538,6 +540,7 @@ Default notes: | `validatorModelId` | `string` | `undefined` | Model ID for plan/code reviewers. | | `validatorFallbackProvider` | `string` | `undefined` | Fallback provider for reviewers; also used by reviewer UNAVAILABLE/error recovery retry before returning terminal UNAVAILABLE. | | `validatorFallbackModelId` | `string` | `undefined` | Fallback model ID for reviewers; paired with `validatorFallbackProvider` for reviewer recovery retry. | +| `validatorFallbackThinkingLevel` | `ThinkingLevel` | `undefined` | Optional workflow reviewer-fallback thinking override. Inherits the validator/default thinking level when unset. | | `workflowStepTimeoutMs` | `number` | `900000` | Maximum time in milliseconds a single workflow step may run before it is timed out. | | `modelPresets` | `ModelPreset[]` | `[]` | Reusable executor/reviewer model presets. | | `autoSelectModelPreset` | `boolean` | `false` | Auto-select presets by task size. | @@ -651,6 +654,7 @@ GitLab configuration examples: leave both URL fields blank for GitLab.com (`http | `titleSummarizerThinkingLevel` | `ThinkingLevel` | `undefined` | Optional project summarization-lane thinking override. Inherits `titleSummarizerGlobalThinkingLevel` or `defaultThinkingLevel` when unset. | | `titleSummarizerFallbackProvider` | `string` | `undefined` | Fallback provider for title summarization. | | `titleSummarizerFallbackModelId` | `string` | `undefined` | Fallback model ID for title summarization. | +| `titleSummarizerFallbackThinkingLevel` | `ThinkingLevel` | `undefined` | Optional project title-summarizer fallback thinking override. Inherits the title-summarizer/global/default thinking level when unset. | | `prTitlePromptInstructions` | `string` | `undefined` | Optional project guidance appended to the Create PR dialog's AI metadata system prompt for the generated PR title. Blank or whitespace-only values are treated as unset and keep the default prompt behavior. | | `prDescriptionPromptInstructions` | `string` | `undefined` | Optional project guidance appended to the Create PR dialog's AI metadata system prompt for generated PR body fields (`summary`, `changes`, `testing`). Blank or whitespace-only values are treated as unset and keep the default prompt behavior. | | `scripts` | `Record` | `undefined` | Named script map used by script-mode workflow steps and setup hooks. | diff --git a/docs/storage.md b/docs/storage.md index 39028c1c95..0538c9bc3f 100644 --- a/docs/storage.md +++ b/docs/storage.md @@ -296,6 +296,7 @@ API endpoints reviewed: | `defaultModelId` | Global | `GET/PUT /api/settings/global` | Default model id | | `fallbackProvider` | Global | `GET/PUT /api/settings/global` | Fallback model provider | | `fallbackModelId` | Global | `GET/PUT /api/settings/global` | Fallback model id | +| `fallbackThinkingLevel` | Global | `GET/PUT /api/settings/global` | Fallback model reasoning effort; unset inherits | | `defaultThinkingLevel` | Global | `GET/PUT /api/settings/global` | Default reasoning effort | | `ntfyEnabled` | Global | `GET/PUT /api/settings/global` | Notifications enabled | | `ntfyTopic` | Global | `GET/PUT /api/settings/global` | Ntfy topic | @@ -347,12 +348,14 @@ API endpoints reviewed: | `executionModelId` | Project | `GET/PUT /api/settings` | AI model ID for task execution | | `planningProvider` | Project | `GET/PUT /api/settings` | Planning model provider | | `planningModelId` | Project | `GET/PUT /api/settings` | Planning model id | -| `planningFallbackProvider` | Project | `GET/PUT /api/settings` | Planning fallback provider | -| `planningFallbackModelId` | Project | `GET/PUT /api/settings` | Planning fallback model id | +| `planningFallbackProvider` | Workflow | `fn_workflow_settings` / workflow settings API | Planning fallback provider | +| `planningFallbackModelId` | Workflow | `fn_workflow_settings` / workflow settings API | Planning fallback model id | +| `planningFallbackThinkingLevel` | Workflow | `fn_workflow_settings` / workflow settings API | Planning fallback reasoning effort; unset inherits | | `validatorProvider` | Project | `GET/PUT /api/settings` | Validator model provider | | `validatorModelId` | Project | `GET/PUT /api/settings` | Validator model id | -| `validatorFallbackProvider` | Project | `GET/PUT /api/settings` | Validator fallback provider | -| `validatorFallbackModelId` | Project | `GET/PUT /api/settings` | Validator fallback model id | +| `validatorFallbackProvider` | Workflow | `fn_workflow_settings` / workflow settings API | Validator fallback provider | +| `validatorFallbackModelId` | Workflow | `fn_workflow_settings` / workflow settings API | Validator fallback model id | +| `validatorFallbackThinkingLevel` | Workflow | `fn_workflow_settings` / workflow settings API | Validator fallback reasoning effort; unset inherits | | `modelPresets` | Project | `GET/PUT /api/settings` | Reusable model presets | | `autoSelectModelPreset` | Project | `GET/PUT /api/settings` | Auto-preset by task size | | `defaultPresetBySize` | Project | `GET/PUT /api/settings` | Size→preset mapping | @@ -381,6 +384,7 @@ API endpoints reviewed: | `titleSummarizerModelId` | Project | `GET/PUT /api/settings` | Title model id | | `titleSummarizerFallbackProvider` | Project | `GET/PUT /api/settings` | Title fallback provider | | `titleSummarizerFallbackModelId` | Project | `GET/PUT /api/settings` | Title fallback model id | +| `titleSummarizerFallbackThinkingLevel` | Project | `GET/PUT /api/settings` | Title fallback reasoning effort; unset inherits | | `scripts` | Project | `GET/PUT /api/settings` | Named script map | | `setupScript` | Project | `GET/PUT /api/settings` | Named setup script reference | | `insightExtractionEnabled` | Project | `GET/PUT /api/settings` | Insight extraction toggle | diff --git a/packages/core/src/__tests__/effective-settings-overlay.test.ts b/packages/core/src/__tests__/effective-settings-overlay.test.ts index 9bbb71d7ff..d547f471c9 100644 --- a/packages/core/src/__tests__/effective-settings-overlay.test.ts +++ b/packages/core/src/__tests__/effective-settings-overlay.test.ts @@ -23,8 +23,10 @@ describe("applyWorkflowSettingsOverlay", () => { planningModelId: "workflow-planner-model", planningFallbackProvider: "workflow-planner-fallback", planningFallbackModelId: "workflow-planner-fallback-model", + planningFallbackThinkingLevel: "high", validatorFallbackProvider: "workflow-validator-fallback", validatorFallbackModelId: "workflow-validator-fallback-model", + validatorFallbackThinkingLevel: "high", workflowStepTimeoutMs: 900_000, runStepsInNewSessions: false, maxParallelSteps: undefined, @@ -38,8 +40,10 @@ describe("applyWorkflowSettingsOverlay", () => { "planningModelId", "planningFallbackProvider", "planningFallbackModelId", + "planningFallbackThinkingLevel", "validatorFallbackProvider", "validatorFallbackModelId", + "validatorFallbackThinkingLevel", ]), }); @@ -60,8 +64,10 @@ describe("applyWorkflowSettingsOverlay", () => { planningModelId: "workflow-planner-model", planningFallbackProvider: "workflow-planner-fallback", planningFallbackModelId: "workflow-planner-fallback-model", + planningFallbackThinkingLevel: "high", validatorFallbackProvider: "workflow-validator-fallback", validatorFallbackModelId: "workflow-validator-fallback-model", + validatorFallbackThinkingLevel: "high", workflowStepTimeoutMs: 10_000, runStepsInNewSessions: false, }); diff --git a/packages/core/src/__tests__/settings-parity.test.ts b/packages/core/src/__tests__/settings-parity.test.ts index 5ba9cd4012..4f93ad7d2d 100644 --- a/packages/core/src/__tests__/settings-parity.test.ts +++ b/packages/core/src/__tests__/settings-parity.test.ts @@ -104,6 +104,8 @@ describe("settings key parity", () => { it("keeps model-lane thinking overrides in their owning settings scopes", () => { expect(DEFAULT_PROJECT_SETTINGS.defaultThinkingLevelOverride).toBeUndefined(); expect(DEFAULT_PROJECT_SETTINGS.titleSummarizerThinkingLevel).toBeUndefined(); + expect(DEFAULT_PROJECT_SETTINGS.titleSummarizerFallbackThinkingLevel).toBeUndefined(); + expect(DEFAULT_GLOBAL_SETTINGS.fallbackThinkingLevel).toBeUndefined(); expect(DEFAULT_GLOBAL_SETTINGS.executionGlobalThinkingLevel).toBeUndefined(); expect(DEFAULT_GLOBAL_SETTINGS.planningGlobalThinkingLevel).toBeUndefined(); expect(DEFAULT_GLOBAL_SETTINGS.validatorGlobalThinkingLevel).toBeUndefined(); @@ -111,6 +113,12 @@ describe("settings key parity", () => { expect(isProjectSettingsKey("defaultThinkingLevelOverride")).toBe(true); expect(isProjectSettingsKey("titleSummarizerThinkingLevel")).toBe(true); + expect(isProjectSettingsKey("titleSummarizerFallbackThinkingLevel")).toBe(true); + expect(isGlobalSettingsKey("fallbackThinkingLevel")).toBe(true); + expect(isProjectSettingsKey("planningFallbackThinkingLevel")).toBe(false); + expect(isGlobalSettingsKey("planningFallbackThinkingLevel")).toBe(false); + expect(isProjectSettingsKey("validatorFallbackThinkingLevel")).toBe(false); + expect(isGlobalSettingsKey("validatorFallbackThinkingLevel")).toBe(false); expect(isGlobalSettingsKey("executionGlobalThinkingLevel")).toBe(true); expect(isGlobalSettingsKey("planningGlobalThinkingLevel")).toBe(true); expect(isGlobalSettingsKey("validatorGlobalThinkingLevel")).toBe(true); @@ -287,10 +295,12 @@ describe("settings key parity", () => { "planningModelId", "planningFallbackProvider", "planningFallbackModelId", + "planningFallbackThinkingLevel", "validatorProvider", "validatorModelId", "validatorFallbackProvider", "validatorFallbackModelId", + "validatorFallbackThinkingLevel", ]; for (const key of movedKeys) { expect(isProjectSettingsKey(key)).toBe(false); diff --git a/packages/core/src/builtin-workflow-settings.ts b/packages/core/src/builtin-workflow-settings.ts index 504d14cac5..6ed43d2e18 100644 --- a/packages/core/src/builtin-workflow-settings.ts +++ b/packages/core/src/builtin-workflow-settings.ts @@ -205,7 +205,10 @@ export const BUILTIN_MOVED_WORKFLOW_SETTINGS: WorkflowSettingDefinition[] = [ // falls through to the global lane / project default (KTD-7). /* * FNXC:Settings-ThinkingLevel 2026-07-10-00:00: - * Workflow-declared primary model lanes may pin a thinking effort per (workflow, project). Empty values inherit through the lane/global/default chain, enum options are validated against THINKING_LEVELS, and fallback/title-summarizer lanes intentionally reuse their primary lane thinking level instead of adding separate companion settings. + * Workflow-declared primary model lanes may pin a thinking effort per (workflow, project). Empty values inherit through the lane/global/default chain, enum options are validated against THINKING_LEVELS. + * + * FNXC:Settings-ThinkingLevel 2026-07-10-11:13: + * FN-7793: planning/validator fallback lanes now get their own companion thinking-level settings (`planningFallbackThinkingLevel`/`validatorFallbackThinkingLevel`, declared below), matching the global `fallbackThinkingLevel` and project `titleSummarizerFallbackThinkingLevel` keys — fallback lanes no longer merely reuse their primary lane's thinking level. */ { id: "executionProvider", @@ -257,6 +260,17 @@ export const BUILTIN_MOVED_WORKFLOW_SETTINGS: WorkflowSettingDefinition[] = [ type: "string", description: "Fallback model id for the planning phase.", }, + { + /* + * FNXC:Settings-ThinkingLevel 2026-07-10-11:13: + * Planning and validator fallback thinking levels are workflow-declared companions to their fallback provider/model lanes, persisted per (workflow, project). Empty means inherit; FN-7793 only declares the storage surface. + */ + id: "planningFallbackThinkingLevel", + name: "Planning fallback thinking level", + type: "enum", + options: THINKING_LEVELS.map((level) => ({ value: level, label: level })), + description: "Thinking effort for the planning fallback model. Empty inherits from the task or default thinking level.", + }, { id: "validatorProvider", name: "Validator provider", @@ -288,6 +302,13 @@ export const BUILTIN_MOVED_WORKFLOW_SETTINGS: WorkflowSettingDefinition[] = [ type: "string", description: "Fallback model id for the validation phase.", }, + { + id: "validatorFallbackThinkingLevel", + name: "Validator fallback thinking level", + type: "enum", + options: THINKING_LEVELS.map((level) => ({ value: level, label: level })), + description: "Thinking effort for the validator fallback model. Empty inherits from the task or default thinking level.", + }, ]; export const BUILTIN_TRIAGE_POLICY_SETTINGS: WorkflowSettingDefinition[] = [ diff --git a/packages/core/src/settings-schema.ts b/packages/core/src/settings-schema.ts index ea9cb0e73e..2f07af69ee 100644 --- a/packages/core/src/settings-schema.ts +++ b/packages/core/src/settings-schema.ts @@ -48,11 +48,13 @@ type MovedProjectSettingsKey = | "planningThinkingLevel" | "planningFallbackProvider" | "planningFallbackModelId" + | "planningFallbackThinkingLevel" | "validatorProvider" | "validatorModelId" | "validatorThinkingLevel" | "validatorFallbackProvider" - | "validatorFallbackModelId"; + | "validatorFallbackModelId" + | "validatorFallbackThinkingLevel"; type ProjectSettingsSchema = Omit; @@ -108,6 +110,11 @@ export const DEFAULT_GLOBAL_SETTINGS = { mergeRequestContractShadowEnabled: false, fallbackProvider: undefined, fallbackModelId: undefined, + /* + FNXC:Settings-ThinkingLevel 2026-07-10-11:13: + Fallback thinking levels mirror their provider/model scope: global fallbackThinkingLevel is global, planning/validator fallback thinking levels are workflow-moved, and titleSummarizerFallbackThinkingLevel stays project-scoped. Undefined preserves inheritance until runtime/UI follow-ups consume the stored values. + */ + fallbackThinkingLevel: undefined, defaultThinkingLevel: undefined, ntfyEnabled: false, ntfyTopic: undefined, @@ -580,6 +587,7 @@ export const DEFAULT_PROJECT_SETTINGS = { titleSummarizerThinkingLevel: undefined, titleSummarizerFallbackProvider: undefined, titleSummarizerFallbackModelId: undefined, + titleSummarizerFallbackThinkingLevel: undefined, prTitlePromptInstructions: undefined, prDescriptionPromptInstructions: undefined, scripts: undefined, diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 4877cd2bdf..ebff481491 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -3236,6 +3236,12 @@ export interface GlobalSettings { * model fails due to transient provider-side issues such as rate limits or * overloaded capacity. Must be set together with `fallbackProvider`. */ fallbackModelId?: string; + /** + * FNXC:Settings-ThinkingLevel 2026-07-10-11:13: + * Fallback model lanes carry optional thinking companions so a swapped-in fallback can run at its own reasoning effort. Undefined means inherit; FN-7793 stores the schema foundation only, without runtime application or UI wiring. + * Optional thinking effort for the global fallback model pair. Inherits the default thinking level when unset. + */ + fallbackThinkingLevel?: ThinkingLevel; /** Default thinking effort level for AI agent sessions. * Controls how much reasoning effort the model uses — higher levels * produce better results but cost more. When undefined, the engine @@ -4081,6 +4087,8 @@ export interface ProjectSettings { /** Fallback model ID for planning/triage. When unset, falls back to the * global fallback model. Must be set together with `planningFallbackProvider`. */ planningFallbackModelId?: string; + /** Workflow-declared planning fallback thinking override. Companion to the planning fallback provider/model pair; inherits when unset. */ + planningFallbackThinkingLevel?: ThinkingLevel; /** Project-level override for the base default AI model provider. * When set, this overrides the global `defaultProvider`/`defaultModelId` baseline * for all lanes that don't have their own explicit project override. @@ -4124,6 +4132,8 @@ export interface ProjectSettings { /** Fallback model ID for validator/reviewer. When unset, falls back to the * global fallback model. Must be set together with `validatorFallbackProvider`. */ validatorFallbackModelId?: string; + /** Workflow-declared validator fallback thinking override. Companion to the validator fallback provider/model pair; inherits when unset. */ + validatorFallbackThinkingLevel?: ThinkingLevel; /** Workflow-declared validator-lane thinking override. Inherits through task/default thinking when unset. */ validatorThinkingLevel?: ThinkingLevel; /** Reusable model configuration presets for task creation. */ @@ -4640,6 +4650,8 @@ export interface ProjectSettings { * planning fallback, then global fallback. Must be set together with * `titleSummarizerFallbackProvider`. */ titleSummarizerFallbackModelId?: string; + /** Optional project summarization fallback thinking override. Companion to the title summarizer fallback provider/model pair; inherits when unset. */ + titleSummarizerFallbackThinkingLevel?: ThinkingLevel; /** * FNXC:PrMetadataGeneration 2026-06-27-00:00: * Project operators can add title-specific guidance to the Create PR metadata prompt without replacing the strict JSON schema contract. Blank or whitespace-only values are treated as unset so the default prompt remains byte-for-byte unchanged.