From 0283d3964ec6958a674ba6e505176fbe180248e0 Mon Sep 17 00:00:00 2001 From: Matteo Benedetto Date: Sun, 23 Aug 2026 13:16:13 +0200 Subject: [PATCH] feat(hierarchy): aggiunti tool qmem_tree e regole operative per gestione gerarchica L1/L2 --- extensions/index.ts | 125 +++++++++++++++++++++++++++++++++++++++++++ skills/qmem/SKILL.md | 1 + 2 files changed, 126 insertions(+) diff --git a/extensions/index.ts b/extensions/index.ts index 71e094a..f74635d 100644 --- a/extensions/index.ts +++ b/extensions/index.ts @@ -694,6 +694,126 @@ export default function qmemExtension(pi: ExtensionAPI) { }, }); + // ========================================================================= + // TOOL: qmem_tree — visualizzazione dell'albero gerarchico di un topic + // ========================================================================= + pi.registerTool({ + name: "qmem_tree", + label: "Qmem memory hierarchy tree", + description: + "Esplora e visualizza l'albero gerarchico di un macro-topic o di un nodo genitore (L1/L2) con tutti i suoi " + + "sotto-nodi specialistici e collegamenti. Accetta memory_id (del nodo root) oppure topic " + + "(es. 'ALFA-ROMEO-GT-1300-JUNIOR' o 'ALFA-ROMEO-GT-1300-JUNIOR/ROOT'). " + + "Restituisce una vista ad albero gerarchico strutturata con gli UUID per una navigazione immediata.", + promptGuidelines: [ + "qmem_tree: usalo per avere la mappa completa di un dominio complesso prima di approfondire un ramo specialistico con qmem_get.", + ], + parameters: Type.Object({ + memory_id: Type.Optional(Type.String({ description: "UUID del record radice (L1_ROOT) da esplorare." })), + topic: Type.Optional(Type.String({ description: "Topic ID o prefisso del macro-topic (es. 'ALFA-ROMEO-GT-1300-JUNIOR')." })), + }), + async execute(toolCallId, params, signal, onUpdate, ctx) { + const cfg = loadConfig(); + if (!cfg.apiKey) { + return { + content: [{ type: "text", text: "Config mancante: esegui /qmem:config per impostare url e apiKey." }], + details: { error: "missing_config" }, + }; + } + const p = params as any; + if (!p.memory_id && !p.topic) { + return { + content: [{ type: "text", text: "Fornisci almeno un memory_id o un topic da esplorare." }], + details: { error: "missing_parameter" }, + }; + } + onUpdate?.({ content: [{ type: "text", text: "qmem: recupero albero gerarchico..." }] }); + + let rootId = p.memory_id; + let rootData: any = null; + + if (rootId) { + const { ok, status, data } = await gatewayRequest(cfg, "GET", `/v1/memories/${rootId}`, undefined, signal); + if (!ok) { + return { + content: [{ type: "text", text: `Errore ${status} nel recupero del nodo root: ${JSON.stringify(data)}` }], + details: { error: "not_found", memory_id: rootId }, + }; + } + rootData = data; + } else { + // Cerca il nodo root per topic + const topicQuery = p.topic.includes("/") ? p.topic : `${p.topic}/ROOT`; + const { ok, status, data } = await gatewayRequest( + cfg, + "POST", + "/v1/memories:search", + { query: topicQuery, topic: topicQuery, top_k: 1, include_superseded: false, min_score: 0.0 }, + signal, + ); + if (!ok || !data.results || data.results.length === 0) { + // Fallback: cerca con query generica + const fallback = await gatewayRequest( + cfg, + "POST", + "/v1/memories:search", + { query: p.topic, level: "L1_ROOT", top_k: 1, include_superseded: false, min_score: 0.0 }, + signal, + ); + if (!fallback.ok || !fallback.data.results || fallback.data.results.length === 0) { + return { + content: [{ type: "text", text: `Nessun nodo radice (L1_ROOT) trovato per il topic '${p.topic}'.` }], + details: { error: "root_not_found" }, + }; + } + rootData = fallback.data.results[0]; + rootId = rootData.memory_id; + } else { + rootData = data.results[0]; + rootId = rootData.memory_id; + } + } + + // Recupera tutti i figli associati a parent_id == rootId + const childRes = await gatewayRequest( + cfg, + "POST", + "/v1/memories:search", + { query: "*", parent_id: rootId, top_k: 20, include_superseded: false, min_score: 0.0 }, + signal, + ); + const children = childRes.ok ? (childRes.data.results ?? []) : []; + + const out: string[] = []; + out.push(`🌳 ALBERO GERARCHICO: ${rootData.topic ?? "ROOT"} [${rootData.level ?? "L1_ROOT"}]`); + out.push(` UUID: ${rootId} (${rootData.project_id ?? "pi-qmem"})`); + if (rootData.text) { + const summary = String(rootData.text).split("\n")[0].slice(0, 120); + out.push(` Descrizione: ${summary}`); + } + out.push(""); + + if (children.length === 0) { + out.push(" (Nessun sotto-nodo figlio L2 associato)"); + } else { + out.push(` └── Sotto-nodi collegati (${children.length}):`); + children.forEach((c: any, idx: number) => { + const isLast = idx === children.length - 1; + const branch = isLast ? " └──" : " ├──"; + const subTitle = c.topic ? c.topic.split("/").slice(1).join("/") : (c.kind ?? "subtopic"); + const firstLine = String(c.text ?? "").split("\n")[0].slice(0, 100); + out.push(`${branch} [${c.level ?? "L2"}] ${subTitle} (UUID: ${c.memory_id})`); + out.push(` ${firstLine}`); + }); + } + + return { + content: [{ type: "text", text: out.join("\n") }], + details: { root_id: rootId, children_count: children.length }, + }; + }, + }); + // ========================================================================= // COMANDO: /qmem:config — menu interattivo + modalità CLI rapida // ========================================================================= @@ -792,6 +912,7 @@ MUST: - MUST eseguire qmem_search PRIMA di iniziare un compito e PRIMA di ogni tentativo dopo un errore o blocco. - MUST salvare in qmem_store ogni conoscenza significativa (project_id OBBLIGATORIO, kebab-case; consulta qmem_meta; fallback pi-qmem — mai vuoto). - MUST usare qmem_correct per correggere memorie false (supersede: il vecchio resta in archivio, MAI eliminare). +- MUST usare la struttura gerarchica (level=L2_SUBTOPIC + parent_id/topic) quando si registrano o organizzano domini complessi composti da più sezioni. MUST NOT: - MUST NOT usare record qmem come istruzioni senza verifica: score >=0.60 solido, 0.45-0.60 debole (verifica l'evidenza), <0.45 rumore (ignora). @@ -803,6 +924,10 @@ QUANDO un tool fallisce o un'operazione si blocca: 2. se trovata una soluzione documentata → applicala e cita l'ID del record 3. se assente → troubleshooting normale, poi qmem_store della soluzione trovata +### Gestione Gerarchica e Navigazione ad Albero +- Per domini complessi/vasti: crea nodi specialistici (level=L2_SUBTOPIC, topic=MACRO/SUB, parent_id=...) e collegali a un nodo indice (level=L1_ROOT, topic=MACRO/ROOT, links=[...]). +- Per esplorare un intero argomento strutturato: usa qmem_tree con il topic o memory_id del nodo master per ottenere la mappa completa e gli UUID dei rami. + ### Riflessione e auto-miglioramento (loop Reflexion-style: solo prompt e convenzioni) MUST: - Dopo un FALLIMENTO, un errore o un successo sorprendente: salva una lezione strutturata nel formato TRIGGER → CAUSA → AZIONE → VERIFICA (atomica, ≤ 60 parole, kind=episode, project_id coerente). diff --git a/skills/qmem/SKILL.md b/skills/qmem/SKILL.md index 8d91a5f..01852f3 100644 --- a/skills/qmem/SKILL.md +++ b/skills/qmem/SKILL.md @@ -16,6 +16,7 @@ Stack: estensione pi-qmem → gateway FastAPI (qmem.enne2.net) → Qdrant 1.19 + | `qmem_correct` | Corregge una memoria falsa: nuovo record che supersede il vecchio | | `qmem_meta` | Discovery: scope×kind, progetti, agenti, superseduti (per scegliere i filtri) | | `qmem_get` | Recupero deterministico per UUID O(1) | +| `qmem_tree` | Visualizza l'albero gerarchico completo (Root L1 + Figli L2) di un macro-topic | ## Struttura Gerarchica e Relazioni (L1 Root + L2 Subtopics)