Files
Matteo Benedetto 12e63d09fa fix(fallback): connectTimeoutMs 2500→15000 e breakerTripAfter 2→3
Il default di 2,5 s per connect+headers è troppo stretto su percorsi VPN/AP:
misurati GET /v1/status 1,23 s e POST /v1/memories:search 1,98 s, talvolta
oltre 2,5 s. In quelle condizioni il breaker si apriva pur con gateway
raggiungibile e tutte le chiamate successive andavano in fast-fail per 2–10
minuti, spingendo di fatto ogni operazione sul solo fallback locale e
lasciando l'outbox non sincronizzata.

- extensions/shared.ts: connectTimeoutMs 2500 → 15_000 (default, fallback nel
  path di richiesta e commento), breakerTripAfter 2 → 3
- README.md: default aggiornati + motivazione nella tabella dei timeout
- skills/qmem/SKILL.md: default aggiornato e sintassi CLI del reset breaker
  (`qmem-sqlite.mjs breaker --reset`)

Verifica sul campo: con connectTimeoutMs=60000 l'outbox (18 record) è stata
sincronizzata completamente e il breaker è rimasto chiuso.
2026-09-16 10:26:52 +02:00

67 lines
3.6 KiB
Markdown
Raw Permalink 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.
---
name: qmem
description: "Shared agent memory (Qdrant + BGE-M3). Search, store, correct, census, or explore hierarchical memory records. Use before any task needing prior knowledge, project discovery, or memory maintenance."
---
# qmem — Shared Memory
Gateway qmem.enne2.net → Qdrant + BGE-M3. No LLM writes: records are deliberate and structured. One API key; `agent_id` is provenance only.
## Tools
| Tool | Use |
|---|---|
| `qmem_search` | Semantic search with filters (kind, project_id, scope, top_k, min_score, parent_id, level, topic, hybrid) |
| `qmem_store` | Save a record (kind, project_id REQUIRED, scope, source, expires_at, supersedes_id, parent_id, level, topic, links) |
| `qmem_correct` | Fix a false record: a new version supersedes the old |
| `qmem_meta` | Discovery: projects, scopes×kinds, agents, superseded (choose filters) |
| `qmem_get` | Fetch one record exactly by UUID (O(1), includes superseded) |
| `qmem_tree` | Show full hierarchy (L1 root + L2 children) of a topic |
## Hierarchy (L1 root + L2 subtopics)
1. Save detail leaves as `L2_SUBTOPIC`, topic `MACRO/SUB`, with `parent_id` when known.
2. Save a macro index as `L1_ROOT`, topic `MACRO/ROOT`, `links=[{target_id: <l2-uuid>, predicate: "parent_of"}]`.
3. Filter search by `parent_id`, `level`, or `topic`.
4. From an L2 node, use its `parent_id` to `qmem_get` the root.
## Search scores
- **>=0.60** solid — use it
- **0.450.60** weak — verify the evidence first
- **<0.45** noise — filtered by default (`min_score` 0.45)
Empty results: reformulate, narrow kind/scope/project_id, or lower `min_score`. Score is not truth: check the cited source.
## Indice locale (fallback quando il gateway è giù)
Se il gateway non risponde, `qmem_search` usa l'**indice locale SQLite/FTS5**
(`~/.local/share/pi-qmem/qmem.sqlite`) e lo dichiara: `fallback: local_sqlite`.
In quel caso:
- la ricerca è **testuale (BM25)**, non neurale: nessuno score semantico e nessuna
soglia 0.45/0.60 da applicare;
- i risultati sono **osservazioni più vecchie** del gateway (storico ricostruito
dalle sessioni pi + ultimo enrich): verifica prima dell'uso;
- comandi: `/qmem:local status | import | find <query> | queue | flush | breaker [reset] | enrich | pull`
(`import` dalle sessioni, `enrich`/`pull` dal gateway quando torna online);
- **circuit breaker**: quando il gateway non risponde entro `connectTimeoutMs` (default 15 s) le
chiamate successive falliscono in ~0 ms **senza toccare la rete** per ~2 minuti (stato in
`~/.local/share/pi-qmem/breaker.json`): il fallback locale è immediato. Un 5xx o un body lento
sono invece "ambigui" (retry con `Retry-After`, apertura dopo 3 fallimenti). Reset:
`/qmem:local breaker reset` (CLI: `node scripts/qmem-sqlite.mjs breaker --reset`).
`qmem_store` accoda in locale: con il gateway giù il record entra nell'**outbox**
locale (SQLite), è subito ricercabile (marcato ⏳) e viene inviato al gateway al
ritorno della connessione (flush automatico su `session_start`, oppure
`/qmem:local flush`). Finché non è sincronizzato **non** è nella memoria
condivisa: i risultati locali marcati ⏳ non sono visibili agli altri agenti.
Stato e gestione della coda:
- `/qmem:local queue` — voci in attesa, tentativi, ultimo errore
- `/qmem:local flush` — invio immediato (idempotente: `Idempotency-Key` = id locale)
- esiti: `synced` (sul gateway, il record locale adotta l'ID remoto), `duplicate`
(409: era già presente, viene registrato l'ID del match), `failed` (4xx di
validazione: non ritentato in automatico)
- supersede offline: una correzione che punta a un record ancora locale viene
rimappata all'ID remoto al momento del flush