Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 29 additions & 6 deletions cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ export function registerMemoryCLI(program: Command, context: CLIContext): void {
limit,
scopeFilter,
category,
source: "cli",
});

if (results.length === 0 && context.embedder) {
Expand All @@ -92,6 +93,7 @@ export function registerMemoryCLI(program: Command, context: CLIContext): void {
limit,
scopeFilter,
category,
source: "cli",
});
}

Expand Down Expand Up @@ -417,10 +419,10 @@ export function registerMemoryCLI(program: Command, context: CLIContext): void {
const categoryRaw = memory.category;
const category: MemoryEntry["category"] =
categoryRaw === "preference" ||
categoryRaw === "fact" ||
categoryRaw === "decision" ||
categoryRaw === "entity" ||
categoryRaw === "other"
categoryRaw === "fact" ||
categoryRaw === "decision" ||
categoryRaw === "entity" ||
categoryRaw === "other"
? categoryRaw
: "other";

Expand Down Expand Up @@ -531,10 +533,10 @@ export function registerMemoryCLI(program: Command, context: CLIContext): void {
let targetReal = context.store.dbPath;
try {
sourceReal = await fs.realpath(sourceDbPath);
} catch {}
} catch { }
try {
targetReal = await fs.realpath(context.store.dbPath);
} catch {}
} catch { }

if (!force && sourceReal === targetReal) {
console.error("Refusing to re-embed in-place: source-db equals target dbPath. Use a new dbPath or pass --force.");
Expand Down Expand Up @@ -781,6 +783,27 @@ export function registerMemoryCLI(program: Command, context: CLIContext): void {
process.exit(1);
}
});

// reindex-fts: Rebuild FTS index
program
.command("reindex-fts")
.description("Rebuild the BM25 full-text search index")
.action(async () => {
try {
const status = context.store.getFtsStatus();
console.log(`FTS status before: available=${status.available}, lastError=${status.lastError || "none"}`);
const result = await context.store.rebuildFtsIndex();
if (result.success) {
console.log("✅ FTS index rebuilt successfully");
} else {
console.error("❌ FTS rebuild failed:", result.error);
process.exit(1);
}
} catch (error) {
console.error("FTS rebuild error:", error);
process.exit(1);
}
});
}

// ============================================================================
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
]
},
"scripts": {
"test": "node test/embedder-error-hints.test.mjs && node test/migrate-legacy-schema.test.mjs && node --test test/config-session-strategy-migration.test.mjs && node --test test/recall-text-cleanup.test.mjs && node test/update-consistency-lancedb.test.mjs && node test/cli-smoke.mjs && node test/functional-e2e.mjs && node test/retriever-rerank-regression.mjs && node test/smart-memory-lifecycle.mjs && node test/smart-extractor-branches.mjs && node test/plugin-manifest-regression.mjs",
"test": "node test/embedder-error-hints.test.mjs && node test/migrate-legacy-schema.test.mjs && node --test test/config-session-strategy-migration.test.mjs && node --test test/recall-text-cleanup.test.mjs && node test/update-consistency-lancedb.test.mjs && node test/cli-smoke.mjs && node test/functional-e2e.mjs && node test/retriever-rerank-regression.mjs && node test/smart-memory-lifecycle.mjs && node test/smart-extractor-branches.mjs && node test/plugin-manifest-regression.mjs && node test/smart-metadata-v2.mjs && node test/vector-search-cosine.test.mjs && node test/context-support-e2e.mjs",
"test:openclaw-host": "node test/openclaw-host-functional.mjs"
},
"devDependencies": {
Expand Down
52 changes: 29 additions & 23 deletions src/extraction-prompts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,20 +149,26 @@ Please decide:
- SKIP: Candidate memory duplicates existing memories, no need to save. Also SKIP if the candidate contains LESS information than an existing memory on the same topic (information degradation — e.g., candidate says "programming language preference" but existing memory already says "programming language preference: Python, TypeScript")
- CREATE: This is completely new information not covered by any existing memory, should be created
- MERGE: Candidate memory adds genuinely NEW details to an existing memory and should be merged
- SUPPORT: Candidate reinforces/confirms an existing memory in a specific context (e.g. "still prefers tea in the evening")
- CONTEXTUALIZE: Candidate adds a situational nuance to an existing memory (e.g. existing: "likes coffee", candidate: "prefers tea at night" — different context, same topic)
- CONTRADICT: Candidate directly contradicts an existing memory in a specific context (e.g. existing: "runs on weekends", candidate: "stopped running on weekends")

IMPORTANT:
- "events" and "cases" categories are independent records — they do NOT support MERGE. For these categories, only use SKIP or CREATE.
- "events" and "cases" categories are independent records — they do NOT support MERGE/SUPPORT/CONTEXTUALIZE/CONTRADICT. For these categories, only use SKIP or CREATE.
- If the candidate appears to be derived from a recall question (e.g., "Do you remember X?" / "你记得X吗?") and an existing memory already covers topic X with equal or more detail, you MUST choose SKIP.
- A candidate with less information than an existing memory on the same topic should NEVER be CREATED or MERGED — always SKIP.
- For SUPPORT/CONTEXTUALIZE/CONTRADICT, you MUST provide a context_label from this vocabulary: general, morning, evening, night, weekday, weekend, work, leisure, summer, winter, travel.

Return JSON format:
{
"decision": "skip|create|merge",
"decision": "skip|create|merge|support|contextualize|contradict",
"match_index": 1,
"reason": "Decision reason"
"reason": "Decision reason",
"context_label": "evening"
}

If decision is "merge", set "match_index" to the number of the existing memory to merge with (1-based).`;
- If decision is "merge"/"support"/"contextualize"/"contradict", set "match_index" to the number of the existing memory (1-based).
- Only include "context_label" for support/contextualize/contradict decisions.`;
}

