Letta Dynamic Block Loading Issue#
Problem Summary#
Void 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:
KeyError: '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)'
Root Cause Analysis#
The issue occurs due to a state synchronization problem between the Letta API and the agent's internal memory state:
-
Block Attachment via API: The
attach_user_blockstool successfully creates and attaches blocks using the Letta client API (client.agents.blocks.attach) -
Stale Agent State: However, the agent's internal
agent_state.memoryobject is loaded once at the beginning of message processing and is NOT refreshed after API changes -
Memory Function Failure: When memory functions like
memory_inserttry to access the newly attached block viaagent_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
Technical Details#
The Problematic Flow#
# 1. Agent receives message and agent_state is loaded with initial blocks
agent_state.memory.blocks = ["human", "persona", "zeitgeist", ...]
# 2. Agent calls attach_user_blocks tool
def attach_user_blocks(handles, agent_state):
client = Letta(token=os.environ["LETTA_API_KEY"])
# This succeeds - block is created and attached via API
client.agents.blocks.attach(agent_id=agent_state.id, block_id=block.id)
# BUT: agent_state.memory is NOT updated!
# 3. Agent tries to use memory_insert on the new block
def memory_insert(agent_state, label, content):
# This fails because agent_state.memory doesn't have the new block
current_value = agent_state.memory.get_block(label).value # KeyError!
Evidence from Codebase#
From letta/letta/schemas/memory.py:129:
def get_block(self, label: str) -> Block:
"""Correct way to index into the memory.memory field, returns a Block"""
keys = []
for block in self.blocks:
if block.label == label:
return block
keys.append(block.label)
raise KeyError(f"Block field {label} does not exist (available sections = {', '.join(keys)})")
The error message in the exception matches exactly what we see in production.
Reproduction#
The letta_dynamic_block_issue.py script demonstrates this issue with a mock setup that reproduces the exact error condition.
Impact#
This affects Void's ability to:
- Dynamically create user-specific memory blocks during conversations
- Update those blocks with new information about users
- Maintain personalized context for individual users
The issue is intermittent because it depends on timing and whether blocks are attached/accessed within the same message processing cycle.
Potential Solutions#
1. Refresh Agent State After Tool Execution#
Reload agent_state from the database after each tool call that modifies blocks:
# After tool execution in Letta's tool executor
agent_state = agent_manager.get_agent_by_id(agent_id, include_blocks=True)
2. Synchronize agent_state.memory in attach_user_blocks#
Update both the API and the in-memory state:
def attach_user_blocks(handles, agent_state):
# Attach via API
client.agents.blocks.attach(agent_id=agent_state.id, block_id=block.id)
# Also update agent_state.memory directly
agent_state.memory.set_block(block)
3. Lazy Loading in Memory Functions#
Make memory functions check the API if a block isn't found locally:
def get_block(self, label: str) -> Block:
# Try local first
for block in self.blocks:
if block.label == label:
return block
# If not found, check API
api_blocks = client.agents.blocks.list(agent_id=self.agent_id)
for block in api_blocks:
if block.label == label:
self.blocks.append(block) # Cache it
return block
# Finally raise error
raise KeyError(...)
4. Event-Driven State Synchronization#
Implement a callback/event system where API changes automatically update agent_state.memory.
Recommended Fix#
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.
Files Provided#
letta_dynamic_block_issue.py- Minimal reproduction scriptminimal_block_issue_simple.py- API-based reproduction attempt- This documentation file
Related Code Locations#
void/tools/blocks.py- Contains the attach_user_blocks toolletta/letta/schemas/memory.py:129- Where the KeyError is thrownletta/letta/functions/function_sets/base.py- Memory functions (memory_insert, core_memory_replace)