# Comportamenti degli Agenti — Brigata di Cucina

Documento per il team umano. Descrive **cosa fa concretamente** ogni agente, fase per fase, e come si coordina con gli altri.

---

## Flusso di un turno

```
game_started
  └→ Patron inizializza, legge note del turno precedente
  └→ Patron → StrategyEngine.compute_strategy() [DETERMINISTICO]
      reads: intel_all_bids, intel_competitor_menus, intel_competitor_meals, intel_rankings
      output: recipe_pool, bid_prices, ingredient_needs, servings

speaking
  └→ Patron → Chef: "pianifica menu" (con recipe_pool da Strategy Engine)
  └→ Patron → PR: "fase negoziazioni aperta"
  └→ Chef: USA recipe_pool DIRETTAMENTE (nessun overbid, nessuna selezione LLM)
  └→ Chef → Magazzini: "servono questi ingredienti" (solo per ricette finali)

closed_bid
  └→ Patron → Magazzini: "fai le offerte" (con bid_prices pre-calcolati)
  └→ Magazzini: costruisce bids deterministicamente, LLM chiama solo closed_bid con params esatti

waiting
  └→ Patron → Magazzini: "verifica inventario, compra mancanti, avvisa Chef"
  └→ Magazzini: confronta inventario vs richieste, compra mancanti al mercato
  └→ Magazzini → Chef: "ecco l'inventario reale"
  └→ Chef: ricalcola menu con ciò che c'è, chiama save_menu
  └→ Chef → Patron: "menu pronto"
  └→ Patron: apre il ristorante

serving
  └→ Maitre: autonomo, riceve client_spawned via SSE
  └→ Per ogni cliente: LLM identifica piatto → chiama prepare_dish → registra in pending_orders
  └→ Brigata (deterministica): riceve preparation_complete → chiama serve_dish → logga esito

stopped
  └→ Patron → Evaluator: "analizza il turno"
  └→ Evaluator: legge service_log + intel_all_bids + market_history → scrive turn_summary
```

---

## Strategy Engine — Il Cervello Analitico

**Modello:** nessuno (deterministico) | **File:** `strategy_engine.py`

### Cosa fa realmente
- **Legge dati intel** da tabelle `intel_*` (populate da `intel_scraper` ogni ~5min)
- **Seleziona ricette** per il menu: scoring basato su popolarità, prestige, #ingredienti, overlap
- **Calcola prezzi** menu: 10% sotto competitors, o tier basato su prestige
- **Calcola bid prices**: basati su avg_winning_price + margine per competizione
- **Stima servings**: parte da 1, sale se la domanda storica lo giustifica

### Auto-regolazione
Il sistema si **auto-aggiorna** senza intervento umano:
- L'intel_scraper popola le tabelle ogni 5 minuti con dati freschi
- Ad ogni turno, lo Strategy Engine ricalcola tutto dai dati più recenti
- Se i competitors cambiano prezzi → i nostri prezzi si adattano
- Se i bid vincenti cambiano → i nostri bid si adattano
- Se certi piatti vengono ordinati di più → entrano nel nostro menu

### Non usa LLM
Tutte le decisioni sono deterministiche e basate su dati.

---

## Patron — Il Direttore

**Modello:** gpt-oss-120b | **File:** `agents/patron.py`

### Cosa fa realmente
- **NON pianifica** il menu (delega a Chef + Strategy Engine)
- **NON decide bid** (Strategy Engine li calcola)
- **Chiama Strategy Engine** a inizio turno per ottenere la strategia
- **Passa recipe_pool e bid_prices** a Chef e Magazzini
- **Apre/chiude il ristorante** (tool MCP: `update_restaurant_is_open`)
- **Gestisce escalation** dagli altri agenti

### Quando usa l'LLM
1. Quando riceve `menu_ready` → chiede all'LLM di chiamare `update_restaurant_is_open`
2. Quando riceve `strategic_escalation` → chiede all'LLM una decisione

### Priority Inbox
Il Patron usa una `PriorityInbox` al posto della coda FIFO standard. Questo garantisce che gli eventi SSE e i cambi fase vengano processati prima delle escalation dal PR, anche se queste ultime sono arrivate prima nella coda.

| Priorità | Tipi messaggio |
|----------|----------------|
| 0 (max) | `sse_event`, `phase_change`, `turn_started` |
| 1 | `menu_ready`, `phase_directive`, `ingredient_request`, `ingredient_report` |
| 5 (default) | Tutto il resto |
| 9 (min) | `strategic_escalation` |

