feat: supersede di memorie false (qmem_correct, include_superseded, playbook sez. 11)
- gateway: supersedes_id validato (404/409), backlink superseded_by+superseded_at+supersede_reason sul vecchio record, indici keyword, search esclude i superseduti di default (include_superseded per lineage), risposta con lineage - estensione: tool qmem_correct (memory_id o query), qmem_store con supersedes_id/supersede_reason, qmem_search con include_superseded - README/playbook aggiornati, versione 1.1.0
This commit is contained in:
+113
-2
@@ -142,6 +142,8 @@ export default function qmemExtension(pi: ExtensionAPI) {
|
||||
),
|
||||
source: Type.Optional(Type.String({ description: "Origine del record (es. conversazione, file, ticket)." })),
|
||||
expires_at: Type.Optional(Type.String({ description: "Scadenza ISO 8601 (es. 2026-09-01T00:00:00Z) per memoria volatile." })),
|
||||
supersedes_id: Type.Optional(Type.String({ description: "UUID del record da supersedere (correzione): il nuovo record diventa la versione attiva, il vecchio resta in archivio marcato superseded." })),
|
||||
supersede_reason: Type.Optional(Type.String({ description: "Motivo della correzione (visibile in audit e sul vecchio record)." })),
|
||||
}),
|
||||
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
||||
const cfg = loadConfig();
|
||||
@@ -165,6 +167,8 @@ export default function qmemExtension(pi: ExtensionAPI) {
|
||||
scope: p.scope ?? "agent",
|
||||
source: p.source,
|
||||
expires_at: p.expires_at,
|
||||
supersedes_id: p.supersedes_id,
|
||||
supersede_reason: p.supersede_reason,
|
||||
},
|
||||
signal,
|
||||
);
|
||||
@@ -175,7 +179,7 @@ export default function qmemExtension(pi: ExtensionAPI) {
|
||||
};
|
||||
}
|
||||
return {
|
||||
content: [{ type: "text", text: `Memoria salvata: ${data.memory_id} (${p.kind ?? "fact"}, scope ${p.scope ?? "agent"})` }],
|
||||
content: [{ type: "text", text: `Memoria salvata: ${data.memory_id} (${p.kind ?? "fact"}, scope ${p.scope ?? "agent"})${p.supersedes_id ? ` — supersede ${p.supersedes_id}` : ""}` }],
|
||||
details: { memory_id: data.memory_id, created_at: data.created_at },
|
||||
};
|
||||
},
|
||||
@@ -207,6 +211,7 @@ export default function qmemExtension(pi: ExtensionAPI) {
|
||||
description: "Filtra per scope di visibilità.",
|
||||
}),
|
||||
),
|
||||
include_superseded: Type.Optional(Type.Boolean({ description: "Includi anche i record già superseduti/corretti (default: false)." })),
|
||||
top_k: Type.Optional(Type.Integer({ description: "Numero massimo di risultati (default: 5, max 20)." })),
|
||||
}),
|
||||
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
||||
@@ -228,6 +233,7 @@ export default function qmemExtension(pi: ExtensionAPI) {
|
||||
kind: p.kind,
|
||||
project_id: p.project_id,
|
||||
scope: p.scope,
|
||||
include_superseded: p.include_superseded ?? false,
|
||||
top_k: p.top_k ?? 5,
|
||||
},
|
||||
signal,
|
||||
@@ -247,7 +253,7 @@ export default function qmemExtension(pi: ExtensionAPI) {
|
||||
}
|
||||
const lines = results.map(
|
||||
(r: any, i: number) =>
|
||||
`${i + 1}. [${r.kind}/${r.scope} score=${r.score}] ${r.text}\n (id: ${r.memory_id}, agente: ${r.agent_id ?? "?"}, creato: ${r.created_at ?? "?"}${r.source ? `, fonte: ${r.source}` : ""})`,
|
||||
`${i + 1}. [${r.kind}/${r.scope} score=${r.score}] ${r.text}\n (id: ${r.memory_id}, agente: ${r.agent_id ?? "?"}, creato: ${r.created_at ?? "?"}${r.source ? `, fonte: ${r.source}` : ""}${r.supersedes_id ? `, supersede ${r.supersedes_id}` : ""}${r.superseded_by ? `, ⚠️ superseduto da ${r.superseded_by}` : ""})`,
|
||||
);
|
||||
return {
|
||||
content: [{ type: "text", text: lines.join("\n") }],
|
||||
@@ -256,6 +262,111 @@ export default function qmemExtension(pi: ExtensionAPI) {
|
||||
},
|
||||
});
|
||||
|
||||
// =========================================================================
|
||||
// TOOL: qmem_correct — supersede di una memoria falsa o superata
|
||||
// =========================================================================
|
||||
pi.registerTool({
|
||||
name: "qmem_correct",
|
||||
label: "Qmem memory correct",
|
||||
description:
|
||||
"Corregge una memoria falsa o superata: crea un NUOVO record che supersede il vecchio (che resta " +
|
||||
"in archivio marcato superseded, mai eliminato). Passa memory_id se lo conosci (dalla risposta di " +
|
||||
"qmem_search), oppure query per individuare automaticamente il record attivo più rilevante. Il testo " +
|
||||
"corretto sostituisce quello vecchio nella ricerca semantica. Usalo quando hai evidenza verificata che " +
|
||||
"una memoria è falsa: contraddizione con fonte autorevole, conferma dell'utente o esito di un'azione.",
|
||||
parameters: Type.Object({
|
||||
memory_id: Type.Optional(Type.String({ description: "UUID del record attivo da supersedere (dalla risposta di qmem_search)." })),
|
||||
query: Type.Optional(Type.String({ description: "Query per trovare il record da correggere (usata solo se memory_id non è fornito)." })),
|
||||
corrected_text: Type.String({ description: "Il testo corretto e verificato che sostituisce quello falso." }),
|
||||
reason: Type.Optional(Type.String({ description: "Motivo della correzione (visibile in audit e sul vecchio record)." })),
|
||||
kind: Type.Optional(
|
||||
Type.Union(
|
||||
[Type.Literal("decision"), Type.Literal("fact"), Type.Literal("episode"), Type.Literal("preference")],
|
||||
{ description: "Tipo del nuovo record (default: eredita dal record superseduto)." },
|
||||
),
|
||||
),
|
||||
project_id: Type.Optional(Type.String({ description: "Progetto del nuovo record (default: eredita dal record superseduto)." })),
|
||||
agent_id: Type.Optional(Type.String({ description: "Nome dell'agente che corregge (solo provenienza)." })),
|
||||
}),
|
||||
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.query) {
|
||||
return {
|
||||
content: [{ type: "text", text: "Serve memory_id (da qmem_search) oppure query per trovare il record da correggere." }],
|
||||
details: { error: "missing_target" },
|
||||
};
|
||||
}
|
||||
|
||||
let memoryId = p.memory_id;
|
||||
let orig: any = {};
|
||||
if (!memoryId) {
|
||||
onUpdate?.({ content: [{ type: "text", text: `qmem: ricerca del record da correggere ("${p.query}")...` }] });
|
||||
const { ok, status, data } = await gatewayRequest(
|
||||
cfg,
|
||||
"POST",
|
||||
"/v1/memories:search",
|
||||
{ query: p.query, top_k: 1, include_superseded: false },
|
||||
signal,
|
||||
);
|
||||
if (!ok) {
|
||||
return {
|
||||
content: [{ type: "text", text: `Errore ${status}: ${JSON.stringify(data)}` }],
|
||||
details: { error: "gateway_error", status },
|
||||
};
|
||||
}
|
||||
const results = data.results ?? [];
|
||||
if (results.length === 0) {
|
||||
return {
|
||||
content: [{ type: "text", text: "Nessun record attivo trovato per la query. Nessuna correzione applicata." }],
|
||||
details: { error: "not_found" },
|
||||
};
|
||||
}
|
||||
memoryId = results[0].memory_id;
|
||||
orig = results[0];
|
||||
}
|
||||
|
||||
onUpdate?.({ content: [{ type: "text", text: `qmem: supersede di ${memoryId}...` }] });
|
||||
const { ok, status, data } = await gatewayRequest(
|
||||
cfg,
|
||||
"POST",
|
||||
"/v1/memories",
|
||||
{
|
||||
text: p.corrected_text,
|
||||
kind: p.kind ?? orig.kind ?? "fact",
|
||||
agent_id: p.agent_id,
|
||||
project_id: p.project_id ?? orig.project_id,
|
||||
scope: orig.scope ?? "agent",
|
||||
source: "qmem_correct",
|
||||
supersedes_id: memoryId,
|
||||
supersede_reason: p.reason,
|
||||
},
|
||||
signal,
|
||||
);
|
||||
if (!ok) {
|
||||
return {
|
||||
content: [{ type: "text", text: `Errore ${status}: ${JSON.stringify(data)}` }],
|
||||
details: { error: "gateway_error", status },
|
||||
};
|
||||
}
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Correzione applicata: nuovo record ${data.memory_id} supersede ${memoryId}${p.reason ? ` (motivo: ${p.reason})` : ""}. Il vecchio record resta in archivio marcato superseded.`,
|
||||
},
|
||||
],
|
||||
details: { new_id: data.memory_id, superseded_id: memoryId },
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
// =========================================================================
|
||||
// COMANDO: /qmem:config — menu interattivo + modalità CLI rapida
|
||||
// =========================================================================
|
||||
|
||||
Reference in New Issue
Block a user