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.
This commit is contained in:
@@ -19,7 +19,7 @@ Oppure copia `extensions/index.ts` in `~/.pi/agent/extensions/pi-qmem/`.
|
||||
|
||||
| Tool | Descrizione |
|
||||
|---|---|
|
||||
| `qmem_store` | Salva un record di memoria (text, kind, agent_id, **project_id obbligatorio**, scope, source, expires_at, supersedes_id, supersede_reason) |
|
||||
| `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) |
|
||||
@@ -43,12 +43,14 @@ Config salvata in `~/.config/pi-qmem/config.json` (0600):
|
||||
"url": "https://qmem.enne2.net",
|
||||
"apiKey": "...",
|
||||
"localDbPath": "~/.local/share/pi-qmem/qmem.sqlite",
|
||||
"localFallback": true
|
||||
"localFallback": true,
|
||||
"offlineQueue": true
|
||||
}
|
||||
```
|
||||
|
||||
`localFallback: false` disabilita il fallback sull'indice locale (utile per
|
||||
misurare il comportamento "solo gateway").
|
||||
`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)
|
||||
|
||||
@@ -67,6 +69,14 @@ testualmente la conoscenza **senza gateway, senza modelli, senza dipendenze**:
|
||||
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)
|
||||
@@ -77,18 +87,23 @@ Comandi (TUI) e CLI standalone:
|
||||
/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/test-local.mjs # suite di test (14 controlli, HOME temporanea)
|
||||
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 `qmem_store` **non ha coda locale** — a gateway giù il record non
|
||||
viene salvato.
|
||||
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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user