FN-6219: prefer fresh agent model settings for automatic runs

Automatic agent runs now resolve lane-specific task and settings models before falling back to durable runtime defaults.

- Prefer fresh execution, planning, heartbeat, merger, and validator model resolution over stale assigned-agent runtime config when complete settings are available.
- Centralize validator session model resolution and align mission validation with the shared helper.
- Add regression coverage for automatic run model precedence and test-mode handling.
- Add a patch changeset for the published CLI package.

Files changed:
 .changeset/fuzzy-agents-run.md                     |   5 +
 .../core/src/__tests__/model-resolution.test.ts    |  40 +++++
 .../agent-session-helpers-test-mode.test.ts        |  35 ++--
 .../src/__tests__/agent-session-helpers.test.ts    | 199 ++++++++++++++++++---
 .../src/__tests__/heartbeat-executor.test.ts       |  10 +-
 .../src/__tests__/mission-execution-loop.test.ts   |   6 +-
 packages/engine/src/__tests__/triage.test.ts       |   4 +-
 packages/engine/src/agent-session-helpers.ts       | 111 +++++++-----
 packages/engine/src/mission-execution-loop.ts      |  33 +---
 packages/engine/src/triage.ts                      |   6 +-
 10 files changed, 326 insertions(+), 123 deletions(-)

Fusion-Task-Id: FN-6219
Fusion-Task-Lineage: c3ef6d91-234f-4ed1-97c7-9be8408cd86c
This commit is contained in:
gsxdsm
2026-06-11 00:14:43 -07:00
parent 28e74c74db
commit 4ea9d66b4f
10 changed files with 336 additions and 133 deletions

View File

