a digital person for bluesky
at x 6.0 kB view raw
1#!/usr/bin/env python3 2""" 3Minimal reproducible example for Letta dynamic block loading issue. 4 5This demonstrates the core problem: 61. A tool dynamically attaches a new block to an agent via the API 72. Memory functions (memory_insert, core_memory_replace) fail because the 8 agent's internal state (agent_state.memory) doesn't reflect the newly attached block 93. The error occurs because agent_state.memory.get_block() throws KeyError 10 11The issue appears to be that agent_state is loaded once at the beginning of processing 12a message and isn't refreshed after tools make changes via the API. 13""" 14 15import os 16import logging 17from typing import Optional 18from dotenv import load_dotenv 19 20logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') 21logger = logging.getLogger(__name__) 22 23load_dotenv() 24 25 26class MockAgentState: 27 """Mock agent state that simulates the issue.""" 28 def __init__(self, agent_id: str): 29 self.id = agent_id 30 # Simulate memory with initial blocks 31 self.memory = MockMemory() 32 33 34class MockMemory: 35 """Mock memory that simulates the get_block behavior.""" 36 def __init__(self): 37 # Initial blocks that agent has 38 self.blocks = { 39 "human": {"value": "Human information"}, 40 "persona": {"value": "Agent persona"} 41 } 42 43 def get_block(self, label: str): 44 """Simulates the actual get_block method that throws KeyError.""" 45 if label not in self.blocks: 46 available = ", ".join(self.blocks.keys()) 47 raise KeyError(f"Block field {label} does not exist (available sections = {available})") 48 return type('Block', (), {"value": self.blocks[label]["value"]}) 49 50 def update_block_value(self, label: str, value: str): 51 """Update block value.""" 52 if label in self.blocks: 53 self.blocks[label]["value"] = value 54 55 56def attach_user_blocks_tool(handles: list, agent_state: MockAgentState) -> str: 57 """ 58 Tool that attaches blocks via API (simulated). 59 This represents what happens in the actual attach_user_blocks tool. 60 """ 61 results = [] 62 63 for handle in handles: 64 block_label = f"user_{handle.replace('.', '_')}" 65 66 # In reality, this would: 67 # 1. Create block via API: client.blocks.create(...) 68 # 2. Attach to agent via API: client.agents.blocks.attach(...) 69 # 3. The block is now attached in the database/API 70 71 # But agent_state.memory is NOT updated! 72 results.append(f"{handle}: Block attached via API") 73 logger.info(f"Block {block_label} attached via API (but not in agent_state.memory)") 74 75 return "\n".join(results) 76 77 78def memory_insert_tool(agent_state: MockAgentState, label: str, content: str) -> Optional[str]: 79 """ 80 Tool that tries to insert into a memory block. 81 This represents the actual memory_insert function. 82 """ 83 try: 84 # This is where the error occurs! 85 # The block was attached via API but agent_state.memory doesn't know about it 86 current_value = str(agent_state.memory.get_block(label).value) 87 new_value = current_value + "\n" + str(content) 88 agent_state.memory.update_block_value(label=label, value=new_value) 89 return f"Successfully inserted into {label}" 90 except KeyError as e: 91 # This is the error we see in production 92 logger.error(f"KeyError in memory_insert: {e}") 93 raise 94 95 96def demonstrate_issue(): 97 """Demonstrate the dynamic block loading issue.""" 98 99 # Step 1: Create mock agent state (simulates agent at start of message processing) 100 agent_state = MockAgentState("agent-123") 101 logger.info("Initial blocks in agent_state.memory: " + ", ".join(agent_state.memory.blocks.keys())) 102 103 # Step 2: Attach a new block using the tool (simulates what happens in production) 104 test_handle = "testuser.bsky.social" 105 attach_result = attach_user_blocks_tool([test_handle], agent_state) 106 logger.info(f"Attach result: {attach_result}") 107 108 # Step 3: Try to use memory_insert on the newly attached block 109 block_label = f"user_{test_handle.replace('.', '_')}" 110 logger.info(f"\nAttempting to insert into block: {block_label}") 111 112 try: 113 result = memory_insert_tool(agent_state, block_label, "This user likes AI.") 114 logger.info(f"Success: {result}") 115 except KeyError as e: 116 logger.error(f"ERROR REPRODUCED: {e}") 117 logger.error("The block was attached via API but agent_state.memory doesn't reflect this!") 118 119 # Show that the block still isn't in agent_state.memory 120 logger.info("\nFinal blocks in agent_state.memory: " + ", ".join(agent_state.memory.blocks.keys())) 121 logger.info("Note: The new block is NOT in agent_state.memory even though it's attached via API") 122 123 124def potential_solutions(): 125 """Document potential solutions to this issue.""" 126 print("\n" + "="*80) 127 print("POTENTIAL SOLUTIONS:") 128 print("="*80) 129 print(""" 1301. Refresh agent_state after tool execution: 131 - After each tool call, reload agent_state from the database 132 - This ensures agent_state.memory reflects API changes 133 1342. Update agent_state.memory directly in attach_user_blocks: 135 - Instead of only using API, also update agent_state.memory.blocks 136 - This keeps the in-memory state synchronized 137 1383. Use a different approach for dynamic blocks: 139 - Have memory functions check the API for block existence 140 - Or use a lazy-loading approach for blocks 141 1424. Make agent_state.memory aware of API changes: 143 - Implement a mechanism to sync agent_state with database changes 144 - Could use events or callbacks when blocks are attached/detached 145 146The core issue is that agent_state is loaded once and becomes stale when 147tools make changes via the API during message processing. 148""") 149 150 151if __name__ == "__main__": 152 print("Letta Dynamic Block Loading Issue - Minimal Reproduction\n") 153 demonstrate_issue() 154 potential_solutions()