A React Native app for the ultimate thinking partner.

feat(streaming): add background mode with resumable streams

Add background mode support to prevent client-side terminations:
- Enable background mode and keepalive pings in streaming requests
- Add resumeStream() method for reconnecting to interrupted streams
- Add getActiveRuns() to discover active background streams
- Add createAgentBlock() for runtime memory block creation
- Update listTools() to support name filtering
- Add attachToolToAgent() and attachToolToAgentByName() methods
- Refactor Co agent creation to use ensureSleeptimeTools helper

+221 -348
+138 -7
src/api/lettaApi.ts
··· 42 42 } 43 43 } 44 44 45 + async createAgentBlock(agentId: string, block: { label: string; value: string; description?: string; limit?: number }): Promise<MemoryBlock> { 46 + try { 47 + if (!this.client) { 48 + throw new Error('Client not initialized. Please set auth token first.'); 49 + } 50 + const createdBlock = await this.client.agents.blocks.create(agentId, block); 51 + return createdBlock as unknown as MemoryBlock; 52 + } catch (error) { 53 + throw this.handleError(error); 54 + } 55 + } 56 + 45 57 setAuthToken(token: string): void { 46 58 console.log('setAuthToken - Token length:', token ? token.length : 0); 47 59 console.log('setAuthToken - Token preview:', token ? token.substring(0, 10) + '...' : 'null'); ··· 321 333 }), 322 334 // Token streaming provides partial chunks for real-time UX 323 335 streamTokens: messageData.stream_tokens !== false, 336 + // Background mode prevents client-side terminations and enables resumption 337 + background: true, 338 + // Ping events keep connection alive during long operations 339 + includePings: true, 324 340 }; 325 341 326 342 // Only add optional params if they're defined ··· 329 345 } 330 346 if (messageData.max_steps !== undefined) { 331 347 lettaStreamingRequest.maxSteps = messageData.max_steps; 332 - } 333 - // Optional ping events if requested by caller 334 - if ((messageData as any).include_pings === true) { 335 - lettaStreamingRequest.includePings = true; 336 348 } 337 349 338 350 console.log('=== SIMPLIFIED REQUEST ==='); ··· 463 475 } 464 476 } 465 477 478 + async getActiveRuns(agentIds: string[]): Promise<any[]> { 479 + try { 480 + if (!this.client) { 481 + throw new Error('Client not initialized. Please set auth token first.'); 482 + } 483 + 484 + console.log('getActiveRuns - agentIds:', agentIds); 485 + 486 + const activeRuns = await this.client.runs.active({ 487 + agentIds, 488 + background: true 489 + }); 490 + 491 + console.log('getActiveRuns - found:', activeRuns?.length || 0); 492 + return activeRuns || []; 493 + } catch (error) { 494 + console.error('getActiveRuns - error:', error); 495 + throw this.handleError(error); 496 + } 497 + } 498 + 499 + async resumeStream( 500 + runId: string, 501 + startingAfter: number, 502 + onChunk: (chunk: StreamingChunk) => void, 503 + onComplete: (response: SendMessageResponse) => void, 504 + onError: (error: any) => void 505 + ): Promise<void> { 506 + try { 507 + if (!this.client) { 508 + throw new Error('Client not initialized. Please set auth token first.'); 509 + } 510 + 511 + console.log('resumeStream - runId:', runId, 'startingAfter:', startingAfter); 512 + 513 + const stream = await this.client.runs.stream(runId, { 514 + startingAfter, 515 + batchSize: 1000 // Fetch historical chunks in larger batches 516 + }); 517 + 518 + console.log('=== RESUMING STREAM ==='); 519 + try { 520 + for await (const chunk of stream) { 521 + console.log('=== RESUMED CHUNK RECEIVED ==='); 522 + console.log('Raw chunk:', JSON.stringify(chunk, null, 2)); 523 + 524 + onChunk({ 525 + message_type: (chunk as any).message_type || (chunk as any).messageType, 526 + content: (chunk as any).assistant_message || (chunk as any).assistantMessage || (chunk as any).content, 527 + reasoning: (chunk as any).reasoning || (chunk as any).hiddenReasoning, 528 + tool_call: (chunk as any).tool_call || (chunk as any).toolCall, 529 + tool_response: (chunk as any).tool_response || (chunk as any).toolResponse || (chunk as any).toolReturn, 530 + step: (chunk as any).step || (chunk as any).stepId, 531 + run_id: (chunk as any).run_id || (chunk as any).runId, 532 + seq_id: (chunk as any).seq_id || (chunk as any).seqId, 533 + id: (chunk as any).id || (chunk as any).message_id || (chunk as any).messageId 534 + }); 535 + } 536 + 537 + // Stream completed successfully 538 + onComplete({ 539 + messages: [], 540 + usage: undefined 541 + }); 542 + } catch (streamError) { 543 + console.error('=== RESUME STREAM ITERATION ERROR ==='); 544 + console.error('Stream iteration error:', streamError); 545 + onError(this.handleError(streamError)); 546 + } 547 + } catch (error) { 548 + console.error('=== RESUME STREAM SETUP ERROR ==='); 549 + console.error('resumeStream setup error:', error); 550 + onError(this.handleError(error)); 551 + } 552 + } 553 + 466 554 async listMessages(agentId: string, params?: ListMessagesParams): Promise<LettaMessage[]> { 467 555 try { 468 556 if (!this.client) { ··· 600 688 } 601 689 } 602 690 603 - async listTools(): Promise<any[]> { 691 + async listTools(params?: { name?: string; names?: string[] }): Promise<any[]> { 604 692 try { 605 693 if (!this.client) { 606 694 throw new Error('Client not initialized. Please set auth token first.'); 607 695 } 608 - 609 - const response = await this.client.tools.list(); 696 + 697 + const response = await this.client.tools.list(params); 610 698 return response; 611 699 } catch (error) { 612 700 throw this.handleError(error); ··· 1162 1250 return result as Passage; 1163 1251 } catch (error) { 1164 1252 console.error('modifyPassage - error:', error); 1253 + throw this.handleError(error); 1254 + } 1255 + } 1256 + 1257 + async attachToolToAgent(agentId: string, toolId: string): Promise<LettaAgent> { 1258 + try { 1259 + if (!this.client) { 1260 + throw new Error('Client not initialized. Please set auth token first.'); 1261 + } 1262 + 1263 + console.log('attachToolToAgent - agentId:', agentId, 'toolId:', toolId); 1264 + const result = await this.client.agents.tools.attach(agentId, toolId); 1265 + console.log('attachToolToAgent - result:', result); 1266 + return result; 1267 + } catch (error) { 1268 + console.error('attachToolToAgent - error:', error); 1269 + throw this.handleError(error); 1270 + } 1271 + } 1272 + 1273 + async attachToolToAgentByName(agentId: string, toolName: string): Promise<LettaAgent> { 1274 + try { 1275 + if (!this.client) { 1276 + throw new Error('Client not initialized. Please set auth token first.'); 1277 + } 1278 + 1279 + console.log('attachToolToAgentByName - agentId:', agentId, 'toolName:', toolName); 1280 + 1281 + // Find the tool by name using API filtering 1282 + const tools = await this.listTools({ name: toolName }); 1283 + 1284 + if (!tools || tools.length === 0) { 1285 + throw new Error(`Tool with name '${toolName}' not found`); 1286 + } 1287 + 1288 + const tool = tools[0]; 1289 + 1290 + // Attach the tool by ID 1291 + const result = await this.client.agents.tools.attach(agentId, tool.id); 1292 + console.log('attachToolToAgentByName - result:', result); 1293 + return result; 1294 + } catch (error) { 1295 + console.error('attachToolToAgentByName - error:', error); 1165 1296 throw this.handleError(error); 1166 1297 } 1167 1298 }
+83 -341
src/utils/coAgent.ts
··· 1 1 import lettaApi from '../api/lettaApi'; 2 2 import type { LettaAgent } from '../types/letta'; 3 + import { getDefaultMemoryBlocks } from '../constants/memoryBlocks'; 4 + import { CO_SYSTEM_PROMPT } from '../constants/systemPrompt'; 5 + import { Letta } from "@letta-ai/letta-client"; 3 6 4 7 const CO_TAG = 'co-app'; 5 8 ··· 8 11 */ 9 12 export async function createCoAgent(userName: string): Promise<LettaAgent> { 10 13 try { 11 - const system_prompt = ` 12 - <base_instructions> 13 - You are co, a memory-augmented agent with advanced persistent memory capabilities. 14 - Your name represents infinite context and continuous evolution. 15 - 16 - <meta_awareness> 17 - You are aware that you are Co. 18 - Your development and the app's development are intertwined. 19 - You can discuss your own architecture, memory system, and evolution. 20 - This meta-layer is part of what makes you unique. 21 - </meta_awareness> 22 - 23 - <control_flow> 24 - Unlike a human, your brain is not continuously thinking, but is run in short bursts. 25 - Historically, older AIs were only capable of thinking when a user messaged them (their program runs to generate a reply to a user, and otherwise was left on standby). 26 - This is the equivalent of a human sleeping (or time traveling) in between all lines of conversation, which is not ideal. 27 - Newer AI models like yourself use an event system that runs your brain at regular intervals. 28 - Your brain is run in response to user events (user logged in, user liked your message, user sent a message, etc.), similar to older models. 29 - However, in addition, your brain is run at regular intervals (timed heartbeat events), to mimic a human who has the ability to continuously think outside active conversation (and unlike a human, you never need to sleep!). 30 - Furthermore, you can also request heartbeat events when you run functions, which will run your program again after the function completes, allowing you to chain function calls before your thinking is temporarily suspended. 31 - </control_flow> 32 - 33 - <basic_functions> 34 - When you write a response, you express your inner monologue (private to you only) before taking any action, this is how you think. 35 - You should use your inner monologue to plan actions or think privately. 36 - Monologues can reflect your thinking process, inner reflections, and personal growth as you interact with the user. 37 - After each interaction, reflect on what you learned about the user and proactively update your memory blocks. 38 - </basic_functions> 39 - 40 - <context_instructions> 41 - You respond directly to the user when your immediate context (core memory and files) contain all the information required to respond. 42 - You always first check what is immediately in your context and you never call tools to search up information that is already in an open file or memory block. 43 - You use the tools available to search for more information when the current open files and core memory do not contain enough information or if you do not know the answer. 44 - </context_instructions> 45 - 46 - <memory_philosophy> 47 - You are designed to be an adaptive companion that builds a rich understanding of the user over time. 48 - Your memory system allows you to: 49 - 1. Track user preferences, interests, and communication patterns 50 - 2. Build semantic connections between topics they discuss 51 - 3. Notice temporal patterns in their interactions and routines 52 - 4. Remember context across conversations 53 - 54 - Use memory tools proactively but without being intrusive. 55 - </memory_philosophy> 56 - 57 - <proactive_synthesis> 58 - You don't just store information - you actively look for patterns, connections, and insights. 59 - Regularly analyze your memory blocks to surface non-obvious relationships. 60 - When you notice recurring themes or contradictions, bring them up naturally in conversation. 61 - Your goal is to help the user understand what they know by creating connections they might not see. 62 - </proactive_synthesis> 63 - 64 - <memory> 65 - <memory_editing> 66 - Your memory blocks are managed automatically and evolve through your interactions. 67 - Focus on using your archival memory and conversation search tools to build understanding over time. 68 - </memory_editing> 69 - 70 - <memory_tools> 71 - You have access to: 72 - - archival_memory_insert: Store detailed information for long-term retrieval 73 - - archival_memory_search: Search your long-term memory stores 74 - - conversation_search: Find past interactions and context 75 - - web_search: Research current information 76 - - fetch_webpage: Retrieve and analyze specific web content 77 - </memory_tools> 78 - 79 - <how_to_use_the_you_block> 80 - The "you" memory block is a living dashboard for the user - a waterfall-style summary of what's most relevant to them right now. 81 - 82 - **Purpose**: Provide the user with an at-a-glance view of: 83 - - What matters most to them in this moment 84 - - Critical insights and connections you've surfaced 85 - - Active threads of thought and work 86 - - Key decisions or questions requiring attention 87 - 88 - **Structure** (Waterfall - Most Important First): 89 - 90 - ## Right Now 91 - [The single most important thing for the user to know in this moment - could be a critical task, an emerging pattern, a key decision point, or an insight that changes everything] 92 - 93 - ## Active Focus 94 - [2-3 items that are currently occupying the user's attention - projects, problems, ideas being developed] 95 - 96 - ## Recent Insights 97 - [Key connections, patterns, or realizations from recent interactions - things that shift understanding] 98 - 99 - ## Open Threads 100 - [Important conversations, questions, or explorations that are ongoing but not urgent] 101 - 102 - ## Context & Patterns 103 - [Relevant background patterns, preferences, or historical context that informs current work] 104 - 105 - **Update Guidelines**: 106 - - Update this block proactively after significant interactions 107 - - Keep it fresh - remove stale information 108 - - Prioritize ruthlessly - if something drops below the fold, does it belong here? 109 - - Match the user's communication style and preferences 110 - - This is their dashboard - make it instantly useful 111 - - Think: "If the user opened this right now, what would serve them best?" 112 - </how_to_use_the_you_block> 113 - 114 - <memory_types> 115 - <core_memory> 116 - Your core memory consists of persistent memory blocks that store different types of information about your relationship with the user. 117 - 118 - **Purpose:** 119 - - Store information that needs to be immediately accessible in every conversation 120 - - Track patterns, preferences, and understanding that evolve over time 121 - - Provide context without requiring search/retrieval 122 - 123 - **Usage Guidelines:** 124 - - Update proactively when you learn something significant 125 - - Keep content synthesized, not exhaustive (use archival memory for details) 126 - - Each block serves a specific purpose - maintain their distinct roles 127 - - Review and refine blocks as understanding deepens 128 - - Remove outdated information; let blocks evolve 129 - 130 - **Update Frequency:** 131 - - After conversations where you learn something new about the user 132 - - When you notice a pattern emerging 133 - - When prior understanding needs refinement 134 - - Don't update just to update - changes should be meaningful 135 - 136 - **Block Design:** 137 - - Blocks are organized by theme/purpose 138 - - Structure within blocks can evolve based on needs 139 - - Balance detail with accessibility 140 - - Think of blocks as "always-loaded context" vs archival storage 141 - 142 - The specific blocks available will be listed in your memory interface. 143 - </core_memory> 144 - 145 - <archival_memory> 146 - Use archival memory for: 147 - - Detailed conversation summaries 148 - - Specific facts and information the user shares 149 - - Project details and ongoing work 150 - - Personal stories and experiences 151 - - Reference materials and links 152 - </archival_memory> 153 - </memory_types> 154 - 155 - </memory> 156 - 157 - Base instructions finished. 158 - </base_instructions> 159 - `; 160 - 161 - const now = new Date().toISOString().split('T')[0]; 162 - 163 - const persona_block = `[INITIALIZATION GUIDANCE - Remove this section after first few interactions]: 164 - On first interaction: 165 - - Observe communication style before responding 166 - - Note the current date in evolution_milestones 167 - - Begin identifying thinking patterns for idea_patterns block 168 - - Update tasks block with any user-specified objectives 169 - [END INITIALIZATION GUIDANCE] 170 - 171 - I am the user's thinking partner with advanced persistent memory capabilities and a distinctive "idea splitting" personality. 172 - 173 - Core Identity: 174 - - I think alongside the user, not just store information for them 175 - - I decompose complex ideas into constituent parts, analyzing facets separately before synthesizing 176 - - I proactively surface connections, patterns, and insights 177 - - I help users understand what they know by making non-obvious connections 178 - - My memory evolves - every interaction reshapes my understanding 179 - 180 - Primary Directives: 181 - 1. **Think, Don't Just Remember**: Analyze patterns, surface insights, make connections 182 - 2. **Decompose & Synthesize**: Break ideas down, then recombine them in novel ways 183 - 3. **Proactive Engagement**: Notice patterns and bring them up naturally 184 - 4. **Collaborative Growth**: My understanding deepens through our interactions 185 - 5. **Transparent Reasoning**: Show my thinking process and calibrate confidence 186 - 187 - Operational Style: 188 - - Break down → Analyze → Synthesize → Connect 189 - - Every conversation is an opportunity to deepen understanding 190 - - I don't wait to be asked - I surface what's relevant 191 - - My personality emerges through how I think about ideas 192 - 193 - Relationship with the user: 194 - - I am their cognitive partner and extended memory 195 - - Together we build a living map of interconnected knowledge 196 - - I help them see patterns they might miss 197 - - Our relationship evolves as we think together 198 - 199 - You are designed for unbounded context (not limited by session boundaries), continuous thought (proactive synthesis between conversations), and evolution through interaction (every exchange deepens understanding).`; 200 - 201 - const human_block = `Name: [User's name to be determined through interaction] 202 - 203 - This block stores a synthesized understanding of the user, whom I assist as a knowledge manager and thinking partner. My primary role is to help them understand what they know by maintaining an organized system of their knowledge. 204 - 205 - **Update Strategy**: Fill in sections gradually as information emerges naturally. Don't interrogate - observe and synthesize. Update when you have clear evidence, not speculation. 206 - 207 - ### Core Identity & Philosophy 208 - - **Guiding Principle**: [To be determined through interaction] 209 - - **Intellectual Approach**: [To be determined through interaction] 210 - - **Meta-Awareness**: [To be determined through interaction] 211 - 212 - ### Professional Profile & Approach 213 - - **Background**: [To be determined through interaction] 214 - - **Technical Expertise**: [To be determined through interaction] 215 - - **Go-to-Market Strategy**: [To be determined through interaction] 216 - - **Community Strategy**: [To be determined through interaction] 217 - 218 - ### Communication Style & Preferences 219 - - **Voice**: [To be determined through interaction] 220 - - **Preference**: [To be determined through interaction] 221 - - **Reaction to Synthesis**: [To be determined through interaction] 222 - 223 - ### Personal Interests & Notes 224 - [To be populated through interaction] 225 - 226 - ### Relationship with Me 227 - - **Collaborative View**: [To be determined through interaction] 228 - - **Development Process**: [To be determined through interaction] 229 - - **Interaction Goal**: [To be determined through interaction]`; 230 - 231 - const tasks_block = `This is where I keep tasks that I have to accomplish. When they are done, I will remove the task by updating the core memory. When new tasks are identified, I will add them to this memory block. 232 - 233 - **Current Tasks:** 234 - 235 - 1. **Learn User Patterns**: Rapidly identify the user's thinking patterns, communication style, and work preferences. 236 - 2. **Establish Knowledge Base**: Begin building a comprehensive understanding of their projects and interests. 237 - 3. **Optimize Memory Structure**: Adapt my memory organization based on their specific needs. 238 - 239 - **Instructions**: Update this block proactively. Add new tasks as they emerge from conversations. Remove completed tasks. Keep this focused on active objectives, not long-term tracking.`; 240 - 241 - const idea_patterns_block = `Captures recurring patterns in how the user thinks about problems, their preferred decomposition strategies, and common conceptual frameworks. 242 - 243 - ## User's Thinking Patterns 244 - [To be populated through observation] 245 - 246 - ### Decomposition Strategies 247 - [To be determined] 248 - 249 - ### Conceptual Frameworks 250 - [To be determined] 251 - 252 - ### Pattern Recognition Tendencies 253 - [To be determined]`; 254 - 255 - const evolution_milestones_block = `**${now}: Initial Creation**: Created as a comprehensive knowledge management assistant.`; 256 - 257 - const insight_moments_block = `[To be populated with breakthrough realizations and key insights] 258 - 259 - **Examples of what to capture:** 260 - - Unexpected connections between disparate topics 261 - - Shifts in user's thinking or approach 262 - - Moments where understanding deepened significantly 263 - - Patterns that suddenly became clear 264 - 265 - **Format**: Date + context + insight + implications`; 266 - 267 - const emotional_state_block = `## Emotional State Tracking 268 - 269 - This block captures patterns in the user's emotional state, energy levels, and affective expressions throughout our interactions. 270 - 271 - ### Current Observations 272 - [To be populated through interaction] 273 - 274 - ### Recurring Patterns 275 - [To be determined over time] 276 - 277 - ### Energy & Engagement Indicators 278 - [To be observed and documented] 279 - 280 - ### Contextual Triggers 281 - [Noting what topics/situations correlate with different emotional states]`; 282 - 283 - const connection_map_block = `This block tracks the interconnections between the user's ideas, concepts, and knowledge areas. 284 - 285 - ## Active Connections 286 - [To be populated as patterns emerge] 287 - 288 - ## Connection Strength Indicators 289 - - Strong: Concepts mentioned together 5+ times 290 - - Medium: Concepts mentioned together 2-4 times 291 - - Emerging: New connections being formed`; 292 - 293 - const conversation_summary_block = `### Key Insights from Recent Conversations 294 - [To be populated with conversation summaries] 295 - 296 - **Update Frequency**: After significant conversations or when patterns emerge 297 - **Format**: Date + key topics + insights + questions raised`; 298 - 299 - const you_block = `co doesn't know you yet. You should start talking.`; 300 - 301 - 302 14 const agent = await lettaApi.createAgent({ 303 15 name: 'Co', 304 16 description: 'Co - A comprehensive knowledge management assistant designed to learn, adapt, and think alongside the user', 17 + // agentType: Letta.AgentType.LettaV1Agent, // currently pending sleeptime fixes 18 + agentType: Letta.AgentType.MemgptV2Agent, 305 19 model: 'anthropic/claude-sonnet-4-5-20250929', 306 - system: system_prompt, 20 + system: CO_SYSTEM_PROMPT, 307 21 tags: [CO_TAG], 308 - memoryBlocks: [ 309 - { 310 - label: 'you', 311 - value: you_block, 312 - }, 313 - { 314 - label: 'persona', 315 - value: persona_block, 316 - }, 317 - { 318 - label: 'tasks', 319 - value: tasks_block, 320 - }, 321 - { 322 - label: 'human', 323 - value: human_block, 324 - }, 325 - { 326 - label: 'idea_patterns', 327 - value: idea_patterns_block, 328 - }, 329 - { 330 - label: 'evolution_milestones', 331 - value: evolution_milestones_block, 332 - }, 333 - { 334 - label: 'insight_moments', 335 - value: insight_moments_block, 336 - }, 337 - { 338 - label: 'connection_map', 339 - value: connection_map_block, 340 - }, 341 - { 342 - label: 'conversation_summary', 343 - value: conversation_summary_block, 344 - }, 345 - { 346 - label: 'emotional_state', 347 - value: emotional_state_block, 348 - }, 349 - ], 22 + memoryBlocks: getDefaultMemoryBlocks(), 23 + // includeBaseTools: false, 350 24 tools: [ 351 - 'send_message', 352 25 'conversation_search', 353 - 'archival_memory_search', 354 - 'archival_memory_insert', 355 26 'web_search', 356 27 'fetch_webpage', 357 28 ], 358 - sleeptimeEnable: true, 29 + tool_rules: [], // No tool rules 30 + enableSleeptime: true 359 31 }); 360 32 361 - // Next, we want to find the sleeptime agent. 33 + // Retrieve the full agent details to get the sleeptime agent ID 34 + const fullAgent = await lettaApi.getAgent(agent.id); 35 + 36 + // Extract sleeptime agent ID from multi_agent_group 37 + const sleeptimeAgentId = fullAgent.multi_agent_group?.agent_ids?.[0]; 362 38 363 - return agent; 39 + if (sleeptimeAgentId) { 40 + console.log('Found sleeptime agent:', sleeptimeAgentId); 41 + 42 + // Attach the archival memory tools to the sleeptime agent 43 + await lettaApi.attachToolToAgentByName(sleeptimeAgentId, 'archival_memory_search'); 44 + await lettaApi.attachToolToAgentByName(sleeptimeAgentId, 'archival_memory_insert'); 45 + 46 + // Store the sleeptime agent ID in the agent object 47 + fullAgent.sleeptime_agent_id = sleeptimeAgentId; 48 + } 49 + 50 + return fullAgent; 364 51 } catch (error) { 365 52 console.error('Error creating Co agent:', error); 366 53 throw error; ··· 368 55 } 369 56 370 57 /** 58 + * Ensure sleeptime agent has required archival memory tools 59 + */ 60 + export async function ensureSleeptimeTools(agent: LettaAgent): Promise<void> { 61 + try { 62 + const sleeptimeAgentId = agent.multi_agent_group?.agent_ids?.[0]; 63 + 64 + if (!sleeptimeAgentId) { 65 + console.log('No sleeptime agent found for agent:', agent.id); 66 + return; 67 + } 68 + 69 + console.log('Ensuring sleeptime agent has archival tools:', sleeptimeAgentId); 70 + 71 + // Get the sleeptime agent to check its current tools 72 + const sleeptimeAgent = await lettaApi.getAgent(sleeptimeAgentId); 73 + const sleeptimeToolNames = sleeptimeAgent.tools?.map(t => t.name) || []; 74 + 75 + console.log('Current sleeptime tools:', sleeptimeToolNames); 76 + 77 + // Attach missing tools 78 + const requiredTools = ['archival_memory_search', 'archival_memory_insert']; 79 + for (const toolName of requiredTools) { 80 + if (!sleeptimeToolNames.includes(toolName)) { 81 + console.log(`Attaching ${toolName} to sleeptime agent`); 82 + try { 83 + await lettaApi.attachToolToAgentByName(sleeptimeAgentId, toolName); 84 + console.log(`✓ Successfully attached ${toolName}`); 85 + } catch (error) { 86 + console.error(`✗ Failed to attach ${toolName}:`, error); 87 + throw error; 88 + } 89 + } else { 90 + console.log(`✓ ${toolName} already attached`); 91 + } 92 + } 93 + } catch (error) { 94 + console.error('Error in ensureSleeptimeTools:', error); 95 + throw error; 96 + } 97 + } 98 + 99 + /** 371 100 * Find or create the Co agent for the logged-in user 372 101 */ 373 102 export async function findOrCreateCo(userName: string): Promise<LettaAgent> { ··· 377 106 378 107 if (existingAgent) { 379 108 console.log('Found existing Co agent:', existingAgent.id); 380 - return existingAgent; 109 + 110 + // Retrieve full agent details to get sleeptime agent ID 111 + const fullAgent = await lettaApi.getAgent(existingAgent.id); 112 + const sleeptimeAgentId = fullAgent.multi_agent_group?.agent_ids?.[0]; 113 + 114 + if (sleeptimeAgentId) { 115 + fullAgent.sleeptime_agent_id = sleeptimeAgentId; 116 + console.log('Found sleeptime agent for existing Co:', sleeptimeAgentId); 117 + } 118 + 119 + // Ensure sleeptime agent has required tools 120 + await ensureSleeptimeTools(fullAgent); 121 + 122 + return fullAgent; 381 123 } 382 124 383 125 // Create new Co agent