feat(onboarding): add memory optimization step to wizard

Add a new 'Memory Optimization' step to both interactive and non-interactive
onboarding flows. This gives users the option to enable four powerful memory
features during initial setup:

1. **Hybrid search (BM25 + vector)**: Combines semantic similarity with keyword
   matching for dramatically better recall of exact names, IDs, code symbols,
   and URLs. Uses a 70/30 vector/text weight blend with 4x candidate pool.

2. **Embedding cache**: Caches chunk embeddings in SQLite so reindexing and
   frequent updates don't re-embed unchanged text. Saves API calls and speeds
   up search on startup. Defaults to 50k max entries.

3. **Pre-compaction memory flush**: Triggers a silent agentic turn before
   context compaction, giving the agent a chance to write durable notes to
   disk. Prevents the 'woke up with amnesia' problem after long sessions.

4. **Session transcript search**: Indexes past session transcripts and makes
   them searchable via memory_search. Uses lower delta thresholds (50KB/25
   messages) for faster indexing of recent conversations.

Interactive mode presents a multiselect prompt so users can pick which features
they want. Non-interactive mode applies sensible defaults (hybrid search,
embedding cache, and memory flush) without requiring flags.

These features are already supported by the memory-core plugin and config
schema — this PR simply surfaces them during onboarding so users discover
and enable them from the start rather than having to manually edit config.

AI-assisted: Built with Claude (Opus 4.5) via OpenClaw. Tested on a live
OpenClaw instance running these exact memory optimizations for 5 days.
The improvements to recall quality and context persistence are significant.
This commit is contained in:
Dewaldt Huysamen 2026-01-30 15:51:50 +02:00
parent da71eaebd2
commit ca038e8b6e
3 changed files with 215 additions and 0 deletions

View File

@ -0,0 +1,166 @@
import type { OpenClawConfig } from "../config/config.js";
import type { RuntimeEnv } from "../runtime.js";
import type { WizardPrompter } from "../wizard/prompts.js";
/**
* Memory optimization step for the onboarding wizard.
*
* Offers users a curated set of memory enhancements that dramatically improve
* agent recall, search quality, and context persistence across compactions.
*
* These settings are off or minimal by default but provide significant benefits
* when enabled especially for long-running agents with rich conversation history.
*/
export async function setupMemoryOptimization(
cfg: OpenClawConfig,
_runtime: RuntimeEnv,
prompter: WizardPrompter,
): Promise<OpenClawConfig> {
await prompter.note(
[
"Memory optimization improves how your agent remembers, searches, and",
"persists context across sessions and compactions.",
"",
"These features are safe to enable and can dramatically improve recall.",
"",
"Learn more: https://docs.openclaw.ai/concepts/memory",
].join("\n"),
"Memory",
);
const enableMemory = await prompter.confirm({
message: "Enable advanced memory optimization?",
initialValue: true,
});
if (!enableMemory) {
return cfg;
}
const features = await prompter.multiselect({
message: "Select memory features to enable",
options: [
{
value: "hybrid-search",
label: "🔍 Hybrid search (BM25 + vector)",
hint: "Combines semantic + keyword matching for better recall of names, IDs, and exact terms",
},
{
value: "embedding-cache",
label: "💾 Embedding cache",
hint: "Caches embeddings so reindexing is faster and cheaper (saves API calls)",
},
{
value: "memory-flush",
label: "🧠 Pre-compaction memory flush",
hint: "Auto-saves durable notes before context is compacted (prevents memory loss)",
},
{
value: "session-memory",
label: "📜 Session transcript search",
hint: "Index and search past session transcripts (experimental, opt-in)",
},
],
});
if (features.length === 0) {
return cfg;
}
const selected = new Set(features);
const agentDefaults = cfg.agents?.defaults ?? {};
const memorySearch = agentDefaults.memorySearch ?? {};
const compaction = agentDefaults.compaction ?? {};
// Hybrid search: combine vector similarity with BM25 keyword relevance
if (selected.has("hybrid-search")) {
memorySearch.query = {
...memorySearch.query,
hybrid: {
enabled: true,
vectorWeight: 0.7,
textWeight: 0.3,
candidateMultiplier: 4,
},
};
}
// Embedding cache: avoid re-embedding unchanged chunks
if (selected.has("embedding-cache")) {
memorySearch.cache = {
enabled: true,
maxEntries: 50000,
};
}
// Pre-compaction memory flush: save notes before context wipe
if (selected.has("memory-flush")) {
compaction.memoryFlush = {
enabled: true,
};
}
// Session transcript indexing: search past conversations
if (selected.has("session-memory")) {
memorySearch.sources = ["memory", "sessions"];
memorySearch.experimental = {
...memorySearch.experimental,
sessionMemory: true,
};
// Lower thresholds for faster session indexing
memorySearch.sync = {
...memorySearch.sync,
sessions: {
deltaBytes: 50000,
deltaMessages: 25,
},
};
}
const next: OpenClawConfig = {
...cfg,
agents: {
...cfg.agents,
defaults: {
...agentDefaults,
memorySearch: {
...agentDefaults.memorySearch,
...memorySearch,
},
compaction: {
...agentDefaults.compaction,
...compaction,
},
},
},
};
const summary = features.map((f) => {
switch (f) {
case "hybrid-search":
return "Hybrid search (70% semantic + 30% keyword)";
case "embedding-cache":
return "Embedding cache (up to 50k entries)";
case "memory-flush":
return "Pre-compaction memory flush";
case "session-memory":
return "Session transcript search (faster indexing thresholds)";
default:
return f;
}
});
await prompter.note(
[
`Enabled ${features.length} memory feature${features.length > 1 ? "s" : ""}:`,
"",
...summary.map((s) => `${s}`),
"",
"Your agent will now have significantly improved recall and context",
"persistence across sessions and compactions.",
].join("\n"),
"Memory Optimized",
);
return next;
}

