diff --git a/checklist.md b/checklist.md new file mode 100644 index 0000000..e813485 --- /dev/null +++ b/checklist.md @@ -0,0 +1,181 @@ +# TrueRecall v2 - Installation Checklist + +Pre-install and post-install validation steps. + +--- + +## Pre-Installation Checks + +### 1. System Requirements + +| Check | Command | Expected Result | +|-------|---------|---------------| +| Python 3.8+ | `python3 --version` | 3.8 or higher | +| pip available | `pip3 --version` | Working | +| curl available | `curl --version` | Working | +| git available | `git --version` | Working | + +### 2. Qdrant Verification + +| Check | Command | Expected Result | +|-------|---------|---------------| +| Qdrant running | `curl http://localhost:6333` | Returns version | +| Collections accessible | `curl http://localhost:6333/collections` | Returns list | +| Network reachable | `curl http://127.0.0.1:6333` | Same result | + +### 3. Ollama Verification + +| Check | Command | Expected Result | +|-------|---------|---------------| +| Ollama running | `curl http://localhost:11434/api/tags` | Returns models | +| Embedding model | Check in list | snowflake or mxbai | +| Curator model | Check in list | qwen3:4b or 30b | +| Network reachable | `curl http://127.0.0.1:11434/api/tags` | Same result | + +### 4. OpenClaw Status + +| Check | Command | Expected Result | +|-------|---------|---------------| +| Gateway running | `openclaw gateway status` | Active | +| Config valid | `openclaw doctor` | No errors | +| Plugin available | `openclaw plugins list` | memory-qdrant present | + +--- + +## Installation Steps + +### Step 1: Choose Mode + +| Mode | Description | Use Case | +|------|-------------|----------| +| **full** | Watcher + Curator + Gems | Complete TrueRecall v2 | +| **simple** | Watcher only | Just capture memories | + +### Step 2: Full Mode Prompts + +| # | Prompt | Default | Validate | +|---|--------|---------|----------| +| 1 | Installation mode | `full` | Must be "full" or "simple" | +| 2 | Embedding model | `mxbai-embed-large` | Model must exist in Ollama | +| 3 | Timer interval | `5` minutes | 5, 30, 60, or custom | +| 4 | Batch size | `100` | 50, 100, 500, or custom | +| 5 | Qdrant URL | `http://localhost:6333` | Must be reachable | +| 6 | Ollama URL | `http://localhost:11434` | Must be reachable | +| 7 | Curator LLM | `qwen3:30b-a3b-instruct` | Model must exist in Ollama | +| 8 | User ID | `rob` | Non-empty string | + +### Step 3: Simple Mode Prompts + +| # | Prompt | Default | Validate | +|---|--------|---------|----------| +| 1 | Installation mode | `full` | Must be "simple" | +| 2 | Embedding model | `snowflake-arctic-embed2` | Model must exist in Ollama | +| 3 | Qdrant URL | `http://localhost:6333` | Must be reachable | +| 4 | User ID | `rob` | Non-empty string | + +--- + +## Post-Installation Validation + +### 1. Config Files Created + +| File | Location | Check | +|------|----------|-------| +| `curator_config.json` | `tr-continuous/` | Contains all settings | +| `curator_timer.py` | `tr-continuous/` | Executable, no syntax errors | +| `mem-qdrant-watcher.service` | systemd path | Loaded and enabled | + +### 2. Watcher Service + +| Check | Command | Expected Result | +|-------|---------|---------------| +| Service installed | `systemctl status mem-qdrant-watcher` | Loaded | +| Service running | `systemctl is-active mem-qdrant-watcher` | `active` | +| Logs available | `sudo journalctl -u mem-qdrant-watcher -n 5` | Recent entries | + +### 3. Cron Job (Full Mode Only) + +| Check | Command | Expected Result | +|-------|---------|---------------| +| Cron entry exists | `crontab -l \| grep true-recall` | Entry present | +| Syntax valid | Visual check | No errors | +| Timer matches | Compare to config | Same interval | + +### 4. Collections Created + +| Check | Command | Expected Result | +|-------|---------|---------------| +| memories_tr exists | `curl http://localhost:6333/collections/memories_tr` | 200 OK | +| gems_tr exists (full) | `curl http://localhost:6333/collections/gems_tr` | 200 OK | +| Can write | Test point insertion | Success | +| Can read | Test point query | Success | + +### 5. First Run (Full Mode) + +| Check | Command | Expected Result | +|-------|---------|---------------| +| Curator runs | Wait for first cron | Log entry created | +| Log file exists | `ls /var/log/true-recall-timer.log` | File present | +| Gems extracted | Check `gems_tr` count | Increased from 0 | +| Memories tagged | Query with `curated: false` | All have field | + +### 6. Injection Test + +| Check | Method | Expected Result | +|-------|--------|-----------------| +| Plugin loads | `openclaw plugins list` | memory-qdrant active | +| Recall works | Send test message | Gems injected in context | +| Capture works | Check `memories_tr` | New point added | + +--- + +## Troubleshooting + +### Common Issues + +| Symptom | Likely Cause | Fix | +|---------|--------------|-----| +| Qdrant unreachable | Not running | `sudo systemctl start qdrant` | +| Ollama unreachable | Not running | `ollama serve` | +| Model not found | Not pulled | `ollama pull mxbai-embed-large` | +| Permission denied | Wrong user | Run with `sudo` or check ownership | +| Cron not running | Service stopped | `sudo systemctl start cron` | +| Watcher not capturing | Session path wrong | Check `session.jsonl` location | + +--- + +## Validation Commands Quick Reference + +```bash +# Check all services +openclaw status && curl -s http://localhost:6333 && curl -s http://localhost:11434/api/tags + +# Check collections +curl -s http://localhost:6333/collections/memories_tr | jq .result.points_count +curl -s http://localhost:6333/collections/gems_tr | jq .result.points_count + +# Check watcher +sudo systemctl status mem-qdrant-watcher + +# Check cron +crontab -l | grep true-recall + +# Check logs +tail -f /var/log/true-recall-timer.log +sudo journalctl -u mem-qdrant-watcher -f +``` + +--- + +## Sign-off + +| Check | Status | Date | +|-------|--------|------| +| Pre-install checks passed | ⏳ | | +| Installation completed | ⏳ | | +| Post-install validation passed | ⏳ | | +| First curator run successful | ⏳ | | +| Injection test passed | ⏳ | | + +**Installer:** _______________ +**Date:** _______________ diff --git a/install.py b/install.py new file mode 100755 index 0000000..8ee0b09 --- /dev/null +++ b/install.py @@ -0,0 +1,318 @@ +#!/usr/bin/env python3 +""" +TrueRecall v2 - Interactive Install Script + +Prompts for configuration and sets up the system. +Simple inline format with go-back navigation. +""" + +import json +import os +import sys +from pathlib import Path + +# Configuration state +config = { + "mode": None, + "embedding_model": "mxbai-embed-large", + "timer_minutes": 5, + "batch_size": 100, + "qdrant_url": "http://localhost:6333", + "ollama_url": "http://localhost:11434", + "curator_model": "qwen3:30b-a3b-instruct", + "user_id": "rob", + "source_collection": "memories_tr", + "target_collection": "gems_tr" +} + +# Track position for go-back +history = [] + +def prompt_inline(question, options, default_key, allow_custom=False): + """ + Show inline prompt with 0. go back option. + + options: dict like {"1": ("value", "description"), ...} + Returns: (value, go_back_flag) + """ + print(f"\n{question} [{default_key}]") + print(" 0. go back (return to previous question)") + for key, (value, desc) in options.items(): + marker = " [default]" if key == default_key else "" + print(f" {key}. {value:<25} ({desc}){marker}") + if allow_custom: + print(f" {len(options)+1}. custom (enter your own value)") + + while True: + choice = input(f"\nSelect [0-{len(options) + (1 if allow_custom else 0)} or type name]: ").strip() + + # Go back + if choice == "0": + return None, True + + # Empty = default + if choice == "": + return options[default_key][0], False + + # Number selection + if choice in options: + return options[choice][0], False + + # Custom + if allow_custom and choice == str(len(options) + 1): + custom = input("Enter custom value: ").strip() + return custom if custom else options[default_key][0], False + + # Direct name entry + for key, (value, desc) in options.items(): + if choice.lower() == value.lower(): + return value, False + + print("Invalid choice. Try again.") + +def prompt_custom(question, default): + """Prompt for custom text input with go-back option.""" + print(f"\n{question}") + print(" 0. go back (return to previous question)") + print(f" 1. use default ({default})") + print(" 2. custom (enter your own value)") + + while True: + choice = input("\nSelect [0-2]: ").strip() + + if choice == "0": + return None, True + if choice == "1" or choice == "": + return default, False + if choice == "2": + custom = input("Enter value: ").strip() + return custom if custom else default, False + + print("Invalid choice. Try again.") + +def install_full(): + """Full installation prompts.""" + prompts = [ + # (key, question, options, default_key, allow_custom) + ("embedding_model", "Embedding model", { + "1": ("snowflake-arctic-embed2", "fast, MTEB ~60, high-volume"), + "2": ("mxbai-embed-large", "accurate, MTEB 66.5, recommended") + }, "2", True), + + ("timer_minutes", "Timer interval", { + "1": ("5", "minutes, fast backlog clearing"), + "2": ("30", "minutes, balanced, recommended"), + "3": ("60", "minutes, minimal overhead") + }, "2", True), + + ("batch_size", "Batch size", { + "1": ("50", "conservative, less memory"), + "2": ("100", "balanced, recommended"), + "3": ("500", "aggressive, faster backlog") + }, "2", True), + + ("qdrant_url", "Qdrant URL", { + "1": ("http://localhost:6333", "localhost hostname"), + "2": ("http://127.0.0.1:6333", "localhost IP") + }, "1", True), + + ("ollama_url", "Ollama URL", { + "1": ("http://localhost:11434", "localhost hostname"), + "2": ("http://127.0.0.1:11434", "localhost IP") + }, "1", True), + + ("curator_model", "Curator LLM", { + "1": ("qwen3:4b-instruct", "fast ~10s, basic quality"), + "2": ("qwen3:30b-a3b-instruct", "quality ~3s, recommended"), + "3": ("qwen3:30b-a3b-instruct-2507-q8_0", "quality, specific version") + }, "2", True), + + ("user_id", "User ID", { + "1": ("rob", "default") + }, "1", True), + ] + + idx = 0 + while idx < len(prompts): + key, question, options, default_key, allow_custom = prompts[idx] + + if key in ["qdrant_url", "ollama_url", "user_id"] and allow_custom: + # These use custom prompt + value, go_back = prompt_custom(question, options[default_key][0]) + else: + value, go_back = prompt_inline(question, options, default_key, allow_custom) + + if go_back and idx > 0: + idx -= 1 + continue + if go_back and idx == 0: + return False # Go back to mode selection + + config[key] = value + idx += 1 + + return True + +def install_simple(): + """Simple installation prompts (watcher only).""" + prompts = [ + ("embedding_model", "Embedding model", { + "1": ("snowflake-arctic-embed2", "fast, MTEB ~60, high-volume"), + "2": ("mxbai-embed-large", "accurate, MTEB 66.5, recommended") + }, "1", True), + + ("qdrant_url", "Qdrant URL", { + "1": ("http://localhost:6333", "localhost hostname"), + "2": ("http://127.0.0.1:6333", "localhost IP") + }, "1", True), + + ("user_id", "User ID", { + "1": ("rob", "default") + }, "1", True), + ] + + idx = 0 + while idx < len(prompts): + key, question, options, default_key, allow_custom = prompts[idx] + + if allow_custom: + value, go_back = prompt_custom(question, options[default_key][0]) + else: + value, go_back = prompt_inline(question, options, default_key, allow_custom) + + if go_back and idx > 0: + idx -= 1 + continue + if go_back and idx == 0: + return False # Go back to mode selection + + config[key] = value + idx += 1 + + return True + +def write_config(): + """Write configuration files.""" + script_dir = Path(__file__).parent + config_path = script_dir / "tr-continuous" / "curator_config.json" + + # Ensure directory exists + config_path.parent.mkdir(parents=True, exist_ok=True) + + output = { + "timer_minutes": int(config["timer_minutes"]), + "batch_size": int(config["batch_size"]), + "user_id": config["user_id"], + "source_collection": config["source_collection"], + "target_collection": config["target_collection"], + "embedding_model": config["embedding_model"], + "curator_model": config["curator_model"], + "qdrant_url": config["qdrant_url"], + "ollama_url": config["ollama_url"] + } + + # Remove curator settings for simple mode + if config["mode"] == "simple": + output.pop("timer_minutes", None) + output.pop("batch_size", None) + output.pop("curator_model", None) + output.pop("ollama_url", None) + output["target_collection"] = None # No gems in simple mode + + with open(config_path, 'w') as f: + json.dump(output, f, indent=2) + + return config_path + +def show_summary(): + """Display configuration summary.""" + print("\n" + "=" * 60) + print("Configuration Summary") + print("=" * 60) + print(f"Mode: {config['mode']}") + print(f"Embedding model: {config['embedding_model']}") + print(f"Qdrant URL: {config['qdrant_url']}") + print(f"User ID: {config['user_id']}") + print(f"Source collection: {config['source_collection']}") + + if config["mode"] == "full": + print(f"Timer interval: {config['timer_minutes']} minutes") + print(f"Batch size: {config['batch_size']}") + print(f"Ollama URL: {config['ollama_url']}") + print(f"Curator LLM: {config['curator_model']}") + print(f"Target collection: {config['target_collection']}") + else: + print("Target collection: (none - simple mode)") + + print("=" * 60) + +def main(): + print("=" * 60) + print("TrueRecall v2 - Interactive Installation") + print("=" * 60) + + # Mode selection + while True: + print("\nSelect installation mode:") + print(" 0. exit (cancel installation)") + print(" 1. full (watcher + curator + gems)") + print(" 2. simple (watcher only - memories)") + + choice = input("\nSelect [0-2]: ").strip() + + if choice == "0": + print("\nInstallation cancelled.") + return + if choice == "1": + config["mode"] = "full" + if install_full(): + break + elif choice == "2": + config["mode"] = "simple" + if install_simple(): + break + else: + print("Invalid choice. Try again.") + + # Show summary + show_summary() + + # Confirm + confirm = input("\nApply this configuration? [Y/n]: ").strip().lower() + if confirm and confirm not in ("y", "yes"): + print("\nInstallation cancelled.") + return + + # Write config + config_path = write_config() + print(f"\n✅ Configuration written to: {config_path}") + + # Next steps + print("\n" + "=" * 60) + print("Next Steps") + print("=" * 60) + + if config["mode"] == "full": + cron_expr = f"*/{config['timer_minutes']} * * * *" + print(f"1. Add to crontab:") + print(f" {cron_expr} cd {config_path.parent} && python3 curator_timer.py >> /var/log/true-recall-timer.log 2>&1") + print(f"\n2. Start the watcher:") + print(f" sudo systemctl start mem-qdrant-watcher") + print(f" sudo systemctl enable mem-qdrant-watcher") + else: + print(f"1. Start the watcher:") + print(f" sudo systemctl start mem-qdrant-watcher") + print(f" sudo systemctl enable mem-qdrant-watcher") + print(f"\nNote: Simple mode has no curator. Memories are captured only.") + + print(f"\n3. Check logs:") + print(f" tail -f /var/log/true-recall-timer.log") + print(f" sudo journalctl -u mem-qdrant-watcher -f") + + print(f"\n4. Run validation:") + print(f" cat checklist.md") + + print("\n✅ Installation complete!") + +if __name__ == "__main__": + main()