fix(memory): clamp remote batch timers

This commit is contained in:
Peter Steinberger
2026-05-30 17:27:57 -04:00
parent 7eeea30d8c
commit 37fbc8cd8f
2 changed files with 80 additions and 4 deletions

View File

@@ -4,6 +4,7 @@ import {
clearMemoryEmbeddingProviders,
registerMemoryEmbeddingProvider,
} from "../plugins/memory-embedding-providers.js";
import { MAX_TIMER_TIMEOUT_MS } from "../shared/number-coercion.js";
import { resolveMemorySearchConfig, resolveMemorySearchSyncConfig } from "./memory-search.js";
const asConfig = (cfg: OpenClawConfig): OpenClawConfig => cfg;
@@ -529,6 +530,50 @@ describe("memory search config", () => {
expectDefaultRemoteBatch(resolved);
});
it("normalizes remote batch timer config once before provider adapters receive it", () => {
const cfg = asConfig({
agents: {
defaults: {
memorySearch: {
provider: "openai",
remote: {
batch: {
pollIntervalMs: Number.MAX_SAFE_INTEGER,
timeoutMinutes: Number.MAX_SAFE_INTEGER,
},
},
},
},
},
});
const resolved = resolveMemorySearchConfig(cfg, "main");
expect(resolved?.remote?.batch?.pollIntervalMs).toBe(MAX_TIMER_TIMEOUT_MS);
expect(resolved?.remote?.batch?.timeoutMinutes).toBe(Math.floor(MAX_TIMER_TIMEOUT_MS / 60_000));
});
it("keeps the default remote batch poll delay for zero intervals", () => {
const cfg = asConfig({
agents: {
defaults: {
memorySearch: {
provider: "openai",
remote: {
batch: {
pollIntervalMs: 0,
},
},
},
},
},
});
const resolved = resolveMemorySearchConfig(cfg, "main");
expect(resolved?.remote?.batch?.pollIntervalMs).toBe(2000);
});
it("keeps remote unset for local provider without overrides", () => {
const cfg = configWithDefaultProvider("local");
const resolved = resolveMemorySearchConfig(cfg, "main");

View File

@@ -4,6 +4,10 @@ import {
findNormalizedProviderValue,
normalizeProviderId,
} from "@openclaw/model-catalog-core/provider-id";
import {
MAX_TIMER_TIMEOUT_MS,
resolvePositiveTimerTimeoutMs,
} from "@openclaw/normalization-core/number-coercion";
import {
normalizeStringEntries,
uniqueStrings,
@@ -126,6 +130,29 @@ const DEFAULT_TEMPORAL_DECAY_HALF_LIFE_DAYS = 30;
const DEFAULT_CACHE_ENABLED = true;
const DEFAULT_SOURCES: Array<"memory" | "sessions"> = ["memory"];
const DEFAULT_MEMORY_EMBEDDING_PROVIDER = "openai";
const DEFAULT_REMOTE_BATCH_POLL_INTERVAL_MS = 2_000;
const DEFAULT_REMOTE_BATCH_TIMEOUT_MINUTES = 60;
const MAX_REMOTE_BATCH_TIMEOUT_MINUTES = Math.floor(MAX_TIMER_TIMEOUT_MS / 60_000);
function resolveRemoteBatchPollIntervalMs(
overrideValue: number | undefined,
defaultValue: number | undefined,
): number {
return resolvePositiveTimerTimeoutMs(
overrideValue ?? defaultValue,
DEFAULT_REMOTE_BATCH_POLL_INTERVAL_MS,
);
}
function resolveRemoteBatchTimeoutMinutes(
overrideValue: number | undefined,
defaultValue: number | undefined,
): number {
const value = overrideValue ?? defaultValue;
return typeof value === "number" && Number.isFinite(value) && value > 0
? clampInt(value, 1, MAX_REMOTE_BATCH_TIMEOUT_MINUTES)
: DEFAULT_REMOTE_BATCH_TIMEOUT_MINUTES;
}
function normalizeSources(
sources: Array<"memory" | "sessions"> | undefined,
@@ -221,10 +248,14 @@ function mergeConfig(
1,
overrideRemote?.batch?.concurrency ?? defaultRemote?.batch?.concurrency ?? 2,
),
pollIntervalMs:
overrideRemote?.batch?.pollIntervalMs ?? defaultRemote?.batch?.pollIntervalMs ?? 2000,
timeoutMinutes:
overrideRemote?.batch?.timeoutMinutes ?? defaultRemote?.batch?.timeoutMinutes ?? 60,
pollIntervalMs: resolveRemoteBatchPollIntervalMs(
overrideRemote?.batch?.pollIntervalMs,
defaultRemote?.batch?.pollIntervalMs,
),
timeoutMinutes: resolveRemoteBatchTimeoutMinutes(
overrideRemote?.batch?.timeoutMinutes,
defaultRemote?.batch?.timeoutMinutes,
),
};
const remote = includeRemote
? {