# HackAPizza 2k25 — Architecture Overview

A **multi-agent restaurant management system** for a competitive galactic game (Ciclo Cosmico 790). It uses 6 LLM-based agents + 2 deterministic components to autonomously manage all aspects of restaurant operations in real-time.

## Architecture Diagram

```
┌─────────────────────────────────────────────────────────────────────────┐
│                        GAME SERVER (hackapizza.datapizza.tech)          │
│   REST API (/recipes, /restaurant, /meals, /market, /bid_history)      │
│   SSE Stream (/events/{TEAM_ID})                                       │
│   MCP Tools (/mcp - JSON-RPC 2.0)                                      │
└────────┬──────────────────────┬──────────────────────────┬──────────────┘
         │ SSE Events           │ REST Calls               │ MCP Tool Calls
         ▼                      ▼                          ▼
┌─────────────────┐   ┌─────────────────┐   ┌──────────────────────────┐
│  SSE Middleware  │   │   API Client    │   │   MCP Game Tools         │
│  (infra/sse.py) │   │(infra/api_client│   │ (save_menu, closed_bid,  │
│                 │   │     .py)        │   │  prepare_dish, serve_dish│
│ - Connect/retry │   │ - get_recipes() │   │  send_message,           │
│ - Parse events  │   │ - get_restaurant│   │  create_market_entry...) │
│ - Route to bus  │   │ - get_meals()   │   └──────────┬───────────────┘
└────────┬────────┘   │ - get_market()  │              │
         │            └────────┬────────┘              │
         ▼                     │                       │
┌──────────────────────────────┼───────────────────────┼──────────────────┐
│                      MESSAGE BUS (infra/message_bus.py)                 │
│              asyncio.Queue per agent — point-to-point + broadcast       │
└──┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────────┘
   │          │          │          │          │          │
   ▼          ▼          ▼          ▼          ▼          ▼
┌────────┐ ┌────────┐ ┌──────────┐ ┌───────┐ ┌────┐ ┌──────────┐
│ PATRON │ │  CHEF  │ │MAGAZZINI │ │MAITRE │ │ PR │ │EVALUATOR │
│  120b  │ │  120b  │ │   20b    │ │  20b  │ │120b│ │   120b   │
│        │ │        │ │          │ │       │ │    │ │          │
│Strategy│ │ Menu   │ │ Auction  │ │Orders │ │Msg │ │Post-turn │
│Orchestr│ │Planning│ │ & Market │ │Service│ │Triage│ │Analysis │
└───┬────┘ └───┬────┘ └────┬─────┘ └──┬────┘ └─┬──┘ └────┬─────┘
    │          │           │          │        │         │
    └──────────┴───────────┴──────────┴────────┴─────────┘
                              │
                    ┌─────────┴─────────┐
                    │  BRIGATA (no LLM) │
                    │ (infra/brigata.py) │
                    │                   │
                    │ Deterministic     │
                    │ dish serving +    │
                    │ intolerance learn │
                    └─────────┬─────────┘
                              │
                              ▼
              ┌───────────────────────────────┐
              │   SQLite DB (hackapizza.db)   │
              │                               │
              │ service_log | turn_summary   │
              │ market_history | messages_log │
              │ recipes | known_intolerances  │
              │ intel_* (7 tables via scraper)│
              └───────────────┬───────────────┘
                              │
              ┌───────────────┴───────────────┐
              │  STRATEGY ENGINE (no LLM)     │
              │  (strategy_engine.py)         │
              │                               │
              │  Deterministic recipe pool,   │
              │  bid pricing, menu sizing     │
              │  from intel data              │
              └───────────────────────────────┘

              ┌───────────────────────────────┐
              │  INTEL SCRAPER (background)   │
              │  (intel_scraper.py)           │
              │                               │
              │  Polls /restaurants, /meals,  │
              │  /bid_history, /market →      │
              │  populates intel_* tables     │
              └───────────────────────────────┘
```

## Turn Lifecycle (Phase Flow)

```
  ┌──────────────┐
  │ game_started │  Patron: broadcast turn_id, sync recipes,
  └──────┬───────┘  call Strategy Engine → recipe_pool, bid_prices
         ▼
  ┌──────────────┐  Chef: receive recipe_pool → plan menu, send ingredient_request
  │   speaking   │  PR: (optional) proactive outreach to other restaurants
  └──────┬───────┘
         ▼
  ┌──────────────┐  Patron: send bid_prices to Magazzini
  │  closed_bid  │  Magazzini: build bids from pre-computed prices, call closed_bid
  └──────┬───────┘
         ▼
  ┌──────────────┐  Magazzini: check inventory, buy missing on market,
  │   waiting    │             send ingredient_report to Chef
  └──────┬───────┘  Chef: revise menu → save_menu, send menu_ready + menu_finalized
         │          Patron: receive menu_ready → open restaurant
         ▼
  ┌──────────────┐  Maitre: interpret client orders → prepare_dish
  │   serving    │  Brigata: preparation_complete → serve_dish, log intolerances
  └──────┬───────┘  Maitre: on missing ingredients → emergency_buy_request
         │          Magazzini: emergency buy from market + broadcast_buy_request via PR
         ▼
  ┌──────────────┐  Evaluator: analyze performance, write turn_summary
  │   stopped    │  (metrics: served, failed, cost, revenue, waste)
  └──────┬───────┘
         │
         └──────── loop back to game_started ────►
```

