# @adva/agent

Extraktions-Pipeline des Voice-Agent-PoC: `extract(preparation, transkript, benutzer) → report`.
**Text-only** – kein Voice, kein Mikrofon (das ist Phase 3). Dies ist der Kern-Beweis des PoC.

## Architektur: LLM vs. deterministisch
- **LLM (Spezialisten + Zusammenfassung + Doku-Coaching):** nur *Interpretation* Transkript →
  Objekt-Kandidaten. `doku_feedback` gibt dem AD Tipps zur ergebnisorientierten Doku (kein CRM-Feld).
  Prompts liegen als editierbare Dateien unter `prompts/` (Deutsch). Output je Spezialist gegen ein
  striktes Zod-Schema (`src/extraktion/schemas.ts`) via AI SDK `generateObject`.
- **Deterministisch (Code, keine Prompts):**
  - `bearbeiter-resolver.ts` – Name/Alias/Rolle → gguid; **`aktiv:false` ausgeschlossen**.
  - `coverage-checker.ts` – offene Agenda − adressierte gguids → `offen_geblieben` (Mengendifferenz).
  - `assemble.ts` – baut den validen Bericht, vergibt `temp_id`, verknüpft nächste Schritte.
- **Supervisor** = `extract.ts` (deterministischer Orchestrator), kein LLM.

## Regeln
- IDs nie erfinden: bestehende Objekte behalten gguid, neue bekommen `temp_id`.
- Bearbeiter-Auflösung ist Code, nicht LLM → garantiert unabhängig vom LLM-Zufall.
- Extraktion nie im Realtime-Voice-Loop (separater Text-Schritt).

## Provider (agnostisch)
Vercel AI SDK, umschaltbar per Env: `LLM_PROVIDER=xai|anthropic`, `LLM_MODEL=<id>`.
Default **xAI Grok** (`grok-4.5-latest`, EU-Residenz). Keys nur server-seitig via `.env` (siehe `.env.example`).

## Voice-Ebene (Phase 3)
- `src/server/` – `node:http`-Server: `POST /token` (LiveKit-JWT für die iOS-App; Room-Name
  kodiert `<visitId>__<mode>`; Keys nur server-seitig) und `POST /visits/:id/freigabe`
  (Phase 4: setzt `status: freigegeben` + `freigabe`, kein CRM-Write-back).
- `src/voice/worker.ts` – LiveKit-Agents-Worker (`defineAgent`), wählt Flow aus dem Room-Namen:
  - **vorbereitung**: liest das deterministische Briefing (`briefing.ts`) vor, gegroundet in `preparation.json`.
  - **dokumentation**: Speech-to-Speech-Dialog (Provider waehlbar via `voice/realtime-model.ts`:
    xAI Grok Voice = Default, alternativ OpenAI `gpt-live-1`/`gpt-realtime-*`),
    `TranscriptCollector` sammelt Turns → `transkript.generated.json`, danach läuft die Phase-2-`extract()`.
- Prompts editierbar in `prompts/voice/`.
- **Grenze:** echtes Sprechen braucht LiveKit-Cloud-Creds + Client (iOS-App); headless nicht voll testbar.

## Live-Monitor (Telemetrie fuers Leitungs-Cockpit)
`src/telemetrie/` emittiert Beobachtungs-Ereignisse (Verbindung, Grounding, Gespraech, Extraktion,
Assembler, Artefakt, Anhang, Governance, Kosten) als JSONL nach `<dataDir>/.live/ereignisse.jsonl`.
- **Warum Datei:** Server und Worker sind zwei Prozesse mit gemeinsamem `dataDir` -> kein Broker noetig,
  Persistenz/Replay fallen ab. Der Server tailt die Datei und streamt sie als SSE.
- **Kontext binden statt global:** `ereignisKontext({quelle, lauf_id, besuch_id, modus})` liefert ein
  `Emit`; der Worker faehrt mehrere Sessions parallel im selben Prozess (kein Modul-Zustand!).
  `lauf_id` = LiveKit-Room, den auch der Token-Endpunkt kennt -> Ereignisse beider Prozesse gruppieren.
- **Nie den Flow kippen:** alles in try/catch; ohne `emit` (Tests/CLI) laeuft die Pipeline unveraendert.
- **`art: ki | code | io`** ist der Determinismus-Marker (KI-Interpretation vs. deterministische Logik).
- **Datenschutz:** `config.live_log.inhalt` (`aus|gekuerzt|voll`) steuert den Wortlaut im Log
  (`kuerzeInhalt`). Default `gekuerzt`. `.live/` ist gitignored.
- Endpunkte: `GET /leitung/live` (SSE, `Last-Event-ID` = Byte-Offset) + `GET /leitung/live/verlauf`
  (Polling-Fallback/Replay). Beide hinter dem `LEITUNG_API_KEY`-Gate.

## Befehle
```bash
pnpm --filter @adva/agent test        # deterministische Tests (Mock-LLM, offline) – Akzeptanz 1–4 + Briefing
pnpm --filter @adva/agent extract 2026-08-03_bauzentrum-nord   # Live-Extraktion (braucht LLM-Key)
pnpm --filter @adva/agent server      # Token-Server (braucht LIVEKIT_*)
pnpm --filter @adva/agent worker      # Voice-Worker (braucht LIVEKIT_* + XAI_API_KEY + Client)
```

## Tests / Nachweis
- `test/bearbeiter-resolver.test.ts`, `test/coverage-checker.test.ts` – deterministische Kernregeln.
- `test/extract.mock.test.ts` – volle Pipeline mit `MockLlm` → Akzeptanzkriterien 1–4 ohne Netz.
- Kriterium 5 (Konsistenz mit Golden-`report.json`) = manueller Live-Lauf + Diff.

## Mandantenfaehigkeit (Kunden-Overlay)
Die Instanz ist Single-Tenant (`KUNDE`/`DATA_DIR` -> `kunden/<id>/`). Drei Stellschrauben:
- **Prompt-Texte = geteilter Kern, pro Kunde ueberschreibbar.** `src/io/prompts.ts` `ladePrompt(rel)`
  nimmt zuerst `<dataDir>/prompts/<rel>` (also `kunden/<id>/prompts/...`), sonst `packages/agent/prompts/<rel>`.
  Ein Kunde ueberschreibt gezielt einzelne Prompts, ohne den Kern zu forken. (Voice **und** Extraktion.)
- **Aktive Agenten pro Kunde:** `config.agenten` (aus `config.json`) steuert, welche Spezialisten
  `extract()` faehrt. Abgeschaltete liefern ein leeres, schema-valides Ergebnis -> `assemble` bleibt
  lauffaehig. Default (kein `agenten`) = alle. Verdrahtet in `worker.ts`/`cli.ts` via `ladeConfig().agenten`.
- **Branchenwissen** liegt als Daten im Overlay (`kunden/<id>/leitlinien/*.md`), nicht im Prompt.

**Neuen Objekttyp (z.B. "Auftraege") ergaenzen** (Extension-Rezept): Schema-Felder in
`packages/schemas` (`preparation.ts`/`report.ts`), Prompt unter `prompts/extraktion/<typ>.system.md`,
Spezialist + Ergebnis-Schema in `extraktion/spezialisten.ts`/`schemas.ts`, `AgentId`/`Objekttyp` in
`kunde-config.ts` erweitern, in `extract()` einhaengen, iOS-UI-Block ergaenzen. Erst bauen, wenn ein Kunde es braucht.
