feat: Add interactive install.py and validation checklist.md
- Install script with full/simple mode selection - Inline prompt format with 'go back' navigation - Custom input option on all prompts - Configuration summary and validation - Comprehensive installation checklist
This commit is contained in:
181
checklist.md
Normal file
181
checklist.md
Normal file
@@ -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:** _______________
|
||||
318
install.py
Executable file
318
install.py
Executable file
@@ -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()
|
||||
Reference in New Issue
Block a user