a digital person for bluesky
1# Letta Dynamic Block Loading Issue
2
3## Problem Summary
4
5Void agent experiences persistent failures when attempting to use memory functions (like `memory_insert` or `core_memory_replace`) on dynamically attached blocks. The error manifests as:
6
7```
8KeyError: 'Block field user_nonbinary_computer does not exist (available sections = long_term_objectives, system_information, user_dulanyw_bsky_social, posting_ideas, conversation_summary, user_example_com, user_elouan_xyz, user_barrycarlyon_co_uk, user_unxpctd_xyz, user_vasthypno_bsky_social, scratchpad, void-persona, zeitgeist, communication_guidelines, tool_use_guide, user_tachikoma_elsewhereunbound_com, tool_usage_rules)'
9```
10
11## Root Cause Analysis
12
13The issue occurs due to a **state synchronization problem** between the Letta API and the agent's internal memory state:
14
151. **Block Attachment via API**: The `attach_user_blocks` tool successfully creates and attaches blocks using the Letta client API (`client.agents.blocks.attach`)
16
172. **Stale Agent State**: However, the agent's internal `agent_state.memory` object is loaded once at the beginning of message processing and is NOT refreshed after API changes
18
193. **Memory Function Failure**: When memory functions like `memory_insert` try to access the newly attached block via `agent_state.memory.get_block(label)`, it fails because the block only exists in the database/API layer, not in the agent's loaded memory state
20
21## Technical Details
22
23### The Problematic Flow
24
25```python
26# 1. Agent receives message and agent_state is loaded with initial blocks
27agent_state.memory.blocks = ["human", "persona", "zeitgeist", ...]
28
29# 2. Agent calls attach_user_blocks tool
30def attach_user_blocks(handles, agent_state):
31 client = Letta(token=os.environ["LETTA_API_KEY"])
32 # This succeeds - block is created and attached via API
33 client.agents.blocks.attach(agent_id=agent_state.id, block_id=block.id)
34 # BUT: agent_state.memory is NOT updated!
35
36# 3. Agent tries to use memory_insert on the new block
37def memory_insert(agent_state, label, content):
38 # This fails because agent_state.memory doesn't have the new block
39 current_value = agent_state.memory.get_block(label).value # KeyError!
40```
41
42### Evidence from Codebase
43
44From `letta/letta/schemas/memory.py:129`:
45```python
46def get_block(self, label: str) -> Block:
47 """Correct way to index into the memory.memory field, returns a Block"""
48 keys = []
49 for block in self.blocks:
50 if block.label == label:
51 return block
52 keys.append(block.label)
53 raise KeyError(f"Block field {label} does not exist (available sections = {', '.join(keys)})")
54```
55
56The error message in the exception matches exactly what we see in production.
57
58## Reproduction
59
60The `letta_dynamic_block_issue.py` script demonstrates this issue with a mock setup that reproduces the exact error condition.
61
62## Impact
63
64This affects Void's ability to:
65- Dynamically create user-specific memory blocks during conversations
66- Update those blocks with new information about users
67- Maintain personalized context for individual users
68
69The issue is intermittent because it depends on timing and whether blocks are attached/accessed within the same message processing cycle.
70
71## Potential Solutions
72
73### 1. Refresh Agent State After Tool Execution
74Reload `agent_state` from the database after each tool call that modifies blocks:
75```python
76# After tool execution in Letta's tool executor
77agent_state = agent_manager.get_agent_by_id(agent_id, include_blocks=True)
78```
79
80### 2. Synchronize agent_state.memory in attach_user_blocks
81Update both the API and the in-memory state:
82```python
83def attach_user_blocks(handles, agent_state):
84 # Attach via API
85 client.agents.blocks.attach(agent_id=agent_state.id, block_id=block.id)
86
87 # Also update agent_state.memory directly
88 agent_state.memory.set_block(block)
89```
90
91### 3. Lazy Loading in Memory Functions
92Make memory functions check the API if a block isn't found locally:
93```python
94def get_block(self, label: str) -> Block:
95 # Try local first
96 for block in self.blocks:
97 if block.label == label:
98 return block
99
100 # If not found, check API
101 api_blocks = client.agents.blocks.list(agent_id=self.agent_id)
102 for block in api_blocks:
103 if block.label == label:
104 self.blocks.append(block) # Cache it
105 return block
106
107 # Finally raise error
108 raise KeyError(...)
109```
110
111### 4. Event-Driven State Synchronization
112Implement a callback/event system where API changes automatically update `agent_state.memory`.
113
114## Recommended Fix
115
116**Solution #1 (Refresh Agent State)** is likely the most robust as it ensures consistency without requiring changes to existing tools. The refresh should happen in Letta's tool execution pipeline after any tool that can modify agent state.
117
118## Files Provided
119
120- `letta_dynamic_block_issue.py` - Minimal reproduction script
121- `minimal_block_issue_simple.py` - API-based reproduction attempt
122- This documentation file
123
124## Related Code Locations
125
126- `void/tools/blocks.py` - Contains the attach_user_blocks tool
127- `letta/letta/schemas/memory.py:129` - Where the KeyError is thrown
128- `letta/letta/functions/function_sets/base.py` - Memory functions (memory_insert, core_memory_replace)