1. Overview generale
arcocat e' una constellation MCP: 1 Supervisor centrale + N Knowledge Tool brand standalone. Il chatbot e' un client del Supervisor, non sa nulla dei brand.
FastAPI multi-turno LAN] end Chatbot -->|risolvi_query / compose_query| Supervisor subgraph Core[Core arcocat] Wiki[wiki_arcocat/
CategorySchema MD-Karpathy
+ TQL sinonimi] PIM[pim-lite/
pim.db prodotti tipati] Supervisor[supervisor-mcp/
Filter-then-Validate
+ context store + registry] end Supervisor --> Wiki Supervisor --> PIM subgraph Brand[Knowledge Tool brand standalone] direction LR BlumKT[blum-knowledge-mcp
4 tool: cerca, dettaglio,
valida, configure] BoschKT[bosch-knowledge-mcp
3 tool: cerca, dettaglio, valida
NO configure] Altri[...futuri:
Hettich, Salice, Falmec] end Supervisor -->|BRAND_CLIENTS| BlumKT Supervisor -.->|BRAND_CLIENTS| BoschKT Supervisor -.-> Altri Eval[eval/
promptfoo baseline] Eval -.->|Tier-2 GT| Supervisor classDef done fill:#a5d6a7,stroke:#2e7d32,color:#1b5e20 classDef pending fill:#fff59d,stroke:#f57f17,color:#e65100 classDef atomic fill:#ffcdd2,stroke:#c62828,color:#b71c1c class Chatbot,Wiki,PIM,Supervisor,BlumKT,Eval done class BoschKT atomic class Altri pending
Vertice singolo Supervisor
Un solo orchestratore conosce la pipeline. I brand sono fungibili dietro a un contratto comune.
Knowledge Tool brand standalone
Ogni brand e' un package MCP autosufficiente, con il suo wiki, le sue regole, il suo lookup. Niente import incrociati tra brand.
PIM-lite condiviso
Un solo pim.db con tabella unica e fonte_brand per riga. Filtra + categoria fa il routing.
Capability negotiation
Il 4° tool configure e' opzionale. try/except ImportError popola il registry solo per brand componibili.
2. Onboarding 7 fasi
Playbook per aggiungere un nuovo brand alla constellation. Validato su Blum (done) + applicato a Bosch (atomico, in corso) come stress test del pattern.
Ask-slot attivo cross-brand
compose_required / search_required
nel CategorySchema] F1[Fase 1
Decisione catalogo target
pilot stretto, es. lavastoviglie 60cm] F2[Fase 2
Estrazione PDF
MinerU + pdfplumber
extracted.md + supplementary] F3[Fase 3
CategorySchema MD-Karpathy
wiki_arcocat/categorie/Cxxx.md
attributi tipati + valori_validi] F4[Fase 4
Popolazione PIM-lite
+50-100 codici reali in pim.db] F5[Fase 5
Knowledge Tool MCP brand
contratto comune 3 o 4 tool
+ wiki R*/F*/D*/G*] F6[Fase 6
Eval baseline brand-specific
promptfoo + ground truth Tier-2] F7[Fase 7
2a categoria stesso brand
riusa schema + tooling] F0 -.->|opzionale prima| F1 F1 --> F2 --> F3 --> F4 --> F5 --> F6 --> F7 classDef done fill:#a5d6a7,stroke:#2e7d32,color:#1b5e20 classDef next fill:#fff59d,stroke:#f57f17,color:#e65100 class F1,F2,F3,F4,F5,F6 done class F0,F7 next
Fase 0 e' la chiave conversazionale
Distingue brand componibili (Blum: compose_required NL, altezza, portata) da atomici (Bosch: search_required larghezza, classe). Stesso meccanismo, output diverso: distinta vs shortlist filtrata.
Fase 5 e' il punto di verita'
Se il modulo NON espone configure, l'ImportError silente nel Supervisor lo classifica come atomico. Niente flag espliciti, niente registry hard-coded.
Vincolo non negoziabile
I 6 nodi pre-esistenti restano invariati. Solo modifiche additive: nuovo modulo brand, nuova categoria PIM, nuovo CategorySchema. Il Supervisor cambia solo nel registry.
Effort osservato
Master stima 2-4 settimane per developer da zero. Pattern Claude osservato 8-15h calendar in 1-2 sessioni (factor 5-15x sotto-stima).
3. Runtime pipeline
Cosa succede tra POST /chat e la risposta renderizzata. Tre intent distinti: search, compose, ask-slot.
Apri il runtime explorer
Particella animata che segue la richiesta lungo i lifelines, tooltip al volo su ogni step, pan/zoom, side panel con file:line del codice e tag (deterministic / LLM / I/O / parallel). Tre flow selezionabili.
Filter-then-Validate
PIM filtra strutturalmente, il brand valida semanticamente. Niente LLM sui filtri, niente regex sulla compatibilita'.
Parallelismo per candidato
ThreadPoolExecutor chiama valida_compatibilita in parallelo. Ogni call apre/chiude la sua SQLite (no pool).
Determinismo come default
slot_fill regex, synthesize_item pure fn, valida_compatibilita a temperature=0. LLM solo dove inevitabile.
Fail-closed
Brand sconosciuto o eccezione = validations=[] = synthesizer mappa a sconsigliato. La pipeline non si rompe mai.
4. Multiagent — contratto comune e capability negotiation
Il Supervisor non sa quali brand esistono finche' non li importa. Il contratto comune di 4 tool e l'ImportError silente fanno il resto.
from BRAND_knowledge_mcp.contract] REG1[(BRAND_CLIENTS
obbligatorio: valida)] REG2[(BRAND_CONFIGURE_CLIENTS
opzionale: configure)] IMP -->|ok| REG2 IMP -->|ImportError| FAIL[skip silente:
brand atomico] end subgraph BLUM[blum-knowledge-mcp] B1[cerca_knowledge] B2[dettaglio_codice] B3[valida_compatibilita] B4[configure ✓] end subgraph BOSCH[bosch-knowledge-mcp] X1[cerca_knowledge] X2[dettaglio_codice] X3[valida_compatibilita] X4[configure ✗ NON definito] end BLUM --> REG1 BLUM --> IMP BOSCH --> REG1 BOSCH -.->|ImportError| FAIL Q1[Query intent=compose
brand=Bosch] -->|capability check| REG2 REG2 -.->|key assente| EMPTY[ConfigureResult empty
+ nota 'atomico'] Q2[Query intent=compose
brand=Blum] --> REG2 REG2 -->|key presente| CONF[ConfigureResult
componenti distinta
es. 8 codici LEGRABOX] classDef ok fill:#a5d6a7,stroke:#2e7d32,color:#1b5e20 classDef atom fill:#ffcdd2,stroke:#c62828,color:#b71c1c classDef neutral fill:#fff,stroke:#6b7280 class B1,B2,B3,B4,REG2,CONF ok class X1,X2,X3,X4,FAIL,EMPTY atom
Contratto comune = 3 obbligatori + 1 opzionale
Obbligatori: cerca_knowledge, dettaglio_codice, valida_compatibilita. Opzionale: configure per brand componibili.
Negotiation senza protocollo
Niente handshake, niente metadati. La presenza/assenza dell'import e' la negoziazione. Pythonic, deterministica, audibile.
Routing per fonte_brand
Ogni candidato PIM porta con se' il suo brand. _validate_candidate instrada a BRAND_CLIENTS[fonte_brand]. Niente switch hard-coded.
Bosch e' il test di stress
Brand atomico (1 codice = 1 prodotto). Se il pattern regge per Bosch, regge per tutti i brand futuri (Hettich, Salice, Whirlpool, BSH).
5. ConfigurationContext — multi-turno tipato
Il chatbot e' multi-turno ma stateless: lo stato vive nel Supervisor. config_context.db ephemeral, una riga per session_id, mergeabile per accumulo slot.
famiglia=LEGRABOX
NL_mm=500] TQ1 --> MG1[merge_typed_query
prev=null] MG1 --> R1{slot completi?} R1 -->|no| ASK[ask-slot:
'altezza? portata?'
compose_ask_only=true] MG1 --> SAVE1[(context_store.save)] end subgraph T2[Turno 2: 'altezza M portata 70'] I2[Input testo] --> SF2[slot_fill] SF2 --> TQ2[TypedQuery
altezza=M
portata_kg=70] LOAD[(context_store.get)] --> MG2[merge_typed_query
prev=ctx1] TQ2 --> MG2 MG2 --> TQM[TypedQuery merged
famiglia=LEGRABOX
NL_mm=500
altezza=M
portata_kg=70] TQM --> R2{slot completi?} R2 -->|si| COMP[compose_query
distinta 8 codici
753.5001S, 770M5002S, ...] MG2 --> SAVE2[(context_store.save)] end SAVE1 -.->|stesso session_id| LOAD classDef store fill:#fff59d,stroke:#f57f17,color:#e65100 classDef pure fill:#bbdefb,stroke:#1565c0,color:#0d47a1 classDef result fill:#a5d6a7,stroke:#2e7d32,color:#1b5e20 class SAVE1,SAVE2,LOAD store class SF1,SF2,MG1,MG2 pure class COMP,ASK result
Storage lato Supervisor, non chatbot
supervisor-mcp/data/config_context.db. Il context e' dato del Supervisor: i test e la CLI possono fare risolvi_query("...", contesto_id="x") senza FastAPI.
Pure function di merge
merge_typed_query(prev, current) e' pure: stesso input, stesso output. Testabile senza LLM, senza I/O. 9 unit test deterministici.
Ephemeral SQLite + GC
Lifespan FastAPI fa garbage collection delle sessioni vecchie. Niente memory leak, niente Redis, niente operations.
Generalizzabile
Pattern "<nodo>/data/<context>.db" candidato a master v2.3.8 sez Livello 4. Precondizione per multi-tenant futuro.