diff --git a/extensions/active-memory/index.test.ts b/extensions/active-memory/index.test.ts index 7354f7db3f5..8dd18a641c1 100644 --- a/extensions/active-memory/index.test.ts +++ b/extensions/active-memory/index.test.ts @@ -1384,6 +1384,22 @@ describe("active-memory plugin", () => { expect(api.logger.warn).toHaveBeenCalledWith( expect.stringContaining("config.modelFallbackPolicy is deprecated"), ); + // #74587: deprecation warning must spell out the chain-resolution + // semantics so operators don't read it as a promise of runtime failover. + // The previous wording ("set config.modelFallback if you want a fallback + // model") cost real users hours of debug time before they hit the source + // and saw `getModelRef` only walks candidates once. + const warnCalls = (api.logger.warn as ReturnType).mock.calls; + const deprecationMessage = warnCalls + .map(([first]) => (typeof first === "string" ? first : "")) + .find((message) => message.includes("config.modelFallbackPolicy is deprecated")); + expect(deprecationMessage).toBeDefined(); + // Positive: the warning describes chain-resolution last-resort behavior. + expect(deprecationMessage).toContain("chain-resolution"); + expect(deprecationMessage).toContain("last-resort"); + // Negative: the warning explicitly disclaims runtime failover, since + // that's the wrong mental model the previous wording invited. + expect(deprecationMessage).toMatch(/NOT a runtime failover/i); }); it("does not use a built-in fallback model even when default-remote is configured", async () => { diff --git a/extensions/active-memory/index.ts b/extensions/active-memory/index.ts index ee89f59cc48..d8b0194fdfa 100644 --- a/extensions/active-memory/index.ts +++ b/extensions/active-memory/index.ts @@ -2431,8 +2431,19 @@ export default definePluginEntry({ let config = normalizePluginConfig(api.pluginConfig); const warnDeprecatedModelFallbackPolicy = (pluginConfig: unknown) => { if (hasDeprecatedModelFallbackPolicy(pluginConfig)) { + // Wording matters here: the previous text ("set config.modelFallback + // explicitly if you want a fallback model") read naturally as runtime + // failover (model A errors → switch to model B), but `getModelRef` + // only consults `modelFallback` as the *last candidate* in the + // resolution chain after `config.model`, the current run's model, + // and the agent's configured default have all resolved to nothing. + // Surface the chain-resolution semantics directly so operators + // don't waste debug cycles assuming runtime failover (#74587). api.logger.warn?.( - "active-memory: config.modelFallbackPolicy is deprecated and no longer changes runtime behavior; set config.modelFallback explicitly if you want a fallback model", + "active-memory: config.modelFallbackPolicy is deprecated and no longer changes runtime behavior. " + + "config.modelFallback is a chain-resolution last-resort (consulted only when config.model, " + + "the current run's model, and the agent's configured default all resolve to nothing) — " + + "it is NOT a runtime failover that substitutes a different model when the resolved model errors out.", ); } };