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
46 changes: 46 additions & 0 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ import {
type AdmissionRejectionAuditEntry,
} from "./src/admission-control.js";
import { analyzeIntent, applyCategoryBoost } from "./src/intent-analyzer.js";
import { createEntityGraph, type EntityGraph } from "./src/entity-graph.js";
import { createConfidenceTracker, type ConfidenceTracker } from "./src/confidence-tracker.js";
import { createProactiveInjector, type ProactiveInjector } from "./src/proactive-injector.js";

// ============================================================================
// Configuration & Types
Expand Down Expand Up @@ -183,6 +186,7 @@ interface PluginConfig {
default?: string;
definitions?: Record<string, { description: string }>;
agentAccess?: Record<string, string[]>;
shared?: { enabled?: boolean; autoPromote?: boolean };
};
enableManagementTools?: boolean;
sessionStrategy?: SessionStrategy;
Expand Down Expand Up @@ -225,6 +229,13 @@ interface PluginConfig {
skipLowValue?: boolean;
maxExtractionsPerHour?: number;
};
entityGraph?: { enabled?: boolean };
proactive?: {
enabled?: boolean;
staleMemoryDays?: number;
entityPrefetch?: boolean;
patternTriggers?: Record<string, string>;
};
}

type ReflectionThinkLevel = "off" | "minimal" | "low" | "medium" | "high";
Expand Down Expand Up @@ -1690,6 +1701,14 @@ const memoryLanceDBProPlugin = {
);
const scopeManager = createScopeManager(config.scopes);

// Configure shared scope based on config (default: enabled)
const sharedEnabled = config.scopes?.shared?.enabled !== false;
if (sharedEnabled && !scopeManager.getScopeDefinition("shared")) {
scopeManager.addScopeDefinition("shared", {
description: "Cross-agent shared knowledge — read by all agents, written only by explicit writes or dreaming engine",
});
}

// ClawTeam integration: extend accessible scopes via env var
const clawteamScopes = parseClawteamScopes(process.env.CLAWTEAM_MEMORY_SCOPE);
if (clawteamScopes.length > 0) {
Expand Down Expand Up @@ -2085,6 +2104,22 @@ const memoryLanceDBProPlugin = {
);
});

// ========================================================================
// Initialize Priority 3 Enhancements
// ========================================================================

const entityGraph = createEntityGraph({ enabled: config.entityGraph?.enabled ?? false });
const confidenceTracker = createConfidenceTracker({ enabled: true });
const proactiveInjector = createProactiveInjector(
{ retriever, entityGraph, scopeManager },
{
enabled: config.proactive?.enabled ?? false,
staleMemoryDays: config.proactive?.staleMemoryDays ?? 7,
entityPrefetch: config.proactive?.entityPrefetch ?? true,
patternTriggers: config.proactive?.patternTriggers ?? {},
},
);

// ========================================================================
// Markdown Mirror
// ========================================================================
Expand All @@ -2106,6 +2141,8 @@ const memoryLanceDBProPlugin = {
workspaceDir: getDefaultWorkspaceDir(),
mdMirror,
workspaceBoundary: config.workspaceBoundary,
entityGraph,
confidenceTracker,
},
{
enableManagementTools: config.enableManagementTools,
Expand Down Expand Up @@ -2485,6 +2522,11 @@ const memoryLanceDBProPlugin = {
}),
);

// Track confidence for auto-recalled memories
for (const item of selected) {
confidenceTracker.recordRecall(item.id);
}

const memoryContext = selected.map((item) => item.line).join("\n");

