stage-10: memory search with Ollama embeddings + SQLite

Semantic search over memory/*.md files using all-minilm embeddings.
Adds /search Discord command and `echo memory search/reindex` CLI.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
MoltBot Service
2026-02-13 16:49:57 +00:00
parent 0bc4b8cb3e
commit 0ecfa630eb
4 changed files with 1007 additions and 0 deletions

View File

@@ -124,6 +124,7 @@ def create_bot(config: Config) -> discord.Client:
"`/logs [n]` — Show last N log lines (default 10)",
"`/restart` — Restart the bot process (owner only)",
"`/heartbeat` — Run heartbeat health checks",
"`/search <query>` — Search Echo's memory",
"",
"**Cron Jobs**",
"`/cron list` — List all scheduled jobs",
@@ -413,6 +414,41 @@ def create_bot(config: Config) -> discord.Client:
f"Heartbeat error: {e}", ephemeral=True
)
@tree.command(name="search", description="Search Echo's memory")
@app_commands.describe(query="What to search for")
async def search_cmd(
interaction: discord.Interaction, query: str
) -> None:
await interaction.response.defer()
try:
from src.memory_search import search
results = await asyncio.to_thread(search, query)
if not results:
await interaction.followup.send(
"No results found (index may be empty — run `echo memory reindex`)."
)
return
lines = [f"**Search results for:** {query}\n"]
for i, r in enumerate(results, 1):
score = r["score"]
preview = r["chunk"][:150]
if len(r["chunk"]) > 150:
preview += "..."
lines.append(
f"**{i}.** `{r['file']}` (score: {score:.3f})\n{preview}\n"
)
text = "\n".join(lines)
if len(text) > 1900:
text = text[:1900] + "\n..."
await interaction.followup.send(text)
except ConnectionError as e:
await interaction.followup.send(f"Search error: {e}")
except Exception as e:
logger.exception("Search command failed")
await interaction.followup.send(f"Search error: {e}")
@tree.command(name="channels", description="List registered channels")
async def channels(interaction: discord.Interaction) -> None:
ch_map = config.get("channels", {})