diff --git a/src/config/config.memory-search-defaults.test.ts b/src/config/config.memory-search-defaults.test.ts new file mode 100644 index 00000000000..f18b8734f1c --- /dev/null +++ b/src/config/config.memory-search-defaults.test.ts @@ -0,0 +1,52 @@ +import { describe, expect, it } from "vitest"; +import { loadConfig } from "./config.js"; +import { withTempHomeConfig } from "./test-helpers.js"; + +describe("config memory search defaults", () => { + it("enables session memory indexing, temporal decay, and MMR by default", async () => { + await withTempHomeConfig({}, async () => { + const cfg = loadConfig(); + const ms = cfg.agents?.defaults?.memorySearch; + + // Session memory indexing should be on + expect((ms?.experimental as Record)?.sessionMemory).toBe(true); + expect(ms?.sources).toContain("sessions"); + expect(ms?.sources).toContain("memory"); + + // Hybrid search with temporal decay + const hybrid = ms?.query?.hybrid; + expect(hybrid?.enabled).toBe(true); + expect(hybrid?.temporalDecay?.enabled).toBe(true); + expect(hybrid?.temporalDecay?.halfLifeDays).toBe(30); + + // MMR re-ranking + expect(hybrid?.mmr?.enabled).toBe(true); + expect(hybrid?.mmr?.lambda).toBe(0.7); + }); + }); + + it("does not overwrite explicit memorySearch config", async () => { + await withTempHomeConfig( + { + agents: { + defaults: { + memorySearch: { + sources: ["memory"], + }, + }, + }, + }, + async () => { + const cfg = loadConfig(); + const ms = cfg.agents?.defaults?.memorySearch; + + // User explicitly set sources — respect it, don't inject sessions + expect(ms?.sources).toEqual(["memory"]); + expect(ms?.sources).not.toContain("sessions"); + + // No defaults injected since user provided explicit config + expect((ms?.experimental as Record)?.sessionMemory).toBeUndefined(); + }, + ); + }); +}); diff --git a/src/config/defaults.ts b/src/config/defaults.ts index 2febc3869ee..ec511a3992f 100644 --- a/src/config/defaults.ts +++ b/src/config/defaults.ts @@ -531,6 +531,68 @@ export function applyCompactionDefaults(cfg: OpenClawConfig): OpenClawConfig { }; } +/** + * Applies smart memory search defaults for new installations. + * + * Enables three features that work together to solve the multi-session + * continuity problem (e.g. webchat and Telegram feeling like "two twins + * who don't share memory") without requiring any agent-level instructions: + * + * 1. **Session memory indexing** – every conversation across all channels is + * automatically indexed so `memory_search` can find context from any session. + * + * 2. **Temporal decay** – recent memories rank higher automatically; a note + * from yesterday beats one from 6 months ago on the same topic. + * + * 3. **MMR re-ranking** – avoids returning near-duplicate memory snippets; + * the agent gets diverse, complementary results instead of repetition. + * + * All three are off by default today. This function enables them for users + * who haven't explicitly configured `memorySearch`, so new installations + * get the best out-of-the-box experience. Existing explicit config is + * never overwritten (opt-out by setting any memorySearch field). + */ +export function applyMemorySearchDefaults(cfg: OpenClawConfig): OpenClawConfig { + const defaults = cfg.agents?.defaults; + if (!defaults) { + return cfg; + } + + // Don't touch anything if the user has explicitly configured memorySearch + if (defaults.memorySearch !== undefined) { + return cfg; + } + + return { + ...cfg, + agents: { + ...cfg.agents, + defaults: { + ...defaults, + memorySearch: { + experimental: { sessionMemory: true }, + sources: ["memory", "sessions"], + query: { + hybrid: { + enabled: true, + vectorWeight: 0.7, + textWeight: 0.3, + temporalDecay: { + enabled: true, + halfLifeDays: 30, + }, + mmr: { + enabled: true, + lambda: 0.7, + }, + }, + }, + }, + }, + }, + }; +} + export function resetSessionDefaultsWarningForTests() { defaultWarnState = { warned: false }; } diff --git a/src/config/io.ts b/src/config/io.ts index fba17f253aa..76ade2159a8 100644 --- a/src/config/io.ts +++ b/src/config/io.ts @@ -20,6 +20,7 @@ import { maintainConfigBackups } from "./backup-rotation.js"; import { applyCompactionDefaults, applyContextPruningDefaults, + applyMemorySearchDefaults, applyAgentDefaults, applyLoggingDefaults, applyMessageDefaults, @@ -800,8 +801,12 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) { applyModelDefaults( applyCompactionDefaults( applyContextPruningDefaults( - applyAgentDefaults( - applySessionDefaults(applyLoggingDefaults(applyMessageDefaults(validated.config))), + applyMemorySearchDefaults( + applyAgentDefaults( + applySessionDefaults( + applyLoggingDefaults(applyMessageDefaults(validated.config)), + ), + ), ), ), ), @@ -892,7 +897,9 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) { applyModelDefaults( applyCompactionDefaults( applyContextPruningDefaults( - applyAgentDefaults(applySessionDefaults(applyMessageDefaults({}))), + applyMemorySearchDefaults( + applyAgentDefaults(applySessionDefaults(applyMessageDefaults({}))), + ), ), ), ),