const injectedIds = selected.map((item) => item.id).join(",") || "(none)";
Expand Down Expand Up @@ -2755,6 +2797,10 @@ const memoryLanceDBProPlugin = {
conversationText, sessionKey,
{ scope: defaultScope, scopeFilter: accessibleScopes },
);
// Extract entities from conversation text into the entity graph
if (config.entityGraph?.enabled) {
entityGraph.addEntitiesAndRelationships(conversationText);
}
// Charge rate limiter only after successful extraction
extractionRateLimiter.recordExtraction();
if (stats.created > 0 || stats.merged > 0) {
Expand Down
88 changes: 88 additions & 0 deletions openclaw.plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -853,6 +853,94 @@
"description": "Maximum number of auto-capture extractions allowed per hour"
}
}
},
"scopes": {
"type": "object",
"additionalProperties": false,
"properties": {
"default": {
"type": "string",
"default": "global"
},
"definitions": {
"type": "object",
"additionalProperties": {
"type": "object",
"properties": {
"description": {
"type": "string"
}
}
}
},
"agentAccess": {
"type": "object",
"additionalProperties": {
"type": "array",
"items": {
"type": "string"
}
}
},
"shared": {
"type": "object",
"additionalProperties": false,
"properties": {
"enabled": {
"type": "boolean",
"default": true,
"description": "Enable cross-agent shared memory scope (read by all agents)"
},
"autoPromote": {
"type": "boolean",
"default": false,
"description": "Auto-promote memories accessed by 3+ agents to shared scope during dream cycle"
}
}
}
}
},
"entityGraph": {
"type": "object",
"additionalProperties": false,
"properties": {
"enabled": {
"type": "boolean",
"default": false,
"description": "Enable entity relationship extraction and graph"
}
}
},
"proactive": {
"type": "object",
"additionalProperties": false,
"description": "Proactive memory injection alongside auto-recall",
"properties": {
"enabled": {
"type": "boolean",
"default": false,
"description": "Enable proactive memory injection"
},
"staleMemoryDays": {
"type": "number",
"minimum": 1,
"maximum": 365,
"default": 7,
"description": "Days before a memory is considered stale for proactive injection"
},
"entityPrefetch": {
"type": "boolean",
"default": true,
"description": "Pre-fetch related memories when user mentions a known entity"
},
"patternTriggers": {
"type": "object",
"additionalProperties": {
"type": "string"
},
"description": "Map of regex patterns to search queries for proactive injection"
}
}
}
}
},
Expand Down
1 change: 0 additions & 1 deletion package-lock.json

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

111 changes: 111 additions & 0 deletions src/confidence-tracker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/**
* Memory Confidence Scoring
* Tracks per-memory confidence based on recall/useful signals.
* Stores data in memory metadata — no separate table needed.
*/

// ============================================================================
// Types
// ============================================================================

export interface ConfidenceTrackerConfig {
enabled: boolean;
decayFactor: number;
}

interface MemoryConfidenceState {
recallCount: number;
usefulCount: number;
decayBoost: number;
lastRecallAt: number;
}

// ============================================================================
// Implementation
// ============================================================================

export interface ConfidenceTracker {
recordRecall(memoryId: string): void;
recordUseful(memoryId: string): void;
getConfidence(memoryId: string): number;
getTopConfident(limit: number): string[];
getState(memoryId: string): MemoryConfidenceState | undefined;
reset(): void;
}

export function createConfidenceTracker(config: ConfidenceTrackerConfig = { enabled: true, decayFactor: 0.95 }): ConfidenceTracker {
if (!config.enabled) {
return createNoopTracker();
}

const state = new Map<string, MemoryConfidenceState>();

return {
recordRecall(memoryId: string): void {
const existing = state.get(memoryId);
const now = Date.now();
if (existing) {
existing.recallCount++;
existing.lastRecallAt = now;
// If recalled but not marked useful recently, decay slightly
existing.decayBoost = Math.max(0.5, existing.decayBoost * config.decayFactor);
} else {
state.set(memoryId, {
recallCount: 1,
usefulCount: 0,
decayBoost: 1.0,
lastRecallAt: now,
});
}
},

recordUseful(memoryId: string): void {
const existing = state.get(memoryId);
if (existing) {
existing.usefulCount++;
// Restore decay boost on useful signal
existing.decayBoost = Math.min(1.0, existing.decayBoost + 0.1);
} else {
state.set(memoryId, {
recallCount: 0,
usefulCount: 1,
decayBoost: 1.0,
lastRecallAt: Date.now(),
});
}
},

getConfidence(memoryId: string): number {
const s = state.get(memoryId);
if (!s) return 0;
return (s.usefulCount / Math.max(s.recallCount, 1)) * s.decayBoost;
},

getTopConfident(limit: number): string[] {
return Array.from(state.entries())
.map(([id, s]) => ({ id, score: s.usefulCount / Math.max(s.recallCount, 1) * s.decayBoost }))
.sort((a, b) => b.score - a.score)
.slice(0, limit)
.map(e => e.id);
},

getState(memoryId: string): MemoryConfidenceState | undefined {
return state.get(memoryId);
},

reset(): void {
state.clear();
},
};
}

function createNoopTracker(): ConfidenceTracker {
return {
recordRecall: () => {},
recordUseful: () => {},
getConfidence: () => 0,
getTopConfident: () => [],
getState: () => undefined,
reset: () => {},
};
}
Loading