Files
pi-qmem/README.md
T
Matteo Benedetto 322b4cf446 feat(outbox): store offline con coda locale e sincronizzazione al ritorno della rete
Prima qmem_store falliva se il gateway non era raggiungibile: la conoscenza
andava persa. Ora il record entra in una coda locale persistente e viene
inviato automaticamente quando la connessione torna.

Core (extensions/local-db.ts):
- tabella `pending` (local_id, payload JSON, attempts, last_error, status,
  remote_id) + colonna `records.pending` (migrazione automatica dei DB esistenti)
- queueStore(): accoda e crea subito il placeholder locale ricercabile (⏳)
- flushQueue(): POST /v1/memories con Idempotency-Key = local_id (retry senza
  duplicati), FIFO, pacing sotto il rate limit, timeout 12s per richiesta
- esiti: synced (il record locale adotta l'ID remoto, niente duplicati) ·
  duplicate (409: registra l'ID del match e NON sovrascrive il testo locale
  autorevole) · failed (4xx di validazione, non ritentato) · 0/429/5xx: resta in
  coda e il flush si ferma
- supersede offline: supersedes_id che punta a un local_id viene rimappato al
  remote_id al flush (se il genitore non è sincronizzato → failed esplicito)
- submitOrQueue(): online → gateway + indicizzazione locale; offline → coda
- maybeBackgroundFlush() (single-flight) e flushQueueIfPending() per session_start
- stato/report: queued/synced/duplicate/failed, più vecchio, ultimo errore,
  last_flush, record pendenti in indice

Estensione:
- qmem_store: gateway giù → accoda e risponde con id locale, dimensione coda e
  spiegazione (details.queued/local_id/queue_size)
- fallback offline di session_start: flush in background (non blocca l'avvio)
- /qmem:local queue|flush; status con la coda; marker "⏳ in coda" nei risultati
  locali di qmem_search/qmem_get
- regole e skill: un record in coda NON è ancora nella memoria condivisa

CLI: store [--queue-only], queue, flush (+ status con la coda).
Test: scripts/test-local.mjs ora copre anche outbox → 24 controlli (flush con
2 sync + 1 duplicato 409 + 1 fallito 422, Idempotency-Key, rimappatura del
supersede, ricerca del record con l'ID remoto dopo il sync).

Verifiche: 24/24 test superati; demo reale su DB temporaneo: store accodato,
queue con local_id, flush con gateway giù → "fermato: HTTP 0" e voce che resta
in coda con l'errore registrato.
2026-09-13 17:29:09 +02:00

139 lines
6.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# pi-qmem
Memoria centralizzata e condivisa per agenti AI — estensione per pi.
Salva e cerca record semantici (fatti, decisioni, preferenze, episodi) in un
Memory Gateway (FastAPI + Qdrant + BGE-M3) ospitato su un server remoto
raggiungibile via VPN. Nessun LLM in scrittura: l'agente salva record
deliberati e strutturati; il retrieval è vettoriale + filtri metadata.
## Installazione
```bash
pi install git:git.enne2.net/enne2/pi-qmem
```
Oppure copia `extensions/index.ts` in `~/.pi/agent/extensions/pi-qmem/`.
## Tool
| Tool | Descrizione |
|---|---|
| `qmem_store` | Salva un record di memoria (text, kind, agent_id, **project_id obbligatorio**, scope, source, expires_at, supersedes_id, supersede_reason). **Se il gateway è giù il record viene accodato localmente (outbox)** e inviato automaticamente al ritorno della connessione |
| `qmem_search` | Ricerca semantica su tutta la conoscenza condivisa (query, kind, project_id, scope, top_k, include_superseded, min_score). **Se il gateway non risponde degrada all'indice locale SQLite/FTS5** (testuale, etichettato `fallback: local_sqlite`) |
| `qmem_correct` | Corregge una memoria falsa: crea un nuovo record che **supersede** il vecchio (che resta in archivio marcato superseded) |
| `qmem_meta` | Discovery: panoramica di scope×kind, progetti, agenti e superseduti (per scegliere i filtri di ricerca) |
## Comando
`/qmem:config` — menu interattivo (TUI):
- 🌐 Imposta URL gateway
- 🔑 Cambia API key
- 🔌 Test connessione (verifica URL + validità chiave)
- 📋 Mostra configurazione
- ↩️ Annulla
Modalità CLI rapida: `/qmem:config url <URL> | apikey <KEY> | test`
Config salvata in `~/.config/pi-qmem/config.json` (0600):
```json
{
"url": "https://qmem.enne2.net",
"apiKey": "...",
"localDbPath": "~/.local/share/pi-qmem/qmem.sqlite",
"localFallback": true,
"offlineQueue": true
}
```
`localFallback: false` disabilita il fallback in lettura; `offlineQueue: false`
disabilita l'accodamento offline in scrittura (lo store torna a fallire come
prima).
## Indice locale (fallback offline)
Il gateway remoto non è sempre raggiungibile (VPN giù, nodi offline). L'estensione
mantiene quindi un **indice locale SQLite + FTS5** che permette di cercare
testualmente la conoscenza **senza gateway, senza modelli, senza dipendenze**:
- **DB**: `~/.local/share/pi-qmem/qmem.sqlite` (override: `localDbPath` in
`~/.config/pi-qmem/config.json` oppure env `QMEM_SQLITE`)
- **Ricostruzione**: dalle sessioni pi (`~/.pi/agent/sessions/<cwd>/*.jsonl`),
incrociando `toolCall` ↔ `toolResult`: `qmem_store` e `qmem_correct` forniscono
l'**ID del gateway** e il testo integrale, `qmem_get` il payload completo,
`qmem_search` i record visti (anche creati da altri agenti)
- **Ricerca**: FTS5 `unicode61 remove_diacritics 2` (accenti e prefissi),
ranking BM25, filtro dei superseduti/privati di default; se la query in AND non
trova nulla si ripiega su OR (match parziale, dichiarato)
- **Fallback automatico**: `qmem_search`/`qmem_get` usano l'indice locale quando
il gateway risponde 0/429/5xx, etichettando i risultati come **non neurali**
- **Outbox (store offline)**: `qmem_store` con gateway irraggiungibile accoda il
record in SQLite: è subito ricercabile (marcato ⏳ `pending`) e viene inviato a
`POST /v1/memories` al ritorno della connessione — `Idempotency-Key` = id locale
(retry senza duplicati), poi il record locale **adotta l'ID del gateway**.
Esiti: `synced` · `duplicate` (409, con l'ID del match) · `failed` (4xx di
validazione). Le correzioni che puntano a un record ancora locale vengono
rimappate all'ID remoto al flush. Trigger: `session_start` (background, non
blocca l'avvio), dopo uno store riuscito, o `/qmem:local flush`
- **Arricchimento**: quando il gateway torna online, `enrich` completa
testo/`project_id`/`private`/stato supersede via `GET /v1/memories/{id}`, e
`pull` sincronizza dall'`export` (endpoint previsto lato gateway)
Comandi (TUI) e CLI standalone:
```bash
/qmem:local status # record, copertura, lag, duplicati
/qmem:local import # ricostruisce/aggiorna dalle sessioni pi
/qmem:local find "circuit breaker" # ricerca testuale locale
/qmem:local queue # stato della coda (in attesa/sync/dup/fallite)
/qmem:local flush # invia subito la coda al gateway
/qmem:local enrich [--all] # arricchisce dal gateway
/qmem:local pull # pull incrementale dall'export
# equivalente standalone (stesso core, nessuna dipendenza)
node scripts/qmem-sqlite.mjs status|import|find "query"|enrich|pull
node scripts/qmem-sqlite.mjs store --project P --text "..." [--queue-only]
node scripts/qmem-sqlite.mjs queue|flush
node scripts/test-local.mjs # suite di test (24 controlli, HOME temporanea)
```
Limiti dichiarati: è uno **storico osservato** (più vecchio del gateway), la
ricerca è **lessicale** (nessuno score 0.45/0.60: non applicare le soglie
semantiche) e un record in coda (⏳) **non è ancora nella memoria condivisa**:
sarà visibile agli altri agenti solo dopo il flush. La coda è locale alla
macchina (nessuna sincronizzazione tra macchine diverse).
## Regole comportamentali (autocontenute)
Le regole vincolanti (obbligo `project_id`, punteggi, correzione/supersede, discovery, **identificazione macchina nei record locali**) sono **distribuite con l'estensione**, senza toccare AGENTS.md:
- **`promptGuidelines`** sui 4 tool (bullets nel `Guidelines` del system prompt, solo quando i tool sono attivi)
- **`before_agent_start`** → blocco "Regole pi-qmem" iniettato nel system prompt a ogni turno (solo se i tool qmem sono attivi); include la sezione **Identificazione macchina** con `MACCHINA CORRENTE` rilevata dinamicamente dall'estensione (`os.hostname()` + `/etc/os-release`)
- **Skill `qmem`** (`skills/qmem/SKILL.md`, standard agentskills.io) → procedura completa on-demand, caricabile con `/skill:qmem`
- Manifest: `pi.skills` nel package.json
## Gateway (componente server)
La cartella `gateway/` contiene il Memory Gateway FastAPI da deployare sul
server (Docker Compose con Qdrant 1.19 + Ollama BGE-M3). Vedi
`gateway/README.md` per il deploy.
## Architettura
```
pi (estensione) ──HTTPS/VPN──▶ Memory Gateway (FastAPI) ──▶ Qdrant 1.19
│
└──▶ Ollama BGE-M3 (embedding locale)
```
- Accesso condiviso: una chiave API con accesso completo in lettura/scrittura
- `agent_id` è solo metadata di provenienza, non isolamento
- Rate limit, audit log, cleanup automatico dei record scaduti
- **Correzioni**: i record sono immutabili; correggere una memoria falsa = nuovo record che supersede il vecchio (`qmem_correct` o `qmem_store` con `supersedes_id`). Il vecchio resta in archivio con `superseded_by`, escluso dalla ricerca di default (`include_superseded=true` per la lineage)
## Licenza
MIT