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
This commit is contained in:
Dora Salib 2026-03-20 17:13:44 -07:00
parent e78129a4d9
commit bf282b5055
3 changed files with 124 additions and 3 deletions

View File

@ -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<string, unknown>)?.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<string, unknown>)?.sessionMemory).toBeUndefined();
},
);
});
});

View File

@ -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 };
}

View File

@ -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({}))),
),
),
),
),