feat(hierarchy): aggiunti tool qmem_tree e regole operative per gestione gerarchica L1/L2

This commit is contained in:
Matteo Benedetto
2026-08-23 13:16:13 +02:00
parent 369a2c2d9e
commit 0283d3964e
2 changed files with 126 additions and 0 deletions
+125
View File
@@ -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).