## Inter-Agent Communication

```
                    phase_directive (all phases)
              ┌──────────────────────────────────────┐
              │                                      │
              ▼                                      │
┌────────┐  ingredient_request   ┌──────────┐       │
│  CHEF  │ ────────────────────► │MAGAZZINI │       │
│        │ ◄──────────────────── │          │  ┌────┴───┐
│        │  ingredient_report    │          │  │ PATRON │
│        │                       │          │  │        │
│        │  menu_finalized       │          │  └────┬───┘
│        │ ────────────────────► │          │       │
└────┬───┘                       └────┬─────┘       │
     │ menu_ready                      │             │
     └─────────────────────────────────┼─────────►   │
                                       │             │
              trade_proposal           │             │
         ┌─────────────────────► ──────┘             │
┌────┐   │   trade_decision                          │
│ PR │ ◄─┤   capture_market_entry                    │
│    │   │                                           │
│    │ ◄─┤  broadcast_buy_request (emergency)        │
└────┘   │                                           │
         │  strategic_escalation                     │
         └───────────────────────────────────────►   │

┌────────┐  client_spawned    ┌─────────┐  preparation_complete  ┌─────────┐
│  SSE   │ ─────────────────► │ MAITRE  │ ◄───────────────────── │ BRIGATA │
│Middlew.│                    │         │  (shares pending_orders)│(no LLM) │
└────────┘                    │         │                        └─────────┘
                              │         │  emergency_buy_request  ┌──────────┐
                              │         │ ──────────────────────► │MAGAZZINI │
                              │         │ ◄────────────────────── │          │
                              └─────────┘  emergency_buy_result   └──────────┘
```

## Key Components

| Component | File | Model | Responsibility |
|-----------|------|-------|----------------|
| **Patron** | `agents/patron.py` | 120b | Strategic orchestrator — budget planning, phase coordination |
| **Chef** | `agents/chef.py` | 120b | Recipe selection, menu planning, ingredient requests |
| **Magazzini** | `agents/magazzini.py` | 120b | Auction bidding, market buy/sell, inventory liquidation |
| **Maitre** | `agents/maitre.py` | 20b | Interpret client orders, call prepare_dish |
| **PR** | `agents/pr.py` | 20b | Triage incoming messages, negotiate trades |
| **Evaluator** | `agents/evaluator.py` | 20b | Post-turn analytics, writes turn_summary |
| **Brigata** | `infra/brigata.py` | None | Deterministic serving + intolerance learning |
| **SSE** | `infra/sse.py` | None | Event stream connection, parsing, routing |
| **Priority Inbox** | `infra/priority_inbox.py` | None | Priority queue for Patron's inbox (phase-critical messages first) |
| **Message Bus** | `infra/message_bus.py` | None | asyncio.Queue-based inter-agent messaging |
| **API Client** | `infra/api_client.py` | None | REST client for game server |
| **Repository** | `db/repository.py` | None | SQLite data access (6 core + 7 intel tables) |
| **Strategy Engine** | `strategy_engine.py` | None | Deterministic strategy computation from intel data |
| **Intel Scraper** | `intel_scraper.py` | None | Background polling of public APIs for competitive intel |

## Directory Structure

```
src/hackapizza/
├── main.py                  # Entry point & orchestration
├── config.py                # Environment config & API credentials
├── utils.py                 # Ingredient normalization, safe-to-sell computation
├── feature_flags.py         # Runtime feature toggles
├── strategy_engine.py       # Deterministic strategy computation (recipe selection, bid pricing)
├── intel_scraper.py         # Background competitive intelligence scraper
│
├── models/
│   ├── game.py              # Domain models (Recipe, MenuItem, etc.)
│   └── messages.py          # Inter-agent message types
│
├── infra/
│   ├── sse.py               # SSE connection & event routing
│   ├── api_client.py        # HTTP client for REST API
│   ├── message_bus.py       # asyncio.Queue message routing
│   ├── priority_inbox.py    # Priority queue inbox (used by Patron)
│   └── brigata.py           # Deterministic serving middleware
│
├── db/
│   ├── schema.py            # SQLite schema (6 tables + intel tables)
│   └── repository.py        # Data access layer
│
└── agents/
    ├── base.py              # BaseAgent class & LLM interface
    ├── prompts.py           # System prompts for all roles
    ├── patron.py            # Strategic orchestrator
    ├── chef.py              # Menu planning & recipe selection
    ├── magazzini.py         # Auction & market operations
    ├── maitre.py            # Order interpretation & service
    ├── pr.py                # External communications
    └── evaluator.py         # Post-turn analysis & reporting
```