View File

@ -21,6 +21,48 @@ import { logNonInteractiveOnboardingJson } from "./local/output.js";
import { applyNonInteractiveSkillsConfig } from "./local/skills-config.js";
import { resolveNonInteractiveWorkspaceDir } from "./local/workspace.js";
/**
* Apply sensible memory optimization defaults for non-interactive onboarding.
* Enables hybrid search, embedding cache, and pre-compaction memory flush.
*/
function applyNonInteractiveMemoryDefaults(cfg: OpenClawConfig): OpenClawConfig {
const agentDefaults = cfg.agents?.defaults ?? {};
const memorySearch = agentDefaults.memorySearch ?? {};
const compaction = agentDefaults.compaction ?? {};
return {
...cfg,
agents: {
...cfg.agents,
defaults: {
...agentDefaults,
memorySearch: {
...memorySearch,
query: {
...memorySearch.query,
hybrid: memorySearch.query?.hybrid ?? {
enabled: true,
vectorWeight: 0.7,
textWeight: 0.3,
candidateMultiplier: 4,
},
},
cache: memorySearch.cache ?? {
enabled: true,
maxEntries: 50000,
},
},
compaction: {
...compaction,
memoryFlush: compaction.memoryFlush ?? {
enabled: true,
},
},
},
},
};
}
export async function runNonInteractiveOnboardingLocal(params: {
opts: OnboardOptions;
runtime: RuntimeEnv;
@ -73,6 +115,9 @@ export async function runNonInteractiveOnboardingLocal(params: {
nextConfig = applyNonInteractiveSkillsConfig({ nextConfig, opts, runtime });
// Apply memory optimization defaults for non-interactive mode
nextConfig = applyNonInteractiveMemoryDefaults(nextConfig);
nextConfig = applyWizardMetadata(nextConfig, { command: "onboard", mode });
await writeConfigFile(nextConfig);
logConfigUpdated(runtime);

View File

@ -20,6 +20,7 @@ import {
import { promptRemoteGatewayConfig } from "../commands/onboard-remote.js";
import { setupSkills } from "../commands/onboard-skills.js";
import { setupInternalHooks } from "../commands/onboard-hooks.js";
import { setupMemoryOptimization } from "../commands/onboard-memory.js";
import type {
GatewayAuthChoice,
OnboardMode,
@ -435,6 +436,9 @@ export async function runOnboardingWizard(
// Setup hooks (session memory on /new)
nextConfig = await setupInternalHooks(nextConfig, runtime, prompter);
// Memory optimization (hybrid search, caching, flush, session memory)
nextConfig = await setupMemoryOptimization(nextConfig, runtime, prompter);
nextConfig = applyWizardMetadata(nextConfig, { command: "onboard", mode });
await writeConfigFile(nextConfig);