a digital person for bluesky

Add user memory block management tools

- user_note_append: Appends text to a user's memory block
- user_note_replace: Replaces specific text within a user's memory block
- user_note_set: Sets the complete content of a user's memory block

All tools handle block creation and attachment automatically.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

Changed files
+135 -1
tools
+13 -1
register_tools.py
··· 13 from tools.search import search_bluesky_posts, SearchArgs 14 from tools.post import create_new_bluesky_post, PostArgs 15 from tools.feed import get_bluesky_feed, FeedArgs 16 - from tools.blocks import attach_user_blocks, detach_user_blocks, user_note_append, AttachUserBlocksArgs, DetachUserBlocksArgs, UserNoteAppendArgs 17 from tools.reply import bluesky_reply, ReplyArgs 18 from tools.halt import halt_activity, HaltArgs 19 ··· 60 "args_schema": UserNoteAppendArgs, 61 "description": "Append a note to a user's memory block. Creates the block if it doesn't exist.", 62 "tags": ["memory", "blocks", "user", "append"] 63 }, 64 { 65 "func": bluesky_reply,
··· 13 from tools.search import search_bluesky_posts, SearchArgs 14 from tools.post import create_new_bluesky_post, PostArgs 15 from tools.feed import get_bluesky_feed, FeedArgs 16 + from tools.blocks import attach_user_blocks, detach_user_blocks, user_note_append, user_note_replace, user_note_set, AttachUserBlocksArgs, DetachUserBlocksArgs, UserNoteAppendArgs, UserNoteReplaceArgs, UserNoteSetArgs 17 from tools.reply import bluesky_reply, ReplyArgs 18 from tools.halt import halt_activity, HaltArgs 19 ··· 60 "args_schema": UserNoteAppendArgs, 61 "description": "Append a note to a user's memory block. Creates the block if it doesn't exist.", 62 "tags": ["memory", "blocks", "user", "append"] 63 + }, 64 + { 65 + "func": user_note_replace, 66 + "args_schema": UserNoteReplaceArgs, 67 + "description": "Replace text in a user's memory block.", 68 + "tags": ["memory", "blocks", "user", "replace"] 69 + }, 70 + { 71 + "func": user_note_set, 72 + "args_schema": UserNoteSetArgs, 73 + "description": "Set the complete content of a user's memory block.", 74 + "tags": ["memory", "blocks", "user", "set"] 75 }, 76 { 77 "func": bluesky_reply,
+122
tools/blocks.py
··· 228 raise Exception(f"Error appending note to user block: {str(e)}") 229 230
··· 228 raise Exception(f"Error appending note to user block: {str(e)}") 229 230 231 + def user_note_replace(handle: str, old_text: str, new_text: str, agent_state: "AgentState") -> str: 232 + """ 233 + Replace text in a user's memory block. 234 + 235 + Args: 236 + handle: User Bluesky handle (e.g., 'cameron.pfiffer.org') 237 + old_text: Text to find and replace 238 + new_text: Text to replace the old_text with 239 + agent_state: The agent state object containing agent information 240 + 241 + Returns: 242 + String confirming the text was replaced 243 + """ 244 + import os 245 + import logging 246 + from letta_client import Letta 247 + 248 + logger = logging.getLogger(__name__) 249 + 250 + try: 251 + client = Letta(token=os.environ["LETTA_API_KEY"]) 252 + 253 + # Sanitize handle for block label 254 + clean_handle = handle.lstrip('@').replace('.', '_').replace('-', '_').replace(' ', '_') 255 + block_label = f"user_{clean_handle}" 256 + 257 + # Check if block exists 258 + blocks = client.blocks.list(label=block_label) 259 + 260 + if not blocks or len(blocks) == 0: 261 + raise Exception(f"No memory block found for user: {handle}") 262 + 263 + block = blocks[0] 264 + current_value = block.value 265 + 266 + # Check if old_text exists in the block 267 + if old_text not in current_value: 268 + raise Exception(f"Text '{old_text}' not found in {handle}'s memory block") 269 + 270 + # Replace the text 271 + new_value = current_value.replace(old_text, new_text) 272 + 273 + # Update the block 274 + client.blocks.modify( 275 + block_id=str(block.id), 276 + value=new_value 277 + ) 278 + logger.info(f"Replaced text in block: {block_label}") 279 + return f"✓ Replaced text in {handle}'s memory block" 280 + 281 + except Exception as e: 282 + logger.error(f"Error replacing text in user block: {e}") 283 + raise Exception(f"Error replacing text in user block: {str(e)}") 284 + 285 + 286 + def user_note_set(handle: str, content: str, agent_state: "AgentState") -> str: 287 + """ 288 + Set the complete content of a user's memory block. 289 + 290 + Args: 291 + handle: User Bluesky handle (e.g., 'cameron.pfiffer.org') 292 + content: Complete content to set for the memory block 293 + agent_state: The agent state object containing agent information 294 + 295 + Returns: 296 + String confirming the content was set 297 + """ 298 + import os 299 + import logging 300 + from letta_client import Letta 301 + 302 + logger = logging.getLogger(__name__) 303 + 304 + try: 305 + client = Letta(token=os.environ["LETTA_API_KEY"]) 306 + 307 + # Sanitize handle for block label 308 + clean_handle = handle.lstrip('@').replace('.', '_').replace('-', '_').replace(' ', '_') 309 + block_label = f"user_{clean_handle}" 310 + 311 + # Check if block exists 312 + blocks = client.blocks.list(label=block_label) 313 + 314 + if blocks and len(blocks) > 0: 315 + # Block exists, update it 316 + block = blocks[0] 317 + client.blocks.modify( 318 + block_id=str(block.id), 319 + value=content 320 + ) 321 + logger.info(f"Set content for existing block: {block_label}") 322 + return f"✓ Set content for {handle}'s memory block" 323 + 324 + else: 325 + # Block doesn't exist, create it 326 + block = client.blocks.create( 327 + label=block_label, 328 + value=content, 329 + limit=5000 330 + ) 331 + logger.info(f"Created new block with content: {block_label}") 332 + 333 + # Check if block needs to be attached to agent 334 + current_blocks = client.agents.blocks.list(agent_id=str(agent_state.id)) 335 + current_block_labels = {block.label for block in current_blocks} 336 + 337 + if block_label not in current_block_labels: 338 + # Attach the new block to the agent 339 + client.agents.blocks.attach( 340 + agent_id=str(agent_state.id), 341 + block_id=str(block.id) 342 + ) 343 + logger.info(f"Attached new block to agent: {block_label}") 344 + return f"✓ Created and attached {handle}'s memory block" 345 + else: 346 + return f"✓ Created {handle}'s memory block" 347 + 348 + except Exception as e: 349 + logger.error(f"Error setting user block content: {e}") 350 + raise Exception(f"Error setting user block content: {str(e)}") 351 + 352 +