WhatsApp-Channel fuer Restaurant-Bot (depends on Plan 004)

2026-05-13 Re-Scoping: Phase A dieses Plans (Odoo Self-Host auf Fargate) wurde nach Document-Review-Findings durch Plan 004 ersetzt (Odoo Online SaaS statt Self-Host, multi-vertical-tauglich, 0–24 EUR/Mo statt 70–85 EUR/Mo). Phasen B + C dieses Plans (WhatsApp-Worker, Aylem-Smoke-Test) bleiben gueltig, depends on Plan 004 (_odoo.py muss existieren).

Status: bei ce:work-Start zuerst Plan 004 abschliessen, dann Phase B/C aus diesem Plan ziehen. Units 1, 2, 3 hier sind durch Plan 004 ersetzt. Aktive Units: 4, 5, 6, 7.

Overview

Zwei Sachen in einem Plan, weil sie nur zusammen Sinn ergeben:

  1. Odoo Community 18 als Restaurant-Backend in av-production Fargate — Bestellungen, Reservierungen, grafischer Tischplan, Kitchen Display, Bondrucker-Support, Inhaber-Mobile-App. Ersetzt SQLite + Telegram-Push als „View-Layer fuer den Inhaber”. Wird Bundle-Asset fuer alle kuenftigen Restaurant-Vertikalen-Kunden.

  2. WhatsApp-Kanal parallel zum LiveKit-Voice-Kanal via Meta Cloud API. Gleiche Bot-Logik, gleiche MCP-Tools, gleiche Persona — nur Text statt Voice. Anrufer/Chatter kommt entweder ueber Telefon ODER WhatsApp rein, der Backend (Odoo) ist gemeinsam. „a la HeyJulia, nur eben einfach” = productized Restaurant-Vertical, aber MVP-schmal: ein Tenant, ein WhatsApp-Bot, ein Odoo-Workspace.

Heute fehlt: kein echtes Restaurant-Management-Backend (Reservierungen liegen in SQLite, Inhaber sieht das nicht), kein zweiter Kanal (Voice-only ist limitierend — viele DACH-Restaurant-Gaeste schreiben WhatsApp).

Problem Frame

Reference-Case ist „Production-Ready Voice-Bot fuer Aylem” (siehe Master-Plan 2026-05-13-001). Sub-Plan 2026-05-13-002 hat Knowledge-Base + Verfuegbarkeit + Confirm-Loop implementiert, aber zwei Schwaechen bleiben:

  1. Inhaber sieht die Reservierungen nicht. Telegram-Push ist „Benachrichtigung”, nicht „Management-UI”. Restaurant-Inhaber wollen Tabelle + Tischplan + Filter. SQLite-File ist nicht inspect-tauglich fuer Nicht-Techniker.
  2. Voice ist nicht der einzige Kanal. DACH-Restaurant-Realitaet 2026: ein nicht-trivialer Anteil der Gaeste schreibt WhatsApp statt anzurufen — Reservierungs- und Bestell-Anfragen kommen dort ohne dass das Restaurant darauf reagiert.

Die Loesung „simple aber komplett”: Odoo Community als Backend (open source, modul-reich, Restaurant-Inhabern bekannt) + WhatsApp-Worker als zweiten Kanal mit gleichem Bot-Brain.

Aylem ist Demo-Tenant. Reference-Case-Asset, kein zahlender Live-Pilot — d.h. WhatsApp-Number wird Marvin’s eigene zum Testen verwenden, Aylem-Inhaber nicht involviert.

Requirements Trace

  • R1. Odoo Community 18 mit POS-Restaurant-Modul laeuft in av-production (eu-central-1) Fargate, erreichbar unter odoo-aylem.agenticventures.de ueber Cloudflare-Tunnel. Postgres-RDS als Storage.
  • R2. Aylem-Workspace in Odoo angelegt mit: Tisch-Layout (~20 Tische), Speisekarte (mind. die Hauptgerichte aus knowledge/aylem/menu.md), Oeffnungszeiten, Reservierungs-Form aktiv.
  • R3. tools-mcp/_odoo.py Helper-Modul schreibt Reservierungen + Bestellungen via Odoo-JSON-RPC in Odoo. Feature-Flag STORAGE_BACKEND=odoo (statt SQLite) — beide Pfade lauffaehig.
  • R4. WhatsApp-Worker (FastAPI + PyWa + Bedrock Haiku 4.5 EU) laeuft in av-production Fargate, registriert als Meta Cloud API Webhook unter wa-aylem.agenticventures.de.
  • R5. WhatsApp-Bot nutzt dieselben MCP-Tools wie der LiveKit-Voice-Bot (book_reservation, take_order, escalate_to_human, search_knowledge, get_menu, get_hours, get_allergens, check_availability) — kein paralleles Tool-Layer.
  • R6. Voice-Bot und WhatsApp-Bot teilen Persona + Tonalitaet via gemeinsamem System-Prompt-Modul. Nur Sprach-spezifische Anweisungen (Voice: kurze Saetze, keine URLs vorlesen vs WhatsApp: knappe Text-Nachrichten, gerne URLs, Emoji sparsam) unterscheiden sich.
  • R7. Aylem-Inhaber-Onboarding-Skript: Login zu Odoo + Test-Chat auf WhatsApp + Verifikation dass beide Kanaele in Odoo landen.
  • R8. Meta-Policy-konform: Bot-Disclosure im ersten Conversation-Turn („Hier ist Aylem Eat & Meet, ich bin der KI-Assistent.”), task-specific Use-Cases (Booking + Order + KB-Frage + Eskalation) — kein generischer Chat.

Scope Boundaries

In Scope:

  • Odoo Community 18 Hosting in av-production Fargate + Postgres-RDS + Cloudflare-Tunnel
  • tools-mcp/_odoo.py als Helper-Modul (NICHT als separates Repo, analog _telegram.py/_knowledge.py)
  • Feature-Flag STORAGE_BACKEND=odoo|sqlite in tools-mcp/server.py — SQLite-Pfad bleibt funktional
  • WhatsApp-Worker FastAPI-Service in av-production Fargate
  • Meta Cloud API Webhook-Receiver mit Signature-Verification
  • Aylem-Workspace in Odoo befuellen (Tische, Karte, Hours) ueber Odoo-UI manuell
  • Shared System-Prompt zwischen Voice + WhatsApp (1 Modul, 2 Channel-spezifische Add-on-Blocks)
  • Aylem-WhatsApp-Test-Number (Marvin verwendet seine eigene Mobile-Number fuer Test, Aylem-Inhaber nicht involviert)
  • End-to-End-Smoke-Test: Voice-Call → Odoo + WhatsApp-Chat → Odoo

Out of Scope (Folge-Plaene oder spaeter):

  • LiveKit-Worker-Migration nach Fargate (bleibt auf Marvin-Mac fuer POC)
  • Multi-Tenant in Odoo (Aylem-only fuer diesen Plan; Master-Unit 11 macht das spaeter)
  • Kitchen Display System Hardware (Odoo hat KDS-Modul aber UI-Konfig + Hardware-Setup ist eigener Plan)
  • Bondrucker-Hardware (Odoo unterstuetzt ESC/POS, aber Aylem hat keinen physischen Bondrucker fuer Demo)
  • Multi-WhatsApp-Numbers (1 Number fuer Aylem)
  • Voice-→-WhatsApp-Bestaetigungs-SMS (wenn Anrufer per Voice reserviert, kriegt er KEINE WhatsApp-Bestaetigung — das ist eigener kleiner Folge-Plan)
  • Aylem-Inhaber-Live-Onboarding (Reference-Case-Demo, kein zahlender Kunde)
  • Echte Lieferzonen / Stripe-Payment / Online-Shop-Integration (Odoo kann’s, aber nicht Phase 1)
  • Bestellungs-Status-Updates via WhatsApp outbound (Restaurant pingt Anrufer „Essen ist fertig zur Abholung”) — eigener Folge-Plan
  • Synthetic Test-Suite (Master-Plan-Decision: out of scope)

Context & Research

