a digital entity named phi that roams bsky

Add comprehensive debug logging for troubleshooting

- Set debug mode to True by default for development
- Add model validator to configure logging based on debug setting
- Add debug logging to agent to see prompts and responses
- Log when ignore_notification tool is called with reasons
- Fix message handler to accept both mentions and replies
- Add detailed logging throughout notification processing flow

This will help diagnose why the bot isn't responding to certain messages.

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

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

+4
justfile
··· 18 18 test-agent-search: 19 19 uv run python scripts/test_agent_search.py 20 20 21 + # Test ignore notification tool 22 + test-ignore: 23 + uv run python scripts/test_ignore_tool.py 24 + 21 25 # Run tests 22 26 test: 23 27 uv run pytest tests/ -v
+11 -1
src/bot/agents/anthropic_agent.py
··· 1 1 """Anthropic agent for generating responses""" 2 2 3 + import logging 3 4 import os 4 5 5 6 from pydantic import BaseModel, Field ··· 8 9 from bot.config import settings 9 10 from bot.personality import load_personality 10 11 from bot.tools.google_search import search_google 12 + 13 + logger = logging.getLogger("bot.agent") 11 14 12 15 13 16 class Response(BaseModel): ··· 52 55 53 56 Categories: 'spam', 'not_relevant', 'bot_loop', 'handled_elsewhere' 54 57 """ 58 + logger.debug(f"🚫 ignore_notification called: category={category}, reason={reason}") 55 59 return f"IGNORED_NOTIFICATION::{category}::{reason}" 56 60 57 61 async def generate_response( ··· 68 72 prompt_parts.append(f"{author_handle} said: {mention_text}") 69 73 70 74 prompt = "\n".join(prompt_parts) 75 + 76 + logger.debug(f"🤖 Agent prompt:\n{prompt}") 71 77 72 78 # No need for hint - agent knows about its tools 73 79 74 80 result = await self.agent.run(prompt) 75 - return result.output.text[:300] 81 + response_text = result.output.text[:300] 82 + 83 + logger.debug(f"💬 Agent response: {response_text}") 84 + 85 + return response_text
+22 -1
src/bot/config.py
··· 1 + import logging 2 + 3 + from pydantic import model_validator 1 4 from pydantic_settings import BaseSettings, SettingsConfigDict 2 5 3 6 ··· 33 36 notification_poll_interval: int = 10 # seconds (faster for testing) 34 37 35 38 # Debug mode 36 - debug: bool = False 39 + debug: bool = True # Default to True for development 40 + 41 + @model_validator(mode="after") 42 + def configure_logging(self): 43 + """Configure logging based on debug setting""" 44 + if self.debug: 45 + logging.basicConfig( 46 + level=logging.DEBUG, 47 + format="%(asctime)s [%(levelname)s] %(name)s: %(message)s", 48 + datefmt="%H:%M:%S", 49 + ) 50 + logging.getLogger("bot").setLevel(logging.DEBUG) 51 + else: 52 + logging.basicConfig( 53 + level=logging.INFO, 54 + format="%(asctime)s [%(levelname)s] %(message)s", 55 + datefmt="%H:%M:%S", 56 + ) 57 + return self 37 58 38 59 39 60 settings = Settings() # type: ignore[call-arg]
+12 -3
src/bot/services/message_handler.py
··· 1 + import logging 2 + 1 3 from atproto import models 2 4 3 5 from bot.config import settings ··· 5 7 from bot.database import thread_db 6 8 from bot.response_generator import ResponseGenerator 7 9 from bot.status import bot_status 10 + 11 + logger = logging.getLogger("bot.handler") 8 12 9 13 10 14 class MessageHandler: ··· 13 17 self.response_generator = ResponseGenerator() 14 18 15 19 async def handle_mention(self, notification): 16 - """Process a mention notification""" 20 + """Process a mention or reply notification""" 17 21 try: 18 - # Skip if not a mention 19 - if notification.reason != "mention": 22 + logger.debug(f"📨 Processing notification: reason={notification.reason}, uri={notification.uri}") 23 + 24 + # Skip if not a mention or reply 25 + if notification.reason not in ["mention", "reply"]: 26 + logger.debug(f"⏭️ Skipping notification with reason: {notification.reason}") 20 27 return 21 28 22 29 post_uri = notification.uri ··· 31 38 mention_text = post.record.text 32 39 author_handle = post.author.handle 33 40 author_did = post.author.did 41 + 42 + logger.debug(f"📝 Post details: author=@{author_handle}, text='{mention_text}'") 34 43 35 44 # Record mention received 36 45 bot_status.record_mention()
+8
src/bot/services/notification_poller.py
··· 1 1 import asyncio 2 + import logging 2 3 3 4 from bot.config import settings 4 5 from bot.core.atproto_client import BotClient 5 6 from bot.services.message_handler import MessageHandler 6 7 from bot.status import bot_status 8 + 9 + logger = logging.getLogger("bot.poller") 7 10 8 11 9 12 class NotificationPoller: ··· 92 95 for notification in reversed(notifications): 93 96 # Skip if already seen or processed 94 97 if notification.is_read or notification.uri in self._processed_uris: 98 + logger.debug(f"⏭️ Skipping already processed: {notification.uri}") 95 99 continue 96 100 101 + logger.debug(f"🔍 Found notification: reason={notification.reason}, uri={notification.uri}") 102 + 97 103 if notification.reason in ["mention", "reply"]: 98 104 # Process mentions and replies in threads 99 105 self._processed_uris.add(notification.uri) 100 106 await self.handler.handle_mention(notification) 101 107 processed_any_mentions = True 108 + else: 109 + logger.debug(f"⏭️ Ignoring notification type: {notification.reason}") 102 110 103 111 # Mark all notifications as seen using the initial timestamp 104 112 # This ensures we don't miss any that arrived during processing