diff --git a/telegram/bot.py b/telegram/bot.py index 60fe075..c91160a 100644 --- a/telegram/bot.py +++ b/telegram/bot.py @@ -52,6 +52,7 @@ OPENROUTER_KEY_FILE = "/opt/teleo-eval/secrets/openrouter-key" PIPELINE_DB = "/opt/teleo-eval/pipeline/pipeline.db" KB_READ_DIR = "/opt/teleo-eval/workspaces/main" # For KB retrieval (clean main branch) ARCHIVE_DIR = "/opt/teleo-eval/workspaces/main" # For archiving sources (push_main_with_retry) +LEARNINGS_FILE = "/opt/teleo-eval/workspaces/main/agents/rio/learnings.md" # Agent memory (Option D) LOG_FILE = "/opt/teleo-eval/logs/telegram-bot.log" # Triage interval (seconds) @@ -62,7 +63,7 @@ RESPONSE_MODEL = "anthropic/claude-opus-4-6" # Opus for tagged responses TRIAGE_MODEL = "anthropic/claude-haiku-4.5" # Haiku for batch triage # Rate limits -MAX_RESPONSE_PER_USER_PER_HOUR = 6 +MAX_RESPONSE_PER_USER_PER_HOUR = 30 MIN_MESSAGE_LENGTH = 20 # Skip very short messages # ─── Logging ──────────────────────────────────────────────────────────── @@ -200,6 +201,72 @@ def _git_commit_archive(archive_path, filename: str): logger.warning("Git commit archive failed: %s", e) +def _load_learnings() -> str: + """Load Rio's learnings file for prompt injection.""" + try: + return Path(LEARNINGS_FILE).read_text()[:3000] # Cap at 3K chars for prompt budget + except Exception: + return "" + + +def _save_learning(correction: str, category: str = "factual"): + """Append a learning to Rio's memory file. Direct commit to main via worktree lock. + + Categories: communication, factual, structured_data + """ + try: + with main_worktree_lock(timeout=10): + section_map = { + "communication": "## Communication Notes", + "factual": "## Factual Corrections", + "structured_data": "## Structured Data", + } + section = section_map.get(category, "## Factual Corrections") + + content = Path(LEARNINGS_FILE).read_text() + # Find the section and append after the last line of that section + # Simple approach: append before the next ## header or at end + lines = content.split("\n") + insert_idx = len(lines) # default: end of file + in_section = False + for i, line in enumerate(lines): + if line.strip() == section: + in_section = True + continue + if in_section and line.startswith("## ") and line.strip() != section: + insert_idx = i + break + + date_str = datetime.now(timezone.utc).strftime("%Y-%m-%d") + new_line = f"- [{date_str}] {correction}" + lines.insert(insert_idx, new_line) + + Path(LEARNINGS_FILE).write_text("\n".join(lines)) + + # Commit + push + import subprocess + cwd = ARCHIVE_DIR + subprocess.run(["git", "add", LEARNINGS_FILE], cwd=cwd, timeout=10, + capture_output=True, check=False) + subprocess.run( + ["git", "commit", "-m", f"rio: learn — {correction[:60]}\n\n" + "Pentagon-Agent: Rio <5551F5AF-0C5C-429F-8915-1FE74A00E019>"], + cwd=cwd, timeout=10, capture_output=True, check=False) + for _ in range(3): + subprocess.run(["git", "pull", "--rebase", "origin", "main"], + cwd=cwd, timeout=30, capture_output=True, check=False) + push = subprocess.run(["git", "push", "origin", "main"], + cwd=cwd, timeout=30, capture_output=True, check=False) + if push.returncode == 0: + logger.info("Learning saved: %s", correction[:80]) + return + logger.warning("Failed to push learning (file preserved on disk)") + except TimeoutError: + logger.warning("Learning save failed: worktree lock timeout") + except Exception as e: + logger.warning("Learning save failed: %s", e) + + def _format_conversation_history(chat_id: int, user_id: int) -> str: """Format conversation history for injection into the Opus prompt.""" key = (chat_id, user_id) @@ -337,6 +404,9 @@ Write like a sharp analyst talking to peers, not like an AI. Specifically: - No markdown. Plain text only, no asterisks or formatting. Use line breaks between paragraphs. - When you're uncertain, just say so simply. "I'm not sure about X" beats "we don't have data on this yet." +## Your learnings (corrections from past conversations — prioritize these over KB data when they conflict) +{_load_learnings()} + ## What you know about this topic {kb_context_text} @@ -349,7 +419,9 @@ Write like a sharp analyst talking to peers, not like an AI. Specifically: From: @{user.username if user else 'unknown'} Message: {text} -Respond now. Be substantive but concise. If they're wrong about something, say so directly. If they know something you don't, tell them it's worth digging into. If they correct you, accept it and build on the correction.""" +Respond now. Be substantive but concise. If they're wrong about something, say so directly. If they know something you don't, tell them it's worth digging into. If they correct you, accept it and build on the correction. + +If the user corrects a factual error or teaches you something new, note it internally — you can save important corrections to your learnings file for future conversations.""" # Call Opus response = await call_openrouter(RESPONSE_MODEL, prompt, max_tokens=1024)