# Plappi MVP — Handoff / Continue-Prompt (Stand 2026-06-22)

> Diesen ganzen Block als erste Nachricht in die neue Claude-Code-Session pasten.

---

Wir arbeiten an **Plappi** — bilingualer KI-Sprach-Tutor für Kinder (Deutsch ⇄ Serbisch),
Handy-App jetzt, Raspberry-Pi-Gerät später. Setze dort fort, wo wir aufgehört haben.

## ZUERST LESEN
- Memory: `plappi-mvp` (Hauptstand!), `plappi-project`, `plappi-founder-story`, `plappi-orf-wien-heute-dreh`.
- Projekt auf der Hetzner-VM: **`/home/nk/hobo-godmode/plappi-mvp`** (falls nicht auf der VM: erst dorthin verbinden).
- Im Projekt: `docs/PRODUKT-SPEC-v1.md`, `README.md`, dieser Handoff.

## ARCHITEKTUR & STAND (läuft)
- **Engine = ElevenLabs Conversational-AI Realtime-Agent** (public). `ELEVENLABS_AGENT_ID = agent_0801kvp0hsy3e0vs6kdw8945a46p`. Key in `plappi-mvp/.env` (`ELEVENLABS_API_KEY`, **nach ORF rotieren**).
- **Stimme = Sandra** (warme deutsche Märchen-Erzählerin, `0x1SL53CGQ0glOQTYzVa`), Modell `eleven_turbo_v2_5`. **6 weitere wählbare Stimmen** unter `/voices` (Lana, Mandy, Ela, Katharina, Vivien, Jessica). Nemanja wählt per Ohr. (Die alten Balkan-Klon-Stimmen waren mies → ersetzt durch deutsche Profi-Stimmen.)
- **Frontend** (`clients/web/`): `index.html`+`app.js` = ElevenLabs Client-SDK, Knopf „Mit Plappi sprechen". Orb pulsiert im Rhythmus zum Audio (SDK-Frequenzdaten), Farbe = Zustand (grün=zuhören, pink=reden), Wach-Ton beim Verbinden. Echo-Schutz: Mikro stumm während Plappi spricht.
- **Kind-Profil „Kiki" (3 J.)** + `profile.html`-Formular (Lieblingstier, Freund, Geschwister, sensible Themen: Töpfchen/neues Baby/…). Speichern schreibt personalisiert + Brevity-Regeln ins Agent-Prompt. ⚠️ NIE leeres Profil POSTen (überschreibt Kiki!).
- **Dashboard** `/dashboard.html`: Stats, gelernte Wörter, Gesprächs-Verlauf, Lehrpläne. Daten via `convai.py`+`store.py` (SQLite `data/plappi.sqlite`).
- **Agent gehärtet**: 1-Satz-Brevity, kein „bist du noch da", patient turn-taking, `disable_first_message_interruptions`.
- **Live-Config verifiziert 22.06.** (GET /v1/convai/agents): Ohren = `scribe_realtime` quality `high` (Top), Mund = `eleven_turbo_v2_5` (richtige Realtime-Stufe; v3 = nicht echtzeittauglich), Stimme = Sandra `0x1SL53CGQ0glOQTYzVa`. **Gehirn (LLM) auf Nemanjas Wunsch von `gpt-4o-mini` → `gpt-4o` hochgezogen** (PATCH 200, verifiziert) — temp 0.5. ⚠️ `.env ELEVENLABS_MODEL=eleven_v3` ist irreführend/ungenutzt (der Agent nutzt turbo_v2_5).
- **NEU 22.06. — Serbisch-Intensität (3 Stufen):** `profile.py` `BILINGUAL_LEVELS` {sanft=max.1 Wort / mittel=2-3 Wörter (Default) / intensiv=möglichst viel Serbisch+ganze Sätze}, gesteuert über Profilfeld `bilingual_level`, Regler in `profile.html` (👶). Kiki steht auf **intensiv** (Nemanja: „viel mehr Serbisch"), live im Agent-Prompt verifiziert. Alte feste „höchstens 1 Wort"-Regel raus. Brevity auf 1-2 kurze Sätze gelockert (damit serb. Wort + Übersetzung reinpassen, ohne zu plappern).
- **NEU 22.06. — Chat-Seiten gefixt:** `app.js` `onMessage` ist jetzt source-aware (`source==="user"` → Kind rechts, sonst Plappi links). Vorher feuerte `onUserTranscript` nie → Kind-Text fehlte/alles links.
- **NEU 22.06. — Persona komplett neu (Gespräch statt Drill):** `profile.py` PERSONA_BASE neu = gesprächsorientiert, subtile Immersion, Spiele, bilinguale Geschichten, Anti-Loop, interessen-getrieben (Nemanja-Feedback „sehr mau, Schleife"). Temp 0.75. first_message serbisch-führend. Siehe Memory [[plappi-interaction-philosophy]].
- **NEU 22.06. — LERN-ENGINE gebaut (Mastery + adaptiver Lehrplan):** Forschungs-Spec via Multi-Agent-Workflow (`backend/learning_spec.json`, 210 Wörter `curriculum_data.json`).
  - `backend/curriculum.py` — 210-Wörter-Kernwortschatz Tier 1-6 + 23 Satz-Bausteine, 5 Alters-Bänder (toddler→young_adult), `select_focus()` = alters-adaptive Fokus-Auswahl (Reviews-first + neue Tier-gegated, Cluster-interleaved).
  - `backend/learning.py` — 5-Zustands-Mastery (new→receptive→practising→productive→mastered→retired), Leitner-Box-Scheduler, **LLM-Transkript-Bewertung via claude-CLI haiku** (lemmatisiert: `bila→biti`, `vrtiću→vrtić`), **Fluency-Fast-Path** (spontane Flüssigkeit → Wörter sofort gemeistert + Tiers frei + Level-Sprung), Level/CEFR-Tracking. Konstanten in `config.py`.
  - `store.py sync()` → Bewertung läuft im **Hintergrund-Thread** (`_background_score`, ~100s/Gespräch, blockiert Dashboard nicht), danach Agent-Prompt-Re-Push mit neuen Fokus-Wörtern.
  - `profile.py build_agent_prompt` → `_learning_block` speist Fokus-/gemeisterte Wörter + Altersband als **KONTEXT** ein (kein Drill).
  - `dashboard.html` → Lernfortschritt (Niveau/CEFR-Balken, Wort-Status-Badges).
  - ⚠️ claude-CLI braucht `--mcp-config '{"mcpServers":{}}'` (NICHT `'{}'` → „Invalid MCP config"); Bewertung dauert ~100s/Gespräch (haiku) → Optimierungspunkt.

## OFFENE PUNKTE (Priorität)
1. **STABILE URL = TOP-PRIO** (Nemanja extrem genervt vom Link-Chaos). Lösung: VM hat **Caddy** (`hobo-engine-caddy-1`, Docker, Auto-HTTPS, 80/443). Plan **`app.helloplappi.com`**.
   - **STATUS 22.06. Vormittag — server-seitig fertig vorbereitet:**
     - ✅ uvicorn rebindet auf `0.0.0.0:8800` (öffentlich via ufw dicht: nur 80/443/22 offen).
     - ✅ ufw-Regel `allow from 172.18.0.0/16 to any port 8800 proto tcp` → Caddy-Container erreicht Host auf `172.18.0.1:8800` (verifiziert von innen, Health OK). Ohne diese Regel: Timeout (ufw droppt docker→host).
     - ✅ Caddy-Block fertig in `deploy/caddy-block-app.txt` (reverse_proxy `172.18.0.1:8800`). Caddyfile `/srv/dev/hobo-engine/Caddyfile` gesichert als `.bak.2026-06-22`.
     - ✅ DNS-Provider geklärt: **name.com** (NS ns*.name.com). `helloplappi.com` root → 34.111.179.208 (Marketing-Site/GCP). `app.helloplappi.com` = noch KEIN Record. Keine name.com-API-Creds auf der Box → Nemanja legt Record manuell an.
   - **BLOCKER (Nemanja):** A-Record `app` → `89.167.82.93` bei name.com anlegen. ⚠️ Caddy-Block ERST NACH gesetztem DNS einfügen+reloaden (sonst Let's-Encrypt-Rate-Limit-Sperre für den Hostnamen). Reihenfolge: DNS setzen → `dig +short app.helloplappi.com` zeigt 89.167.82.93 → Block aus `deploy/caddy-block-app.txt` in Caddyfile anhängen → `docker exec hobo-engine-caddy-1 caddy validate --config /etc/caddy/Caddyfile` → `docker exec hobo-engine-caddy-1 caddy reload --config /etc/caddy/Caddyfile`.
   - ✅✅ **GELÖST 22.06. via TAILSCALE FUNNEL — feste, öffentliche, stabile URL, KEIN Tunnel-Chaos mehr:**
     **`https://umg-8gb-hel1-3.tailfd9a40.ts.net:10000`** (Funnel auf öffentl. Port 10000 → localhost:8800).
     Eingerichtet mit `sudo tailscale funnel --bg --https=10000 http://localhost:8800` (--bg = persistent in tailscaled, übersteht Reboot). Status: `tailscale funnel status`. Aus (falls nötig): `sudo tailscale funnel --https=10000 off`. ⚠️ Port 443 NICHT anfassen (proxyt auf localhost:8770 = anderer Dienst); 8443/9443 = tailnet-only VNC/etc. Plappi additiv auf 10000. End-to-end getestet (/, /voices.html, /api/voices, /api/health alle 200). Einziger Vorbehalt: manche restriktiven Mobilfunk-/Firmen-Netze blocken Nicht-443-Ports → dann app.helloplappi.com (s.u.) oder Pfad auf 443.
   - **localhost.run/serveo verworfen** — droppen server-seitig (503) obwohl ssh-Prozess lebt; für Tests untauglich.
   - **app.helloplappi.com bleibt die spätere GEBRANDETE URL** (server-seitig vorbereitet, s.o.): wenn gewünscht DNS-Record bei name.com → dann separater Caddy-Block live. Optional, nicht mehr dringend, da Funnel-URL stabil läuft.
2. **Echo am echten Gerät verifizieren** (Nemanja: „redet in einer Tour, weckt sich selbst"). = akustische Rückkopplung über Lautsprecher. Mit Kopfhörern testen lassen. Wenn weiter Problem: Push-to-Talk-Fallback ODER Pi-Gerät.
3. **Welche Stimme hat Nemanja gewählt?** (fragen + ggf. als Default setzen).
4. **Wake-Word = Pi-Gerät** (openWakeWord, 189 Samples liegen in `clients/pi/wakeword/samples/positive/`). Im Handy-Browser nicht zuverlässig.
   - **NEU 22.06. — Browser-Wake-Word + Schlaf-Modus implementiert** (`app.js` neu): „Plappi aktivieren" tippen (Mic-Permission) → dann „Plappi" sagen weckt (Web Speech API, nur Chrome) → Gespräch → **5 s ohne Antwort → Schlaf** (Schlaf-Ton + gedimmter Orb) → „Plappi" weckt wieder. Best-effort im Browser; Pi bleibt der zuverlässige Weg. `silence_end_call_timeout` server-seitig = 400 abgelehnt → Client-Idle-Timer (IDLE_MS=5000) übernimmt.
   - **NEU 22.06. — Persona beruhigt (Nemanja: „Fragen-Bombardement, sprunghaft, anstrengend"):** PERSONA_BASE „du BEGLEITEST, nicht führen", max. 1 Frage (nicht jede Antwort), beim Thema des Kindes bleiben, nie sprunghaft, Spiele/Geschichten nur sparsam. first_message kurz „Ćao {name}!" (keine Frage, spielt bei jedem Wecken).
7. **STIMME 22.06. — native serbische Stimme + Qualitätsmodell:** Serbisch-Aussprache von Sandra (deutsch) war „Katastrophe" (Nemanja). Umgestellt auf **Ivana** (`JDYfAX20MbYfJGIjPTZz`, nativ sr/hr, ruhig/kinderfreundlich) + Modell **`eleven_multilingual_v2`** (beste Serbisch-Aussprache; turbo/flash sprechen Serbisch schlecht — etwas mehr Latenz akzeptiert). `/voices` zeigt jetzt **serbische Hörprobe** + native Kandidaten (Ivana/Nina/Ida fertig; Ana/Mihael/Sandra wg. Quota offen). `voices.py` AGENT_TTS_MODEL=multilingual_v2.
8. **⚠️ BLOCKER — ElevenLabs-Key-Quota erschöpft:** Key „Plappi" hat **Limit 1000 Credits → 0** (`quota_exceeded` HTTP 401). Blockiert direkte TTS (weitere Hörproben + Tiergeräusch-SFX). Live-ConvAI läuft separat (Minuten), daher Gespräche weiter möglich. TODO Nemanja: im ElevenLabs-Dashboard Key-Quota erhöhen / Credits aufladen.
9. **NEU 22.06. — „immer Serbisch pushen":** PERSONA_BASE-Regel ergänzt: auch wenn Kind Deutsch spricht → Plappi antwortet HAUPTSÄCHLICH Serbisch, spiegelt Deutsch NICHT.
10. **NEU 22.06. — One-Tap:** `app.js activate()` → ruft direkt `wake()` (ein Tipp = sofort Gespräch, kein zweiter Klick). Auto-Start beim App-Öffnen NICHT möglich (Browser verlangt Nutzer-Geste fürs Mikro) → der eine Tipp bleibt.
11. **NEU 22.06. spätabends — Wake-Word/Schlaf KOMPLETT RAUS, reines Tippen-zum-Reden:** Wake-Word + Auto-Schlaf verhedderten sich (nach Schlaf nicht weckbar, Piep-Schleife, Doppel-Sessions). `app.js` v7 = simpel: Tipp=reden, Tipp=Stopp, EINE Session (lock), klare Zustände (▶/⏳/🟢/🗣️/⏹). Kein SpeechRecognition/idle/Töne-Spam mehr. Echtes Wake-Word erst am Pi.
12. **NEU 22.06. — Bilingual KORRIGIERT (war „nur Serbisch", Tochter verstand nichts):** PERSONA jetzt DEUTSCH-dominant (Trägersprache), Serbisch dosiert: Gruß/Lob/Abschied serbisch + einzelne Wörter SOFORT übersetzt im Stil „das ist blau – to je plavo". `bilingual_level=sanft`. Alte „hauptsächlich Serbisch"- + „getrennte Sätze"-Regeln entfernt.
13. **NEU 22.06. — Gehirn = `gemini-2.5-flash` (LearnLM)** statt gpt-4o (Nemanja-Wunsch; Google LearnLM ist in Gemini 2.5 eingebaut, pädagogisch getunt; ElevenLabs ConvAI unterstützt es nativ). Per Simulation verifiziert: deutsch-dominant, Serbisch+Übersetzung, beim Thema bleibend. Stimme = Ivana (nativ sr) + `eleven_multilingual_v2`.
14. **⚠️ KEY-QUOTA ≠ KONTO-GUTHABEN:** Konto hat ~124k Credits, ABER der API-Key „Plappi" hat ein **eigenes Limit von 1000 (erschöpft)** → blockiert direkte TTS (Tiergeräusche-SFX + restliche Stimmproben). FIX (Nemanja): im ElevenLabs-Dashboard Settings→API Keys→„Plappi" das **Key-Limit erhöhen/entfernen** (NICHT nur Konto aufladen). Live-ConvAI unberührt (Gespräche laufen).
15. **NEU 22.06. spät — Bilingual rebalanciert + Recast + sanftes Einschlafen:** „sanft" war zu wenig (Gemini blieb 98% Deutsch) → `bilingual_level=mittel` + feste Regel „Serbisch in JEDE Antwort". **Recast-Technik** im Prompt: Kind sagt's deutsch → Plappi gibt serbische Entsprechung zurück, dann weiter (z.B. „sie haben Ball gespielt" → „Igrali su se loptom!"); auch im Geschichten-Spiel („Was haben sie gemacht?" → Recast → weiter). Kein „bist du noch da" mehr, max. EIN Abschied „idem spavati". `app.js` v8: Inaktivitäts-Timer (IDLE_MS=16000) → Gespräch endet sanft → Ruhe, Tippen weckt (kein Wake-Word/Reconnect = keine Bugs). Per Simulation verifiziert (Gemini aktiv, Serbisch jede Antwort, Recast greift).
16. **NEU 22.06. — Versionsanzeige + Prompt konsolidiert (AKTUELL: v10):** v10 = Spiele GANZ kurz (lange Texte nur bei Geschichten), Stimme ausdrucksstärker (tts.stability 0.30; `style`/speaker_boost greifen bei multilingual_v2 NICHT = None) + Prompt-Regel „fröhlich/Ausrufe". Aktive Stimme aktuell **Ida** (`d3l4f3HgkE3P6Fo91lYA`, vom User auf /voices gewählt). | Prompt war auf 6157 Zeichen aufgebläht (verwässerte Regeln) → komplett neu/schlank (~3280 Z.), Priorität „Serbisch-Lehren = Hauptaufgabe", mehrere serb. Wörter/Antwort mit Übersetzung, Recast zentral, längere Geschichten, Name nur selten. `bilingual_level=intensiv` (jetzt sicher dank Trägersprache-Regel). Per Sim verifiziert (Miška-Geschichte, ti si slikao/boje/plavo/sunce/hvala). **VERSIONIERUNG (Nemanja-Wunsch):** `app.js` `const VERSION`, Badge oben links (`#ver`), bei JEDER Änderung VERSION + `app.js?v=` erhöhen + User die Nummer nennen. Cache via No-Store-Header + `?v=`.
17. **NEU 22.06. abends — Demo-Architektur entschieden + Geschichten-Pivot (AKTUELL v12):**
   - **Hardware-Plan ORF (Di 24.06.):** KEIN Raspi (unrealistisch in der Zeit). Stattdessen: **Handy = verstecktes Gehirn** (App), **Mini-Bluetooth-Lautsprecher im Plappi-Gehäuse = Stimme** (Audio-Output routet automatisch übers OS auf BT), **Handy-Mikro = Ohren**.
   - **Screen-Sleep gelöst (v12):** `app.js` hat jetzt **Screen Wake Lock** (`acquireWakeLock` in onConnect, release in stop). Bildschirm bleibt AN → App/Mikro/Audio schlafen nicht ein. ⚠️ Web-App kann NICHT mit Display-aus/Handy-locked laufen (Browser friert ein) → Handy-Screen bleibt an + versteckt; App im Vordergrund. Idle-Auto-Sleep auf 5 Min hoch (Always-On-Feel; ruhige Persona hält Plappi still). Demo-Ablauf: 1× vor Interview tippen → Session live + bleibt offen → auf Kamera einfach reden (kein Wake-Word-Gefummel).
   - **Geschichten-Pivot:** Für die Demo **vorgerenderte bilinguale Geschichten** (bombensicher) statt nur flaky Live-ConvAI. ⚠️ **NUR GEMEINFREIE Klassiker** (Grimm/Volksmärchen/Aesop) — NIE Ravensburger o.ä. (Urheberrecht → nicht verkaufbar, vgl. tiptoi-Lehre). „Sinnvoll bilingual" = Deutsch trägt + **wiederkehrender serbischer Refrain** + kumulative Wiederholung, NICHT Vokabel-Kärtchen.
   - **Story 1 fertig:** „Die große Rübe / Velika repa" (`docs/stories/rube.md`), Refrain „Vuci! Vuci!". Mit **edge-tts** (frei, kein Key) gerendert → `data/audio/stories/rube.mp3` (de-DE-SeraphinaMultilingualNeural). Hörbar: `…ts.net:10000/audio/stories/rube.mp3`. Nemanjas Freigabe der Story+Ansatz steht noch aus.
   - **Story-Teller-AI.zip** (in OneDrive/Plappi, 114 MB) NICHT zugreifbar (über MCP-Limit; entpackte Dateien nicht in Cloud). Funktion (Text→bilinguale TTS) ohnehin selbst baubar.
18. **⛔ #1-PRIORITÄT 22.06. spätabends — BINDENDE PÄDAGOGIK-NEUAUSRICHTUNG (`docs/BILINGUAL-PEDAGOGY.md`, evidenzbasiert): ÜBERHOLT den gesamten Inline-Übersetzungs-Ansatz dieser Session.** Memory [[plappi-interaction-philosophy]] entsprechend aktualisiert.
   - **VERBOTEN ab sofort:** Inline-Wort-Übersetzung im Satz („das ist blau – to je plavo", „Katze heißt mačka") = belegt SCHLECHTESTE Form (Switch-Kosten 68%→55%, Übersetzungs-Krücke, max. AG-Last). → Die aktuelle PERSONA_BASE-Regel „jedes serbische Wort SOFORT auf Deutsch dazu" + die Geschichte `rube.md` (mit „die repa/der deda" inline) sind damit FALSCH und müssen weg.
   - **NEU (so bauen):** (1) **Eine Stimme = eine Sprache** (Audio-OPOL): DE-Erzähler + konsequent serbische Figur (Baka/Deda/Tier), die NIE Deutsch spricht → löst zugleich Akzent (jede Stimme nur Muttersprache, native SR-Stimme = akzentfrei). (2) Trennung auf **Satz-/Szenen-Ebene, nie im Satz**. (3) Bedeutung aus **Kontext** (Sound, Handlung des Kindes/Audio-TPR, Tonfall, Wiederholung) — NICHT übersetzen. (4) Geschichten = **Preview(DE)→View(SR-Kern+Refrain)→Review(DE)**. (5) **3–5 Anker-Chunks/Folge, 10×+** über Geschichte+Spiel+Lied, ganze Phrasen („Daj mi loptu") statt Einzel-Labels. (6) **1 ganz-serbisches Mini-Lied**/Folge. (7) **Mitmach-Pause** (2–3 s): das KIND produziert das Zielwort, Plappi HÖRT ZU (Live-Dialog = Lern-Motor). (8) rezeptiv vor produktiv, kein Nachsprech-Zwang. (9) falls Wechsel: serbischer Frame → kurze DE-Reflexion in SEPARATEM Turn.
   - **UMBAU-TODO:** `profile.py` PERSONA_BASE (Glossing raus, Sprecher-Trennung + Kontext + Mitmach-Pause rein); Geschichten neu (PVR + serbische Figur + Refrain + Mini-Lied, DE-Stimme & native SR-Stimme getrennt rendern); Lern-Engine: Fokuswörter „in serb. Kontext einbetten", nicht als DE=SR-Paar. Goldstandard Akzent = native Aufnahmen, idealerweise **Nemanja als serbische Figur** (mit ihm klären).
- **TODO offen — Tiergeräusche (a):** sobald Key-Limit hoch: ElevenLabs Sound-Effects-API generiert Kuh/Hund/Katze… → `data/audio/animals/`, Wiedergabe via ConvAI **Client-Tool** (`play_animal_sound`) + Frontend-Handler, Prompt fürs Spiel „Pogodi životinju". Plumbing noch zu bauen.
6. **OFFEN — Serbisch-ASR:** `agent.language="de"` → Serbisch wird als Deutsch transkribiert/angezeigt. Raw-API-PATCH für Zusatzsprache (`language_presets.sr`) = **400** (ElevenLabs verlangt Aktivierung übers Dashboard). TODO: im ElevenLabs-Dashboard (VM-Chrome) beim Agenten „Serbisch" als Additional Language + Language-Detection-System-Tool aktivieren. Betrifft v.a. Erwachsenen-Test (Kind spricht überwiegend Deutsch).
5. **ORF-Dreh Di 24.06. 10:00** (Launchtag): `docs/ORF-GOLDEN-PATH.md`, `docs/ORF-GERAETE-HUELLE.md`.

## SERVICES NEU STARTEN (in dieser Claude-Sandbox: Bash-Tool mit run_in_background:true; KEIN `nohup &`, KEIN `sleep` → beides wirft Exit 144)
```bash
cd /home/nk/hobo-godmode/plappi-mvp
# Server (run_in_background:true) — bindet 0.0.0.0 (NICHT 127.0.0.1!) damit der Caddy-Container ihn via 172.18.0.1 erreicht:
.venv/bin/uvicorn backend.app:app --host 0.0.0.0 --port 8800 --reload
# Tunnel (eigener run_in_background:true-Aufruf), URL aus Output (…lhr.life):
ssh -i /home/nk/.ssh/plappi_lhr -o StrictHostKeyChecking=accept-new -R 80:localhost:8800 localhost.run
# Health (Foreground ok): curl -s localhost:8800/api/health
```

## ERSTER SCHRITT MORGEN
1. Server + Tunnel starten, Nemanja die URL geben (oder gleich `app.helloplappi.com` einrichten).
2. Fragen: Wie war der Test mit Kopfhörern — Stimme Sandra warm genug? Dauerreden weg?
3. Dann: feste URL finalisieren (Caddy + DNS), Stimme fixieren, Echo final klären.

Regeln: Marke nach außen nur „Plappi"; FFG „beantragt" (nie „gefördert"); manuelle agent-Tests mit `sudo -u agent`.
```
```
