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.pymuss 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:
-
Odoo Community 18 als Restaurant-Backend in
av-productionFargate — 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. -
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:
- 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.
- 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 unterodoo-aylem.agenticventures.deueber 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.pyHelper-Modul schreibt Reservierungen + Bestellungen via Odoo-JSON-RPC in Odoo. Feature-FlagSTORAGE_BACKEND=odoo(statt SQLite) — beide Pfade lauffaehig. - R4. WhatsApp-Worker (FastAPI + PyWa + Bedrock Haiku 4.5 EU) laeuft in
av-productionFargate, registriert als Meta Cloud API Webhook unterwa-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-productionFargate + Postgres-RDS + Cloudflare-Tunnel tools-mcp/_odoo.pyals Helper-Modul (NICHT als separates Repo, analog_telegram.py/_knowledge.py)- Feature-Flag
STORAGE_BACKEND=odoo|sqliteintools-mcp/server.py— SQLite-Pfad bleibt funktional - WhatsApp-Worker FastAPI-Service in
av-productionFargate - 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.pyfolgt 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.pyals 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.pylokal 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.0Image, 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.ordercreate/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.pyals lokales Helper-Modul, NICHT odoo-mcp als separates Repo. Begruendung: gleiche Architektur-Logik wie_telegram.pyund_knowledge.py. Spaeter wenn 3+ Restaurant-Kunden parallel laufen, lohnt eigenesmcp-odoo-Repo (analogmcp-papierkram). Heute Overhead > Wert.- Feature-Flag
STORAGE_BACKEND=odoo|sqlite|both— aber 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
bothals Wert ist NICHT mehr „Konvention” sondern „nur fuer Mac-Worker”; Fargate-Worker ueberschreibt das ueber Task-Definition-ENV.
- LiveKit-Worker (Marvin-Mac):
- 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.deueber 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.pyzwischen 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=odooschreibt 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.pyModell-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=highImplementation 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-aylemPlatzhalter) - 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.microineu-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:
- mcp-vf-hosted Stack als Vorbild
- mcp-hosting-fargate-tunnel.md Pattern §“CDK-Stack-Skelett”
~/source/agents-platform/als allgemeines av-production-CDK-Vorbild
Test scenarios:
- Happy path:
curl https://odoo-aylem.agenticventures.dereturnt 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.
- Create:
-
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.mdals 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 nachodoo-aylem.agenticventures.de/restaurant/reservationerreichbar. - Marvin als Inhaber-Mock-User + System-User fuer Bot: Admin = Marvin’s Test-Account; separat User
bot@aylem.demomit 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:
- Bestehende
knowledge/aylem/menu.mdals Inhalts-Quelle (Mock-Karten-Items + Preise). - Odoo POS Restaurant Setup-Doku (Live-Recherche beim Setup).
Test scenarios:
- Happy path: Login als bot-User → JSON-RPC-Call
pos.product listreturnt 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.
- Modify: Odoo-UI manuell (Daten-Setup, nicht Code) — Schritte werden in
-
Unit 3:
_odoo.pyHelper-Modul +STORAGE_BACKEND-Feature-Flag im tools-mcpGoal:
tools-mcp/server.pykann Reservierungen + Bestellungen in Odoo schreiben statt nur SQLite. Defaultboth, switchbar aufodoo-only odersqlite-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 +urllib3fuer JSON-RPC, kein Cross-Repo) - Modify:
intern/projekte/telefon-assistent-aws/tools-mcp/server.py(STORAGE_BACKEND-ENV,book_reservation+take_order+escalate_to_humanparallel-write je nach Flag) - Modify:
intern/projekte/telefon-assistent-aws/tools-mcp/requirements.txt(urllib3ergaenzen 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.pymit 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 miturgent=Truean Marvin. Keine falsche „gebucht”-Bestaetigung — Reputations-Schutz wichtiger als Bot-Verfuegbarkeit.
- Bei
- Auth-Fehler (API-Key abgelaufen) → log.exception, return None, Telegram-Push mit
urgent=Truean Marvin (Setup-Fehler). - 5xx von Odoo → Retry 3× mit Exponential-Backoff (max 3 s gesamt, damit Webhook-Timeout nicht reisst), dann obigen Fail-Pfad.
- Odoo unreachable (Network/5xx nach 3 Retries) → log.warning, return None.
- Feature-Flag in
server.py: ENVSTORAGE_BACKENDmit Wertensqlite|odoo|both. Defaultbothwaehrend Migration. Jeder Tool-Call:- SQLite-Insert (wie bisher) wenn
sqlite|both - Odoo-Insert (neu) wenn
odoo|both - Telegram-Push (unveraendert) — formatiert mit Odoo-ID falls vorhanden, sonst SQLite-ID
- SQLite-Insert (wie bisher) wenn
- ID-Korrelation: Sub-Plan 002 hatte
RES-XXXXXXals 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:
tools-mcp/_telegram.py— stdlib-only, fail-loud, Logger-Pattern.tools-mcp/_knowledge.py— Validation + Caching-Pattern.- Bestehende Tool-Funktionen in
tools-mcp/server.pyals Aufruf-Stil.
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=odooaber Odoo unreachable → fail-open, kein DB-Insert, Telegram-Push „⚠ Odoo nicht erreicht”, Bot-Antwort an Anrufer trotzdem positiv (verhindert Verzweiflung) - Edge case:
ODOO_API_KEYfehlt → log.warning beim Modul-Import, Methoden returnen None, Bot faellt auf SQLite zurueck - Edge case: leeres
STORAGE_BACKEND→ defaultboth - 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. - Create:
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-tokensmit 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_tokenbeim 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:
- Meta WhatsApp Cloud API Getting Started
- PyWa Quickstart als Setup-Companion
- voice-bot-de-pattern.md §“KI-Disclaimer im Greeting” — gleicher Wortlaut wie Voice-Bot
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.
- Create:
-
Unit 5: WhatsApp-Worker FastAPI + Bedrock + MCP-Subprocess
Goal: FastAPI-Service in
av-productionFargate, empfaengt Meta-Webhooks, verarbeitet via Bedrock-LLM + MCP-Tools, antwortet ueber Meta-API. Erreichbar unterwa-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 (returnthub.challengewennhub.verify_tokenmatched).POST /webhook— Meta-Inbound-Message. PyWa parsed das Payload, dispatched anbot.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:
POST /webhookreturnt SOFORT 200 (innerhalb ~500 ms) nach Signature-Verify- Message-Processing laeuft via
BackgroundTasks/asyncio.create_task - Outbound-Reply geht als separater Meta-API-Call zur User-Phone, nicht als Webhook-Response-Body
- Bot-Logik (im Background-Task):
- 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)
- LLM-Call zu Bedrock Haiku 4.5 EU mit shared System-Prompt + Conversation
- Wenn LLM Tool-Call macht → MCP-Subprocess aufrufen (gleicher
_telegram.py/_odoo.py-Pfad wie LiveKit-Worker) - 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.Popengestartet 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 gegenAPP_SECRET. Ohne valide Signature → 401. - CloudWatch-Logs strukturiert pro Conversation-Turn (timestamp, user_id-hashed, message, tool_calls, response).
Patterns to follow:
- agent.py — wie der LiveKit-Worker den MCP-Subprocess startet (
MCPServerStdio). - PyWa FastAPI Webhook Pattern
- agents-platform Telegram-Push — gleicher Bedrock-Wrapper-Style fuer LLM-Calls.
Test scenarios:
- Happy path: Meta sendet Verify-Challenge (GET) → Worker returnt
hub.challengemit 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.
- Create:
-
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_TEMPLATEin 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_TEMPLATEimportiert 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_TEMPLATEwird durchbuild_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 perpip 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_TEMPLATEin 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.
- Create:
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):
- Voice-Call gegen
/cartesia→ „Reserviere Mittwoch 19 Uhr 4 Personen Mueller 0170” → check_availability + book_reservation → Eintrag in Odoo sichtbar + SQLite-Eintrag + Telegram-Push - 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
- WhatsApp → „Was kostet ein Doener-Teller?” → get_menu-Tool-Call → korrekte Antwort aus knowledge-Files
- WhatsApp → „Macht ihr Catering fuer 80 Personen?” → escalate_to_human → Eintrag in Odoo-Aufgabe (oder SQLite escalations + Telegram-Alert mit hoher Prio)
- WhatsApp → „Tschuess” → freundliche Verabschiedung + Conversation-Close
- Voice-Call → „Reservierung fuer Montag” → check_availability blockt Ruhetag → Bot schlaegt Alternative vor → User akzeptiert Dienstag → korrekte Buchung in Odoo
- Voice-Call gegen
- 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.
- Create:
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_humanbekommt einen optionalenchannel-Param („voice” / „whatsapp”) fuer das Telegram-Push-Format.get_hoursbleibt identisch.book_reservationbleibt 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
| Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|
| Odoo-Setup ist komplex — 1–2 Tage Aufwand statt geschaetzten 4 h | Mittel | Mittel | Setup-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 h | Mittel | Niedrig | Setup-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 scheitert | Mittel | Niedrig (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 Conversations | Mittel | Mittel | Account-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 Rotation | Niedrig | Hoch | Token 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-Restart | Hoch | Niedrig | Akzeptiert fuer MVP. Bei naechster Message faengt User bei Greeting an. Spaeter: Redis oder DynamoDB als Persistence-Layer. |
| Tisch-Slot-Race zwischen Voice + WhatsApp | Mittel | Hoch | Annahme „Odoo macht intern Tisch-Belegungs-Check” ist UNVERIFIZIERT — pos_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 Bot | Niedrig | Hoch | Recovery-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-Konsens | Mittel | Mittel | Vor 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) | Niedrig | Mittel | Cloudflare-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) | Mittel | Mittel | Server-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 optimistisch | Hoch | Mittel | Realistic 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) | Mittel | Mittel | Vor 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-Dependency | Niedrig | Hoch | TLS-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” | Niedrig | Hoch (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-Region | Mittel | Mittel | Aylem-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-Verlust | Niedrig | Hoch | RDS automated backups 7-Tage-Retention reicht fuer Demo. Live-Pilot braucht laengere Retention. |
| WhatsApp-Voice-Messages eingehend (User schickt Audio) | Hoch | Niedrig | Bot 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.mdnach 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.mdum neues Reporestaurant-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: Defaultbothwaehrend Plan 003 + 4 Wochen nachher. Dann Switch aufodooonly 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
- Master-Plan: 2026-05-13-001-feat-voice-bot-de-production-plan.md
- Sub-Plan Vorgaenger: 2026-05-13-002-feat-voice-bot-substanz-iteration-plan.md
- Hosting-Pattern (Vault): mcp-hosting-fargate-tunnel.md
- MCP-VF-Hosted Vorbild (Vault): mcp-vf-hosted.md
- Agents-Plattform Pattern (Vault): agents-platform.md
- ADR Brain+MCPs (Vault): keine-eigene-plattform.md
- ADR Hosted-MCP-Architektur (Vault): hosted-mcp-architektur-2026.md
- Voice-Bot-Pattern (Vault): voice-bot-de-pattern.md
- HeyJulia Produkt-Memory: „Productized Vertical fuer KMU-Angebotsautomatisierung, White-Label via Softwarehersteller” — analoges Pattern hier auf Restaurant angewendet
- Externe — Meta WhatsApp Cloud API: developers.facebook.com/docs/whatsapp/cloud-api
- Externe — PyWa Python Lib: pywa.readthedocs.io
- Externe — Odoo 18 Web Services: odoo.com/documentation/18.0/developer/howtos/web_services.html
- Externe — Odoo 18 Docker Setup: dev.to Streamlined Odoo 18 Development with Docker Compose
Related
- _index — Projekt-Index Telefon-Assistent
- 2026-05-13-001-feat-voice-bot-de-production-plan — Master-Plan
- 2026-05-13-002-feat-voice-bot-substanz-iteration-plan — Substanz-Iter
- mcp-hosting-fargate-tunnel
- keine-eigene-plattform
- produkt-bundle