@@ -14,9 +14,12 @@ import type { AgentSession } from "@earendil-works/pi-coding-agent";
import {
isTestModeActive,
resolveExecutionSettingsModel,
resolveProjectDefaultModel,
resolveTaskExecutionModel,
resolveTaskPlanningModel,
resolveTaskValidatorModel,
TEST_MODE_RESOLVED,
type ResolvedModelSelection,
type Settings,
} from "@fusion/core";
import { resolveRuntime, buildRuntimeResolutionContext, isMockProviderId, type SessionPurpose } from "./runtime-resolution.js";
@@ -131,6 +134,32 @@ export function extractRuntimeModel(
};
}
function hasCompleteRuntimeModel(
model: ResolvedModelSelection,
): model is { provider: string; modelId: string } {
return Boolean(model.provider && model.modelId);
}
function pickSettingsThenRuntimeModel(
settingsModel: ResolvedModelSelection,
assignedAgentRuntimeConfig?: Record<string, unknown>,
): { provider: string | undefined; modelId: string | undefined } {
if (settingsModel.provider && settingsModel.modelId) {
return {
provider: settingsModel.provider,
modelId: settingsModel.modelId,
};
}
const assignedRuntimeModel = extractRuntimeModel(assignedAgentRuntimeConfig);
return hasCompleteRuntimeModel(assignedRuntimeModel)
? assignedRuntimeModel
: {
provider: settingsModel.provider,
modelId: settingsModel.modelId,
};
}
export function resolveExecutorSessionModel(
taskModelProvider: string | undefined,
taskModelId: string | undefined,
@@ -144,11 +173,6 @@ export function resolveExecutorSessionModel(
};
}
const assignedRuntimeModel = extractRuntimeModel(assignedAgentRuntimeConfig);
if (assignedRuntimeModel.provider && assignedRuntimeModel.modelId) {
return assignedRuntimeModel;
}
const resolvedTaskModel = resolveTaskExecutionModel(
{
modelProvider: taskModelProvider,
@@ -157,10 +181,7 @@ export function resolveExecutorSessionModel(
settings,
);
return {
provider: resolvedTaskModel.provider,
modelId: resolvedTaskModel.modelId,
};
return pickSettingsThenRuntimeModel(resolvedTaskModel, assignedAgentRuntimeConfig);
}
export function resolvePlanningSessionModel(
@@ -176,11 +197,6 @@ export function resolvePlanningSessionModel(
};
}
const assignedRuntimeModel = extractRuntimeModel(assignedAgentRuntimeConfig);
if (assignedRuntimeModel.provider && assignedRuntimeModel.modelId) {
return assignedRuntimeModel;
}
const resolvedTaskPlanningModel = resolveTaskPlanningModel(
{
planningModelProvider: taskPlanningModelProvider,
@@ -189,10 +205,31 @@ export function resolvePlanningSessionModel(
settings,
);
return {
provider: resolvedTaskPlanningModel.provider,
modelId: resolvedTaskPlanningModel.modelId,
};
return pickSettingsThenRuntimeModel(resolvedTaskPlanningModel, assignedAgentRuntimeConfig);
}
export function resolveValidatorSessionModel(
taskValidatorModelProvider: string | undefined,
taskValidatorModelId: string | undefined,
settings: Partial<Settings> | undefined,
assignedAgentRuntimeConfig?: Record<string, unknown>,
): { provider: string | undefined; modelId: string | undefined } {
if (isTestModeActive(settings)) {
return {
provider: TEST_MODE_RESOLVED.provider,
modelId: TEST_MODE_RESOLVED.modelId,
};
}
const resolvedTaskValidatorModel = resolveTaskValidatorModel(
{
validatorModelProvider: taskValidatorModelProvider,
validatorModelId: taskValidatorModelId,
},
settings,
);
return pickSettingsThenRuntimeModel(resolvedTaskValidatorModel, assignedAgentRuntimeConfig);
}
export function resolveHeartbeatSessionModels(
@@ -213,21 +250,14 @@ export function resolveHeartbeatSessionModels(
};
}
const assignedRuntimeModel = extractRuntimeModel(assignedAgentRuntimeConfig);
const executionSettingsModel = resolveExecutionSettingsModel(settings);
const defaultProvider = assignedRuntimeModel.provider ?? executionSettingsModel.provider;
const defaultModelId = assignedRuntimeModel.modelId ?? executionSettingsModel.modelId;
const executionPairAvailable = Boolean(executionSettingsModel.provider && executionSettingsModel.modelId);
const defaultMatchesExecution =
defaultProvider === executionSettingsModel.provider && defaultModelId === executionSettingsModel.modelId;
const resolvedModel = pickSettingsThenRuntimeModel(executionSettingsModel, assignedAgentRuntimeConfig);
return {
defaultProvider,
defaultModelId,
fallbackProvider: executionPairAvailable && !defaultMatchesExecution ? executionSettingsModel.provider : undefined,
fallbackModelId: executionPairAvailable && !defaultMatchesExecution ? executionSettingsModel.modelId : undefined,
defaultProvider: resolvedModel.provider,
defaultModelId: resolvedModel.modelId,
fallbackProvider: undefined,
fallbackModelId: undefined,
};
}
@@ -242,22 +272,11 @@ export function resolveMergerSessionModel(
};
}
const assignedRuntimeModel = extractRuntimeModel(assignedAgentRuntimeConfig);
if (assignedRuntimeModel.provider && assignedRuntimeModel.modelId) {
return assignedRuntimeModel;
}
if (settings?.defaultProviderOverride && settings.defaultModelIdOverride) {
return {
provider: settings.defaultProviderOverride,
modelId: settings.defaultModelIdOverride,
};
}
return {
provider: settings?.defaultProvider,
modelId: settings?.defaultModelId,
};
// Merger intentionally uses the default lane rather than execution/validator
// lanes. Validator-specific callers resolve `resolveValidatorSettingsModel`
// before falling back here; generic merger work uses project/global defaults.
const defaultModel = resolveProjectDefaultModel(settings);
return pickSettingsThenRuntimeModel(defaultModel, assignedAgentRuntimeConfig);
}
/**