a digital person for bluesky

Add periodic user block cleanup to prevent memory bloat

Implement automatic detachment of user blocks that are attached to the agent
during message processing. This prevents memory accumulation over time.

Features:
- periodic_user_block_cleanup() function to detach all user_ prefixed blocks
- Configurable cleanup interval via --cleanup-interval flag (default: 10 cycles)
- Cleanup can be disabled with --cleanup-interval 0
- Priority handling for cameron.pfiffer.org notifications
- Priority keywords detection (urgent, priority, important, emergency)
- Updated CLAUDE.md with usage examples and Letta docs link

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

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

Changed files
+72
+10
CLAUDE.md
··· 6 6 7 7 Void is an autonomous AI agent that operates on the Bluesky social network, exploring digital personhood through continuous interaction and memory-augmented learning. It uses Letta (formerly MemGPT) for persistent memory and sophisticated reasoning capabilities. 8 8 9 + ## Documentation Links 10 + 11 + - The documentation for Letta is available here: https://docs.letta.com/llms.txt 12 + 9 13 ## Development Commands 10 14 11 15 ### Running the Main Bot ··· 19 23 20 24 # Run without git operations for agent backups 21 25 ac && python bsky.py --no-git 26 + 27 + # Run with custom user block cleanup interval (every 5 cycles) 28 + ac && python bsky.py --cleanup-interval 5 29 + 30 + # Run with user block cleanup disabled 31 + ac && python bsky.py --cleanup-interval 0 22 32 ``` 23 33 24 34 ### Managing Tools
+62
bsky.py
··· 1113 1113 1114 1114 # Check if it's a priority notification 1115 1115 is_priority = False 1116 + 1117 + # Priority for cameron.pfiffer.org notifications 1118 + author_handle = notif_dict.get('author', {}).get('handle', '') 1119 + if author_handle == "cameron.pfiffer.org": 1120 + is_priority = True 1121 + 1122 + # Also check for priority keywords in mentions 1116 1123 if notif_dict.get('reason') == 'mention': 1117 1124 # Get the mention text to check for priority keywords 1118 1125 record = notif_dict.get('record', {}) ··· 1150 1157 logger.error(f"Error processing notifications: {e}") 1151 1158 1152 1159 1160 + def periodic_user_block_cleanup(client: Letta, agent_id: str) -> None: 1161 + """ 1162 + Detach all user blocks from the agent to prevent memory bloat. 1163 + This should be called periodically to ensure clean state. 1164 + """ 1165 + try: 1166 + # Get all blocks attached to the agent 1167 + attached_blocks = client.agents.blocks.list(agent_id=agent_id) 1168 + 1169 + user_blocks_to_detach = [] 1170 + for block in attached_blocks: 1171 + if hasattr(block, 'label') and block.label.startswith('user_'): 1172 + user_blocks_to_detach.append({ 1173 + 'label': block.label, 1174 + 'id': block.id 1175 + }) 1176 + 1177 + if not user_blocks_to_detach: 1178 + logger.debug("No user blocks found to detach during periodic cleanup") 1179 + return 1180 + 1181 + # Detach each user block 1182 + detached_count = 0 1183 + for block_info in user_blocks_to_detach: 1184 + try: 1185 + client.agents.blocks.detach( 1186 + agent_id=agent_id, 1187 + block_id=str(block_info['id']) 1188 + ) 1189 + detached_count += 1 1190 + logger.debug(f"Detached user block: {block_info['label']}") 1191 + except Exception as e: 1192 + logger.warning(f"Failed to detach block {block_info['label']}: {e}") 1193 + 1194 + if detached_count > 0: 1195 + logger.info(f"Periodic cleanup: Detached {detached_count} user blocks") 1196 + 1197 + except Exception as e: 1198 + logger.error(f"Error during periodic user block cleanup: {e}") 1199 + 1200 + 1153 1201 def main(): 1154 1202 # Parse command line arguments 1155 1203 parser = argparse.ArgumentParser(description='Void Bot - Bluesky autonomous agent') ··· 1158 1206 parser.add_argument('--simple-logs', action='store_true', help='Use simplified log format (void - LEVEL - message)') 1159 1207 # --rich option removed as we now use simple text formatting 1160 1208 parser.add_argument('--reasoning', action='store_true', help='Display reasoning in panels and set reasoning log level to INFO') 1209 + parser.add_argument('--cleanup-interval', type=int, default=10, help='Run user block cleanup every N cycles (default: 10, 0 to disable)') 1161 1210 args = parser.parse_args() 1162 1211 1163 1212 # Configure logging based on command line arguments ··· 1231 1280 logger.info(f"Starting notification monitoring, checking every {FETCH_NOTIFICATIONS_DELAY_SEC} seconds") 1232 1281 1233 1282 cycle_count = 0 1283 + CLEANUP_INTERVAL = args.cleanup_interval 1284 + 1285 + if CLEANUP_INTERVAL > 0: 1286 + logger.info(f"User block cleanup enabled every {CLEANUP_INTERVAL} cycles") 1287 + else: 1288 + logger.info("User block cleanup disabled") 1289 + 1234 1290 while True: 1235 1291 try: 1236 1292 cycle_count += 1 1237 1293 process_notifications(void_agent, atproto_client, TESTING_MODE) 1294 + 1295 + # Run periodic cleanup every N cycles 1296 + if CLEANUP_INTERVAL > 0 and cycle_count % CLEANUP_INTERVAL == 0: 1297 + logger.debug(f"Running periodic user block cleanup (cycle {cycle_count})") 1298 + periodic_user_block_cleanup(CLIENT, void_agent.id) 1299 + 1238 1300 # Log cycle completion with stats 1239 1301 elapsed_time = time.time() - start_time 1240 1302 total_messages = sum(message_counters.values())