fix(active-memory): remove built-in fallback model (#65047)

* fix(active-memory): remove built-in fallback model

* fix(active-memory): tighten fallback cleanup
This commit is contained in:
Tak Hoffman
2026-04-11 20:24:07 -05:00
committed by GitHub
parent 21cbc15b71
commit 52bf19c45e
3 changed files with 41 additions and 41 deletions

View File

@@ -111,7 +111,7 @@ What this means:
- `config.agents: ["main"]` opts only the `main` agent into active memory
- `config.allowedChatTypes: ["direct"]` keeps active memory on for direct-message style sessions only by default
- if `config.model` is unset, active memory inherits the current session model first
- `config.modelFallbackPolicy: "default-remote"` keeps the built-in remote fallback as the default when no explicit or inherited model is available
- `config.modelFallback` optionally provides your own fallback provider/model for recall
- `config.promptStyle: "balanced"` uses the default general-purpose prompt style for `recent` mode
- active memory still runs only on eligible interactive persistent chat sessions
@@ -335,26 +335,22 @@ If `config.model` is unset, Active Memory tries to resolve a model in this order
explicit plugin model
-> current session model
-> agent primary model
-> optional built-in remote fallback
-> optional configured fallback model
```
`config.modelFallbackPolicy` controls the last step.
`config.modelFallback` controls the configured fallback step.
Default:
Optional custom fallback:
```json5
modelFallbackPolicy: "default-remote"
modelFallback: "google/gemini-3-flash"
```
Other option:
If no explicit, inherited, or configured fallback model resolves, Active Memory
skips recall for that turn.
```json5
modelFallbackPolicy: "resolved-only"
```
Use `resolved-only` if you want Active Memory to skip recall instead of falling
back to the built-in remote default when no explicit or inherited model is
available.
`config.modelFallbackPolicy` is retained only as a deprecated compatibility
field for older configs. It no longer changes runtime behavior.
## Advanced escape hatches

View File

@@ -771,12 +771,12 @@ describe("active-memory plugin", () => {
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
});
it("uses config.modelFallback before the built-in default fallback", async () => {
it("uses config.modelFallback when no session or agent model resolves", async () => {
api.config = {};
api.pluginConfig = {
agents: ["main"],
modelFallback: "google/gemini-3-flash",
modelFallbackPolicy: "resolved-only",
modelFallbackPolicy: "default-remote",
};
await plugin.register(api as unknown as OpenClawPluginApi);
@@ -794,6 +794,9 @@ describe("active-memory plugin", () => {
provider: "google",
model: "gemini-3-flash-preview",
});
expect(api.logger.warn).toHaveBeenCalledWith(
expect.stringContaining("config.modelFallbackPolicy is deprecated"),
);
});
it("does not use a built-in fallback model even when default-remote is configured", async () => {

View File

@@ -238,6 +238,11 @@ function normalizePromptConfigText(value: unknown): string | undefined {
return text ? text : undefined;
}
function hasDeprecatedModelFallbackPolicy(pluginConfig: unknown): boolean {
const raw = asRecord(pluginConfig);
return raw ? Object.hasOwn(raw, "modelFallbackPolicy") : false;
}
function resolveSafeTranscriptDir(baseSessionsDir: string, transcriptDir: string): string {
const normalized = transcriptDir.trim();
if (!normalized || normalized.includes(":") || path.isAbsolute(normalized)) {
@@ -1217,35 +1222,22 @@ function getModelRef(
modelProviderId?: string;
modelId?: string;
},
): {
modelRef?: {
provider: string;
model: string;
};
source: "plugin-model" | "session-model" | "agent-primary" | "config-fallback" | "none";
} {
): { provider: string; model: string } | undefined {
const currentRunModel =
ctx?.modelProviderId && ctx?.modelId ? `${ctx.modelProviderId}/${ctx.modelId}` : undefined;
const agentPrimaryModel = resolveAgentEffectiveModelPrimary(api.config, agentId);
const candidates: Array<{
source: "plugin-model" | "session-model" | "agent-primary" | "config-fallback";
value?: string;
}> = [
{ source: "plugin-model", value: config.model },
{ source: "session-model", value: currentRunModel },
{ source: "agent-primary", value: agentPrimaryModel },
{ source: "config-fallback", value: config.modelFallback },
const candidates = [
config.model,
currentRunModel,
resolveAgentEffectiveModelPrimary(api.config, agentId),
config.modelFallback,
];
for (const candidate of candidates) {
const parsed = parseModelCandidate(candidate.value);
const parsed = parseModelCandidate(candidate);
if (parsed) {
return {
modelRef: parsed,
source: candidate.source,
};
return parsed;
}
}
return { source: "none" };
return undefined;
}
async function runRecallSubagent(params: {
@@ -1263,7 +1255,7 @@ async function runRecallSubagent(params: {
}): Promise<{ rawReply: string; transcriptPath?: string }> {
const workspaceDir = resolveAgentWorkspaceDir(params.api.config, params.agentId);
const agentDir = resolveAgentDir(params.api.config, params.agentId);
const { modelRef } = getModelRef(params.api, params.agentId, params.config, {
const modelRef = getModelRef(params.api, params.agentId, params.config, {
modelProviderId: params.currentModelProviderId,
modelId: params.currentModelId,
});
@@ -1507,11 +1499,20 @@ export default definePluginEntry({
description: "Proactively surfaces relevant memory before eligible conversational replies.",
register(api: OpenClawPluginApi) {
let config = normalizePluginConfig(api.pluginConfig);
const warnDeprecatedModelFallbackPolicy = (pluginConfig: unknown) => {
if (hasDeprecatedModelFallbackPolicy(pluginConfig)) {
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",
);
}
};
warnDeprecatedModelFallbackPolicy(api.pluginConfig);
const refreshLiveConfigFromRuntime = () => {
config = normalizePluginConfig(
const livePluginConfig =
resolveActiveMemoryPluginConfigFromConfig(api.runtime.config.loadConfig()) ??
api.pluginConfig,
);
api.pluginConfig;
config = normalizePluginConfig(livePluginConfig);
warnDeprecatedModelFallbackPolicy(livePluginConfig);
};
api.registerCommand({
name: "active-memory",