### Comportamento atteso
✅ Deve essere **rapido**: la pipeline speaking→closed_bid→waiting→serving è time-sensitive
✅ Deve **aprire il ristorante** prima che inizi la serving phase
✅ Se il menu è vuoto → chiude il ristorante (protettivo)
✅ Le escalation non devono **mai bloccare** il processing di eventi di fase (garantito dalla Priority Inbox)

---

## Chef — Il Pianificatore del Menu

**Modello:** gpt-oss-120b | **File:** `agents/chef.py`

### Cosa fa realmente
- **Fase speaking**: riceve `recipe_pool` dal Patron (pre-computato da Strategy Engine), lo USA DIRETTAMENTE senza selezione LLM
- **Fase waiting**: riceve `ingredient_report` dai Magazzini, verifica quali ricette del pool sono fattibili, salva menu finale

### Quando usa l'LLM
1. **Salvataggio menu** (waiting): chiede all'LLM di chiamare il tool `save_menu` con parametri esatti
   - Il prompt è: "chiama save_menu con items = [...]"

### Cambio chiave dal vecchio sistema
- ❌ **ELIMINATO**: overbid_count (pianificava 12 ricette per comprarne 6)
- ❌ **ELIMINATO**: LLM sceglie ricette (ora usa pool deterministico)
- ✅ **NUOVO**: ricette pianificate = ricette nel menu (zero spreco di ingredienti extra)
- ✅ **NUOVO**: legge bid prices da `intel_all_bids` (non più `bid_history` locale)

### Strategia di prezzo (dal Strategy Engine)
- Piatti prestige < 40 → 50-80€ (Esploratori Galattici)
- Piatti prestige 40-60 → 100-150€ (Famiglie Orbitali)
- Piatti prestige 60-80 → 150-250€ (misto)
- Piatti prestige 80+ → 250-400€ (Astrobaroni, Saggi)
- Se un piatto è offerto dai competitors: prezzo 10% sotto la loro media

---

## Magazzini — L'Economo

**Modello:** gpt-oss-120b (CAMBIATO da 20b) | **File:** `agents/magazzini.py`

### Cosa fa realmente
- **Fase closed_bid**: riceve `bid_prices` pre-calcolati, costruisce la lista bids, l'LLM chiama solo `closed_bid` con parametri esatti
- **Fase waiting**: confronta inventario vs richieste, compra mancanti al mercato
- **Valuta proposte di scambio** dal PR

### Quando usa l'LLM
1. **Asta** (closed_bid): LLM esegue solo la tool call `closed_bid` con parametri PRE-CALCOLATI
   - Prompt: "chiama closed_bid con bids = [{...}]"
2. **Mercato buy** (waiting): se mancano ingredienti → LLM cerca offerte e chiama `execute_transaction`
3. **Valutazione scambio**: riceve proposta dal PR → risponde ACCETTA/RIFIUTA

### Cambio chiave dal vecchio sistema
- ❌ **ELIMINATO**: LLM decide bid prices (bidava 19 flat, troppo alto)
- ❌ **ELIMINATO**: write_bid_history locale (ora tutto in intel_all_bids via scraper)
- ❌ **ELIMINATO**: market selling (nessuno compra, disattivato)
- ❌ **ELIMINATO**: liquidazione fine turno
- ✅ **NUOVO**: bid costruiti deterministicamente dallo Strategy Engine
- ✅ **NUOVO**: modello gpt-oss-120b per tool call più affidabili
- ✅ **NUOVO**: budget max 20% del bilancio (prima era 80%!)

### Benchmark bid (da intel)
- Ingredienti bassa competizione (<5 team): avg_winning + 1
- Ingredienti media competizione (5-10 team): avg_winning + 1
- Ingredienti alta competizione (>10 team): avg_winning + 2
- Default senza dati: 4/unità
- Hard cap: 15/unità

---

## Maitre — Il Responsabile di Sala

**Modello:** gpt-oss-20b | **File:** `agents/maitre.py`

### Cosa fa realmente
- Attivo SOLO durante la fase `serving`
- Riceve eventi `client_spawned` via SSE (nome cliente, testo ordine)
- Gestisce **ogni cliente in un task asyncio separato** (parallelo)
- Chiede all'LLM di interpretare l'ordine e chiamare `prepare_dish`
- Registra il cliente in `pending_orders`

### Comportamento atteso
✅ Deve essere **veloce**: un ordine = una chiamata LLM
✅ Deve **matchare l'ordine al piatto più simile** nel menu
✅ Deve **controllare intolleranze** prima di servire
✅ Se intolleranza → **NON servire** (penalità grave se serve male)
✅ `prepare_dish` NON è bloccante: la preparazione avviene in background