## Data Models

### Game Domain (`models/game.py`)

| Model | Purpose | Key Fields |
|-------|---------|------------|
| `Recipe` | Blueprint for dishes | name, preparation_time_ms, ingredients (dict), prestige |
| `MenuItem` | Menu entry with pricing | name, price |
| `MarketEntry` | Market listing | id, side (BUY/SELL), ingredient_name, quantity, price |
| `ClientOrder` | Customer order | client_id, client_name (persona), order_text |
| `PendingOrder` | In-flight tracking | client_id, dish, status (preparing/ready/served) |
| `RestaurantState` | Current status | balance, inventory, reputation, is_open, menu_items |

### Internal Messages (`models/messages.py`)

| Message Type | Sender | Receiver | Purpose |
|-------------|--------|----------|---------|
| `ingredient_request` | Chef | Magazzini | Request ingredient procurement |
| `ingredient_report` | Magazzini | Chef | Report inventory + missing/surplus |
| `trade_proposal` | PR | Magazzini | Forward trade offer for evaluation |
| `trade_decision` | Magazzini | PR | Accept/reject trade |
| `capture_market_entry` | PR | Magazzini | Trigger market polling for agreed trade |
| `phase_directive` | Patron | All agents | Phase-specific instructions |
| `menu_ready` | Chef | Patron | Menu finalized, ready to open |
| `menu_finalized` | Chef | Magazzini | Final menu after post-auction revision |
| `strategic_escalation` | Any | Patron | Escalate strategic decisions |
| `sse_event` | SSE | Agents | Forwarded server events |
| `emergency_buy_request` | Maitre | Magazzini | prepare_dish failed, need ingredients |
| `emergency_buy_result` | Magazzini | Maitre | Result of emergency market buy |
| `broadcast_buy_request` | Magazzini | PR | Ask all teams to sell ingredients |

## Database Schema

**Core Tables (6)**

| Table | Purpose | Key Fields |
|-------|---------|------------|
| `service_log` | Order fulfillment history | turn_id, client_id, client_name, interpreted_dish, served, outcome, refusal_reason |
| `turn_summary` | End-of-turn analytics | turn_id, balance, reputation, dishes_served/failed, revenue, cost, menu_json, notes |
| `market_history` | Trade transaction log | turn_id, counterpart_id, side (BUY/SELL), ingredient, quantity, price |
| `messages_log` | Inter-restaurant comms | turn_id, counterpart_id, counterpart_name, direction (IN/OUT), text |
| `known_intolerances` | Dietary restrictions | client_type, ingredient, confidence (low/medium/high), source (failure/info/message) |
| `recipes` | Cached recipe database | name, preparation_time_ms, ingredients_json, prestige, persona_scores_json, available, menu_price |

**Competitive Intelligence Tables (7)** — populated by `intel_scraper.py`

| Table | Purpose | Key Fields |
|-------|---------|------------|
| `intel_restaurant_snapshots` | Latest per-restaurant state (deduplicated) | turn_id, restaurant_id, balance, reputation, is_open, menu_json |
| `intel_restaurant_snapshots_raw` | Time-series of every poll (trend analysis) | poll_ts, turn_id, restaurant_id, balance, reputation, avg_menu_price |
| `intel_competitor_menus` | Competitor menu items per turn | turn_id, restaurant_id, dish_name, price |
| `intel_competitor_meals` | Competitor order fulfillment data | meal_id, turn_id, restaurant_id, customer_name, request, status, price, matched_dish_name |
| `intel_all_bids` | All 26 teams' auction bids | bid_api_id, turn_id, restaurant_id, ingredient_name, quantity, price_each, status |
| `intel_market_entries` / `_raw` | Public market entries (latest + time-series) | entry_api_id, side, ingredient_name, quantity, total_price, status |
| `intel_price_benchmarks` | Aggregated ingredient pricing per turn | turn_id, ingredient_name, avg/min/max_bid_price, avg_market_price |
| `intel_rankings` / `_raw` | Leaderboard snapshots (latest + time-series) | turn_id, restaurant_id, balance, reputation, rank_by_reputation, rank_by_balance |

## External Services

| Service | Purpose | Protocol |
|---------|---------|----------|
| `hackapizza.datapizza.tech` | Game server | REST + SSE + MCP (JSON-RPC 2.0) |
| `api.regolo.ai/v1` | LLM inference | OpenAI-compatible API |
| `datapizza-monitoring.datapizza.tech` | Tracing (optional) | OTLP |