Relevant Code and Patterns

  • mcp-hosting-fargate-tunnel.md — Standard-Hosting-Pattern seit 2026-05-11: klassisches ECS Fargate + cloudflared-Sidecar, kein ALB, kein ACM-Cert. Auch fuer Odoo + WhatsApp-Worker anwendbar (ein Tunnel mit 2 Hostnames, oder zwei separate Tunnels — entscheidet Implementation).
  • mcp-vf-hosted.md — erste Implementation des Fargate-Tunnel-Patterns, Tunnel-ID + Hostname-Setup-Doku.
  • agents-platform.md — Lambda-Layer-Pattern fuer Telegram-Push + Bedrock-Wrapper. WhatsApp-Worker ist KEIN Lambda (long-running FastAPI), aber _telegram.py-Helper-Pattern wird wiederverwendet.
  • _telegram.py — stdlib-only-Wrapper-Pattern. _odoo.py folgt demselben Stil.
  • _knowledge.py — Helper-Modul-Pattern mit Validation, fail-open, Logging.
  • server.py — bestehender MCP-Server mit 9 Tools. Wird um Feature-Flag erweitert, nicht refactored.
  • agent.py — SYSTEM_PROMPT_TEMPLATE. Wird umstrukturiert in shared_prompt.py (gemeinsam mit WhatsApp) + Channel-Adapter (Voice / Text).

Institutional Learnings

  • Brain+MCPs-Modell ist Pflicht (ADR keine-eigene-plattform): Tools bleiben in MCP, Channels (Voice / WhatsApp) sind nur Adapter. odoo-mcp als separates Repo waere konsequent, aber Aufwand fuer 1 Tenant ueberzogen — _odoo.py als Helper im tools-mcp ist pragmatisch und stack-konsistent.
  • Hosted-MCP-Architektur 2026 (ADR): klassisches Fargate ohne Express, cloudflared-Sidecar. Identisches Pattern fuer Odoo und WhatsApp-Worker.
  • Voice-Bot-DE-Pattern (voice-bot-de-pattern.md): Persona, Tool-Discipline, Anti-IVR-Liste, Confirm-Loop. Shared System-Prompt uebernimmt diese 1:1, Channel-Adapter ergaenzt textspezifische Variationen.
  • agentic_common.telegram als Layer-Modul war Cross-Repo-Falle (Plan 002 hat _telegram.py lokal gebaut). Lesson: jedes Helper-Modul lokal halten, kein Cross-Repo-Import.

External References

  • Meta WhatsApp Cloud API Policy 2026 — ab 15. Januar 2026 sind „general-purpose AI chatbots” verboten, task-specific (Booking/Order/Support) explizit erlaubt. Konsequenz: Bot-Greeting muss Use-Case klar machen, kein „Wie kann ich Ihnen helfen?”-Free-Roam.
  • PyWa Python Library — Production-ready WhatsApp Cloud API Wrapper mit nativer FastAPI-Integration, Signature-Verification, Webhook-Dispatch. Vermeidet manuelle urllib-Wrapper.
  • Odoo 18 Web Services Docs — JSON-RPC ist offizielle Schnittstelle. API-Key-basierte Auth statt Username/Password (ab Odoo 14). Webhooks built-in seit Odoo 18.
  • Odoo Restaurant POS Module — Restaurant-Erweiterung mit Tischplan, Reservierungen. Community-Edition kostenlos.
  • Odoo 18 Docker Compose Setup — Standard-Pattern. Wir nehmen odoo:18.0 Image, externe Postgres (RDS) statt Postgres-Container.

Key Technical Decisions

  • Odoo Community 18 statt 17 oder Enterprise. Begruendung: 18 hat native Webhooks (Odoo schickt POST bei pos.order create/update), DBs sind kompatibel zu spaeteren Updates, Community ist gratis. Enterprise lockt mit Mehrwerten (LDAP, Studio-UI-Editor), aber 31 EUR/User/Monat — nicht noetig fuer Aylem-Demo.
  • JSON-RPC statt XML-RPC. Beide werden von Odoo unterstuetzt, JSON-RPC ist moderner + besser dokumentiert + Python-natuerlicher. API-Key-Auth (Odoo-Benutzer mit Developer-Mode-Token), nicht Session-Cookie.
  • _odoo.py als lokales Helper-Modul, NICHT odoo-mcp als separates Repo. Begruendung: gleiche Architektur-Logik wie _telegram.py und _knowledge.py. Spaeter wenn 3+ Restaurant-Kunden parallel laufen, lohnt eigenes mcp-odoo-Repo (analog mcp-papierkram). Heute Overhead > Wert.
  • Feature-Flag STORAGE_BACKEND=odoo|sqlite|bothaber pro Worker-Standort unterschiedlich, weil zwei Worker-Instanzen jeweils EIGENE MCP-Subprocesses spawnen und eigene SQLite-Files haben (Mac vs Fargate-Container, nicht dieselbe DB):
    • LiveKit-Worker (Marvin-Mac): STORAGE_BACKEND=both — schreibt lokal-SQLite (Audit) + Odoo (Single-Source-of-Truth). SQLite-File lebt auf Marvin’s Mac persistent.
    • WhatsApp-Worker (Fargate-Container): STORAGE_BACKEND=odoo — nur Odoo. Container-SQLite waere ephemer (Restart-weg) und sowieso nicht im selben Filesystem wie der Mac-Mirror, also kein gemeinsamer Audit-Trail moeglich. Odoo ist hier alleine Source-of-Truth.
    • Default both als Wert ist NICHT mehr „Konvention” sondern „nur fuer Mac-Worker”; Fargate-Worker ueberschreibt das ueber Task-Definition-ENV.
  • WhatsApp-Worker als FastAPI in eigenem Fargate-Task, NICHT als Lambda. Begruendung: Long-running connection-Pool zu Bedrock + MCP-Subprocess + Odoo-Session, plus Webhook-Receiver braucht Always-On. Lambda haette Cold-Starts + Subprocess-Lifecycle-Probleme.
  • PyWa als WhatsApp-Lib statt eigener urllib-Wrapper. Begruendung: Signature-Verification + Media-Handling + Webhook-Routing sind nicht-trivial — PyWa hat das in Production-Reife. Pure-Python-Dependency, kein Bundle-Lock-in.
  • Ein Tunnel mit 2 Hostnames, nicht zwei separate Tunnels. odoo-aylem.agenticventures.de + wa-aylem.agenticventures.de ueber einen cloudflared-Container, der zwei Routes proxiet. Spart Container + Vereinfacht Tunnel-Token-Management. Bei Multi-Tenant (Phase B+) ggf. ein Tunnel pro Tenant.
  • Shared System-Prompt-Modul shared_prompt.py zwischen Voice und WhatsApp. Anti-Drift-Pattern — eine Quelle fuer Persona/Tonalitaet/Tool-Discipline. Channel-Adapter (Voice / Text) liefern ihre eigenen Vor/Nach-Saetze (z.B. Voice: keine URLs vorlesen; Text: kurz, gerne URL fuer Online-Shop). Speicherort konkret: tools-mcp/shared_prompt.py — beide Worker importieren ueber sys.path-Append (LiveKit-Worker hat tools-mcp eh als Subprocess-cwd; WhatsApp-Worker mountet tools-mcp ueber Docker-Volume oder pip-installiert via editable). Vermeidet Cross-Repo-Falle (vorher dokumentierte Lesson aus agentic_common.telegram-Failure).
  • Aylem-WhatsApp-Number = Marvin’s eigene zum Testen. Reference-Case-Asset, kein zahlender Kunde — Marvin registriert eine Number (eigene Test-SIM oder MVNO-Wegwerf-Karte) bei Meta Business Manager. Aylem-Inhaber-Verifikation ist Folge-Plan (Live-Pilot).
  • STORAGE_BACKEND=odoo schreibt zusaetzlich Telegram-Push. Auch wenn Odoo-UI da ist, behaelt der Restaurant-Inhaber Telegram als Push-Channel — wer permanent in Odoo schaut, ist niemand. Telegram bleibt Pflicht-Notification.

