q / core /memory.js
Humuhumu33's picture
Upload folder using huggingface_hub
3365e13 verified
Raw
History Blame Contribute Delete
3.98 kB
// core/memory.js — persistent USER MEMORY (LibreChat memory semantics, substrate-native).
// Memories are key/value facts (key validated /^[a-z_]+$/, value a complete sentence) stored as
// κ-objects with pointers in the boot index. They inject into the system block as the
// "# Existing memory:" section, and the model maintains them itself through two LOCAL tools
// (set_memory / delete_memory) armed alongside MCP tools in the agentic loop — no server,
// every write content-addressed and verifiable.
const LC = { lc: "https://librechat.ai/ns#" };
export const KEY_RE = /^[a-z_]+$/;
export function makeMemory(chatStore, { tokenLimit = 2000 } = {}) {
const { store, getIndex, newId } = chatStore;
const putIndex = async (idx) => { const b = new TextEncoder().encode(JSON.stringify(idx)); return store.backend.putRaw ? store.backend.putRaw("index:org.hologram.HoloQ", b) : store.backend.put("index:org.hologram.HoloQ", b); };
const tokensOf = (s) => Math.ceil((s || "").length / 4); // a fair, dependency-free estimate
async function list() { return (await getIndex()).memories || []; }
async function set(key, value) {
key = String(key || "").toLowerCase().trim();
if (!KEY_RE.test(key)) return { ok: false, error: "invalid key — use lowercase letters and underscores" };
const idx = await getIndex();
idx.memories = idx.memories || [];
const used = idx.memories.filter((m) => m.key !== key).reduce((n, m) => n + (m.tokenCount || 0), 0);
const tc = tokensOf(value);
if (used + tc > tokenLimit) return { ok: false, error: `memory full: ${used}+${tc} > ${tokenLimit} tokens` };
const obj = await store.makeObject({
type: ["schema:Statement", "prov:Entity"], context: [LC],
"lc:key": key, "lc:value": String(value || ""), "lc:tokenCount": tc,
"schema:dateModified": new Date().toISOString(),
});
const i = idx.memories.findIndex((m) => m.key === key);
const ptr = { key, kappa: obj.id, value: String(value || ""), tokenCount: tc, updated_at: new Date().toISOString() };
if (i >= 0) idx.memories[i] = ptr; else idx.memories.push(ptr);
await putIndex(idx);
return { ok: true, key, kappa: obj.id };
}
async function remove(key) {
const idx = await getIndex();
idx.memories = (idx.memories || []).filter((m) => m.key !== key);
await putIndex(idx);
return { ok: true };
}
// The system-block injection ("# Existing memory:" — the LibreChat convention).
async function injection() {
const mems = await list();
if (!mems.length) return "";
const used = mems.reduce((n, m) => n + (m.tokenCount || 0), 0);
return `# Memory Status:\nCurrent memory usage: ${used} tokens\nToken limit: ${tokenLimit} tokens\nRemaining capacity: ${Math.max(0, tokenLimit - used)} tokens\n\n# Existing memory:\n` +
mems.map((m) => `- ${m.key}: ${m.value}`).join("\n");
}
// The two LOCAL tools the agentic loop arms (same shape as MCP hub tools).
function localTools() {
return [
{
def: { name: "set_memory", description: "Remember a fact about the user across conversations. Only when the user explicitly asks (\"remember that…\"). key: lowercase_with_underscores; value: one complete sentence.", inputSchema: { type: "object", properties: { key: { type: "string" }, value: { type: "string" } }, required: ["key", "value"] } },
serverName: "memory", call: async (a) => { const r = await set(a.key, a.value); return { text: JSON.stringify(r), isError: !r.ok }; },
},
{
def: { name: "delete_memory", description: "Forget a remembered fact, by key. Only when the user explicitly asks.", inputSchema: { type: "object", properties: { key: { type: "string" } }, required: ["key"] } },
serverName: "memory", call: async (a) => { const r = await remove(a.key); return { text: JSON.stringify(r), isError: false }; },
},
];
}
return { list, set, remove, injection, localTools, tokenLimit };
}