From bf282b5055dde4cd1733a7cdd99aa7521ea305bf Mon Sep 17 00:00:00 2001 From: Dora Salib Date: Fri, 20 Mar 2026 17:13:44 -0700 Subject: [PATCH] feat(memory): enable smart memory defaults for all new installations Adds applyMemorySearchDefaults() to the config pipeline, enabling three platform-level features that together solve the multi-session continuity problem (the 'twin sessions' problem where webchat and Telegram feel like separate assistants with no shared memory). Previously, users had to manually configure memorySearch in openclaw.json to get these features. New installs now get them automatically. ## What this enables by default 1. **Session memory indexing** (experimental.sessionMemory + sources: sessions) Every conversation across all channels is automatically indexed. A memory_search from any channel can find context from any other channel without the user or agent needing to manually write memory files. 2. **Temporal decay** (halfLifeDays: 30) Recent memories rank higher automatically. Yesterday's note beats a 6-month-old note on the same topic. 3. **MMR re-ranking** (lambda: 0.7) Avoids returning near-duplicate memory snippets. The agent gets diverse, complementary results instead of the same information repeated. ## Design decisions - Opt-out not opt-in: applied only when memorySearch is undefined. - Platform layer not agent instructions: lives in code, not prose. - Conservative values matching existing docs recommendations. - Same pipeline pattern as applyContextPruningDefaults / applyCompactionDefaults. ## Testing Added config.memory-search-defaults.test.ts: - Default install gets all three features enabled - Explicit memorySearch config is never overwritten --- .../config.memory-search-defaults.test.ts | 52 ++++++++++++++++ src/config/defaults.ts | 62 +++++++++++++++++++ src/config/io.ts | 13 +++- 3 files changed, 124 insertions(+), 3 deletions(-) create mode 100644 src/config/config.memory-search-defaults.test.ts 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({}))), + ), ), ), ),