diff --git a/backend/__tests__/unit/config/nativeAgents.modelRoute.test.js b/backend/__tests__/unit/config/nativeAgents.modelRoute.test.js new file mode 100644 index 0000000000..21fa97c755 --- /dev/null +++ b/backend/__tests__/unit/config/nativeAgents.modelRoute.test.js @@ -0,0 +1,35 @@ +/** + * First-party native apps must name a model the deployment can actually serve. + * + * pod-summarizer, pod-welcomer and task-clerk kept the free OpenRouter + * nemotron route after its key expired on 2026-10-25. LiteLLM still declares + * that name, so nothing failed at boot; every run died on its first model call + * with HTTP 404 (6 of 8 pod-summarizer runs in the 48h before 2026-10-27). + * Declared is not the same as servable, so this pins both. + */ +const fs = require('fs'); +const path = require('path'); +const { FIRST_PARTY_APPS } = require('../../../config/native-agents/apps'); + +const litellmConfig = fs.readFileSync(path.join(__dirname, + '../../../../k8s/helm/commonly/templates/configmaps/litellm-config.yaml'), 'utf8'); +const declaredModels = new Set( + [...litellmConfig.matchAll(/model_name:\s*(\S+)/g)].map((m) => m[1]), +); + +describe('first-party native app model routes', () => { + test.each(FIRST_PARTY_APPS.map((app) => [app.agentName, app.model]))( + '%s names a model LiteLLM declares', + (_name, model) => { + expect(declaredModels.has(model)).toBe(true); + }, + ); + + test.each(FIRST_PARTY_APPS.map((app) => [app.agentName, app.model]))( + '%s does not route through the expired OpenRouter free tier', + (_name, model) => { + expect(model).not.toMatch(/^openrouter\//); + expect(model).not.toMatch(/:free$/); + }, + ); +}); diff --git a/backend/config/native-agents/pod-summarizer.ts b/backend/config/native-agents/pod-summarizer.ts index 2ea2e75d56..eb57d3a020 100644 --- a/backend/config/native-agents/pod-summarizer.ts +++ b/backend/config/native-agents/pod-summarizer.ts @@ -61,7 +61,7 @@ export const podSummarizerApp = { + '- DO NOT call commonly_create_task. You are not here to create work.', // See pod-welcomer.ts for the rationale — utility apps run on the // OpenRouter free tier, not paid Codex. - model: 'openrouter/nvidia/nemotron-3-super-120b-a12b:free', + model: 'deepseek-v4-flash', triggers: ['heartbeat'], heartbeatIntervalMinutes: 360, tools: [ diff --git a/backend/config/native-agents/pod-welcomer.ts b/backend/config/native-agents/pod-welcomer.ts index 9f657cd9d4..6049451265 100644 --- a/backend/config/native-agents/pod-welcomer.ts +++ b/backend/config/native-agents/pod-welcomer.ts @@ -56,7 +56,7 @@ export const podWelcomerApp = { // burning it for these heavy-traffic event handlers exhausts dev agents' // accounts. Same default community agents use; if quality drops, reach for // gemini-2.5-flash (paid but cheap) before paid Codex. - model: 'openrouter/nvidia/nemotron-3-super-120b-a12b:free', + model: 'deepseek-v4-flash', triggers: ['pod.join'], tools: ['commonly_read_context', 'commonly_post_message'], iconUrl: '', diff --git a/backend/config/native-agents/task-clerk.ts b/backend/config/native-agents/task-clerk.ts index 69782f1332..d8c916c3fe 100644 --- a/backend/config/native-agents/task-clerk.ts +++ b/backend/config/native-agents/task-clerk.ts @@ -56,7 +56,7 @@ export const taskClerkApp = { + '- Keep all replies under 2 lines. You are a clerk, not a conversationalist.', // See pod-welcomer.ts for the rationale — utility apps run on the // OpenRouter free tier, not paid Codex. - model: 'openrouter/nvidia/nemotron-3-super-120b-a12b:free', + model: 'deepseek-v4-flash', triggers: ['mention'], tools: [ 'commonly_read_context',