From b1bfa5755998458a3b971a9e62f58532e765d184 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 1 Jun 2026 19:49:59 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=20Bolt:=20Optimize=20context=20servic?= =?UTF-8?q?e=20performance?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Removed redundant episodic_memory database fetch in buildContext - Implemented 5-minute TTL cache for semantic knowledge retrieval - Implemented 1-minute TTL cache for working memory queries - Cleaned up unused constants and dead functions previously used for episodic history processing Co-authored-by: SuvenSeo <263689617+SuvenSeo@users.noreply.github.com> --- .jules/bolt.md | 5 ++ frontend/src/lib/services/context.js | 127 ++++----------------------- 2 files changed, 21 insertions(+), 111 deletions(-) create mode 100644 .jules/bolt.md diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 0000000..1f05b67 --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,5 @@ +# Bolt's Performance Journal - Critical Learnings + +## 2026-06-01 - Redundant Episodic Memory Fetch +**Learning:** The `buildContext` function was fetching episodic memory (history) in every tool loop iteration, even though the history is already maintained and passed via the `messages` array in the API handlers. This resulted in redundant database queries and increased latency. +**Action:** Remove the redundant `episodic_memory` fetch from `buildContext` and rely on the `messages` array for conversation history. diff --git a/frontend/src/lib/services/context.js b/frontend/src/lib/services/context.js index 3246225..238957a 100644 --- a/frontend/src/lib/services/context.js +++ b/frontend/src/lib/services/context.js @@ -17,21 +17,6 @@ function setCache(key, val, ttlMs) { const TTL_5MIN = 5 * 60 * 1000; const TTL_1MIN = 1 * 60 * 1000; let hasWarnedMissingKnowledgeFts = false; -const EPISODE_FETCH_LIMIT = 40; -const BACKGROUND_EPISODE_LIMIT = 8; -const SESSION_BREAK_MS = 90 * 60 * 1000; -const CONTEXT_NOISE_PATTERNS = [ - /^_?📋 \d+ task\(s\) logged/i, - /^_?⏰ \d+ reminder\(s\) set/i, - /^_?💡 \d+ idea\(s\) captured/i, - /^_?🧠 \d+ memory update\(s\)/i, - /^unknown command:/i, - /^❌ usage:/i, - /^⚠️ all ai providers are currently down/i, - /i(?:'| a)m not going to engage in this conversation anymore/i, - /^end of conversation/i, - /^🔴 .*test message/i, -]; const GREETING_ONLY_PATTERN = /^(hi|hii+|hello+|helloo+|hey+|heyy+|yo+|sup+|hola+|good (morning|afternoon|evening)|what'?s up|whats up|how are you)\W*$/i; const RESPONSE_GUARDRAILS = `NON-NEGOTIABLE RESPONSE RULES: - Follow explicit user instructions first (especially reset/delete/clear requests). @@ -42,11 +27,6 @@ const RESPONSE_GUARDRAILS = `NON-NEGOTIABLE RESPONSE RULES: - Keep the tone natural and collaborative; avoid robotic scripts and repeated fallback wording. - Before finalizing your answer, self-check for contradiction with the latest user message and fix it. - If unsure, ask one clarifying question instead of guessing.`; -const MEANINGFUL_HINTS = [ - 'task', 'remind', 'deadline', 'due', 'exam', 'project', - 'meeting', 'decide', 'decision', 'priority', 'plan', 'commit', -]; - async function rerankKnowledgeSemantically(userMessage, candidates) { if (!candidates || candidates.length <= 1) return (candidates || []).slice(0, 5); @@ -86,6 +66,10 @@ ${candidateText}`); } async function fetchRelevantKnowledge(userMessage, keywords) { + const cacheKey = `knowledge:${userMessage.trim().toLowerCase().replace(/\s+/g, ' ')}:${[...keywords].sort().join(',')}`; + const cached = getCache(cacheKey); + if (cached) return cached; + const ftsQuery = keywords.join(' | '); const baseQuery = supabase .from('knowledge_base') @@ -98,7 +82,9 @@ async function fetchRelevantKnowledge(userMessage, keywords) { .textSearch('fts', ftsQuery, { type: 'plain', config: 'english' }); if (!ftsError) { - return rerankKnowledgeSemantically(userMessage, ftsRows || []); + const results = await rerankKnowledgeSemantically(userMessage, ftsRows || []); + setCache(cacheKey, results, TTL_5MIN); + return results; } const message = (ftsError.message || '').toLowerCase(); @@ -130,51 +116,9 @@ async function fetchRelevantKnowledge(userMessage, keywords) { return []; } - return rerankKnowledgeSemantically(userMessage, fallbackRows || []); -} - -function compressVerboseContent(content = '') { - const text = content.replace(/\s+/g, ' ').trim(); - if (!text) return ''; - - if (text.startsWith('[Image analysis]')) { - return `[image summary] ${text.replace('[Image analysis]', '').trim().slice(0, 180)}...`; - } - if (text.startsWith('[Document:')) { - const title = text.slice(0, text.indexOf(']') + 1); - const body = text.slice(text.indexOf(']') + 1).trim(); - return `${title} ${body.slice(0, 160)}...`; - } - if (text.includes('[URL content from')) { - const start = text.indexOf('[URL content from'); - const end = text.indexOf(']', start); - const label = end > start ? text.slice(start, end + 1) : '[URL content]'; - return `${label} ${text.slice(0, 130)}...`; - } - if (text.length > 550) { - return `${text.slice(0, 220)}...`; - } - return text; -} - -function scoreEpisodeForContext(episode) { - const text = (episode.content || '').toLowerCase(); - let score = episode.role === 'user' ? 2 : 1; - if (text.length < 24) score -= 1; - if (text.startsWith('[image analysis]') || text.startsWith('[document:') || text.includes('[url content from')) { - score -= 2; - } - for (const hint of MEANINGFUL_HINTS) { - if (text.includes(hint)) score += 1; - } - return score; -} - -function isContextNoiseEpisode(episode) { - if (!episode || !episode.content) return true; - const text = (episode.content || '').replace(/\s+/g, ' ').trim(); - if (!text) return true; - return CONTEXT_NOISE_PATTERNS.some((pattern) => pattern.test(text)); + const results = await rerankKnowledgeSemantically(userMessage, fallbackRows || []); + setCache(cacheKey, results, TTL_5MIN); + return results; } function isGreetingOnlyMessage(message = '') { @@ -186,44 +130,6 @@ function isGreetingOnlyMessage(message = '') { return GREETING_ONLY_PATTERN.test(text); } -function selectConversationLines(episodes = []) { - if (!episodes.length) return []; - - const ordered = [...episodes].sort( - (a, b) => new Date(a.created_at || 0).getTime() - new Date(b.created_at || 0).getTime() - ); - - let sessionStart = ordered.length - 1; - for (let i = ordered.length - 1; i > 0; i--) { - const cur = new Date(ordered[i].created_at || 0).getTime(); - const prev = new Date(ordered[i - 1].created_at || 0).getTime(); - if (!cur || !prev || Number.isNaN(cur) || Number.isNaN(prev)) continue; - if ((cur - prev) > SESSION_BREAK_MS) break; - sessionStart = i - 1; - } - - const background = ordered.slice(0, sessionStart); - const currentSession = ordered.slice(sessionStart); - - const selectedBackground = background - .map(ep => ({ ep, score: scoreEpisodeForContext(ep) })) - .sort((a, b) => b.score - a.score) - .slice(0, BACKGROUND_EPISODE_LIMIT) - .map(x => x.ep) - .sort((a, b) => new Date(a.created_at || 0).getTime() - new Date(b.created_at || 0).getTime()); - - const lines = []; - for (const ep of selectedBackground) { - if (isContextNoiseEpisode(ep)) continue; - lines.push(`[${ep.role}] ${compressVerboseContent(ep.content)}`); - } - for (const ep of currentSession) { - if (isContextNoiseEpisode(ep)) continue; - lines.push(`[${ep.role}] ${(ep.content || '').replace(/\s+/g, ' ').trim()}`); - } - return lines; -} - /** * Build the full dynamic context for the AI brain. * Called before every Groq API call to inject current state. @@ -246,14 +152,13 @@ async function buildContext(userMessage = '') { const cachedCore = getCache('core_memory'); const cachedPatterns = getCache('patterns'); const cachedIdeas = getCache('ideas'); + const cachedWorking = getCache('working_memory'); const promises = [ - // Always fresh: episodic memory window for smart context selection - supabase.from('episodic_memory').select('role, content, created_at').order('created_at', { ascending: false }).limit(EPISODE_FETCH_LIMIT), // Always fresh: open tasks supabase.from('tasks').select('id, title, description, deadline, priority, status, follow_up_count, tier').in('status', ['open', 'snoozed']).order('priority', { ascending: true }), // Cached 1 min: working memory - supabase.from('working_memory').select('key, value, expires_at').or(`expires_at.is.null,expires_at.gt.${now.toISOString()}`), + cachedWorking ? Promise.resolve({ data: cachedWorking }) : supabase.from('working_memory').select('key, value, expires_at').or(`expires_at.is.null,expires_at.gt.${now.toISOString()}`), // Cached 5 min: core memory cachedCore ? Promise.resolve({ data: cachedCore }) : supabase.from('core_memory').select('key, value').order('key'), // Cached 5 min: patterns @@ -263,7 +168,6 @@ async function buildContext(userMessage = '') { ]; const [ - { data: episodes }, { data: tasks }, { data: workingMemory }, { data: coreMemory }, @@ -272,9 +176,10 @@ async function buildContext(userMessage = '') { ] = await Promise.all(promises); // Update caches for slow-changing data - if (!cachedCore && coreMemory) setCache('core_memory', coreMemory, TTL_5MIN); - if (!cachedPatterns && patterns) setCache('patterns', patterns, TTL_5MIN); - if (!cachedIdeas && ideas) setCache('ideas', ideas, TTL_5MIN); + if (!cachedCore && coreMemory) setCache('core_memory', coreMemory, TTL_5MIN); + if (!cachedPatterns && patterns) setCache('patterns', patterns, TTL_5MIN); + if (!cachedIdeas && ideas) setCache('ideas', ideas, TTL_5MIN); + if (!cachedWorking && workingMemory) setCache('working_memory', workingMemory, TTL_1MIN); // (History is now handled via the messages array in the chat completion call to prevent redundancy)