a digital person for bluesky

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:

  1. Block Attachment via API: The attach_user_blocks tool successfully creates and attaches blocks using the Letta client API (client.agents.blocks.attach)

  2. 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

  3. 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

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.

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 script
  • minimal_block_issue_simple.py - API-based reproduction attempt
  • This documentation file
  • void/tools/blocks.py - Contains the attach_user_blocks tool
  • letta/letta/schemas/memory.py:129 - Where the KeyError is thrown
  • letta/letta/functions/function_sets/base.py - Memory functions (memory_insert, core_memory_replace)