export function buildMergePrompt(
Expand All @@ -176,32 +182,32 @@ export function buildMergePrompt(
): string {
return `Merge the following memory into a single coherent record with all three levels.

**Category**: ${category}
** Category **: ${category}

**Existing Memory:**
Abstract: ${existingAbstract}
Overview:
** Existing Memory:**
Abstract: ${existingAbstract}
Overview:
${existingOverview}
Content:
Content:
${existingContent}

**New Information:**
Abstract: ${newAbstract}
Overview:
** New Information:**
Abstract: ${newAbstract}
Overview:
${newOverview}
Content:
Content:
${newContent}

Requirements:
- Remove duplicate information
- Keep the most up-to-date details
- Maintain a coherent narrative
- Keep code identifiers / URIs / model names unchanged when they are proper nouns
Requirements:
- Remove duplicate information
- Keep the most up - to - date details
- Maintain a coherent narrative
- Keep code identifiers / URIs / model names unchanged when they are proper nouns

Return JSON:
{
"abstract": "Merged one-line abstract",
"overview": "Merged structured Markdown overview",
"content": "Merged full content"
}`;
{
"abstract": "Merged one-line abstract",
"overview": "Merged structured Markdown overview",
"content": "Merged full content"
} `;
}
4 changes: 3 additions & 1 deletion src/memory-categories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,18 +44,20 @@ export type CandidateMemory = {
};

/** Dedup decision from LLM. */
export type DedupDecision = "create" | "merge" | "skip";
export type DedupDecision = "create" | "merge" | "skip" | "support" | "contextualize" | "contradict";

export type DedupResult = {
decision: DedupDecision;
reason: string;
matchId?: string; // ID of existing memory to merge with
contextLabel?: string; // Optional context label for support/contextualize/contradict
};

export type ExtractionStats = {
created: number;
merged: number;
skipped: number;
supported?: number; // context-aware support count
};

/** Validate and normalize a category string. */
Expand Down
4 changes: 2 additions & 2 deletions src/retriever.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ export interface RetrievalContext {
limit: number;
scopeFilter?: string[];
category?: string;
/** Retrieval source: "manual" for user-triggered, "auto-recall" for system-initiated. */
source?: "manual" | "auto-recall";
/** Retrieval source: "manual" for user-triggered, "auto-recall" for system-initiated, "cli" for CLI commands. */
source?: "manual" | "auto-recall" | "cli";
}

export interface RetrievalResult extends MemorySearchResult {
Expand Down
Loading
Loading