diff --git a/src/config.py b/src/config.py index f43be924..62b46809 100644 --- a/src/config.py +++ b/src/config.py @@ -87,8 +87,12 @@ class Settings(BaseSettings): xai_base_url: str = "https://api.x.ai/v1" grok_default_model: str = "grok-3-fast" grok_max_sats_per_query: int = 200 + grok_sats_hard_cap: int = 100 # Absolute ceiling on sats per Grok query grok_free: bool = False # Skip Lightning invoice when user has own API key + # ── Database ────────────────────────────────────────────────────────── + db_busy_timeout_ms: int = 5000 # SQLite PRAGMA busy_timeout (ms) + # ── Claude (Anthropic) — cloud fallback backend ──────────────────────── # Used when Ollama is offline and local inference isn't available. # Set ANTHROPIC_API_KEY to enable. Default model is Haiku (fast + cheap). diff --git a/src/timmy/memory/unified.py b/src/timmy/memory/unified.py index 97fd888d..407c2754 100644 --- a/src/timmy/memory/unified.py +++ b/src/timmy/memory/unified.py @@ -14,6 +14,8 @@ from dataclasses import dataclass, field from datetime import UTC, datetime from pathlib import Path +from config import settings + logger = logging.getLogger(__name__) # Paths @@ -28,7 +30,7 @@ def get_connection() -> Generator[sqlite3.Connection, None, None]: with closing(sqlite3.connect(str(DB_PATH))) as conn: conn.row_factory = sqlite3.Row conn.execute("PRAGMA journal_mode=WAL") - conn.execute("PRAGMA busy_timeout=5000") + conn.execute(f"PRAGMA busy_timeout={settings.db_busy_timeout_ms}") _ensure_schema(conn) yield conn diff --git a/src/timmy/memory_system.py b/src/timmy/memory_system.py index 76ddd5c3..c3d2d14c 100644 --- a/src/timmy/memory_system.py +++ b/src/timmy/memory_system.py @@ -20,6 +20,7 @@ from dataclasses import dataclass, field from datetime import UTC, datetime, timedelta from pathlib import Path +from config import settings from timmy.memory.embeddings import ( EMBEDDING_DIM, EMBEDDING_MODEL, # noqa: F401 — re-exported for backward compatibility @@ -111,7 +112,7 @@ def get_connection() -> Generator[sqlite3.Connection, None, None]: with closing(sqlite3.connect(str(DB_PATH))) as conn: conn.row_factory = sqlite3.Row conn.execute("PRAGMA journal_mode=WAL") - conn.execute("PRAGMA busy_timeout=5000") + conn.execute(f"PRAGMA busy_timeout={settings.db_busy_timeout_ms}") _ensure_schema(conn) yield conn @@ -949,7 +950,7 @@ class SemanticMemory: with closing(sqlite3.connect(str(self.db_path))) as conn: conn.row_factory = sqlite3.Row conn.execute("PRAGMA journal_mode=WAL") - conn.execute("PRAGMA busy_timeout=5000") + conn.execute(f"PRAGMA busy_timeout={settings.db_busy_timeout_ms}") # Ensure schema exists conn.execute(""" CREATE TABLE IF NOT EXISTS memories ( diff --git a/src/timmy/tools.py b/src/timmy/tools.py index 38ca74b1..37009092 100644 --- a/src/timmy/tools.py +++ b/src/timmy/tools.py @@ -24,6 +24,9 @@ from config import settings logger = logging.getLogger(__name__) +# Max characters of user query included in Lightning invoice memo +_INVOICE_MEMO_MAX_LEN = 50 + # Lazy imports to handle test mocking _ImportError = None try: @@ -447,7 +450,6 @@ def consult_grok(query: str) -> str: ) except (ImportError, AttributeError) as exc: logger.warning("Tool execution failed (consult_grok logging): %s", exc) - pass # Generate Lightning invoice for monetization (unless free mode) invoice_info = "" @@ -456,12 +458,11 @@ def consult_grok(query: str) -> str: from lightning.factory import get_backend as get_ln_backend ln = get_ln_backend() - sats = min(settings.grok_max_sats_per_query, 100) - inv = ln.create_invoice(sats, f"Grok query: {query[:50]}") + sats = min(settings.grok_max_sats_per_query, settings.grok_sats_hard_cap) + inv = ln.create_invoice(sats, f"Grok query: {query[:_INVOICE_MEMO_MAX_LEN]}") invoice_info = f"\n[Lightning invoice: {sats} sats — {inv.payment_request[:40]}...]" except (ImportError, OSError, ValueError) as exc: logger.warning("Tool execution failed (Lightning invoice): %s", exc) - pass result = backend.run(query) @@ -940,7 +941,7 @@ def _merge_catalog( "available_in": available_in, } except ImportError: - pass + logger.debug("Optional catalog %s.%s not available", module_path, attr_name) def get_all_available_tools() -> dict[str, dict]: