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
26 changes: 26 additions & 0 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ interface PluginConfig {
autoRecallMaxItems?: number;
autoRecallMaxChars?: number;
autoRecallPerItemMaxChars?: number;
autoRecallExcludeAgents?: string[];
recallMode?: "full" | "summary" | "adaptive" | "off";
captureAssistant?: boolean;
retrieval?: {
Expand Down Expand Up @@ -1577,14 +1578,27 @@ const pluginVersion = getPluginVersion();
// Plugin Definition
// ============================================================================

let _initialized = false;

const memoryLanceDBProPlugin = {
id: "memory-lancedb-pro",
name: "Memory (LanceDB Pro)",
description:
"Enhanced LanceDB-backed long-term memory with hybrid retrieval, multi-scope isolation, and management CLI",
kind: "memory" as const,

// PR #365: Reset idempotent guard for testing
_resetInitialized() { _initialized = false; },

register(api: OpenClawPluginApi) {

// Idempotent guard: skip re-init on repeated register() calls
if (_initialized) {
api.logger.debug("memory-lancedb-pro: register() called again — skipping re-init (idempotent)");
return;
}
_initialized = true;

// Parse and validate configuration
const config = parsePluginConfig(api.pluginConfig);

Expand Down Expand Up @@ -2243,6 +2257,12 @@ const memoryLanceDBProPlugin = {
const agentId = resolveHookAgentId(ctx?.agentId, (event as any).sessionKey);
const accessibleScopes = resolveScopeFilter(scopeManager, agentId);

// PR #365: autoRecallExcludeAgents
if (config.autoRecallExcludeAgents?.includes(agentId)) {
api.logger.info(`memory-lancedb-pro: auto-recall skipped for excluded agent=${agentId}`);
return undefined;
}

// FR-04: Truncate long prompts (e.g. file attachments) before embedding.
// Auto-recall only needs the user's intent, not full attachment text.
const MAX_RECALL_QUERY_LENGTH = 1_000;
Expand Down Expand Up @@ -2323,10 +2343,12 @@ const memoryLanceDBProPlugin = {
const meta = parseSmartMetadata(r.entry.metadata, r.entry);
if (meta.state !== "confirmed") {
stateFilteredCount++;
api.logger.debug(`memory-lancedb-pro: governance: filtered id=${r.entry.id} reason=state(${meta.state}) score=${r.score?.toFixed(3)} text=${r.entry.text.slice(0, 50)}`);
return false;
}
if (meta.memory_layer === "archive" || meta.memory_layer === "reflection") {
stateFilteredCount++;
api.logger.debug(`memory-lancedb-pro: governance: filtered id=${r.entry.id} reason=layer(${meta.memory_layer}) score=${r.score?.toFixed(3)} text=${r.entry.text.slice(0, 50)}`);
return false;
}
if (meta.suppressed_until_turn > 0 && currentTurn <= meta.suppressed_until_turn) {
Expand Down Expand Up @@ -3766,6 +3788,8 @@ export function parsePluginConfig(value: unknown): PluginConfig {
autoRecallMaxItems: parsePositiveInt(cfg.autoRecallMaxItems) ?? 3,
autoRecallMaxChars: parsePositiveInt(cfg.autoRecallMaxChars) ?? 600,
autoRecallPerItemMaxChars: parsePositiveInt(cfg.autoRecallPerItemMaxChars) ?? 180,
autoRecallExcludeAgents: Array.isArray(cfg.autoRecallExcludeAgents) ? cfg.autoRecallExcludeAgents : undefined,
recallMode: (cfg.recallMode === "full" || cfg.recallMode === "summary" || cfg.recallMode === "adaptive" || cfg.recallMode === "off") ? cfg.recallMode : "full",
captureAssistant: cfg.captureAssistant === true,
retrieval: typeof cfg.retrieval === "object" && cfg.retrieval !== null ? cfg.retrieval as any : undefined,
decay: typeof cfg.decay === "object" && cfg.decay !== null ? cfg.decay as any : undefined,
Expand Down Expand Up @@ -3904,4 +3928,6 @@ export function parsePluginConfig(value: unknown): PluginConfig {
};
}

export function _resetInitialized() { _initialized = false; }

export default memoryLanceDBProPlugin;
103 changes: 88 additions & 15 deletions openclaw.plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -137,9 +137,22 @@
"default": 180,
"description": "Maximum character budget per auto-injected memory summary."
},
"autoRecallExcludeAgents": {
"type": "array",
"items": {
"type": "string"
},
"default": [],
"description": "List of agent IDs that should be excluded from auto-recall memory injection."
},
"recallMode": {
"type": "string",
"enum": ["full", "summary", "adaptive", "off"],
"enum": [
"full",
"summary",
"adaptive",
"off"
],
"default": "full",
"description": "Auto-recall depth mode. 'full': inject with configured per-item budget. 'summary': L0 abstracts only (compact). 'adaptive': analyze query intent to auto-select category and depth. 'off': disable auto-recall injection."
},
Expand Down Expand Up @@ -238,23 +251,78 @@
"type": "object",
"additionalProperties": false,
"properties": {
"utility": { "type": "number", "minimum": 0, "maximum": 1, "default": 0.1 },
"confidence": { "type": "number", "minimum": 0, "maximum": 1, "default": 0.1 },
"novelty": { "type": "number", "minimum": 0, "maximum": 1, "default": 0.1 },
"recency": { "type": "number", "minimum": 0, "maximum": 1, "default": 0.1 },
"typePrior": { "type": "number", "minimum": 0, "maximum": 1, "default": 0.6 }
"utility": {
"type": "number",
"minimum": 0,
"maximum": 1,
"default": 0.1
},
"confidence": {
"type": "number",
"minimum": 0,
"maximum": 1,
"default": 0.1
},
"novelty": {
"type": "number",
"minimum": 0,
"maximum": 1,
"default": 0.1
},
"recency": {
"type": "number",
"minimum": 0,
"maximum": 1,
"default": 0.1
},
"typePrior": {
"type": "number",
"minimum": 0,
"maximum": 1,
"default": 0.6
}
}
},
"typePriors": {
"type": "object",
"additionalProperties": false,
"properties": {
"profile": { "type": "number", "minimum": 0, "maximum": 1, "default": 0.95 },
"preferences": { "type": "number", "minimum": 0, "maximum": 1, "default": 0.9 },
"entities": { "type": "number", "minimum": 0, "maximum": 1, "default": 0.75 },
"events": { "type": "number", "minimum": 0, "maximum": 1, "default": 0.45 },
"cases": { "type": "number", "minimum": 0, "maximum": 1, "default": 0.8 },
"patterns": { "type": "number", "minimum": 0, "maximum": 1, "default": 0.85 }
"profile": {
"type": "number",
"minimum": 0,
"maximum": 1,
"default": 0.95
},
"preferences": {
"type": "number",
"minimum": 0,
"maximum": 1,
"default": 0.9
},
"entities": {
"type": "number",
"minimum": 0,
"maximum": 1,
"default": 0.75
},
"events": {
"type": "number",
"minimum": 0,
"maximum": 1,
"default": 0.45
},
"cases": {
"type": "number",
"minimum": 0,
"maximum": 1,
"default": 0.8
},
"patterns": {
"type": "number",
"minimum": 0,
"maximum": 1,
"default": 0.85
}
}
}
}
Expand Down Expand Up @@ -1014,8 +1082,8 @@
},
"recallMode": {
"label": "Recall Mode",
"help": "Auto-recall depth: full (default), summary (L0 only), adaptive (intent-based category routing), off.",
"advanced": false
"help": "full = full text injection; summary = count hint only; adaptive = intent-driven; off = disabled.",
"advanced": true
},
"captureAssistant": {
"label": "Capture Assistant Messages",
Expand Down Expand Up @@ -1335,6 +1403,11 @@
"label": "Max Extractions Per Hour",
"help": "Rate limit for auto-capture extractions. Prevents excessive LLM calls during rapid-fire sessions.",
"advanced": true
},
"autoRecallExcludeAgents": {
"label": "Auto-Recall Exclude Agents",
"help": "Agent IDs to exclude from auto-recall memory injection. Background/cron agents should be listed here.",
"advanced": true
}
}
}
}
7 changes: 4 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
},
"devDependencies": {
"commander": "^14.0.0",
"jiti": "^2.6.0",
"jiti": "^2.6.1",
"typescript": "^5.9.3"
}
}
Loading