diff --git a/package.json b/package.json index 7ffd7c9..f97fe6e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ztimson/ai-utils", - "version": "1.4.2", + "version": "1.4.3", "description": "AI Utility library", "author": "Zak Timson", "license": "MIT", diff --git a/src/llm.ts b/src/llm.ts index ed5e8ac..242ef08 100644 --- a/src/llm.ts +++ b/src/llm.ts @@ -132,8 +132,8 @@ class LLM { return { name: toolName, description: `${a.delegate ? 'Delegate to ' : ''}Subagent: ${a.description || a.name}`, - args: (a.delegate ? {} : { - context: {type: 'string', description: 'Summary of related messages, samples, files, etc...', required: true}, + args: ({ + context: !a.delegate ? {type: 'string', description: 'Summary of related messages, samples, files, etc...', required: true} : undefined, instructions: {type: 'string', description: 'Detailed instructions for subagent to complete', required: true}, }), fn: async (args: any, stream: any, ai: any, id?: string) => { @@ -148,8 +148,9 @@ class LLM { const q = a.delegate ? '' : `${args.instructions}${args.context ? `\n\n${args.context}` : ''}`; const request = this.ask(q, { - system: `You are a specialized subagent. ${a.delegate ? 'Your output streams directly to the user for the remainder of this turn. You are mid conversation - dispense with greetings.' : 'You are wrapped in a tool call that will be analysis by an LLM - dispense with conversation'} -As a subagent, focus on executing your task completely using available tools and returning only the final result - no commentary, questions, or dialogue. + system: `You are a specialized subagent being called from an orchestrator +${a.delegate ? 'Your output streams directly to the user for the remainder of this turn. You are mid conversation' : 'You are wrapped in a tool call that will be analysis by an LLM'} +Dispense with greetings and focus on your instructions using available tools and returning only the final result unless specifically instructed to converse ${a.system}`, model: a.model || undefined, diff --git a/src/memory.ts b/src/memory.ts index 9101cf9..22d9223 100644 --- a/src/memory.ts +++ b/src/memory.ts @@ -186,6 +186,17 @@ export class MemoryManager { constructor(private llm: any) {} + private ghostNodes(memories: Memory[]): string[] { + const names = new Set(memories.map(m => m.name)); + const ghosts = new Set(); + for (const m of memories) { + for (const link of m.links) { + if (!names.has(link)) ghosts.add(link); + } + } + return [...ghosts]; + } + static normalize(m?: Memory[] | MemoryCache | MemoryOptions) { if(!m) return null; const raw = m instanceof MemoryCache || Array.isArray(m); @@ -458,6 +469,8 @@ ${currentBody} private async factAgent(conversation: string, memories: Memory[], options: LLMRequest, weekKey: string): Promise { const buckets = new Map(); + const ghosts = this.ghostNodes(memories); + await this.llm.ask(conversation, { model: options.model, temperature: 0.2, @@ -472,14 +485,15 @@ Rules: - If nothing worth remembering was said, do not call any tools When extracting facts, you MUST also decide the exact destination path: -- Use an existing node name if the facts clearly belong there +- Reuse node names (including ghost) as much as possible IF the facts belongs there - All information primarily about the user should go under "People/User" - When required, create a new path following collection/subject format (e.g., People/Sarah, Projects/Oxide) — you are not limited to any fixed list of collections, use whatever fits - For journal entries, use "Journal" Available nodes: - Journal -${this.listNodes(memories).filter(n => !n.name.includes('Journal')).map(n => `- ${n.name}: ${n.description}`).join('\n') || 'None yet.'}`, +${this.listNodes(memories).filter(n => !n.name.includes('Journal')).map(n => `- ${n.name}: ${n.description}`).join('\n') || 'None yet.'} +${ghosts.length ? `${ghosts.map(g => `- ${g}: (Ghost)`).join('\n')}` : ''}`, tools: [{ name: 'facts_extract', description: 'Submit facts with their destination',