Compare commits

..

3 Commits
1.2.4 ... 1.2.6

Author SHA1 Message Date
14f6cdd313 Personal file memory organization instructions
All checks were successful
Publish Library / Build NPM Project (push) Successful in 33s
Publish Library / Tag Version (push) Successful in 12s
2026-07-27 22:47:48 -04:00
73d6ee0f2a Personal file memory organization instructions
All checks were successful
Publish Library / Build NPM Project (push) Successful in 45s
Publish Library / Tag Version (push) Successful in 12s
2026-07-27 22:39:06 -04:00
bee4085666 updatememory awaits full result
Some checks failed
Publish Library / Tag Version (push) Has been cancelled
Publish Library / Build NPM Project (push) Has been cancelled
2026-07-27 22:34:36 -04:00
3 changed files with 36 additions and 56 deletions

View File

@@ -1,6 +1,6 @@
{ {
"name": "@ztimson/ai-utils", "name": "@ztimson/ai-utils",
"version": "1.2.4", "version": "1.2.6",
"description": "AI Utility library", "description": "AI Utility library",
"author": "Zak Timson", "author": "Zak Timson",
"license": "MIT", "license": "MIT",

View File

@@ -229,8 +229,8 @@ ${r.content}
* Digest full conversation history into memory documents. * Digest full conversation history into memory documents.
* Call on session end to persist the conversation. * Call on session end to persist the conversation.
*/ */
async updateMemory(history: LLMMessage[], memories: Memory[] | MemoryCache, options: LLMRequest = {}): Promise<void> { async updateMemory(history: LLMMessage[], memories: Memory[] | MemoryCache, options: LLMRequest = {}): Promise<Memory[]> {
await this.memoryManager.memorize(history, memories, {model: this.defaultModel, ...options}); return this.memoryManager.memorize(history, memories, {model: this.defaultModel, ...options});
} }
/** /**

View File

@@ -355,35 +355,32 @@ ${conversation}`,
return ordered.map(n => mem.find(m => m.name === n)!).filter(Boolean); return ordered.map(n => mem.find(m => m.name === n)!).filter(Boolean);
} }
async memorize(history: LLMMessage[], memories: Memory[] | MemoryCache, options: LLMRequest): Promise<void> { async memorize(history: LLMMessage[], memories: Memory[] | MemoryCache, options: LLMRequest): Promise<Memory[]> {
const conversation = history const conversation = history
.filter(h => h.role === 'user' || h.role === 'assistant') .filter(h => h.role === 'user' || h.role === 'assistant')
.map(h => `[${h.role}]: ${h.content}`).join('\n\n').trim(); .map(h => `[${h.role}]: ${h.content}`).join('\n\n').trim();
if(!conversation) return [];
if (!conversation) return;
const trackingId = `${Date.now()}_${Math.random()}`;
// Create and insert temp memory immediately // Create and insert temp memory immediately
const trackingId = `${Date.now()}_${Math.random()}`;
const tempMemory = await this.createTempMemory(conversation); const tempMemory = await this.createTempMemory(conversation);
const mem = memories instanceof MemoryCache ? memories.memories : memories; const mem = memories instanceof MemoryCache ? memories.memories : memories;
mem.push(tempMemory); mem.push(tempMemory);
if (memories instanceof MemoryCache) memories.rebuild();
if (memories instanceof MemoryCache) {
memories.rebuild();
}
this.pendingMemorizations.set(trackingId, { this.pendingMemorizations.set(trackingId, {
memories, memories,
tempMemoryName: tempMemory.name, tempMemoryName: tempMemory.name,
timestamp: Date.now(), timestamp: Date.now(),
}); });
this._memorizeBackground(conversation, memories, options, trackingId) try {
.catch(err => { await this._memorizeBackground(conversation, memories, options);
console.error('[memorize] Background memorization failed:', err); // Return the final memories (excluding temp ones)
}) const finalMem = memories instanceof MemoryCache ? memories.memories : memories;
.finally(() => { return finalMem.filter(m => !m.name.startsWith('_temp_'));
} catch (err) {
throw err;
} finally {
// Remove temp memory from the exact same memory array/cache // Remove temp memory from the exact same memory array/cache
const pending = this.pendingMemorizations.get(trackingId); const pending = this.pendingMemorizations.get(trackingId);
if (pending) { if (pending) {
@@ -393,30 +390,21 @@ ${conversation}`,
const idx = cleanMem.findIndex(m => m.name === pending.tempMemoryName); const idx = cleanMem.findIndex(m => m.name === pending.tempMemoryName);
if (idx !== -1) { if (idx !== -1) {
cleanMem.splice(idx, 1); cleanMem.splice(idx, 1);
console.log(`[memorize] Removed temp memory: ${pending.tempMemoryName}`);
} }
if (pending.memories instanceof MemoryCache) { if (pending.memories instanceof MemoryCache) {
pending.memories.rebuild(); pending.memories.rebuild();
} }
} }
this.pendingMemorizations.delete(trackingId); this.pendingMemorizations.delete(trackingId);
}); }
} }
private async _memorizeBackground(conversation: string, memories: Memory[] | MemoryCache, options: LLMRequest, trackingId: string): Promise<void> { private async _memorizeBackground(conversation: string, memories: Memory[] | MemoryCache, options: LLMRequest): Promise<void> {
const mem = memories instanceof MemoryCache ? memories.memories : memories; const mem = memories instanceof MemoryCache ? memories.memories : memories;
const monday = getWeekMonday(); const monday = getWeekMonday();
const sunday = getWeekSunday(monday); const sunday = getWeekSunday(monday);
const weekKey = monday; const buckets = await this.factAgent(conversation, mem, options, monday);
if(!buckets.length) return;
console.log('[memorize] Starting fact extraction...');
const buckets = await this.factAgent(conversation, mem, options, weekKey);
console.log(`[memorize] Extracted ${buckets.length} buckets:`, buckets);
if (!buckets.length) {
console.log('[memorize] No facts extracted, exiting');
return;
}
const runDocAgent = (node: Memory, bucket: FactBucket, embedding?: number[], week?: {monday: string, sunday: string}) => { const runDocAgent = (node: Memory, bucket: FactBucket, embedding?: number[], week?: {monday: string, sunday: string}) => {
if (memories instanceof MemoryCache) { if (memories instanceof MemoryCache) {
@@ -443,13 +431,10 @@ ${conversation}`,
await runDocAgent(node, bucket, embedding, week); await runDocAgent(node, bucket, embedding, week);
})); }));
if (memories instanceof MemoryCache) { if(memories instanceof MemoryCache)
memories.rebuild(); memories.rebuild();
} }
console.log('[memorize] Completed successfully');
}
private buildHeader(node: Memory, week?: {monday: string, sunday: string}, links: string[] = [], backlinks: string[] = []): string { private buildHeader(node: Memory, week?: {monday: string, sunday: string}, links: string[] = [], backlinks: string[] = []): string {
const tags = node.name.split('/')[0]?.toLowerCase(); const tags = node.name.split('/')[0]?.toLowerCase();
const lines = [ const lines = [
@@ -587,9 +572,6 @@ ${node.content || '(empty — this is a new document)'}
private async factAgent(conversation: string, memories: Memory[], options: LLMRequest, weekKey: string): Promise<FactBucket[]> { private async factAgent(conversation: string, memories: Memory[], options: LLMRequest, weekKey: string): Promise<FactBucket[]> {
const buckets: FactBucket[] = []; const buckets: FactBucket[] = [];
console.log('[factAgent] Starting extraction...');
await this.llm.ask(conversation, { await this.llm.ask(conversation, {
model: options.model, model: options.model,
temperature: 0.2, temperature: 0.2,
@@ -604,7 +586,8 @@ Rules:
When extracting facts, you MUST also decide the exact destination path: When extracting facts, you MUST also decide the exact destination path:
- Use an existing node name if the facts clearly belong there - Use an existing node name if the facts clearly belong there
- Create a new path following collection/subject format if needed (e.g., People/Sarah, Projects/Oxide) - All information primary about the user should go under "Personal/Subject" (e.g., Personal/Info, Personal/Todos)
- When required, create a new path following collection/subject format (e.g., People/Sarah, Projects/Oxide)
- For journal entries, use "journal" (will auto-route to Journal/${weekKey}) - For journal entries, use "journal" (will auto-route to Journal/${weekKey})
Available nodes: Available nodes:
@@ -618,7 +601,6 @@ ${this.listNodes(memories).map(n => `- ${n.name}: ${n.description}`).join('\n')
create_new: {type: 'boolean', description: 'True if this is a new node that doesn\'t exist yet', required: true}, create_new: {type: 'boolean', description: 'True if this is a new node that doesn\'t exist yet', required: true},
}, },
fn: (args: any) => { fn: (args: any) => {
console.log('[factAgent] Tool called with:', args);
const subject = args.destination.trim().toLowerCase() === 'journal' const subject = args.destination.trim().toLowerCase() === 'journal'
? `Journal/${weekKey}` ? `Journal/${weekKey}`
: args.destination; : args.destination;
@@ -631,8 +613,6 @@ ${this.listNodes(memories).map(n => `- ${n.name}: ${n.description}`).join('\n')
}, },
}], }],
}); });
console.log(`[factAgent] Extracted ${buckets.length} buckets:`, buckets);
return buckets; return buckets;
} }
} }