a digital person for bluesky

Use only first successful bluesky_reply to prevent duplicate responses

- Replace loop with first successful candidate selection to avoid sending multiple replies
- Remove iteration logic that was causing duplicate responses when agent had multiple successful tool calls
- Update logging to show when additional candidates are skipped
- Simplify response handling to single attempt instead of retry loop
- Maintain support for both single replies and threaded replies (1-4 messages)

This fixes the issue where agents making multiple successful bluesky_reply tool calls
would send duplicate responses to the same user.

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

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

+42 -41
+42 -41
bsky.py
··· 482 482 logger.warning(f"⚠️ Skipping bluesky_reply tool call with unknown status: {tool_status}") 483 483 484 484 if reply_candidates: 485 - logger.info(f"Found {len(reply_candidates)} bluesky_reply candidates, trying each until one succeeds...") 485 + logger.info(f"Found {len(reply_candidates)} successful bluesky_reply candidates, using only the first one to avoid duplicates") 486 486 487 - for i, (reply_messages, reply_lang) in enumerate(reply_candidates, 1): 488 - # Print the generated reply for testing 489 - print(f"\n=== GENERATED REPLY {i}/{len(reply_candidates)} ===") 490 - print(f"To: @{author_handle}") 491 - if len(reply_messages) == 1: 492 - print(f"Reply: {reply_messages[0]}") 493 - else: 494 - print(f"Reply thread ({len(reply_messages)} messages):") 495 - for j, msg in enumerate(reply_messages, 1): 496 - print(f" {j}. {msg}") 497 - print(f"Language: {reply_lang}") 498 - print(f"======================\n") 487 + # Only use the first successful reply to avoid sending multiple responses 488 + reply_messages, reply_lang = reply_candidates[0] 489 + 490 + # Print the generated reply for testing 491 + print(f"\n=== GENERATED REPLY (FIRST SUCCESSFUL) ===") 492 + print(f"To: @{author_handle}") 493 + if len(reply_messages) == 1: 494 + print(f"Reply: {reply_messages[0]}") 495 + else: 496 + print(f"Reply thread ({len(reply_messages)} messages):") 497 + for j, msg in enumerate(reply_messages, 1): 498 + print(f" {j}. {msg}") 499 + print(f"Language: {reply_lang}") 500 + if len(reply_candidates) > 1: 501 + print(f"Note: Skipped {len(reply_candidates) - 1} additional successful candidates to avoid duplicates") 502 + print(f"======================\n") 499 503 500 - # Send the reply(s) with language 501 - if len(reply_messages) == 1: 502 - # Single reply - use existing function 503 - logger.info(f"Trying single reply {i}/{len(reply_candidates)}: {reply_messages[0][:50]}... (lang: {reply_lang})") 504 - response = bsky_utils.reply_to_notification( 505 - client=atproto_client, 506 - notification=notification_data, 507 - reply_text=reply_messages[0], 508 - lang=reply_lang 509 - ) 510 - else: 511 - # Multiple replies - use new threaded function 512 - logger.info(f"Trying threaded reply {i}/{len(reply_candidates)} with {len(reply_messages)} messages (lang: {reply_lang})") 513 - response = bsky_utils.reply_with_thread_to_notification( 514 - client=atproto_client, 515 - notification=notification_data, 516 - reply_messages=reply_messages, 517 - lang=reply_lang 518 - ) 504 + # Send the reply(s) with language 505 + if len(reply_messages) == 1: 506 + # Single reply - use existing function 507 + logger.info(f"Sending single reply: {reply_messages[0][:50]}... (lang: {reply_lang})") 508 + response = bsky_utils.reply_to_notification( 509 + client=atproto_client, 510 + notification=notification_data, 511 + reply_text=reply_messages[0], 512 + lang=reply_lang 513 + ) 514 + else: 515 + # Multiple replies - use new threaded function 516 + logger.info(f"Sending threaded reply with {len(reply_messages)} messages (lang: {reply_lang})") 517 + response = bsky_utils.reply_with_thread_to_notification( 518 + client=atproto_client, 519 + notification=notification_data, 520 + reply_messages=reply_messages, 521 + lang=reply_lang 522 + ) 519 523 520 - if response: 521 - logger.info(f"Successfully replied to @{author_handle} with candidate {i}") 522 - return True 523 - else: 524 - logger.warning(f"Failed to send reply candidate {i} to @{author_handle}, trying next...") 525 - 526 - # If we get here, all candidates failed 527 - logger.error(f"All {len(reply_candidates)} reply candidates failed for @{author_handle}") 528 - return False 524 + if response: 525 + logger.info(f"Successfully replied to @{author_handle}") 526 + return True 527 + else: 528 + logger.error(f"Failed to send reply to @{author_handle}") 529 + return False 529 530 else: 530 531 logger.warning(f"No bluesky_reply tool calls found for mention from @{author_handle}, removing notification from queue") 531 532 return True