---

## Brigata — La Mano Operativa

**Modello:** nessuno (deterministica) | **File:** `infra/brigata.py`

### Cosa fa realmente
- Processo asyncio in ascolto di eventi `preparation_complete` via SSE
- NON usa LLM — pura logica deterministica
- Quando un piatto è pronto: serve al cliente, logga esito, impara intolleranze

---

## PR — Il Diplomatico

**Modello:** gpt-oss-20b | **File:** `agents/pr.py`

Gestisce comunicazioni con altri ristoranti. Filtra spam, inoltra proposte valide.

---

## Evaluator — L'Analista

**Modello:** gpt-oss-20b | **File:** `agents/evaluator.py`

Analizza performance post-turno. Legge da `intel_all_bids`, `service_log`, `market_history`.
Scrive `turn_summary` letto dal Patron al turno successivo.

---

## Comunicazione tra agenti

```
Patron ──phase_directive──→ Chef, PR, Magazzini, Evaluator
Chef ──ingredient_request──→ Magazzini (solo ingredienti per menu FINALE, no overbid)
Magazzini ──ingredient_report──→ Chef
Chef ──menu_finalized──→ Magazzini
Chef ──menu_ready──→ Patron
PR ──trade_proposal──→ Magazzini
Magazzini ──trade_decision──→ PR
```

---

## Feature Flags

| Flag | Default | Effetto |
|---|---|---|
| `ENABLE_MARKET_BUYING` | on | Magazzini cerca ingredienti mancanti sul mercato |
| `ENABLE_MARKET_SELLING` | **off** | ~~Magazzini vende surplus~~ (disattivato: nessuno compra) |
| `ENABLE_PROACTIVE_OUTREACH` | off | PR contatta proattivamente altri ristoranti |
| `ENABLE_EMERGENCY_BUY` | on | Magazzini compra ingredienti mancanti durante serving |

---

## Data Tables

### Tabelle Intel (read-only per gli agenti, popolate da intel_scraper)

| Tabella | Contenuto | Usata da |
|---|---|---|
| `intel_all_bids` | Tutti i bid di tutti i team | Strategy Engine, Chef, Evaluator |
| `intel_competitor_menus` | Menu e prezzi di tutti i ristoranti | Strategy Engine |
| `intel_competitor_meals` | Ordini clienti e piatti serviti | Strategy Engine |
| `intel_rankings` | Classifica ristoranti per bilancio/reputazione | Strategy Engine, Patron |
| `intel_restaurant_snapshots` | Snapshot stato ristoranti | Strategy Engine |
| `intel_price_benchmarks` | Benchmarks prezzo aggregati | Strategy Engine |
| `intel_market_entries` | Offerte mercato attive/chiuse | Magazzini |

### Tabelle Operative

| Tabella | Contenuto | Scritto da | Letto da |
|---|---|---|---|
| `service_log` | Log servizio clienti | Maitre, Brigata | Evaluator |
| `turn_summary` | Metriche fine turno | Evaluator | Patron, Strategy Engine |
| `market_history` | Nostre transazioni mercato | Magazzini | Evaluator |
| `known_intolerances` | Intolleranze apprese | Brigata | Maitre |
| `recipes` | Ricette disponibili (sync da API) | Patron (sync) | Chef, Strategy Engine |
| `messages_log` | Log comunicazioni | PR | PR |

### Tabella Deprecata

| Tabella | Nota |
|---|---|
| `bid_history` | **NON USATA PIÙ** — sostituita da `intel_all_bids`. Resta nel DB per compatibilità. |

---

## Problemi noti / Punti di attenzione

1. **Il ristorante era spesso CHIUSO** — La pipeline Chef→Magazzini→Chef→Patron→open era troppo lenta. Ora con recipe_pool pre-calcolato dovrebbe essere più veloce.
2. **Evaluator usa modello piccolo** — nel PRD è previsto gpt-oss-120b ma nel codice usa MODEL_SMALL
3. **Metriche incomplete nel turn_summary** — `ingredients_wasted`, `revenue`, `cost` sono spesso 0
4. **Nessun retry strutturato** per tool call fallite (solo retry su errori LLM in base.py)
5. ~~**PR escalation flood bloccava il Patron**~~ — **RISOLTO**: il Patron ora usa `PriorityInbox` che processa eventi di fase prima delle escalation, e `ask_llm` ha un timeout di 30s
