From 72ebb949c80292b739f171fc08a2726bf7757f3f Mon Sep 17 00:00:00 2001 From: enne2 Date: Thu, 13 Aug 2026 13:24:22 +0200 Subject: [PATCH] feat: project_id obbligatorio (gateway v2.4.0 + estensione v1.4.0) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - gateway: MemoryIn.project_id required (min_length=1) → POST senza project_id = 422 - estensione: qmem_store project_id Type.String (non più Optional), descrizione con obbligo e rinvio a qmem_meta; qmem_correct resta opzionale (eredita dal superseduto) - README/playbook sez. 11.5 aggiornati --- README.md | 2 +- docs/playbook.md | 8 ++++++++ extensions/index.ts | 9 +++++++-- gateway/main.py | 4 ++-- package.json | 2 +- 5 files changed, 19 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 84b29c9..6952353 100644 --- a/README.md +++ b/README.md @@ -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, scope, project_id, 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) | | `qmem_search` | Ricerca semantica su tutta la conoscenza condivisa (query, kind, project_id, scope, top_k, include_superseded, min_score) | | `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) | diff --git a/docs/playbook.md b/docs/playbook.md index 91beba5..2a4bcb1 100644 --- a/docs/playbook.md +++ b/docs/playbook.md @@ -348,6 +348,14 @@ pi install git:git.enne2.net/enne2/ - Tool estensione: `qmem_meta` (nessun parametro) → guida la ricerca settorializzata (`scope`/`kind`/`project_id`) - Note: scope/kind sono enum chiusi (la discovery serve per conteggi e set aperti project_id/agent_id); espone i nomi reali di progetti/agenti (accesso condiviso già scelto) +### 11.5 project_id obbligatorio + +- Dal gateway v2.4.0 / estensione v1.4.0: `project_id` è **obbligatorio** in `POST /v1/memories` (Pydantic `min_length=1`) e nello schema del tool `qmem_store` (Type.String, non più Optional) +- POST senza `project_id` → **422** (validazione Pydantic); il tool rifiuta la chiamata senza project_id +- `qmem_correct` mantiene `project_id` opzionale: lo **eredita** dal record superseduto (GET/search) — il gateway valida comunque il risultato +- L'agente consulta `qmem_meta` per riusare gli id esistenti; domini nuovi → kebab-case +- Regola nel prompt: AGENTS.md sez. "Obbligo di project_id" + ### 11.3 Verifica rapida ```bash diff --git a/extensions/index.ts b/extensions/index.ts index bd77baf..4daea10 100644 --- a/extensions/index.ts +++ b/extensions/index.ts @@ -124,7 +124,8 @@ export default function qmemExtension(pi: ExtensionAPI) { "Nessun LLM in scrittura: salva fatti, decisioni, preferenze o episodi deliberati e strutturati. " + "Usa kind=decision per scelte con motivazione, kind=fact per fatti stabili, kind=preference per " + "preferenze utente, kind=episode per esiti di azioni completate. Non salvare transcript grezzi: " + - "salva un record compatto e ad alto segnale per evento significativo.", + "salva un record compatto e ad alto segnale per evento significativo. " + + "project_id è OBBLIGATORIO: consulta qmem_meta per i progetti esistenti e riusa l'id appropriato.", parameters: Type.Object({ text: Type.String({ description: "Il contenuto del record di memoria (compatto, ad alto segnale)." }), kind: Type.Optional( @@ -134,7 +135,11 @@ export default function qmemExtension(pi: ExtensionAPI) { ), ), agent_id: Type.Optional(Type.String({ description: "Nome dell'agente che scrive (solo provenienza, nessun isolamento)." })), - project_id: Type.Optional(Type.String({ description: "Progetto di appartenenza (organizzativo)." })), + project_id: Type.String({ + description: + "OBBLIGATORIO: progetto/dominio di appartenenza (kebab-case, es. pi-qmem, domotics, frigate-tts). " + + "Consulta qmem_meta per i progetti esistenti e riusa l'id appropriato; per domini nuovi crea un id coerente.", + }), scope: Type.Optional( Type.Union([Type.Literal("agent"), Type.Literal("project"), Type.Literal("org")], { description: "Scope organizzativo (default: agent).", diff --git a/gateway/main.py b/gateway/main.py index 11073c2..e6add88 100644 --- a/gateway/main.py +++ b/gateway/main.py @@ -50,7 +50,7 @@ MAX_TEXT_LEN = int(os.environ.get("MAX_TEXT_LEN", "8000")) logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s") log = logging.getLogger("memory-gateway") -app = FastAPI(title="Memory Gateway", version="2.3.0") +app = FastAPI(title="Memory Gateway", version="2.4.0") qdrant = QdrantClient(url=QDRANT_URL, api_key=QDRANT_API_KEY) # Rate limit in-memory: {key: [timestamps]} @@ -72,7 +72,7 @@ class MemoryIn(BaseModel): text: str = Field(min_length=1, max_length=MAX_TEXT_LEN) kind: Literal["decision", "fact", "episode", "preference"] = "fact" agent_id: Optional[str] = Field(default=None, max_length=64, description="Solo provenienza, nessun isolamento") - project_id: Optional[str] = Field(default=None, max_length=64) + project_id: str = Field(min_length=1, max_length=64, description="OBBLIGATORIO: progetto/dominio di appartenenza (kebab-case)") scope: Literal["agent", "project", "org"] = "agent" source: Optional[str] = Field(default=None, max_length=256) expires_at: Optional[str] = None # ISO 8601 diff --git a/package.json b/package.json index 88f3420..e7cabba 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pi-qmem", - "version": "1.3.0", + "version": "1.4.0", "description": "Memoria centralizzata e condivisa per agenti AI: salva e cerca record semantici (Qdrant + BGE-M3) via Memory Gateway.", "keywords": ["pi-package", "memory", "agent", "qdrant", "rag"], "license": "MIT",