Open Questions

Resolved During Planning

  • Meta Cloud API direkt vs BSP → Cloud API direkt (User-Antwort).
  • WhatsApp-Bot-Scope → Full-Equivalent zum Voice-Bot (User-Antwort).
  • odoo-mcp eigenes Repo vs Helper-Modul → Helper-Modul _odoo.py (Pragmatik fuer 1-Tenant-POC).
  • Odoo 17 vs 18 vs Enterprise → 18 Community.
  • LiveKit-Worker Migration in diesem Plan? → Nein, bleibt auf Mac.

Deferred to Implementation

  • Konkrete Odoo-API-Endpoints fuer Reservation/POS-Order: Modul-Internals differieren leicht zwischen pos_restaurant-Versionen. Beim Schreiben des _odoo.py Modell-Namen (pos.order / restaurant.table / calendar.event) verifizieren ueber Odoo-Studio-UI oder ORM-Inspector.
  • Tunnel-Token-Strategie (ein Token mit 2 Hostnames vs zwei): Cloudflare erlaubt beide Patterns — entscheidet sich beim Cloudflare-Dashboard-Setup, low impact.
  • WhatsApp-Worker-Concurrency: wieviele parallele Conversations gleichzeitig? FastAPI ist async, Bedrock-Limits sind Account-weit. Bei MVP: 5 parallele Conversations sollten reichen, Load-Test in Unit 7.
  • Confirm-Loop-UX auf WhatsApp: Voice-Confirm ist „kurze Wiederholung in einem Satz”. WhatsApp-Equivalent koennte „aufgelistet als Bullet-Points + ja-Button”? Entscheidet sich beim Schreiben des Channel-Adapters; WhatsApp hat Interactive Messages (Quick Replies) als nativen Feature.
  • Konkrete Aylem-Tisch-Anzahl + Layout: Realistic-mock (analog Phase 002): 20 Tische, 4er-Tisch-Default, ein „grosse Gruppe”-Bereich mit 8er-Tischen.

High-Level Technical Design

Das illustriert die intendierte Architektur und ist directional guidance fuer Review, nicht Implementation-Spec.

Komponenten-Diagramm

flowchart TB
    A[Anrufer Browser/Phone]
    B[WhatsApp-User Smartphone]

    L[LiveKit Worker - Marvin-Mac]
    W[WhatsApp Worker - Fargate FastAPI]

    M[tools-mcp Subprocess]
    O[(Odoo 18 - Fargate)]
    P[(Postgres RDS)]
    T[Telegram Bot API]

    CF[Cloudflare Tunnel]
    META[Meta WhatsApp Cloud API]

    A --> L
    B --> META
    META <--> CF
    CF --> W
    L <-->|stdio| M
    W <-->|stdio| M
    M -->|JSON-RPC| O
    M --> T
    O <--> P
    CF --> O

    style M fill:#e1f5e1
    style O fill:#fff4e1

Datenfluss: Reservierung via WhatsApp

WhatsApp-User: "Tisch fuer 4 am Sa 19 Uhr Mueller 0170123"
   ↓ (Meta-Webhook POST)
Cloudflare-Tunnel → WhatsApp-Worker (FastAPI /webhook)
   ↓ (Bedrock-LLM verarbeitet, gleicher System-Prompt wie Voice-Bot)
LLM ruft: check_availability("2026-05-17", "19:00", 4)
   ↓ (MCP stdio)
tools-mcp/server.py → _knowledge.parse_hours("aylem") → returnt JSON
   ↓ (LLM antwortet "ok, ich wiederhole — passt das?")
Meta-API → User-Reply
   ↓ User: "ja"
LLM ruft: book_reservation("2026-05-17", "19:00", 4, "Mueller", "0170123")
   ↓ (mit STORAGE_BACKEND=both:)
   ├─ SQLite-Insert (lokaler Audit-Trail)
   ├─ Telegram-Push (✓ Reservierung RES-XXX...)
   └─ _odoo.create_calendar_event(...) → Odoo-JSON-RPC
         → Odoo legt Reservation auf Tisch 7 an, schickt Inhaber-Notification
   ↓ Bot antwortet "Reservierung notiert, das Team meldet sich."

_odoo.py-Modul-Skelett (Pseudo-Code)

# tools-mcp/_odoo.py — stdlib + urllib3 fuer JSON-RPC, kein Cross-Repo
 
class OdooClient:
    def __init__(self, base_url, db, api_key, uid): ...
    def call(self, model, method, args, kwargs={}): ...  # JSON-RPC execute_kw
    def create_reservation(self, date, time, guests, name, phone) -> str  # res_id
    def create_pos_order(self, items, customer, total) -> str  # order_id
    def list_today_reservations() -> list[dict]
    def find_free_table(date_time, guests) -> int  # table_id
 
# Fail-Behavior:
# - Network-Fehler: log.warning, return None, Caller faellt auf SQLite-only zurueck
# - Auth-Fehler: log.exception, return None, Telegram-Alert mit urgency=high

Implementation Units

flowchart TB
    U1[Unit 1: Odoo Hosting Fargate + RDS + Tunnel]
    U2[Unit 2: Aylem Workspace - Karte, Tische, Hours]
    U3[Unit 3: _odoo.py Helper + STORAGE_BACKEND Flag]
    U4[Unit 4: Meta WhatsApp Business Setup]
    U5[Unit 5: WhatsApp-Worker FastAPI + Bedrock + MCP]
    U6[Unit 6: shared_prompt.py + Channel-Adapter]
    U7[Unit 7: E2E-Smoke Voice + WhatsApp + Odoo]

    U1 --> U2
    U2 --> U3
    U1 --> U4
    U4 --> U5
    U3 --> U5
    U5 --> U6
    U3 --> U7
    U6 --> U7

Phase A — Odoo Foundation (3 Units)

