feat(models): add background task model override config and runtime wiring

This commit is contained in:
William Valentin
2026-02-19 09:52:45 -08:00
parent 4df1291e64
commit 027f7ad283
6 changed files with 367 additions and 4 deletions
+151 -1
View File
@@ -1,6 +1,6 @@
import type { GatewayRequest, OutboundMessage } from '../protocol.js';
import { makeResponse, makeError, ErrorCode } from '../protocol.js';
import type { Config } from '../../config/index.js';
import { MODEL_PROVIDERS, type Config, type ModelProvider } from '../../config/index.js';
export interface ConfigHandlerDeps {
config: Config;
@@ -163,6 +163,156 @@ const PATCHABLE_KEYS: Record<string, (config: Config, value: unknown) => boolean
config.server.nodes.push.enabled = value;
return true;
},
'agents.primary_tier': (config, value) => {
if (value !== 'fast' && value !== 'default' && value !== 'complex' && value !== 'local') {return false;}
config.agents.primary_tier = value;
return true;
},
'agents.delegation.compaction': (config, value) => {
if (value !== 'fast' && value !== 'default' && value !== 'complex' && value !== 'local') {return false;}
config.agents.delegation.compaction = value;
return true;
},
'agents.delegation.memory_extraction': (config, value) => {
if (value !== 'fast' && value !== 'default' && value !== 'complex' && value !== 'local') {return false;}
config.agents.delegation.memory_extraction = value;
return true;
},
'agents.delegation.classification': (config, value) => {
if (value !== 'fast' && value !== 'default' && value !== 'complex' && value !== 'local') {return false;}
config.agents.delegation.classification = value;
return true;
},
'agents.delegation.tool_summarisation': (config, value) => {
if (value !== 'fast' && value !== 'default' && value !== 'complex' && value !== 'local') {return false;}
config.agents.delegation.tool_summarisation = value;
return true;
},
'agents.delegation.complex_reasoning': (config, value) => {
if (value !== 'fast' && value !== 'default' && value !== 'complex' && value !== 'local') {return false;}
config.agents.delegation.complex_reasoning = value;
return true;
},
'agents.background_models.compaction.enabled': (config, value) => {
if (typeof value !== 'boolean') {return false;}
config.agents.background_models.compaction ??= { enabled: true, provider: 'openai', model: 'gpt-4o-mini', fallback_tier: 'fast' };
config.agents.background_models.compaction.enabled = value;
return true;
},
'agents.background_models.compaction.provider': (config, value) => {
if (!MODEL_PROVIDERS.includes(String(value) as ModelProvider)) {return false;}
config.agents.background_models.compaction ??= { enabled: true, provider: 'openai', model: 'gpt-4o-mini', fallback_tier: 'fast' };
config.agents.background_models.compaction.provider = value as ModelProvider;
return true;
},
'agents.background_models.compaction.model': (config, value) => {
if (typeof value !== 'string' || value.trim().length === 0) {return false;}
config.agents.background_models.compaction ??= { enabled: true, provider: 'openai', model: 'gpt-4o-mini', fallback_tier: 'fast' };
config.agents.background_models.compaction.model = value.trim();
return true;
},
'agents.background_models.compaction.fallback_tier': (config, value) => {
if (value !== 'fast' && value !== 'default' && value !== 'complex' && value !== 'local') {return false;}
config.agents.background_models.compaction ??= { enabled: true, provider: 'openai', model: 'gpt-4o-mini', fallback_tier: 'fast' };
config.agents.background_models.compaction.fallback_tier = value;
return true;
},
'agents.background_models.memory_extraction.enabled': (config, value) => {
if (typeof value !== 'boolean') {return false;}
config.agents.background_models.memory_extraction ??= { enabled: true, provider: 'openai', model: 'gpt-4o-mini', fallback_tier: 'fast' };
config.agents.background_models.memory_extraction.enabled = value;
return true;
},
'agents.background_models.memory_extraction.provider': (config, value) => {
if (!MODEL_PROVIDERS.includes(String(value) as ModelProvider)) {return false;}
config.agents.background_models.memory_extraction ??= { enabled: true, provider: 'openai', model: 'gpt-4o-mini', fallback_tier: 'fast' };
config.agents.background_models.memory_extraction.provider = value as ModelProvider;
return true;
},
'agents.background_models.memory_extraction.model': (config, value) => {
if (typeof value !== 'string' || value.trim().length === 0) {return false;}
config.agents.background_models.memory_extraction ??= { enabled: true, provider: 'openai', model: 'gpt-4o-mini', fallback_tier: 'fast' };
config.agents.background_models.memory_extraction.model = value.trim();
return true;
},
'agents.background_models.memory_extraction.fallback_tier': (config, value) => {
if (value !== 'fast' && value !== 'default' && value !== 'complex' && value !== 'local') {return false;}
config.agents.background_models.memory_extraction ??= { enabled: true, provider: 'openai', model: 'gpt-4o-mini', fallback_tier: 'fast' };
config.agents.background_models.memory_extraction.fallback_tier = value;
return true;
},
'agents.background_models.classification.enabled': (config, value) => {
if (typeof value !== 'boolean') {return false;}
config.agents.background_models.classification ??= { enabled: true, provider: 'openai', model: 'gpt-4o-mini', fallback_tier: 'fast' };
config.agents.background_models.classification.enabled = value;
return true;
},
'agents.background_models.classification.provider': (config, value) => {
if (!MODEL_PROVIDERS.includes(String(value) as ModelProvider)) {return false;}
config.agents.background_models.classification ??= { enabled: true, provider: 'openai', model: 'gpt-4o-mini', fallback_tier: 'fast' };
config.agents.background_models.classification.provider = value as ModelProvider;
return true;
},
'agents.background_models.classification.model': (config, value) => {
if (typeof value !== 'string' || value.trim().length === 0) {return false;}
config.agents.background_models.classification ??= { enabled: true, provider: 'openai', model: 'gpt-4o-mini', fallback_tier: 'fast' };
config.agents.background_models.classification.model = value.trim();
return true;
},
'agents.background_models.classification.fallback_tier': (config, value) => {
if (value !== 'fast' && value !== 'default' && value !== 'complex' && value !== 'local') {return false;}
config.agents.background_models.classification ??= { enabled: true, provider: 'openai', model: 'gpt-4o-mini', fallback_tier: 'fast' };
config.agents.background_models.classification.fallback_tier = value;
return true;
},
'agents.background_models.tool_summarisation.enabled': (config, value) => {
if (typeof value !== 'boolean') {return false;}
config.agents.background_models.tool_summarisation ??= { enabled: true, provider: 'openai', model: 'gpt-4o-mini', fallback_tier: 'fast' };
config.agents.background_models.tool_summarisation.enabled = value;
return true;
},
'agents.background_models.tool_summarisation.provider': (config, value) => {
if (!MODEL_PROVIDERS.includes(String(value) as ModelProvider)) {return false;}
config.agents.background_models.tool_summarisation ??= { enabled: true, provider: 'openai', model: 'gpt-4o-mini', fallback_tier: 'fast' };
config.agents.background_models.tool_summarisation.provider = value as ModelProvider;
return true;
},
'agents.background_models.tool_summarisation.model': (config, value) => {
if (typeof value !== 'string' || value.trim().length === 0) {return false;}
config.agents.background_models.tool_summarisation ??= { enabled: true, provider: 'openai', model: 'gpt-4o-mini', fallback_tier: 'fast' };
config.agents.background_models.tool_summarisation.model = value.trim();
return true;
},
'agents.background_models.tool_summarisation.fallback_tier': (config, value) => {
if (value !== 'fast' && value !== 'default' && value !== 'complex' && value !== 'local') {return false;}
config.agents.background_models.tool_summarisation ??= { enabled: true, provider: 'openai', model: 'gpt-4o-mini', fallback_tier: 'fast' };
config.agents.background_models.tool_summarisation.fallback_tier = value;
return true;
},
'agents.background_models.complex_reasoning.enabled': (config, value) => {
if (typeof value !== 'boolean') {return false;}
config.agents.background_models.complex_reasoning ??= { enabled: true, provider: 'openai', model: 'gpt-4o-mini', fallback_tier: 'fast' };
config.agents.background_models.complex_reasoning.enabled = value;
return true;
},
'agents.background_models.complex_reasoning.provider': (config, value) => {
if (!MODEL_PROVIDERS.includes(String(value) as ModelProvider)) {return false;}
config.agents.background_models.complex_reasoning ??= { enabled: true, provider: 'openai', model: 'gpt-4o-mini', fallback_tier: 'fast' };
config.agents.background_models.complex_reasoning.provider = value as ModelProvider;
return true;
},
'agents.background_models.complex_reasoning.model': (config, value) => {
if (typeof value !== 'string' || value.trim().length === 0) {return false;}
config.agents.background_models.complex_reasoning ??= { enabled: true, provider: 'openai', model: 'gpt-4o-mini', fallback_tier: 'fast' };
config.agents.background_models.complex_reasoning.model = value.trim();
return true;
},
'agents.background_models.complex_reasoning.fallback_tier': (config, value) => {
if (value !== 'fast' && value !== 'default' && value !== 'complex' && value !== 'local') {return false;}
config.agents.background_models.complex_reasoning ??= { enabled: true, provider: 'openai', model: 'gpt-4o-mini', fallback_tier: 'fast' };
config.agents.background_models.complex_reasoning.fallback_tier = value;
return true;
},
'automation.delivery_mode': (config, value) => {
if (value !== 'shared_session' && value !== 'isolated_job' && value !== 'announce') {return false;}
config.automation ??= {} as Config['automation'];
+77 -1
View File
@@ -2,12 +2,13 @@ import { randomUUID } from 'crypto';
import type { SessionManager } from '../session/manager.js';
import type { ModelClient } from '../models/types.js';
import type { ModelRouter, ModelTier } from '../models/router.js';
import { createClientFromConfig } from '../daemon/models.js';
import type { Config, ModelConfig, ModelProvider } from '../config/index.js';
import type { ToolRegistry } from '../tools/registry.js';
import type { ToolExecutor } from '../tools/executor.js';
import { AgentOrchestrator, type DelegationConfig } from '../backends/native/orchestrator.js';
import type { ToolUseEvent } from '../backends/native/agent.js';
import type { MemoryStore } from '../memory/store.js';
import type { Config } from '../config/index.js';
import { summarizeSessionOnEnd, type SessionEndSummaryConfig } from '../session/endSummary.js';
export interface SessionBridgeConfig {
@@ -284,6 +285,7 @@ export class SessionBridge {
tool_summarisation: config?.agents.delegation.tool_summarisation ?? 'fast',
complex_reasoning: config?.agents.delegation.complex_reasoning ?? 'complex',
};
const backgroundModelOverrides = this.buildBackgroundModelOverrides();
agent = new AgentOrchestrator({
modelRouter: this.config.modelClient as ModelRouter,
@@ -293,6 +295,7 @@ export class SessionBridge {
toolExecutor: this.config.toolExecutor,
primaryTier,
delegation: delegationConfig,
backgroundModelOverrides,
maxDelegationDepth: config?.agents.max_delegation_depth ?? 3,
maxIterations: config?.agents.max_iterations,
compaction: config?.compaction.enabled ? {
@@ -337,4 +340,77 @@ export class SessionBridge {
}
return agent;
}
private buildBackgroundModelOverrides(): Partial<Record<keyof DelegationConfig, {
client: ModelClient;
label: string;
fallbackTier: ModelTier;
}>> {
const runtimeConfig = this.config.config;
const overrides: Partial<Record<keyof DelegationConfig, {
client: ModelClient;
label: string;
fallbackTier: ModelTier;
}>> = {};
if (!runtimeConfig) {
return overrides;
}
const providerConfigs = this.buildProviderConfigMap(runtimeConfig);
const configured = runtimeConfig.agents?.background_models ?? {};
const tasks: Array<keyof DelegationConfig> = [
'compaction',
'memory_extraction',
'classification',
'tool_summarisation',
'complex_reasoning',
];
for (const task of tasks) {
const entry = configured[task];
if (!entry || entry.enabled === false) {
continue;
}
const template = providerConfigs[entry.provider];
try {
const client = createClientFromConfig(
template
? { ...template, provider: entry.provider, model: entry.model }
: { provider: entry.provider, model: entry.model },
);
overrides[task] = {
client,
label: `${entry.provider}/${entry.model}`,
fallbackTier: entry.fallback_tier,
};
} catch (error) {
console.warn(
`[Flynn:gateway] Failed to initialize background model override for ${task} ` +
`(${entry.provider}/${entry.model}): ${error instanceof Error ? error.message : String(error)}`,
);
}
}
return overrides;
}
private buildProviderConfigMap(config: Config): Partial<Record<ModelProvider, ModelConfig>> {
const providerConfigs: Partial<Record<ModelProvider, ModelConfig>> = {};
const modelConfigs: ModelConfig[] = [
config.models.default,
...(config.models.fast ? [config.models.fast] : []),
...(config.models.complex ? [config.models.complex] : []),
...(config.models.local ? [config.models.local] : []),
...Object.values(config.models.local_providers ?? {}),
];
for (const modelConfig of modelConfigs) {
providerConfigs[modelConfig.provider] = modelConfig;
if (modelConfig.fallback) {
providerConfigs[modelConfig.fallback.provider] = modelConfig.fallback;
}
}
return providerConfigs;
}
}