# Memory Gateway — deploy Componente server di **pi-qmem**: FastAPI + Qdrant 1.19 + Ollama (BGE-M3). Nessun LLM in scrittura: l'agente salva record deliberati e strutturati. ``` pi (estensione pi-qmem) ──HTTPS/VPN──▶ Memory Gateway (FastAPI:8082) ──▶ Qdrant 1.19 (6333) │ └──▶ Ollama BGE-M3 (11434, nativo host) ``` ## Deploy (Docker Compose) ```bash # 1. Prepara l'ambiente (vedi qmem-gateway/docker-compose.yml come riferimento) cp .env.example .env chmod 600 .env # genera le chiavi: # QDRANT_ADMIN_API_KEY=$(openssl rand -hex 32) # QDRANT_READ_ONLY_API_KEY=$(openssl rand -hex 32) # API_KEYS=$(openssl rand -hex 32) # chiave condivisa per gli agenti # 2. Avvia docker compose up -d --build # 3. Verifica curl http://127.0.0.1:8082/v1/status ``` Requisiti: Docker + Compose v2, Ollama con modello `bge-m3` sul host (`ollama pull bge-m3`), porta 8082 libera sull'interfaccia VPN. ## API | Endpoint | Descrizione | |---|---| | `POST /v1/memories` | Crea record (text, kind, agent_id, scope, **project_id obbligatorio**, source, expires_at, supersedes_id, supersede_reason). Applica il guardrail di similarità pre-scrittura | | `POST /v1/memories:search` | Ricerca semantica (query, kind, project_id, scope, top_k, include_superseded, min_score) | | `GET /v1/memories/{id}` | Recupera per UUID | | `DELETE /v1/memories/{id}` | Elimina per UUID | | `GET /v1/meta/overview` | Discovery: scope×kind, progetti, agenti, superseduti (cache 60s) | | `GET /v1/status` | Health + statistiche | Auth: header `X-API-Key` (chiave condivisa, accesso completo). Rate limit 120 req/min per chiave. Audit log in JSON lines (docker logs). ## Versione del codice `GET /v1/version` (pubblico) espone la versione del codice in esecuzione, inclusa l'hash del commit Git da cui è stato costruito il container: ```json {"version": "2.7.0", "git_commit": "eccb2cb...", "guardrail_version": "similarity-v1", ...} ``` Anche `GET /v1/status` include `version`, `git_commit` e `guardrail_version`. L'hash è iniettato al build via `ARG GIT_COMMIT`/`ENV GIT_COMMIT` nel Dockerfile (default `unknown`). Per costruire con l'hash: ```bash docker compose build --build-arg GIT_COMMIT=$(git rev-parse HEAD) gateway # o nel compose: build: { context: ./gateway, args: { GIT_COMMIT: ${GIT_COMMIT:-unknown} } } ``` ## Guardrail di similarità (v1) Enforcement deterministico FUORI dall'LLM, prima di ogni scrittura su `POST /v1/memories`: 1. **Strato 1 — hash esatto**: SHA-256 del testo normalizzato (`text_hash` nel payload). Se esiste un record attivo con lo stesso hash → `409 BLOCK (EXACT_DUPLICATE)`. 2. **Strato 2 — similarità semantica top-3**: embedding BGE-M3 cosine sui record attivi (esclusi i superseded). - top-1 ≥ `GUARDRAIL_BLOCK_THRESHOLD` (default 0.85) → `409 BLOCK (KNOWN_SOLUTION)` - top-1 ≥ `GUARDRAIL_WARN_THRESHOLD` (default 0.70) → `WARN`: salva con flag `guardrail` nel payload - altrimenti → `ALLOW` Il **supersede esplicito** (`supersedes_id`) è una correzione intenzionale: bypassa il guardrail. Configurazione (env): `GUARDRAIL_ENABLED` (default true), `GUARDRAIL_BLOCK_THRESHOLD`, `GUARDRAIL_WARN_THRESHOLD`. Soglie di partenza da calibrare sul corpus reale. Risposta BLOCK (409): ```json {"detail": {"error": "duplicate_memory", "reason": "KNOWN_SOLUTION", "matches": [{"memory_id": "...", "score": 0.92}], "message": "..."}} ``` ## Sicurezza - Qdrant bindato su 127.0.0.1; gateway solo su interfaccia VPN - Chiavi in `.env` (0600), mai committate - JWT RBAC su Qdrant (admin + read-only) - Backup: snapshot Qdrant + rotazione 7 giorni (cron: `0 3 * * * /opt/memory/backup.sh`) ## Dettagli operativi Procedure complete (teardown, restore, nginx, troubleshooting): vedi `docs/playbook.md` nel repo pi-qmem.