From c1f2c484947fc3d8bb7c7333a08b1bc6bb85ad1a Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Mon, 23 Feb 2026 20:11:46 +0000 Subject: [PATCH] Fix bugs: MeshMessage position field, summary loading, Google system prompt, import placement, cleanup timer - 1a: Declare _position as proper dataclass field with field(default=None, init=False) so hasattr() check isn't needed and the attribute always exists - 1b: Load persisted conversation summaries from DB into memory cache on startup via new _load_summaries() method called after backend creation - 1c: Use Gemini's system_instruction parameter on GenerativeModel instead of only prepending to first message, so system prompt persists across all turns - 1d: Move 'import os' from line 198 to top of main.py with other imports - 1e: Replace unreliable modulo-based cleanup timer with _last_cleanup timestamp comparison that won't miss hours due to async sleep jitter Co-Authored-By: Claude Opus 4.6 --- meshai/backends/google_backend.py | 12 ++++---- meshai/connector.py | 7 ++--- meshai/main.py | 47 ++++++++++++++++++++++++++++--- 3 files changed, 53 insertions(+), 13 deletions(-) diff --git a/meshai/backends/google_backend.py b/meshai/backends/google_backend.py index 4fe5fff..4e325ac 100644 --- a/meshai/backends/google_backend.py +++ b/meshai/backends/google_backend.py @@ -162,6 +162,12 @@ class GoogleBackend(LLMBackend): ) try: + # Create model with system instruction for persistent system prompt + model = genai.GenerativeModel( + self.config.model, + system_instruction=enhanced_system if enhanced_system else None, + ) + # Convert messages to Gemini format # Gemini uses "user" and "model" roles history = [] @@ -170,15 +176,11 @@ class GoogleBackend(LLMBackend): history.append({"role": role, "parts": [msg["content"]]}) # Start chat with history - chat = self._model.start_chat(history=history) + chat = model.start_chat(history=history) # Get the last user message last_message = final_messages[-1]["content"] if final_messages else "" - # Prepend system prompt to first message if needed - if enhanced_system and not history: - last_message = f"{enhanced_system}\n\n{last_message}" - # Generate response response = await chat.send_message_async( last_message, diff --git a/meshai/connector.py b/meshai/connector.py index 96c8e24..e66ed6e 100644 --- a/meshai/connector.py +++ b/meshai/connector.py @@ -2,7 +2,7 @@ import asyncio import logging -from dataclasses import dataclass +from dataclasses import dataclass, field from typing import Callable, Optional import meshtastic @@ -26,13 +26,12 @@ class MeshMessage: channel: int # Channel index is_dm: bool # True if direct message to us packet: dict # Raw packet for additional data + _position: Optional[tuple[float, float]] = field(default=None, repr=False, init=False) @property def sender_position(self) -> Optional[tuple[float, float]]: """Get sender's GPS position if available (lat, lon).""" - # Position comes from node info, not the message itself - # This will be populated by the connector if available - return self._position if hasattr(self, "_position") else None + return self._position class MeshConnector: diff --git a/meshai/main.py b/meshai/main.py index e39a1b4..906f0e4 100644 --- a/meshai/main.py +++ b/meshai/main.py @@ -3,6 +3,7 @@ import argparse import asyncio import logging +import os import signal import sys import time @@ -18,6 +19,7 @@ from .commands.status import set_start_time from .config import Config, load_config from .connector import MeshConnector, MeshMessage from .history import ConversationHistory +from .memory import ConversationSummary from .responder import Responder from .router import MessageRouter, RouteType @@ -37,6 +39,7 @@ class MeshAI: self.responder: Optional[Responder] = None self._running = False self._loop: Optional[asyncio.AbstractEventLoop] = None + self._last_cleanup: float = 0.0 async def start(self) -> None: """Start the bot.""" @@ -52,6 +55,7 @@ class MeshAI: self._running = True self._loop = asyncio.get_event_loop() + self._last_cleanup = time.time() # Write PID file self._write_pid() @@ -63,8 +67,9 @@ class MeshAI: await asyncio.sleep(1) # Periodic cleanup - if int(time.time()) % 3600 == 0: # Every hour + if time.time() - self._last_cleanup >= 3600: await self.history.cleanup_expired() + self._last_cleanup = time.time() async def stop(self) -> None: """Stop the bot.""" @@ -121,6 +126,9 @@ class MeshAI: self.config.llm, api_key, window_size, summarize_threshold ) + # Load persisted summaries into memory cache + await self._load_summaries() + # Meshtastic connector self.connector = MeshConnector(self.config.connection) @@ -183,6 +191,40 @@ class MeshAI: except Exception as e: logger.error(f"Error handling message: {e}", exc_info=True) + async def _load_summaries(self) -> None: + """Load persisted summaries from database into memory cache.""" + memory = self.llm.get_memory() + if not memory: + return + + if not self.history or not self.history._db: + return + + try: + async with self.history._lock: + cursor = await self.history._db.execute( + "SELECT user_id, summary, message_count, updated_at " + "FROM conversation_summaries" + ) + rows = await cursor.fetchall() + + loaded = 0 + for row in rows: + user_id, summary_text, message_count, updated_at = row + summary = ConversationSummary( + summary=summary_text, + last_updated=updated_at, + message_count=message_count, + ) + memory.load_summary(user_id, summary) + loaded += 1 + + if loaded: + logger.info(f"Loaded {loaded} conversation summaries from database") + + except Exception as e: + logger.warning(f"Failed to load summaries from database: {e}") + def _write_pid(self) -> None: """Write PID file.""" pid_file = Path("/tmp/meshai.pid") @@ -195,9 +237,6 @@ class MeshAI: pid_file.unlink() -import os - - def setup_logging(verbose: bool = False) -> None: """Configure logging.""" level = logging.DEBUG if verbose else logging.INFO