feat: project_id obbligatorio (gateway v2.4.0 + estensione v1.4.0)

- 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
This commit is contained in:
enne2
2026-08-13 13:24:22 +02:00
parent 334f9c6b44
commit 72ebb949c8
5 changed files with 19 additions and 6 deletions
+1 -1
View File
@@ -19,7 +19,7 @@ Oppure copia `extensions/index.ts` in `~/.pi/agent/extensions/pi-qmem/`.
| Tool | Descrizione | | 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_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_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) | | `qmem_meta` | Discovery: panoramica di scope×kind, progetti, agenti e superseduti (per scegliere i filtri di ricerca) |
+8
View File
@@ -348,6 +348,14 @@ pi install git:git.enne2.net/enne2/<repo>
- Tool estensione: `qmem_meta` (nessun parametro) → guida la ricerca settorializzata (`scope`/`kind`/`project_id`) - 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) - 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 ### 11.3 Verifica rapida
```bash ```bash
+7 -2
View File
@@ -124,7 +124,8 @@ export default function qmemExtension(pi: ExtensionAPI) {
"Nessun LLM in scrittura: salva fatti, decisioni, preferenze o episodi deliberati e strutturati. " + "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 " + "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: " + "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({ parameters: Type.Object({
text: Type.String({ description: "Il contenuto del record di memoria (compatto, ad alto segnale)." }), text: Type.String({ description: "Il contenuto del record di memoria (compatto, ad alto segnale)." }),
kind: Type.Optional( 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)." })), 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( scope: Type.Optional(
Type.Union([Type.Literal("agent"), Type.Literal("project"), Type.Literal("org")], { Type.Union([Type.Literal("agent"), Type.Literal("project"), Type.Literal("org")], {
description: "Scope organizzativo (default: agent).", description: "Scope organizzativo (default: agent).",
+2 -2
View File
@@ -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") logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
log = logging.getLogger("memory-gateway") 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) qdrant = QdrantClient(url=QDRANT_URL, api_key=QDRANT_API_KEY)
# Rate limit in-memory: {key: [timestamps]} # Rate limit in-memory: {key: [timestamps]}
@@ -72,7 +72,7 @@ class MemoryIn(BaseModel):
text: str = Field(min_length=1, max_length=MAX_TEXT_LEN) text: str = Field(min_length=1, max_length=MAX_TEXT_LEN)
kind: Literal["decision", "fact", "episode", "preference"] = "fact" kind: Literal["decision", "fact", "episode", "preference"] = "fact"
agent_id: Optional[str] = Field(default=None, max_length=64, description="Solo provenienza, nessun isolamento") 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" scope: Literal["agent", "project", "org"] = "agent"
source: Optional[str] = Field(default=None, max_length=256) source: Optional[str] = Field(default=None, max_length=256)
expires_at: Optional[str] = None # ISO 8601 expires_at: Optional[str] = None # ISO 8601
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "pi-qmem", "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.", "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"], "keywords": ["pi-package", "memory", "agent", "qdrant", "rag"],
"license": "MIT", "license": "MIT",