From ca038e8b6e00f62d9c5c8836ffefc6168699fd6c Mon Sep 17 00:00:00 2001 From: Dewaldt Huysamen Date: Fri, 30 Jan 2026 15:51:50 +0200 Subject: [PATCH] feat(onboarding): add memory optimization step to wizard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- src/commands/onboard-memory.ts | 166 ++++++++++++++++++ src/commands/onboard-non-interactive/local.ts | 45 +++++ src/wizard/onboarding.ts | 4 + 3 files changed, 215 insertions(+) create mode 100644 src/commands/onboard-memory.ts diff --git a/src/commands/onboard-memory.ts b/src/commands/onboard-memory.ts new file mode 100644 index 000000000..1156f8a84 --- /dev/null +++ b/src/commands/onboard-memory.ts @@ -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 { + 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; +} diff --git a/src/commands/onboard-non-interactive/local.ts b/src/commands/onboard-non-interactive/local.ts index 04bbad832..645b83e0c 100644 --- a/src/commands/onboard-non-interactive/local.ts +++ b/src/commands/onboard-non-interactive/local.ts @@ -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); diff --git a/src/wizard/onboarding.ts b/src/wizard/onboarding.ts index ef2e349c6..295ad3170 100644 --- a/src/wizard/onboarding.ts +++ b/src/wizard/onboarding.ts @@ -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);