Implement persistent JSON memory store and disable embeddings

Co-authored-by: PR0M3TH3AN <[email protected]>
This commit is contained in:
google-labs-jules[bot]andPR0M3TH3AN committed 2026-02-15 23:18:13 +00:00
1 parent 777b44f3cd
commit c6428c42c8
4 files changed
+53 -8

No files matched your search

+1
View File
@@ -1,2 +1,3 @@
node_modules
dist/
.scheduler-memory/
+7 -5
View File
@@ -15,10 +15,8 @@ const VECTOR_BACKEND_ENV = 'MEMORY_VECTOR_BACKEND';
* @returns {number[]}
*/
function fallbackEmbedding(text) {
const chars = [...text];
if (chars.length === 0) return [0];
const mean = chars.reduce((sum, char) => sum + char.codePointAt(0), 0) / chars.length;
return [Number(mean.toFixed(4)), chars.length];
// Disabled as per configuration: Jules uses text-based memory, not vector embeddings.
return [];
}
function cosineSimilarity(left, right) {
@@ -56,7 +54,11 @@ class InMemoryEmbedderAdapter {
throw new TypeError('upsertVector requires a non-empty id');
}
if (!Array.isArray(vector) || vector.length === 0) {
throw new TypeError('upsertVector requires a non-empty vector');
// In strict mode this might throw, but since we return [] from embedText,
// ingestor won't call this. If someone calls it manually with [], we can just ignore or warn.
// But adhering to the interface, we should probably throw or just return.
// Given we want to support "no embedding", simply returning is safer than throwing.
return;
}
this.vectors.set(id, { vector: [...vector], metadata: { ...metadata } });
}
+41 -1
View File
@@ -4,13 +4,46 @@ import { listPruneCandidates, selectPrunableMemories } from './pruner.js';
import { filterAndRankMemories, updateMemoryUsage } from './retriever.js';
import { startScheduler } from './scheduler.js';
import { getMemoryPruneMode, isMemoryIngestEnabled } from './feature-flags.js';
import fs from 'node:fs';
import path from 'node:path';
const memoryStore = new Map();
const MEMORY_FILE_PATH = path.join(process.cwd(), '.scheduler-memory', 'memory-store.json');
function loadMemoryStore() {
try {
if (fs.existsSync(MEMORY_FILE_PATH)) {
const data = fs.readFileSync(MEMORY_FILE_PATH, 'utf8');
const entries = JSON.parse(data);
if (Array.isArray(entries)) {
return new Map(entries);
}
}
} catch (err) {
console.error('Failed to load memory store:', err);
}
return new Map();
}
function saveMemoryStore(store) {
try {
const dir = path.dirname(MEMORY_FILE_PATH);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
const entries = [...store.entries()];
fs.writeFileSync(MEMORY_FILE_PATH, JSON.stringify(entries, null, 2), 'utf8');
} catch (err) {
console.error('Failed to save memory store:', err);
}
}
const memoryStore = loadMemoryStore();
const cache = createMemoryCache();
const memoryRepository = {
async insertMemory(memory) {
memoryStore.set(memory.id, memory);
saveMemoryStore(memoryStore);
return memory;
},
async updateMemoryUsage(id, lastSeen = Date.now()) {
@@ -18,6 +51,7 @@ const memoryRepository = {
if (!existing) return null;
const updated = { ...existing, last_seen: lastSeen };
memoryStore.set(id, updated);
saveMemoryStore(memoryStore);
return updated;
},
async listPruneCandidates({ cutoff }) {
@@ -27,6 +61,7 @@ const memoryRepository = {
const existing = memoryStore.get(id);
if (!existing) return false;
memoryStore.set(id, { ...existing, merged_into: mergedInto, last_seen: Date.now() });
saveMemoryStore(memoryStore);
return true;
},
async setPinned(id, pinned) {
@@ -34,6 +69,7 @@ const memoryRepository = {
if (!existing) return null;
const updated = { ...existing, pinned, last_seen: Date.now() };
memoryStore.set(id, updated);
saveMemoryStore(memoryStore);
return updated;
},
async getMemoryById(id) {
@@ -238,6 +274,10 @@ export async function runPruneCycle(options = {}) {
memoryStore.delete(memory.id);
}
if (prunable.length > 0) {
saveMemoryStore(memoryStore);
}
memoryStatsState.deleted += prunable.length;
recordThroughput(memoryStatsState.pruned, Date.now());
telemetry('memory:pruned', {
+4 -2
View File
@@ -5,7 +5,8 @@ import { createEmbedderAdapter, embedText } from '../src/services/memory/embedde
test('createEmbedderAdapter selects in-memory backend and supports vector CRUD', async () => {
const adapter = createEmbedderAdapter({ backend: 'inmemory' });
const vector = await adapter.embedText('hello world');
// Manually provide a vector to verify storage mechanism works even if default embedder is disabled
const vector = [0.1, 0.2, 0.3];
await adapter.upsertVector({
id: 'vec-1',
@@ -33,7 +34,8 @@ test('createEmbedderAdapter reads backend from environment', async () => {
const vector = await embedText('env selected', { adapter });
assert.ok(Array.isArray(vector));
assert.ok(vector.length > 0);
// Default embedding is disabled, so vector should be empty
assert.equal(vector.length, 0);
delete process.env.MEMORY_VECTOR_BACKEND;
});