## Feature Flags (`feature_flags.py`)

| Flag | Default | Description |
|------|---------|-------------|
| `ENABLE_PROACTIVE_OUTREACH` | `false` | PR contacts other restaurants |
| `ENABLE_MARKET_BUYING` | `true` | Magazzini buys missing ingredients on market |
| `ENABLE_MARKET_SELLING` | `false` | Magazzini sells surplus on market |
| `ENABLE_EMERGENCY_BUY` | `true` | Maitre triggers market buy when prepare_dish fails for missing ingredients |

Override via environment variables (e.g., `ENABLE_PROACTIVE_OUTREACH=1`).

## Strategy Engine (`strategy_engine.py`)

Deterministic, data-driven strategy computation. Called by Patron at `game_started`.

**Input**: balance, reputation
**Output**: `recipe_pool`, `bid_prices`, `ingredient_needs`, `n_recipes`, `servings_per_recipe`, `max_auction_budget`, `competitive_intel`

Key algorithms:
- **Recipe scoring**: fewer ingredients (+15/unit below 7), lower qty (+5/unit below 8), popularity from intel (+20/order), competitor usage (+10/restaurant), prestige (+0.5/point)
- **Recipe selection**: greedy with ingredient overlap maximization, target 14 recipes
- **Bid pricing**: calibrated from `intel_all_bids` benchmarks + competition premium
- **Price tiers**: prestige (0-40)→50-80, (40-60)→100-150, (60-80)→150-250, (80-100)→250-400

## Intel Scraper (`intel_scraper.py`)

Background process that polls public game APIs and populates `intel_*` tables.

```bash
python -m hackapizza.intel_scraper                        # one-shot
python -m hackapizza.intel_scraper --loop 60              # poll every 60s
python -m hackapizza.intel_scraper --loop 30 --turns 8,9  # specific turns
```

**Scrape targets**: `GET /restaurants` → snapshots + rankings, `GET /meals` (all 26 teams) → competitor meals, `GET /bid_history` → all bids, `GET /market/entries` → market entries + price benchmarks.

Rate limiting: 0.5s between API calls, 2s between phases, 5s retry on 429.

## Startup Sequence (`main.py`)

1. Setup logging & monitoring
2. Initialize SQLite DB with schema
3. Create Repository + API Client + init DB tools
4. Sync recipes from API (turn_id starts at 0, set by SSE `game_started`)
5. Load MCP game tools from server (`/mcp tools/list`)
6. Create MessageBus
7. Create shared `pending_orders` dict (Maitre <-> Brigata)
8. Create all 6 agents (each gets filtered subset of game + DB tools)
9. Configure delegation: Patron can call Chef & Evaluator
10. Create SSE middleware + Brigata middleware
11. Register signal handlers (SIGINT, SIGTERM) → emergency cleanup on shutdown
12. Start all async tasks in event loop

## Priority Inbox

Patron's inbox is a `PriorityInbox` (drop-in replacement for `asyncio.Queue`) that dequeues messages by priority, ensuring phase-critical events are never starved by low-priority escalations.

| Priority | Message types |
|----------|---------------|
| 0 (highest) | `sse_event`, `phase_change`, `turn_started` |
| 1 | `menu_ready`, `phase_directive`, `ingredient_request`, `ingredient_report` |
| 5 (default) | Everything else |
| 9 (lowest) | `strategic_escalation` |

Within the same priority level, messages are dequeued in FIFO order (stable ordering via sequence counter).

## LLM Call Timeout

All `ask_llm` calls in `BaseAgent` are wrapped with `asyncio.wait_for(..., timeout=30)`. If an LLM call exceeds 30 seconds, it is cancelled, a warning is logged, and an empty string is returned. This prevents a slow escalation response from blocking phase-critical message processing.

## Data Flow Summary

1. **Intel Scraper** (background) polls public APIs and populates `intel_*` tables
2. **SSE events** from the game server drive the entire system through phase transitions
3. **Patron** receives phase changes, calls **Strategy Engine** for data-driven decisions, issues **phase_directives**
4. **Strategy Engine** reads intel tables to compute recipe pool, bid prices, and menu sizing
5. **Chef <-> Magazzini** collaborate on ingredient procurement (request/report cycle)
6. **PR <-> Magazzini** collaborate on trade opportunities (proposal/decision cycle)
7. **Maitre -> Brigata** share `pending_orders` dict for order fulfillment
8. **Maitre -> Magazzini** emergency buy flow when prepare_dish fails for missing ingredients
9. **Evaluator** reads all DB tables at turn end and writes `turn_summary` for next turn's planning
10. **SQLite** persists all history across turns for learning and strategy refinement