Was diese Phase liefert: Restaurant-Management-UI fuer den Inhaber + JSON-RPC-Backend fuer den Bot. Standalone testbar bevor WhatsApp dazukommt.

  • Unit 1: Odoo Community 18 Hosting auf av-production Fargate

    Goal: Odoo-Web-UI erreichbar unter odoo-aylem.agenticventures.de. Login mit Admin-User. POS-Restaurant-Modul installiert.

    Requirements: R1

    Dependencies: keine

    Files:

    • Create: ~/source/restaurant-backend/ (neues Repo, eigenes Git, separat vom Vault)
    • Create: ~/source/restaurant-backend/cdk/lib/odoo-stack.ts (oder Python-CDK, was Marvin gewohnt ist — siehe ~/source/agents-platform/-Pattern)
    • Create: ~/source/restaurant-backend/docker/odoo/Dockerfile (FROM odoo:18.0)
    • Create: ~/source/restaurant-backend/docker/odoo/odoo.conf (DB-Pfad, Module-Pfad, Worker-Anzahl)
    • Create: ~/source/restaurant-backend/cloudflared/config.yml (Tunnel-Routes: odoo-aylem + wa-aylem Platzhalter)
    • Modify: Cloudflare Dashboard (neue Tunnel ODER neue Hostname-Routes in bestehendem Tunnel — entscheidet sich beim Setup)

    Approach:

    • Reuse mcp-vf-hosted-Pattern aus mcp-hosting-fargate-tunnel.md: klassisches ECS Fargate, kein ALB, kein ACM-Cert. Cloudflared-Sidecar fuer TLS-Edge.
    • Postgres: RDS-Instance db.t4g.micro in eu-central-1 (~14 EUR/Mo), nicht Postgres-Container im Task (Persistence-Issue + Backup-Aufwand).
    • ECS Task: 1 vCPU / 2 GB. Container 1 = Odoo (Port 8069), Container 2 = cloudflared-Sidecar. Localhost-Verbindung intern.
    • Secrets: Odoo-DB-Password + Odoo-Admin-Password + Cloudflare-Tunnel-Token in AWS Secrets Manager.
    • Module-Setup: Beim ersten Container-Start automatisch point_of_sale_restaurant + pos_restaurant_appointment (Reservierungs-Modul) aktivieren via Odoo-CLI (odoo -i base,point_of_sale_restaurant -d aylem --stop-after-init).
    • Memory-Constraint: Odoo 18 mit POS-Modul braucht real ~1.5 GB warm, beim Module-Install-Spike + parallel-Workers schnell ueber 2 GB. Empfehlung 2 vCPU / 4 GB statt 1/2 (Reviewer-Findings: parallele Reservierungs-Form-Inspektion + JSON-RPC-Bot-Calls auf 1 Worker blockieren sich; Cold-Start beim Modul-Load kann >90 s erreichen).
    • Backup: RDS-automated-Backups 7 Tage retention (kostet ~0 zusaetzlich bei db.t4g.micro).

    Patterns to follow:

    Test scenarios:

    • Happy path: curl https://odoo-aylem.agenticventures.de returnt Odoo-Login-Page (HTTP 200, Cloudflare-Edge-TLS, kein Cert-Warning)
    • Happy path: Login mit Admin-User klappt im Browser
    • Happy path: Settings → Apps → “Restaurant” + “POS Restaurant Reservations” sind installiert
    • Edge case: Task-Restart (Fargate kill) → Daten in RDS bleiben (Persistence-Verifikation)
    • Error path: Tunnel-Token rotiert/leer → cloudflared-Container faellt aus, Task-Health-Check failed, ECS startet neu
    • Integration: Cold-Start-Zeit vom Task-Spawn bis Login-Page erreichbar < 60 s

    Verification: Web-UI erreichbar, POS-Restaurant-Modul installiert. RDS persistiert Restart. CloudWatch-Logs zeigen Odoo + cloudflared sauber starten.

  • Unit 2: Aylem-Workspace in Odoo befuellen (Karte, Tische, Oeffnungszeiten)

    Goal: Odoo hat eine plausible Aylem-Konfiguration: Speisekarte mit Preisen, grafischer Tischplan, Oeffnungszeiten, Reservierungs-Form.

    Requirements: R2

    Dependencies: Unit 1

    Files:

    • Modify: Odoo-UI manuell (Daten-Setup, nicht Code) — Schritte werden in ~/source/restaurant-backend/docs/aylem-setup.md als Setup-Runbook dokumentiert.
    • Optional Create: ~/source/restaurant-backend/scripts/seed-aylem.py (idempotenter Seeder via JSON-RPC, falls Setup wiederholt werden muss).

    Approach:

    • Speisekarte (POS Products): Marvin importiert manuell oder per CSV ~20 Gerichte aus knowledge/aylem/menu.md (Doener-Teller, Beilagen, Getraenke, Desserts). Allergene als Tags. Kategorien identisch zu Markdown-Headings.
    • Tische (POS Restaurant Floors): Aylem-Saal mit 20 Tischen, davon 3× 8er, 8× 4er, 9× 2er. Grafische Anordnung via Drag-Drop in Odoo-UI.
    • Oeffnungszeiten (Working Hours / Resource Calendar): identisch zu knowledge/aylem/hours.md: Mo geschlossen, Di mit Mittagspause, Mi-So durchgaengig.
    • Reservierungs-Form: pos_restaurant_appointment-Modul aktiviert (siehe Unit 1), Form ueber Odoo-Website-Modul nach odoo-aylem.agenticventures.de/restaurant/reservation erreichbar.
    • Marvin als Inhaber-Mock-User + System-User fuer Bot: Admin = Marvin’s Test-Account; separat User bot@aylem.demo mit API-Key (nur dieser User schreibt via JSON-RPC).
    • API-Key generieren: in Odoo unter User → Settings → API-Keys, in AWS Secrets Manager hinterlegen.

    Patterns to follow:

    Test scenarios:

    • Happy path: Login als bot-User → JSON-RPC-Call pos.product list returnt 20 Produkte mit Preisen
    • Happy path: Reservierungs-Form-Test ueber Webseite (/restaurant/reservation) → Reservation in Odoo sichtbar
    • Edge case: Reservierung ueber API anlegen → Odoo-UI zeigt sie in Tisch-Kalender
    • Edge case: Allergen-Tags pro Produkt werden ueber JSON-RPC korrekt zurueckgeliefert
    • Integration: Aylem-Setup-Runbook dokumentiert (Marvin kann es in 1 h fuer Kunde Nr 2 wiederholen — der Bundle-Wert-Test)

    Verification: Per Browser-Inspection: Tisch-Plan zeigt 20 Tische, Karte hat alle Items. JSON-RPC mit API-Key liefert Produkte + Reservierungen.

  • Unit 3: _odoo.py Helper-Modul + STORAGE_BACKEND-Feature-Flag im tools-mcp

    Goal: tools-mcp/server.py kann Reservierungen + Bestellungen in Odoo schreiben statt nur SQLite. Default both, switchbar auf odoo-only oder sqlite-only.

    Requirements: R3

    Dependencies: Unit 1 (Odoo erreichbar), Unit 2 (API-Key vorhanden)

    Files:

    • Create: intern/projekte/telefon-assistent-aws/tools-mcp/_odoo.py (stdlib + urllib3 fuer JSON-RPC, kein Cross-Repo)
    • Modify: intern/projekte/telefon-assistent-aws/tools-mcp/server.py (STORAGE_BACKEND-ENV, book_reservation + take_order + escalate_to_human parallel-write je nach Flag)
    • Modify: intern/projekte/telefon-assistent-aws/tools-mcp/requirements.txt (urllib3 ergaenzen falls nicht da)
    • Modify: intern/projekte/telefon-assistent-aws/tools-mcp/.env.example (STORAGE_BACKEND, ODOO_URL, ODOO_DB, ODOO_API_USER, ODOO_API_KEY)
    • Modify: intern/projekte/telefon-assistent-aws/tools-mcp/README.md (neuer Setup-Block fuer Odoo)
    • Create: intern/projekte/telefon-assistent-aws/tools-mcp/tests/test_odoo.py

    Approach:

    • OdooClient-Class in _odoo.py mit Methoden: authenticate() (einmalig), call(model, method, args) (JSON-RPC-Wrapper), create_reservation(...), create_pos_order(...), find_free_table(...), list_today_reservations().
    • Auth-Modus: API-Key (Odoo 18 Standard), uid wird einmal beim Modul-Import ermittelt + gecached.
    • Fail-Behavior (differenziert, nicht pauschal fail-open):
      • Odoo unreachable (Network/5xx nach 3 Retries) → log.warning, return None.
        • Bei STORAGE_BACKEND=both (Mac-Worker): SQLite-Pfad uebernimmt, Bot antwortet Anrufer normal. Telegram-Push enthaelt Warnung „⚠ Odoo nicht erreicht, Eintrag nur in SQLite”. Restaurant-Inhaber MUSS Telegram + SQLite synchen.
        • Bei STORAGE_BACKEND=odoo (Fargate-Worker): kein Fallback. Bot antwortet User degraded: „Es gibt gerade ein technisches Problem, ich kann nicht buchen — bitte rufe direkt im Restaurant an oder schreib mir in einer Stunde nochmal.” + Telegram-Push mit urgent=True an Marvin. Keine falsche „gebucht”-Bestaetigung — Reputations-Schutz wichtiger als Bot-Verfuegbarkeit.
      • Auth-Fehler (API-Key abgelaufen) → log.exception, return None, Telegram-Push mit urgent=True an Marvin (Setup-Fehler).
      • 5xx von Odoo → Retry 3× mit Exponential-Backoff (max 3 s gesamt, damit Webhook-Timeout nicht reisst), dann obigen Fail-Pfad.
    • Feature-Flag in server.py: ENV STORAGE_BACKEND mit Werten sqlite|odoo|both. Default both waehrend Migration. Jeder Tool-Call:
      1. SQLite-Insert (wie bisher) wenn sqlite|both
      2. Odoo-Insert (neu) wenn odoo|both
      3. Telegram-Push (unveraendert) — formatiert mit Odoo-ID falls vorhanden, sonst SQLite-ID
    • ID-Korrelation: Sub-Plan 002 hatte RES-XXXXXX als SQLite-ID. Odoo hat eigene numerische IDs. Telegram-Push enthaelt beide: „Reservierung RES-3F2A8C (Odoo #1234) — 4P am Sa 19:00 …“.
    • Bei both-Mode mit Odoo-Fehler: SQLite-Eintrag wird trotzdem geschrieben + Telegram-Push gesendet. Tool-Antwort an Bot ist normal. Aylem-Inhaber sieht in Telegram die Notiz „⚠ Odoo nicht erreicht, Eintrag nur in SQLite — bitte manuell pruefen”.

    Patterns to follow:

    Test scenarios:

    • Happy path: STORAGE_BACKEND=odoo, book_reservation(...) legt Reservation in Odoo an, kein SQLite-Eintrag
    • Happy path: STORAGE_BACKEND=both, book_reservation(...) legt in beiden an, ID-Korrelation in Telegram-Push
    • Edge case: STORAGE_BACKEND=odoo aber Odoo unreachable → fail-open, kein DB-Insert, Telegram-Push „⚠ Odoo nicht erreicht”, Bot-Antwort an Anrufer trotzdem positiv (verhindert Verzweiflung)
    • Edge case: ODOO_API_KEY fehlt → log.warning beim Modul-Import, Methoden returnen None, Bot faellt auf SQLite zurueck
    • Edge case: leeres STORAGE_BACKEND → default both
    • Error path: Odoo gibt 500 zurueck → 3× Retry mit Backoff, dann log.exception + None
    • Error path: Authentifizierung schlaegt fehl → urgent Telegram-Alert an Marvin
    • Integration: kompletter Bot-Flow ueber tools-mcp/server.py.book_reservation, beide Storages parallel, Odoo-UI zeigt die Reservation, SQLite-DB zeigt sie auch, Telegram-Push enthaelt beide IDs

    Verification: Manueller Smoke-Test: python -c "from server import book_reservation; ..." legt eine Test-Reservation an, Odoo-UI zeigt sie binnen 2 s, SQLite-DB enthaelt sie, Telegram-Push enthaelt beide IDs. 6+ pytest-Tests gruen.

Phase B — WhatsApp-Worker (3 Units)

Was diese Phase liefert: zweiter Kanal mit gleichem Bot-Brain. Voice-Bot bleibt unveraendert lauffaehig auf Marvin’s Mac. WhatsApp und Voice schreiben beide in Odoo (oder SQLite-Mirror).

  • Unit 4: Meta WhatsApp Business Cloud API Setup

    Goal: WhatsApp-Business-Profil fuer „Aylem Demo” ist bei Meta registriert, eine Test-Number-Verifikation erfolgt, Webhook-Endpoint vorbereitet (URL kommt aus Unit 5).

    Requirements: R4, R8 (Meta-Policy-Konformitaet)

    Dependencies: keine — kann parallel zu Phase A laufen

    Files:

    • Create: intern/projekte/telefon-assistent-aws/whatsapp/META_SETUP.md (Setup-Runbook fuer Marvin, dokumentiert die Klicks bei Meta Business Manager)
    • Modify: AWS Secrets Manager → neuer Secret aylem-wa/meta-tokens mit Access-Token + Phone-Number-ID + Webhook-Verify-Token

    Approach:

    • Meta Business Account: Marvin nutzt seinen bestehenden Meta-Business-Account (oder erstellt einen fuer Aylem-Demo). Gewinnst Business → Apps → Neue App → Type „Business” → WhatsApp Product hinzufuegen.
    • Test-Phone-Number-Architektur (wichtig, nicht verwechseln):
      • Aylem-Bot-Number = Meta-Sandbox-Number (von Meta zugewiesen, kostenlos, kein eigener SIM noetig)
      • Empfaenger (z.B. Marvin’s Handy) = verifiziert via SMS-Code bei Meta Business Manager (max 5 verifizierte Numbers in Sandbox-Mode)
      • Test-Flow: Marvin schreibt VON seinem Handy AN die Sandbox-Number — das oeffnet das 24h-Service-Window, Bot antwortet inbound.
      • Outbound-Proaktiv (Bot pingt unaufgefordert): braucht Approved-Template, ist out-of-scope dieser Iter.
      • Demo-Implikation: Bei Sales-Lead-Demo muesste der Lead seine Handy-Number vorher bei Marvin’s Meta-Account verifizieren lassen — bricht Spontanitaet. Realistischer Demo-Modus: Marvin selbst tippt live von seinem Handy, Lead schaut zu.
    • Permanenter Access-Token: System-User in Meta Business Manager, Token „never expire”, in AWS Secrets Manager.
    • Webhook-Verify-Token: random String, Marvin generiert, in Secrets Manager. Meta sendet GET mit hub.verify_token beim Setup, Worker muss antworten.
    • Greeting-Message-Template (notwendig fuer Outbound nach 24-h-Service-Window): „Hallo, hier ist der KI-Assistent von Aylem Eat & Meet. Womit kann ich helfen? (Reservierung, Bestellung, Frage zur Karte)” — als „UTILITY”-Template bei Meta zur Approval einreichen. Approval-Zeit: 1–24 h.
    • Privacy + DSGVO: in Meta Business Manager EU-Data-Region aktivieren (verfuegbar seit 2024). Aylem-Datenschutzerklaerung ist out-of-scope dieser Iter — Reference-Case-Demo, kein Live-Pilot.
    • Bot-Disclosure: erster Bot-Antwort jeder Conversation enthaelt KI-Disclaimer (Pflicht laut Meta-Policy 2026 + EU AI Act Art. 50). System-Prompt sorgt dafuer in Unit 6.

    Patterns to follow:

    Test expectation: none direkt — manueller Setup, Verifikation in Unit 5 sobald Worker laeuft.

    Verification: Meta Business Manager zeigt aktive App mit WhatsApp-Produkt. Test-Number ist „Aktiv”. Greeting-Template ist „Approved”. Access-Token + Verify-Token in Secrets Manager. META_SETUP.md ist fertig + lesbar.

  • Unit 5: WhatsApp-Worker FastAPI + Bedrock + MCP-Subprocess

    Goal: FastAPI-Service in av-production Fargate, empfaengt Meta-Webhooks, verarbeitet via Bedrock-LLM + MCP-Tools, antwortet ueber Meta-API. Erreichbar unter wa-aylem.agenticventures.de/webhook.

    Requirements: R4, R5

    Dependencies: Unit 1 (Tunnel-Infra), Unit 3 (tools-mcp mit _odoo.py), Unit 4 (Meta-Token verfuegbar)

    Files:

    • Create: ~/source/restaurant-backend/whatsapp-worker/app.py (FastAPI-App: /webhook GET fuer Meta-Verify, POST fuer eingehende Messages)
    • Create: ~/source/restaurant-backend/whatsapp-worker/bot.py (Bedrock-Wrapper, Conversation-State-Management, MCP-Tool-Calling)
    • Create: ~/source/restaurant-backend/whatsapp-worker/conversation_store.py (Conversation-State im Memory oder Redis; bei MVP: simples Dict mit TTL, da WhatsApp-Conversations selten >30 min)
    • Create: ~/source/restaurant-backend/whatsapp-worker/Dockerfile
    • Create: ~/source/restaurant-backend/whatsapp-worker/requirements.txt (fastapi, pywa, boto3, mcp client)
    • Modify: ~/source/restaurant-backend/cdk/lib/odoo-stack.ts — zweiter ECS-Task fuer WhatsApp-Worker, gleicher Cluster
    • Create: ~/source/restaurant-backend/whatsapp-worker/tests/test_webhook.py + test_bot.py

    Approach:

    • FastAPI-App mit zwei Endpoints:
      • GET /webhook — Meta-Verify-Challenge (returnt hub.challenge wenn hub.verify_token matched).
      • POST /webhook — Meta-Inbound-Message. PyWa parsed das Payload, dispatched an bot.handle_message(user_id, text).
    • Conversation-State: Dict {phone_number_hash: {messages: [...], started_at: ts}} mit TTL 24 h (entspricht Meta-Service-Window, nicht Voice-30-min-Pattern — WhatsApp-User schreiben diskontinuierlich). Bei MVP keine Persistenz (Restart = State weg, akzeptabel, Risiko siehe Risk-Tabelle). Phone-Number HMAC-gehasht im Memory-Key, Klartext nur in Message-Body. Spaeter Redis oder DynamoDB.
    • Webhook-ACK-Pattern (Pflicht): Meta-Webhook hat 20-s-Timeout, der Bot-Flow (Bedrock + Tool-Calls + Odoo-JSON-RPC + Outbound-Reply) kann das nicht zuverlaessig in einer Synchron-Response halten. Architektur:
      1. POST /webhook returnt SOFORT 200 (innerhalb ~500 ms) nach Signature-Verify
      2. Message-Processing laeuft via BackgroundTasks / asyncio.create_task
      3. Outbound-Reply geht als separater Meta-API-Call zur User-Phone, nicht als Webhook-Response-Body
    • Bot-Logik (im Background-Task):
      1. Conversation-History laden (oder neu starten, falls TTL expired — Bot eroeffnet dann „Hallo, wir hatten frueher gechattet, aber unser Gespraech ist abgelaufen. Wie kann ich helfen?” statt frisches Greeting)
      2. LLM-Call zu Bedrock Haiku 4.5 EU mit shared System-Prompt + Conversation
      3. Wenn LLM Tool-Call macht → MCP-Subprocess aufrufen (gleicher _telegram.py/_odoo.py-Pfad wie LiveKit-Worker)
      4. LLM-Antwort + ggf. Tool-Resultate als WhatsApp-Reply ueber PyWa (Meta-API-Outbound, NICHT Webhook-Response)
    • Multi-Message-Burst (User schickt 3 Messages in 5s): Debouncing — 2-3 s Wait nach erster Message, dann alle gesammelten Messages zusammen als ein LLM-Turn verarbeiten. Vermeidet 3 parallele LLM-Calls + 3 ueberlappende Replies.
    • MCP-Integration: tools-mcp wird als subprocess.Popen gestartet beim Worker-Boot, kommuniziert ueber stdio. Eine MCP-Subprocess-Instanz pro Worker-Replica (5–10 Conversations parallel sollten reichen).
    • WhatsApp-Quick-Replies (Worker-State-Machine, NICHT LLM-Output): PyWa unterstuetzt Interactive Buttons. Bedrock-Haiku gibt Plain-Text-Output zurueck — Worker-State-Machine erkennt CONFIRM-State (alle Slots gesammelt) und sendet programmatisch eine Interactive-Button-Message „Soll ich die Reservierung anlegen? [JA] [Aendern] [Abbrechen]” statt LLM-Text als Buttons zu parsen. LLM-Output bleibt Plain-Text fuer alle anderen Turns.
    • Bot-Disclosure im ersten Turn: System-Prompt zwingt zur Eroeffnung „Hallo, hier ist der KI-Assistent von Aylem. Ich kann fuer dich reservieren, eine Bestellung aufnehmen oder Fragen zur Karte beantworten. Womit kann ich helfen?”
    • Signature-Verification: PyWa macht das mit X-Hub-Signature-256-Header gegen APP_SECRET. Ohne valide Signature → 401.
    • CloudWatch-Logs strukturiert pro Conversation-Turn (timestamp, user_id-hashed, message, tool_calls, response).

    Patterns to follow:

    Test scenarios:

    • Happy path: Meta sendet Verify-Challenge (GET) → Worker returnt hub.challenge mit HTTP 200
    • Happy path: Meta sendet Inbound-Message (POST) → Worker dispatched zu bot.handle, Bot ruft Bedrock + MCP-Tool, antwortet via Meta-API
    • Happy path: User schreibt “Tisch fuer 4 am Sa 19 Uhr Mueller 0170123” → check_availability + book_reservation Tool-Calls, Eintrag in Odoo+SQLite, Telegram-Push, WhatsApp-Antwort mit Confirm-Buttons
    • Edge case: User-Message-Volume von 5 parallelen Conversations → Async-Handling klappt, keine Deadlocks
    • Edge case: Conversation-Timeout 30 min → naechste Message startet neue Conversation mit Greeting + Disclosure
    • Edge case: WhatsApp-Audio-Voice-Message → Worker antwortet „Ich verstehe nur Text — bitte schreib mir deine Anfrage” (kein Audio-STT in MVP)
    • Edge case: User schreibt non-DE Sprache (TR/AR) → System-Prompt-Block “Mehrsprachigkeit” greift wie bei Voice-Bot
    • Error path: Invalide Meta-Signature → 401, log.warning
    • Error path: Bedrock-Throttling → Worker antwortet „Einen Moment bitte, ich bin gerade ueberlastet”, Retry nach 5 s
    • Error path: tools-mcp Subprocess gestorben → respawn, Telegram-Alert
    • Integration: End-to-End — Marvin schreibt von seinem Handy an Aylem-Number, Bot antwortet, Reservation landet in Odoo + Telegram-Push

    Verification: Marvin schreibt manuell ueber WhatsApp 3 Szenarien (Reservierung, Bestellung, Frage zur Karte) — alle drei resultieren in korrekten Tool-Calls, Eintraegen in Odoo + Telegram-Push.

  • Unit 6: shared_prompt.py + Channel-Adapter (Voice vs Text)

    Goal: Eine Quelle fuer Persona/Tonalitaet/Tool-Discipline/Confirm-Loop, gemeinsam fuer LiveKit-Voice und WhatsApp-Text. Channel-Adapter ergaenzen kanalspezifische Variationen.

    Requirements: R6

    Dependencies: Unit 5 (WhatsApp-Worker existiert), Plan 002 (SYSTEM_PROMPT_TEMPLATE in agent.py funktioniert)

    Files:

    • Create: ~/source/restaurant-backend/shared/shared_prompt.py (gemeinsames Modul, oder im tools-mcp wenn closer dort — entscheidet Implementation)
    • Modify: intern/projekte/telefon-assistent-aws/livekit-agent/agent.py (SYSTEM_PROMPT_TEMPLATE importiert aus shared_prompt + Voice-Adapter-Add-On)
    • Modify: ~/source/restaurant-backend/whatsapp-worker/bot.py (importiert shared_prompt + Text-Adapter-Add-On)
    • Create: Tests in beiden Repos zur Konsistenz-Pruefung

    Approach:

    • shared_prompt.py exposes build_shared_prompt(date_context, channel: "voice" | "text") -> str. Enthaelt:
      • Persona (Aylem-Telefonassistent/Chat-Assistent, abhaengig von channel)
      • Restaurant-Stammdaten (statisch, kommen aus Aylem-Meta-File)
      • Tool-Discipline-Block (KB-Tools, check_availability, escalate_to_human)
      • Confirm-Loop-Block (gleich fuer beide)
      • Mehrsprachigkeit (DE/TR/AR/…) — gleich fuer beide
      • Eskalations-Triggers — gleich fuer beide
    • Channel-Adapter (Voice): ergaenzt am Anfang/Ende: „Kurze Saetze. Keine URLs vorlesen. Anti-IVR-Phrasen-Liste.”
    • Channel-Adapter (Text): ergaenzt am Anfang/Ende: „Knappe Text-Nachrichten, gerne URL fuer Online-Shop ergaenzen. Emoji sparsam. Quick-Reply-Buttons nutzen wenn Bedrock im Tool-Response anbietet.”
    • Re-Use im LiveKit-Worker: bestehender SYSTEM_PROMPT_TEMPLATE wird durch build_shared_prompt(date_context=..., channel="voice") ersetzt.
    • Re-Use im WhatsApp-Worker: identischer Import.
    • Cross-Repo-Strategie: shared_prompt.py kann in ~/source/restaurant-backend/shared/ leben, beide Repos installieren es per pip install -e ../restaurant-backend/shared/. Oder vereinfacht: einfach Datei in beide Repos kopieren + Git-Submodule. Entscheidet sich beim Schreiben.

    Patterns to follow:

    • Bestehender SYSTEM_PROMPT_TEMPLATE in agent.py.
    • Plan-002-Decision „Voice-Bot-DE-Pattern als Source-of-Truth fuer Persona” — bleibt erhalten, jetzt ueber shared_prompt-Modul.

    Test scenarios:

    • Happy path: build_shared_prompt("..", "voice") enthaelt „kurze Saetze”, build_shared_prompt("..", "text") enthaelt „knappe Text-Nachrichten”
    • Happy path: beide Channels enthalten identische Tool-Discipline-, Confirm-Loop-, Mehrsprachigkeits-Bloecke
    • Edge case: invalide channel param → ValueError mit aussagekraeftiger Message
    • Integration: LiveKit-Worker startet mit shared_prompt + Voice-Adapter, WhatsApp-Worker mit shared_prompt + Text-Adapter — beide Bots zeigen identisches Verhalten in identischen Konversations-Szenarien (mod Output-Layer)

    Verification: Marvin macht Side-by-Side-Test: ein Voice-Call vs ein WhatsApp-Chat mit identischer Anfrage „Tisch fuer 4 am Sa 19 Uhr Mueller 0170” — beide Bots fuehren denselben Confirm-Loop, beide rufen check_availability + book_reservation, beide schreiben in Odoo. Unterschied nur im Output-Channel.

Phase C — Integration + Smoke (1 Unit)

  • Unit 7: End-to-End-Smoke-Test Voice + WhatsApp + Odoo + Telegram

    Goal: Dokumentierter Walkthrough beweist dass beide Channels gleich gut funktionieren. Substrat fuer Demo-Pack (Master-Plan-Unit 13).

    Requirements: R7

    Dependencies: Units 1–6

    Files:

    • Create: intern/projekte/telefon-assistent-aws/demo-pack/2026-05-XX-multichannel-walkthrough.md (Walkthrough-Doku)

    Approach:

    • Test-Skript (manueller Walkthrough, Marvin spielt durch):
      1. Voice-Call gegen /cartesia → „Reserviere Mittwoch 19 Uhr 4 Personen Mueller 0170” → check_availability + book_reservation → Eintrag in Odoo sichtbar + SQLite-Eintrag + Telegram-Push
      2. WhatsApp an Aylem-Demo-Number → „Hallo, ich moechte Mittwoch 20 Uhr fuer 2 Personen reservieren, Name Schmidt, 0170 9999” → Bot wiederholt, User „Ja” via Quick-Reply → Eintrag in Odoo + SQLite + Telegram-Push
      3. WhatsApp → „Was kostet ein Doener-Teller?” → get_menu-Tool-Call → korrekte Antwort aus knowledge-Files
      4. WhatsApp → „Macht ihr Catering fuer 80 Personen?” → escalate_to_human → Eintrag in Odoo-Aufgabe (oder SQLite escalations + Telegram-Alert mit hoher Prio)
      5. WhatsApp → „Tschuess” → freundliche Verabschiedung + Conversation-Close
      6. Voice-Call → „Reservierung fuer Montag” → check_availability blockt Ruhetag → Bot schlaegt Alternative vor → User akzeptiert Dienstag → korrekte Buchung in Odoo
    • Walkthrough-Doku: pro Schritt User-Input, Bot-Antwort, Tool-Calls aus Log, Odoo-UI-Screenshot (Hash-anonymisiert), Telegram-Push-Screenshot.
    • Performance-Messung: WhatsApp-Latenz Time-to-First-Reply, Voice-Latenz Time-to-First-Audio. Sollte beides <3 s sein.

    Test expectation: none — manueller End-to-End-Walkthrough.

    Verification: Walkthrough-File ist fertig, alle 6 Szenarien sind dokumentiert mit Tool-Call-Spuren + UI-Screenshots. Latenz <3 s in beiden Channels.

System-Wide Impact

  • Interaction graph: Bestehender LiveKit-Worker bleibt unveraendert auf Mac. tools-mcp wird erweitert um _odoo.py + STORAGE_BACKEND-Flag — bestehende Tools (book_reservation, take_order, escalate_to_human) bekommen einen zweiten Persistence-Pfad. Neuer WhatsApp-Worker als separater Service in av-production, ruft denselben tools-mcp-Subprocess (frische Instanz pro Worker-Boot). Odoo ist Single-Source-of-Truth fuer Reservierungen + Bestellungen; SQLite bleibt Local-Mirror fuer Audit + Fallback.
  • Error propagation: Odoo unreachable → fail-open auf SQLite-only mit urgent-Telegram-Push. Meta-API-Error → Worker antwortet User mit „Einen Moment, ich bin gerade ueberlastet”. WhatsApp-Bot crashes → Fargate-Health-Check-Fail → Container-Restart, max 30 s Downtime, Meta queued Inbound-Messages.
  • State lifecycle risks: Race-Condition zwischen parallel-Channels — Voice-Anrufer und WhatsApp-User koennten gleichzeitig denselben Tisch-Slot buchen wollen. check_availability prueft heute nur Oeffnungszeit (NICHT Tisch-Belegung). Odoo-Reservierungs-Modul macht die Tisch-Belegungs-Logik intern, Race-Konflikt detected dort und meldet Fehler zurueck — Bot eskaliert. Vor Live-Pilot: explizit testen. Conversation-State im WhatsApp-Worker ist in-memory + 30 min TTL — bei Worker-Restart sind aktive Conversations verloren (User bekommt nach naechster Message neues Greeting). Akzeptabel fuer MVP.
  • API surface parity: tools-mcp wird von zwei Clients gerufen (LiveKit + WhatsApp) — Tool-Signaturen muessen channel-agnostisch sein. Sind bereits so. Zusaetzlich: escalate_to_human bekommt einen optionalen channel-Param („voice” / „whatsapp”) fuer das Telegram-Push-Format. get_hours bleibt identisch. book_reservation bleibt identisch.
  • Integration coverage: End-to-End nur durch manuellen Voice + WhatsApp + Odoo-Test pruefbar. Mocking ist sinnlos auf dieser Schicht.
  • Unchanged invariants: LiveKit-Worker auf Mac bleibt. Bestehende SQLite-Schema bleibt. Existing 9 MCP-Tools bleiben funktional. Telegram-Push-Konvention bleibt. Voice-Bot kann komplett ohne Odoo + WhatsApp weiter laufen wenn beide ausfallen (SQLite-only-Pfad bleibt aktiv).

Risks & Dependencies

RiskLikelihoodImpactMitigation
Odoo-Setup ist komplex — 1–2 Tage Aufwand statt geschaetzten 4 hMittelMittelSetup-Runbook (aylem-setup.md) parallel zur Implementation schreiben, dokumentiert jeden Klick. Bei Hard-Stuck: Odoo-Cloud-Trial als Setup-Reference-Implementierung.
Meta-API-Approval fuer Greeting-Template dauert > 24 hMittelNiedrigSetup-Antrag in Unit 4 SOFORT stellen, parallel zu Phase A laufen lassen. Bei Verzoegerung: User-initiated-Conversations (24 h Service Window) funktionieren auch ohne Template-Approval.
Aylem-WhatsApp-Number-Verifikation scheitertMittelNiedrig (Reference-Case)Meta Sandbox-Number reicht fuer Reference-Case. Echte Number nur fuer Live-Pilot-Folge-Plan noetig.
Bedrock-Throttling im av-production Account bei parallelen ConversationsMittelMittelAccount-Quota pruefen, ggf. Service-Quota erhoehen lassen. Worker-Retry mit Exponential-Backoff. Bei MVP-Volumen (5 Conversations parallel) sollte Default-Quota reichen.
Cloudflare-Tunnel-Token leak oder RotationNiedrigHochToken in AWS Secrets Manager, niemals in Container-Image, ECS-Exec-Role hat read-only-Zugriff. Rotation alle 6 Monate.
Conversation-State In-Memory-Loss bei Worker-RestartHochNiedrigAkzeptiert fuer MVP. Bei naechster Message faengt User bei Greeting an. Spaeter: Redis oder DynamoDB als Persistence-Layer.
Tisch-Slot-Race zwischen Voice + WhatsAppMittelHochAnnahme „Odoo macht intern Tisch-Belegungs-Check” ist UNVERIFIZIERTpos_restaurant_appointment ist Calendar-Layer, nicht Inventory-Lock. Pre-Unit-7-Verifikation Pflicht: zwei parallele JSON-RPC-create_reservation()-Calls auf denselben Slot feuern und Odoo-Verhalten inspizieren. Wenn kein DB-Constraint: application-level Locking in _odoo.py (Vor-Lookup + Constraint-Insert mit Postgres-Advisory-Lock).
Meta-Account-Sperre trotz task-specific BotNiedrigHochRecovery-Plan: (a) Conversations-Logging als Audit-Trail (zeigt task-specific-Verhalten); (b) zweiter Sandbox-Account als Fallback; (c) typischer Bot-Flow als Pre-Approval-Beleg dokumentiert. Plus: Off-Topic-Refusal-Block im System-Prompt verstaerken — Bot lehnt Free-Roam-Chat aktiv ab.
Aylem-Name-Nutzung ohne Inhaber-KonsensMittelMittelVor Unit 4-Setup-Start: 15-min Anruf bei Aylem-Inhaber fuer Demo-Konsens ODER Pivot auf fiktiven Tenant-Namen („Demo Eat & Meet”, Adresse generisch). Reference-Case mit echtem Aylem-Namen ohne Inhaber-OK ist UWG-/Markenrechtliches Risiko und kann Sales-Reputation kosten wenn Inhaber davon erfaehrt.
Webhook-DoS via WhatsApp-Flood (1000 msgs/min)NiedrigMittelCloudflare-WAF-Rate-Limit am wa-aylem.agenticventures.de (60 req/min pro Source-IP); per-Phone-Number-Rate-Limit im Worker (max 30 msg/min/User); Bedrock-Call-Budget pro User-Tag.
Prompt-Injection ueber User-Input (massenhafte Tool-Calls)MittelMittelServer-side Tool-Limits in tools-mcp: book_reservation max 3/24h pro Phone-Number, take_order max 20 Items + 500 EUR Wert, escalate_to_human max 1/Call. System-Prompt-Constraint reicht NICHT alleine.
Setup-Aufwand: Plan-Schaetzung 1-2 Wochen ist optimistischHochMittelRealistic Wallclock 2-3 Wochen (Reviewer-Findings): Unit 1 2-3 Tage Odoo+RDS+Tunnel, Unit 2 1-2 Tage Odoo-UI-Setup, Unit 3 2-3 Tage _odoo.py+Tests, Unit 4 1-3 Tage Meta-Approval-Wait, Unit 5 3-4 Tage FastAPI-Worker, Unit 6 1-2 Tage Cross-Repo, Unit 7 1 Tag. Bei Opportunitaets-Kosten gegen 10k-MRR-Sprint pruefen.
PyWa Supply-Chain-Risk (Solo-Maintainer)MittelMittelVor Unit 5: gh-repo-check (Stars, Release-Cadence, Maintainer-Anzahl). Bei Solo-Maintainer mit >6 Monaten ohne Release: Fallback auf eigenen stdlib-urllib-Wrapper analog _telegram.py (~200 LoC). PyWa-Version pinnen in requirements.txt.
Cloudflare-Tunnel als Single-Service-DependencyNiedrigHochTLS-Edge sieht Klartext-Webhook-Bodies (PII). AVV mit Cloudflare als Verarbeiter pruefen. Bei DSGVO-strengen Folge-Kunden: Hetzner-Spur (siehe intern/wissen/patterns/hosting-industriekunden.md) als Alternative dokumentieren.
Meta-Policy ab Jan 2026: „general-purpose AI banned”NiedrigHoch (Account-Sperre)Bot ist task-specific (Booking + Order + KB + Escalation), kein Free-Roam. Bot-Disclosure im ersten Turn. System-Prompt verbietet Off-Topic-Antworten. Plan-Decision unter Decisions.
DSGVO: Meta speichert Inbound-Messages auch bei EU-RegionMittelMittelAylem-Reference-Case-Demo, kein zahlender Kunde — DSGVO-Pflichten beim Live-Pilot via Master-Unit 12 abgehandelt. Bot speichert keine Telefonnummern dauerhaft im WhatsApp-Worker (TTL 30 min).
Odoo-DB-Backup-VerlustNiedrigHochRDS automated backups 7-Tage-Retention reicht fuer Demo. Live-Pilot braucht laengere Retention.
WhatsApp-Voice-Messages eingehend (User schickt Audio)HochNiedrigBot antwortet freundlich „Ich verstehe nur Text — bitte schreib deine Anfrage.” Audio-STT in MVP nicht.

Documentation Plan

  • Update _index.md Open-Loops um Plan-003-Eintrag
  • Update Master-Plan 2026-05-13-001 Open-Loops „echte Bestell-Anbindung” → erledigt via Plan 003
  • Create intern/wissen/prozesse/odoo-restaurant-pattern.md nach erfolgreichem Setup — Bundle-Asset fuer Restaurant-Vertical
  • Create intern/wissen/prozesse/whatsapp-bot-pattern.md — Bundle-Asset fuer Multi-Channel-Bots
  • Update intern/capabilities/repos/_index.md um neues Repo restaurant-backend
  • Update intern/firma/produkt-bundle.md — Restaurant-Vertical als „Capability ready ab 2026-05-XX” markieren

Operational / Rollout Notes

  • Rollout-Reihenfolge: Phase A (Units 1+2+3) → Phase B (Units 4+5+6) → Phase C (Unit 7). Phase A liefert Standalone-Wert (Odoo-UI fuer Inhaber, tools-mcp schreibt in Odoo). Phase B kann parallel laufen sobald Unit 1 + 4 fertig sind.
  • „Fertig”-Check: Reservierung via Voice landet in Odoo + Reservierung via WhatsApp landet in Odoo + beide triggern Telegram-Push. Latenz <3 s in beiden Channels.
  • LiveKit-Worker bleibt unveraendert auf Marvin’s Mac waehrend dieses Plans. Phase 2 (zukuenftiger Live-Pilot) wuerde den nach Fargate ziehen.
  • STORAGE_BACKEND-Migration: Default both waehrend Plan 003 + 4 Wochen nachher. Dann Switch auf odoo only nach erfolgter Validation. SQLite-DB bleibt als Read-Only-Historical-Archive.
  • Cost-Schaetzung av-production zusaetzlich:
    • Fargate ECS Task (2 vCPU / 4 GB Odoo, Memory-Constraint-Review siehe Risks): ~45 EUR/Mo
    • Fargate ECS Task (0.5 vCPU / 1 GB) WhatsApp-Worker: ~12 EUR/Mo
    • RDS db.t4g.micro Postgres: ~14 EUR/Mo
    • Cloudflare-Tunnel: 0 EUR
    • Meta WhatsApp Cloud API: 0 EUR fuer user-initiated Conversations im Free-Tier (1000/Mo); ABER business-initiated UTILITY-Templates (z.B. Reservierungs-Bestaetigung nach 24h-Service-Window) kosten ~0.07–0.15 EUR/Stueck — bei 100 Reservierungen mit Bestaetigung = 7–15 EUR/Mo
    • Bedrock-Inferenz (Haiku 4.5 EU): ~0.10 EUR pro 100 Conversations
    • Summe ~70–85 EUR/Mo fuer Aylem-Demo + Test-Volumen (vorher unterschaetzt mit ~50 EUR weil Memory-Spec eng + Template-Konversationen unterschlagen)
  • Pre-Live-Pilot-Checkliste: dieses Plan ist Reference-Case. Live-Pilot mit zahlendem Kunden braucht zusaetzlich AVV mit Aylem (Master-Unit 12), echte Aylem-Phone-Number (eigene SIM verifiziert), Odoo-Multi-Tenant (Master-Unit 11), DPO-Check.

Sources & References