mirror of
https://github.com/PR0M3TH3AN/TORCH.git
synced 2026-11-05 12:21:56 +00:00
Implement persistent JSON memory store and disable embeddings
Co-authored-by: PR0M3TH3AN <[email protected]>
This commit is contained in:
1 parent
777b44f3cd
commit
c6428c42c8
4 files changed
+53
-8
No files matched your search
@@ -1,2 +1,3 @@
|
|||||||
node_modules
|
node_modules
|
||||||
dist/
|
dist/
|
||||||
|
.scheduler-memory/
|
||||||
@@ -15,10 +15,8 @@ const VECTOR_BACKEND_ENV = 'MEMORY_VECTOR_BACKEND';
|
|||||||
* @returns {number[]}
|
* @returns {number[]}
|
||||||
*/
|
*/
|
||||||
function fallbackEmbedding(text) {
|
function fallbackEmbedding(text) {
|
||||||
const chars = [...text];
|
// Disabled as per configuration: Jules uses text-based memory, not vector embeddings.
|
||||||
if (chars.length === 0) return [0];
|
return [];
|
||||||
const mean = chars.reduce((sum, char) => sum + char.codePointAt(0), 0) / chars.length;
|
|
||||||
return [Number(mean.toFixed(4)), chars.length];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function cosineSimilarity(left, right) {
|
function cosineSimilarity(left, right) {
|
||||||
@@ -56,7 +54,11 @@ class InMemoryEmbedderAdapter {
|
|||||||
throw new TypeError('upsertVector requires a non-empty id');
|
throw new TypeError('upsertVector requires a non-empty id');
|
||||||
}
|
}
|
||||||
if (!Array.isArray(vector) || vector.length === 0) {
|
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 } });
|
this.vectors.set(id, { vector: [...vector], metadata: { ...metadata } });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,13 +4,46 @@ import { listPruneCandidates, selectPrunableMemories } from './pruner.js';
|
|||||||
import { filterAndRankMemories, updateMemoryUsage } from './retriever.js';
|
import { filterAndRankMemories, updateMemoryUsage } from './retriever.js';
|
||||||
import { startScheduler } from './scheduler.js';
|
import { startScheduler } from './scheduler.js';
|
||||||
import { getMemoryPruneMode, isMemoryIngestEnabled } from './feature-flags.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 cache = createMemoryCache();
|
||||||
|
|
||||||
const memoryRepository = {
|
const memoryRepository = {
|
||||||
async insertMemory(memory) {
|
async insertMemory(memory) {
|
||||||
memoryStore.set(memory.id, memory);
|
memoryStore.set(memory.id, memory);
|
||||||
|
saveMemoryStore(memoryStore);
|
||||||
return memory;
|
return memory;
|
||||||
},
|
},
|
||||||
async updateMemoryUsage(id, lastSeen = Date.now()) {
|
async updateMemoryUsage(id, lastSeen = Date.now()) {
|
||||||
@@ -18,6 +51,7 @@ const memoryRepository = {
|
|||||||
if (!existing) return null;
|
if (!existing) return null;
|
||||||
const updated = { ...existing, last_seen: lastSeen };
|
const updated = { ...existing, last_seen: lastSeen };
|
||||||
memoryStore.set(id, updated);
|
memoryStore.set(id, updated);
|
||||||
|
saveMemoryStore(memoryStore);
|
||||||
return updated;
|
return updated;
|
||||||
},
|
},
|
||||||
async listPruneCandidates({ cutoff }) {
|
async listPruneCandidates({ cutoff }) {
|
||||||
@@ -27,6 +61,7 @@ const memoryRepository = {
|
|||||||
const existing = memoryStore.get(id);
|
const existing = memoryStore.get(id);
|
||||||
if (!existing) return false;
|
if (!existing) return false;
|
||||||
memoryStore.set(id, { ...existing, merged_into: mergedInto, last_seen: Date.now() });
|
memoryStore.set(id, { ...existing, merged_into: mergedInto, last_seen: Date.now() });
|
||||||
|
saveMemoryStore(memoryStore);
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
async setPinned(id, pinned) {
|
async setPinned(id, pinned) {
|
||||||
@@ -34,6 +69,7 @@ const memoryRepository = {
|
|||||||
if (!existing) return null;
|
if (!existing) return null;
|
||||||
const updated = { ...existing, pinned, last_seen: Date.now() };
|
const updated = { ...existing, pinned, last_seen: Date.now() };
|
||||||
memoryStore.set(id, updated);
|
memoryStore.set(id, updated);
|
||||||
|
saveMemoryStore(memoryStore);
|
||||||
return updated;
|
return updated;
|
||||||
},
|
},
|
||||||
async getMemoryById(id) {
|
async getMemoryById(id) {
|
||||||
@@ -238,6 +274,10 @@ export async function runPruneCycle(options = {}) {
|
|||||||
memoryStore.delete(memory.id);
|
memoryStore.delete(memory.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (prunable.length > 0) {
|
||||||
|
saveMemoryStore(memoryStore);
|
||||||
|
}
|
||||||
|
|
||||||
memoryStatsState.deleted += prunable.length;
|
memoryStatsState.deleted += prunable.length;
|
||||||
recordThroughput(memoryStatsState.pruned, Date.now());
|
recordThroughput(memoryStatsState.pruned, Date.now());
|
||||||
telemetry('memory:pruned', {
|
telemetry('memory:pruned', {
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ import { createEmbedderAdapter, embedText } from '../src/services/memory/embedde
|
|||||||
|
|
||||||
test('createEmbedderAdapter selects in-memory backend and supports vector CRUD', async () => {
|
test('createEmbedderAdapter selects in-memory backend and supports vector CRUD', async () => {
|
||||||
const adapter = createEmbedderAdapter({ backend: 'inmemory' });
|
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({
|
await adapter.upsertVector({
|
||||||
id: 'vec-1',
|
id: 'vec-1',
|
||||||
@@ -33,7 +34,8 @@ test('createEmbedderAdapter reads backend from environment', async () => {
|
|||||||
const vector = await embedText('env selected', { adapter });
|
const vector = await embedText('env selected', { adapter });
|
||||||
|
|
||||||
assert.ok(Array.isArray(vector));
|
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;
|
delete process.env.MEMORY_VECTOR_BACKEND;
|
||||||
});
|
});
|
||||||
Reference in new issue
Block a user