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:
parent
da71eaebd2
commit
ca038e8b6e
166
src/commands/onboard-memory.ts
Normal file
166
src/commands/onboard-memory.ts
Normal 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;
|
||||
}
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user