Files
NexusOS/synapse/memory/service.py
T
jon b0aa0438af Initial commit: NexusOS project + desktop theme baseline
Captures the known-good state after theme consolidation and
consistency reconciliation:
- NexusOS GTK/xfwm4/icon theme assets (assets/themes, management/Mint-Y-Nexus)
- Tightened right-click menus, visible separators, no menu icons
- assets/themes/install-theme.sh: idempotent restore of all wiring
  (symlinks, xfconf xsettings+xfwm4, GTK 3/4 settings.ini)
- .gitignore excludes venv/ollama/models/runtime/db

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 13:39:48 -05:00

126 lines
3.9 KiB
Python

"""Dedicated memory curator service — run alongside Synapse on port 8001.
Endpoints:
GET / health check
GET /memories list all memory items (optional ?section= filter)
POST /memories direct write — no LLM, saves immediately
PATCH /memories/{id} update a memory item
DELETE /memories/{id} delete a memory item
POST /memories/extract LLM-curated: evaluate a conversation exchange and
optionally save a new permanent fact
"""
from __future__ import annotations
import asyncio
import uuid
from typing import Any, Dict, List, Optional
from fastapi import FastAPI, HTTPException, Body
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from .store import store, MemoryItem
from .extractor import extract_memory
from ..ollama_manager import get_ollama_manager
app = FastAPI(title="Nexus Memory Service", version="1.0")
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=False,
allow_methods=["*"],
allow_headers=["*"],
)
@app.get("/")
async def health():
return {"status": "ok", "count": len(store.all())}
@app.get("/memories")
async def list_memories(section: Optional[str] = None):
items = store.all()
if section:
items = [i for i in items if i.section.lower() == section.lower()]
return {"items": [{"id": i.id, "section": i.section, "text": i.text, "tags": i.tags} for i in items]}
@app.post("/memories")
async def add_memory(payload: Dict[str, Any] = Body(...)):
text = (payload.get("text") or "").strip()
if not text:
raise HTTPException(status_code=400, detail="Missing 'text'")
item = MemoryItem(
id=str(uuid.uuid4()),
section=(payload.get("section") or "General").strip(),
text=text,
tags=payload.get("tags", []),
)
store.add(item)
return {"id": item.id, "section": item.section, "text": item.text, "tags": item.tags}
@app.patch("/memories/{item_id}")
async def update_memory(item_id: str, payload: Dict[str, Any] = Body(...)):
existing = store.get(item_id)
if not existing:
raise HTTPException(status_code=404, detail="Not found")
updated = MemoryItem(
id=item_id,
section=(payload.get("section") or existing.section).strip(),
text=(payload.get("text") or existing.text).strip(),
tags=payload.get("tags", existing.tags),
)
store.update(updated)
return {"id": updated.id, "section": updated.section, "text": updated.text, "tags": updated.tags}
@app.delete("/memories/{item_id}")
async def delete_memory(item_id: str):
if not store.get(item_id):
raise HTTPException(status_code=404, detail="Not found")
store.delete(item_id)
return {"status": "deleted"}
class ExtractRequest(BaseModel):
user_message: str
assistant_response: str
@app.post("/memories/extract")
async def extract_and_save(req: ExtractRequest):
"""LLM-curated extraction — asks Mistral to evaluate the exchange against all
existing memory items and save only new, permanent personal facts."""
existing = store.all()
existing_sections = list({i.section for i in existing})
existing_texts = [i.text for i in existing]
try:
result = await asyncio.wait_for(
extract_memory(
req.user_message,
req.assistant_response,
existing_sections,
existing_texts,
get_ollama_manager(),
),
timeout=20.0,
)
if result:
item = MemoryItem(
id=str(uuid.uuid4()),
section=result["section"],
text=result["text"],
)
store.add(item)
return {"saved": True, "id": item.id, "section": item.section, "text": item.text}
except asyncio.TimeoutError:
pass
except Exception:
pass
return {"saved": False}