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) <noreply@runfusion.ai>
This commit is contained in:
gsxdsm
2026-07-10 11:26:16 -07:00
parent db9b9d22c4
commit 84fb5138fc
8 changed files with 78 additions and 6 deletions

View File

@@ -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.

View File

@@ -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<string, string>` | `undefined` | Named script map used by script-mode workflow steps and setup hooks. |

View File

@@ -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 |

View File

@@ -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,
});

View File

@@ -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);

View File

@@ -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[] = [

View File

@@ -48,11 +48,13 @@ type MovedProjectSettingsKey =
| "planningThinkingLevel"
| "planningFallbackProvider"
| "planningFallbackModelId"
| "planningFallbackThinkingLevel"
| "validatorProvider"
| "validatorModelId"
| "validatorThinkingLevel"
| "validatorFallbackProvider"
| "validatorFallbackModelId";
| "validatorFallbackModelId"
| "validatorFallbackThinkingLevel";
type ProjectSettingsSchema = Omit<ProjectSettings, MovedProjectSettingsKey>;
@@ -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,

View File

@@ -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.