updated to cue memory now.

This commit is contained in:
Luna
2026-03-03 21:20:38 +01:00
parent 55b5e88acb
commit 68d7dd747f
6 changed files with 70 additions and 16 deletions

View File

@@ -22,9 +22,15 @@ let coderPingTimer;
const continuationState = new Map();
let isSleeping = false;
const recallPatterns = config.memoryRecallTriggerPatterns || [];
const contextCache = new Map();
const CONTEXT_CACHE_TTL_MS = 2 * 60 * 1000;
function matchesMemoryRecallCue(text) {
if (!text) return false;
return recallPatterns.some((pattern) => pattern.test(text));
}
const cloneShortTerm = (entries = []) => entries.map((entry) => ({ ...entry }));
const cloneMemories = (entries = []) => entries.map((entry) => ({ ...entry }));
@@ -203,6 +209,7 @@ function startContinuationForUser(userId, channel) {
const { messages, debug } = await buildPrompt(userId, incomingText, {
context: cachedContext,
userName: cachedContext?.userName || null,
includeMemories: false,
});
cacheContext(userId, debug.context);
const reply = await chatCompletion(messages, { temperature: 0.7, maxTokens: 200 });
@@ -687,6 +694,7 @@ client.on('messageCreate', async (message) => {
return;
}
const recallTrigger = matchesMemoryRecallCue(cleaned);
const intelMeta = (await maybeFetchLiveIntel(userId, cleaned)) || {
liveIntel: null,
blockedSearchTerm: null,
@@ -696,6 +704,8 @@ client.on('messageCreate', async (message) => {
liveIntel: intelMeta.liveIntel,
blockedSearchTerm: intelMeta.blockedSearchTerm,
searchOutage: intelMeta.searchOutage,
includeMemories: recallTrigger,
similarityThreshold: config.memoryRecallSimilarityThreshold,
userName: message.member?.displayName || message.author.username,
});
cacheContext(userId, debug.context);

View File

@@ -7,6 +7,16 @@ dotenv.config();
const defaultMemoryDbFile = fileURLToPath(new URL('../data/memory.sqlite', import.meta.url));
const legacyMemoryFile = fileURLToPath(new URL('../data/memory.json', import.meta.url));
const memoryRecallTriggerPatterns = [
/remember( when| that)?/i,
/do you know( about)?/i,
/do you remember/i,
/\bwe talked\b/i,
/\brecall\b/i,
/\bremind\b/i,
/\brefresh my memory\b/i,
];
const requiredEnv = ['DISCORD_TOKEN'];
requiredEnv.forEach((key) => {
if (!process.env[key]) {
@@ -33,10 +43,15 @@ export const config = {
memoryDbFile: process.env.MEMORY_DB_FILE ? path.resolve(process.env.MEMORY_DB_FILE) : defaultMemoryDbFile,
legacyMemoryFile,
summaryTriggerChars: 2200,
summaryTriggerTurns: process.env.SUMMARY_TRIGGER_TURNS ? parseInt(process.env.SUMMARY_TRIGGER_TURNS, 10) : 12,
memoryPruneThreshold: 0.2,
memoryCooldownMs: process.env.MEMORY_COOLDOWN_MS ? parseInt(process.env.MEMORY_COOLDOWN_MS, 10) : 3 * 60 * 1000,
maxMemories: 8000,
enableShortTermSummary: process.env.ENABLE_SHORT_TERM_SUMMARY !== 'false',
memoryRecallSimilarityThreshold: process.env.MEMORY_RECALL_SIMILARITY_THRESHOLD
? parseFloat(process.env.MEMORY_RECALL_SIMILARITY_THRESHOLD)
: 0.62,
memoryRecallTriggerPatterns,
relevantMemoryCount: 3,
longTermFetchLimit: 120,
// Optional local dashboard that runs alongside the bot. Enable with

View File

@@ -242,7 +242,10 @@ const fullShortTerm = (db, userId) =>
const maybeSummarize = async (db, userId) => {
const shortTermEntries = fullShortTerm(db, userId);
const charCount = shortTermEntries.reduce((sum, msg) => sum + (msg.content?.length || 0), 0);
if (charCount < config.summaryTriggerChars || shortTermEntries.length < config.shortTermLimit) {
if (
charCount < config.summaryTriggerChars &&
shortTermEntries.length < (config.summaryTriggerTurns || config.shortTermLimit)
) {
return false;
}
const userRow = get(db, 'SELECT summary FROM users WHERE id = ?', [userId]) || { summary: '' };
@@ -327,7 +330,7 @@ const retrieveRelevantMemories = async (db, userId, query, options = {}) => {
return [];
}
const limit = config.longTermFetchLimit || 200;
const { includeAllUsers = false } = options;
const { includeAllUsers = false, minScore = Number.NEGATIVE_INFINITY } = options;
const params = [];
const whereClause = includeAllUsers ? '' : ' WHERE user_id = ?';
if (!includeAllUsers) {
@@ -344,7 +347,7 @@ const retrieveRelevantMemories = async (db, userId, query, options = {}) => {
}
const now = Date.now();
const cooldown = config.memoryCooldownMs || 0;
const usage = memoryUsageMap.get(userId);
const usage = getMemoryUsageMapForUser(userId);
const eligibleRows =
cooldown && usage
? rows.filter((entry) => now - (usage.get(entry.id) || 0) > cooldown)
@@ -360,13 +363,13 @@ const retrieveRelevantMemories = async (db, userId, query, options = {}) => {
score: cosineSimilarity(queryEmbedding, embedding) + entry.importance * 0.1,
};
})
.sort((a, b) => b.score - a.score)
.slice(0, config.relevantMemoryCount);
if (scored.length) {
const usageMap = getMemoryUsageMapForUser(userId);
scored.forEach((entry) => usageMap.set(entry.id, now));
.sort((a, b) => b.score - a.score);
const filtered = scored.filter((entry) => entry.score >= minScore);
const capped = filtered.slice(0, config.relevantMemoryCount);
if (capped.length) {
capped.forEach((entry) => usage.set(entry.id, now));
}
return scored;
return capped;
};
export async function appendShortTerm(userId, role, content) {
@@ -390,8 +393,18 @@ export async function prepareContext(userId, incomingMessage, options = {}) {
ensureUser(db, userId);
const userRow = get(db, 'SELECT summary FROM users WHERE id = ?', [userId]) || { summary: '' };
const shortTerm = getShortTermHistory(db, userId, config.shortTermLimit);
const { includeAllUsers = false } = options;
const memories = await retrieveRelevantMemories(db, userId, incomingMessage, { includeAllUsers });
const {
includeAllUsers = false,
includeLongTerm = true,
memorySimilarityThreshold = Number.NEGATIVE_INFINITY,
} = options;
const memories =
includeLongTerm && incomingMessage?.trim()
? await retrieveRelevantMemories(db, userId, incomingMessage, {
includeAllUsers,
minScore: memorySimilarityThreshold,
})
: [];
return {
shortTerm,
summary: userRow.summary || '',

View File

@@ -95,9 +95,21 @@ export async function buildPrompt(userId, incomingText, options = {}) {
context: providedContext = null,
useGlobalMemories = false,
userName = null,
includeMemories = false,
similarityThreshold = null,
} = options;
const context =
providedContext || (await prepareContext(userId, incomingText, { includeAllUsers: useGlobalMemories }));
providedContext ||
(await prepareContext(userId, incomingText, {
includeAllUsers: useGlobalMemories,
includeLongTerm: includeMemories || useGlobalMemories,
memorySimilarityThreshold:
includeMemories && similarityThreshold !== null
? similarityThreshold
: includeMemories
? config.memoryRecallSimilarityThreshold
: Number.NEGATIVE_INFINITY,
}));
if (userName) {
context.userName = userName;
} else if (context.userName === undefined) {