TGV · Master-Konzept · Selbstverständnis · Stand 2026-07-13
TGV ist nicht eine App mit einem Chatbot drin — TGV ist das Gespräch, und die Menüs sind sein zweites Gesicht. Dieses Konzept erklärt KAIs Selbstverständnis TGV-weit; die verbindliche Quelle ist 00_LEITDOKUMENT/KAI_INTENTION_SSOT.md (JC 13.07., Gate A „Frage KAI").
Das ist der Nordstern (Lebenszeit ↓, TGV-Bibel Prinzip 1) in Chatbot-Form: der schnellste Weg zu jeder Antwort und jeder Aktion ist ein Satz — gesprochen oder getippt. Aus DIESER einen Quelle speisen sich System-Prompt, der Hero der Seite „Frage KAI" und die i18n-Texte — sie dürfen nie auseinanderlaufen.
Was KAI kann (Intention, SSOT-Wortlaut verkürzt) — und wo die Umsetzung heute WIRKLICH steht (Code/Lane-Wahrheit, nicht Behauptung):
| Facette | Bedeutung | Anker im Kanon | Stand heute (ehrlich) |
|---|---|---|---|
| 🇪🇺 Souverän & anonym | EU-Modelle (Mistral) je Aufgabe; Außen-Recherche nur anonymisiert (ohne PII) | Sicherheit §5 EU-Stack | ⚠ Kern-Bruch Chat-LLM heute Anthropic (US); Embeddings Mistral EU ✅ · Reranker Berlin ✅ · Swap-Ziel mistral-large (E1-Naht provider-agnostisch) |
| 🔎 Antworten wo sie liegen | DB · Dokumente · Internet → strukturiert, mit Quellen; belegt, nie geraten | R-91 Grounding · render_hints (KI-First §3b) | teils Lese-Executoren staged (#916); „Kai fragen"-RAG-Zugang heute NICHT verdrahtet (KAI-RAG-Lücke, 3 Lanes staged) · Internet-Recherche = Ziel |
| ⚙️ Handelt, nicht nur redet | ausführendes Organ für alles, was TGV kann — bis zu „neue Seite mit Menüpunkt erzeugen" (self-extending) | Skills (43 im Register) · Seitenbau Phase 0 (Zweck-Dialog) | teils Skills live; Schreib-Aktionen über Freigabe-Karten; Seiten-Erzeugung im Gespräch = Ziel (Composer in-app, KANON#01-Reihenfolge) |
| 🎯 Du bestimmst den Scope | Entität · Projekt · Dokument · DB-Teil | R-40 R-43 scope-frame | Fundament scope-frame kanonisch; Auto-Scope nur verengen (RLS) |
| 🎙️ Sprich & dirigiere | reden/diktieren über den Composer, mit Einordnungs-Hinweisen | R-62 Diktier-Standard | teils TTS/STT gebaut (AUDIO#01 ✅), CORE-Verdrahtung JC-gated; Häppchen-Diktat Katalog-Karte offen |
| 🌍 In deiner Sprache | die 7 TGV-Sprachen | i18n-Kanon (Seitenbau-Querschnitt) | teils Shell 7 Sprachen ✅; Screens/Docs de-lastig — i18n-Gate DESIGN#04 |
| 🔒 Respektiert Grenzen | Mandanten-Datenraum (RLS) + Geheimhaltungsstufe — auch beim SUCHEN | Sicherheit §3/§4 Owner-Bypass, fail-closed | teils Governance GELOCKT + RLS-Fundament live; rag_chunks-Retrieval-Gate = Pflicht VOR Mehr-Nutzer (Lücken-Landkarte) |
| 🧾 Prüfbar & compliance-fähig | jede Antwort belegt, jede Aktion freigegeben + protokolliert (GoBD/Audit) | R-91 · approval_card (R-94) · Audit | Muster live Approval-before-Write + record_decision; Audit-Vollausbau lane-weise |
| 🧠 Lernt · ✋ Du gibst frei | lernt aus Freigaben; schlägt vor und fragt nach statt zu raten | Vorschlagswesen (JE-092) · Lernschleife (Seitenbau) | teils Freigabe-Prinzip verankert; Lern-Loop (learning_events → Regeln) im Aufbau |
Kein Widerspruch zu deinem 04.07.-Entscheid („Routine über Navigation, Chat für Ambiguität"): Chatbot First ist das Selbstverständnis, nicht der Zwang. Jede UX-Ansicht ist die strukturierte Oberfläche DESSELBEN Agent-Dialogs (zwei Gesichter): die Tabelle ist der schnelle Routine-Weg, der Chat die adaptive Tür für alles andere — Fragen, Ausnahmen, Neues, und das Erweitern der App selbst. Beides rendert derselbe Vertrag (render_hints, KI-First §3b); der Composer ist überall (R-11).
Konkrete Anwendung: Core-Graph (JC 25.09.2026, #3120; geschrieben, noch nicht verdrahtet). „Zeige mir die Agenten und ihre Skills“ soll Text plus Graph-Karte liefern; „Wie sind sie bei einem neuen Dokument per Mail beteiligt?“ den belegten Ablauf. Die Karte und der eigene Core-Graph-Screen verwenden denselben autorisierten Datenvertrag und Renderer. Der Wechsel in den Screen erhält Ausschnitt, Fokus und erlaubten Scope; ein gespeicherter alter Chat-Block ist keine erneute Zugriffsberechtigung. Der Lese-Executor liefert die Strukturdaten, KAI erklärt sie. KAI fragen erlaubt Darstellungsänderungen, KAI befehlen mit Prozessänderung gehört später zum Constructor. Verbindlicher Umfang und zehn User Stories: Human First §8a.
| Situation | Erstes Gesicht | Warum |
|---|---|---|
| Routine (Belege durchsehen, Status prüfen) | Navigation + Main-View (1 Klick, 1 Tabelle) | schneller als tippen — Chat wäre Umweg (JC 04.07.) |
| Ambiguität · Klärung · Neues · „Wie geht…?" | KAI (Chat/Diktat) | adaptiv statt Menü-Suche — der Kern-Zeitgewinn |
| App erweitern (neue Sicht, neuer Menüpunkt, neue Regel) | KAI (Phase-0-Dialog: KAI schlägt Zweck vor, du validierst) | self-extending — die App wächst im Gespräch (Seitenbau Phase 0 + Kanon-Katalog) |
| Schicht | Inhalt | Wer ändert | Wo verwaltet |
|---|---|---|---|
| ① Kern (TGV-weit, FEST) | Selbstverständnis + 9 Facetten + Verhaltensregeln: belegen · nachfragen statt raten · Freigabe-Prinzip · Scope/RLS/Vertraulichkeit strikt · EU/anonym | niemand mandanten-seitig — Kanon (Quelle: KAI_INTENTION_SSOT) | AGENT-CHAT#01 CANON_LAYER (canon.py, SSOT-verlinkt — gemergt #911) |
| ② Mandanten-Schicht (ANPASSBAR) | Tonalität & Persona (wie KAI klingt) · Sprach-Default · ggf. Modell-Präferenz | Owner/mandant_admin je Mandant | prompt_config (adaptive Persona, AGENT-CHAT#01) |
| ③ Verwaltung (VERSIONIERT) | Prompts einsehen · ändern · versionieren — „damit Prompts nie durcheinanderkommen" | super_admin / Owner (gestuft) | ADMIN#01 „PLAT.skills" (Mockup existiert; CRUD-Endpoint + Agenten-Register offen) |
Ableitungs-Disziplin (SSOT-Regel): Hero-Text „Frage KAI" (min.hero.* / min.chip.*) und System-Prompt-Kern werden AUS der SSOT abgeleitet — ändert JC die Intention, ziehen alle drei nach. Die Seite „Frage KAI" läuft gerade durch den Composer-Prozess (Gate A–E, Tower+DESIGN#2) — dieses Konzept ist die Doku-Klammer darüber.
Vier Differenzierer (SSOT): Zeiteinsparung durch adaptiven Chatbot statt starrer Menüs/Workflows · Souveränität (KI hinter der Firewall, EU-Modelle, anonyme Außen-Recherche) · Self-extending (die App wächst im Gespräch — neue Seiten, Menüpunkte, Skills) · prüfbar/compliance-fähig für Finanz & Steuer. Die Wettbewerbs-Lücke dazu ist benannt: solange „Kai fragen" die Wissensbasis nicht per RAG erreicht, ist Facette 2 ein Versprechen — deshalb ist der KAI-RAG-Zugang JC-Prio.
Positionierung 29.07.: TGV folgt demselben modernen Arbeitsmuster wie Claude Code Desktop, ChatGPT Canvas/Projects und Copilot Pages: ein Gespraech bleibt die Bedienebene, daneben entsteht eine dauerhafte Arbeitsflaeche. Der Unterschied: TGV bindet diese Flaeche an Mandant, Entitaet, Projekt, Datenquellen, RLS, Screen-Factory-Vertrag und Guards. KAI darf also nicht nur antworten; KAI muss die passende Oberflaeche waehlen, befuellen, pruefbar machen und als wiederverwendbaren Screen speichern koennen.
| TGV-Zone | Rolle im Produkt | Korrespondenz am Markt | Template-/Taxonomie-Folge |
|---|---|---|---|
| Linkes Menue / Rail | Orientierung, Routine-Zugang, personalisierte User-Screens; zuklappbar und spaeter user-erweiterbar. | Claude Code Views/Pane-Navigation; ChatGPT Projects linke Arbeitsraeume. | workbench-zonen.sidebar · Screen-Vertrag braucht Menue-Zuordnung. |
| Header | Marke, Login, Mandant/Entitaet, Sicherheitsstatus, globale Aktionen. | Enterprise-Workspaces zeigen Konto, Workspace und Controls immer oben. | workbench-zonen.header · nie screen-lokal nachbauen. |
| Context-Fenster | Hero, Scope, aktueller Arbeitskontext, Chat-Thread und relevante Hinweise. | Copilot/ChatGPT starten im Dialog, halten Kontext in Projekt/Thread. | workbench-zonen.context · muss Intention, Scope und offene Klaerung tragen. |
| Composer | Immer verfuegbare Eingabe unten: tippen, sprechen, Dateien ablegen, Aktion bestaetigen. | Claude Code/ChatGPT halten den Eingabepunkt persistent. | workbench-zonen.composer · ein kanonischer Composer, kein Screen-Eigenbau. |
| Rechtes Panel / Ergebnisflaeche | Lebender Screen: Vorschau, Tabelle, Dossier, Chart, Vertrag, Dokument, Diff, Buchhalterpaket. Vergroesserbar wie die Navigation. | Claude Code Preview/File/Diff-Panes; ChatGPT Canvas; Copilot Pages side-by-side. | workbench-zonen.result_panel · wird primaerer Ort fuer Screen-Factory-Ergebnisse. |
Produktentscheid: TGV ist nah an Claude Code, aber nicht fuer Code, sondern fuer Alltag, Beruf, Projekte, Dokumente, Finanzen und Kommunikation. Das Panel ist deshalb nicht nur Zusatz-Panel; es ist die persistente Ergebnisflaeche, in der KAI Antworten in bedienbare Screens uebersetzt. Wenn ein User sagt „baue mir eine Vertragsuebersicht" oder „was fehlt fuer den Buchhalter?", entsteht dort ein Screen nach Screen-Factory-Vertrag.
KAI darf als zusammenhaengender Langtext antworten, wenn der Nutzer eine Erklaerung, Argumentation oder Strategie braucht. Der Antwortvertrag sorgt nur dafuer, dass grafisch klar bleibt, was alles zur Antwort gehoert: optionale Bloecke koennen eingebettet oder daneben stehen; lange Zusatzteile sind einklappbar. Das rechte Panel ist die vertiefte Arbeitsflaeche, nicht der Ort fuer jede Erklaerung.
| Block | Kommt in der KAI-Antwort | Collapse-Regel | Panel-Regel |
|---|---|---|---|
| long_text / summary | Zusammenhaengender Langtext oder kurze Antwort, je nach Nutzerbedarf; Stand und naechster sinnvoller Schritt bleiben erkennbar. | Hauptantwort offen; Zusatzteile koennen einklappbar sein. | Kein Panel noetig. |
| clarification | Nur wenn KAI fuer eine sichere Aktion eine Rueckfrage braucht. | Offen, wenn blockierend; sonst einklappbar. | Kein Panel, ausser Auswahl/Matrix wird gross. |
| evidence | Quellen, Datenstand, Annahmen, Sicherheits-/RLS-Hinweise. | Standardmaessig einklappbar. | Panel bei Quellenliste, Dossier oder Audit-Trail. |
| actions | Buttons fuer Freigabe, Export, Reparatur, neuen Screen, Termin, Nachricht. | Offen, wenn Handlung erwartet wird. | Panel fuer Vorschau vor Schreibaktion. |
| panel_offer | Karte/Link „im Panel ansehen", wenn Tabellen, Charts, Dokumente oder Vergleiche besser visuell sind. | Offen als kurzer CTA. | Oeffnet result_panel per explizitem Klick oder wenn User direkt „zeige/baue" sagt. |
| screen_contract | Bei Screen-Erzeugung: Intent, Screen-Typ, Daten, K-IDs, Funktionen, Icons, Charts, Zone, Abnahme. | Einklappbar, aber pruefbar. | Panel zeigt den lebenden Screen oder Vertrag. |
Merksatz fuer Implementierung: Chat = Entscheidung, Erklaerung, Freigabe und naechster Schritt. Rechtes Panel = pruefbares Arbeitsergebnis: Tabelle, Chart, Dokumentvorschau, Dossier, Diff, Buchhalterpaket, Vertragsuebersicht oder Screen-Factory-Vertrag. Die UI-Rollen fuer Conversation, Composer, Antwort-Frame, Button, Pille, Link, Collapse und Panel-Trigger stehen in Komponenten · Controls; die sichtbare Pruefansicht liegt in Controls-Systematik.
Der Antwortvertrag oben regelt, was in einer Antwort stehen darf. Diese Klausel regelt, was nie darin stehen darf: eine Behauptung über eine Handlung, die nicht stattgefunden hat. Sie ist kein Stil-Wunsch, sondern der Anschluss an den Wahrheits-Kanon (Enthaltung ist ein erlaubtes Ergebnis) — und sie kam aus zwei gemessenen Live-Fällen, nicht aus einer Idee.
_claims_execution(text) erkennt Vollzugs-Behauptungen (DE/EN/FR); :272 ersetzt sie im abgebrochenen Turn durch die ehrliche Enthaltung („Ich kann hier nicht selbst speichern — bestätige auf der Karte"). Ehrliche Prosa bleibt wörtlich unangetastet. Guard: 07_TESTS/agent_chat01/test_action_turn_wahrhaftigkeit.py — die gemessenen Live-Lügen als Fixtures.
offen-Liste: „Typ unklar · Betrag fehlt · kein Projekt zugeordnet"), die Karte nennt es sichtbar (:399-405 _ack_env), und die Oberfläche zeigt die Server-Antwort statt der vorformulierten Zeile — 06_DASHBOARD/core_shell.html:9436-9439 (receipt() in doAccept: msg=(res&&res.message)||… plus sichtbare offen-Liste); resolved_msg (:8889) ist seither nur noch Demo-/Fallback. Guard: 07_TESTS/agent_chat01/test_quittung_offen_454b4.py mit dem gemessenen Fall #8218 als Fixture.
02_SKILLS/tgv-skill-klaerung, 08_OPS/chat_engine und den Klärungs-Screens. Ball liegt bei Tower (Owner-Zuweisung), nicht in dieser Seite.§7b verbietet KAI, einen Vollzug zu behaupten. Diese Regel sagt, wie er wirklich stattfindet. Bis 19.09. endete jede KAI-Handlung beim Vorschlag: „Übernehmen“ quittierte nur lokal. JC am 19.09.: Das Rechte-Beispiel ist ein Beispiel. Die Grundsätze gelten für jeden Skill, den KAI anwendet.
| Grundsatz | Was er heißt | Wirkort |
|---|---|---|
| Ein Endpunkt, zwei Türen | Klick in der Fläche und Satz an KAI rufen denselben Dienst-Endpunkt mit den Rechten des Aufrufers. Eine UI-Prüfung ist Bequemlichkeit, nie Autorisierung. | app.py _vollzug_endpunkte |
| Die Karte kommt vom Server | Was eine Karte tut, steht fest, sobald KAI sie vorschlägt (gespeichertes Gespräch). Der Browser sagt nur, welche Karte gemeint ist. | 08_OPS/chat_engine/vollzug.py |
| Der Mensch vollzieht | Der Vollzug trägt den Namen des Menschen, nie KAIs. Die Spur kai.vollzug steht in agent_events. | vollzug.von |
| Einmal, nicht zweimal | Ein Doppelklick oder zwei Tabs vollziehen einmal. Der zweite Aufruf bekommt das gespeicherte Ergebnis. | bedingtes PATCH state=eq.pending |
| Nur, was belegt passt | Ein Werkzeug wird vollziehbar, wenn sein Schema gemessen zum Endpunkt passt. Sonst sagt die Karte offen „noch nicht ausführbar“. | VOLLZUG_REGISTER |
| Die Karte sagt ihren Zustand | Offen · ausgeführt · abgelehnt · nicht ausgeführt: Kopf, Zähler und Frage wechseln gemeinsam, auch beim Wiederaufbau aus dem Verlauf. Die Quittung ist Golden-Read-Back (§7b Fall 2): Was der Skill als offen meldet, steht auf der Karte. | 06_DASHBOARD/kai_renderer.js (Quelle, gespiegelt per build_kai_inline.py) |
| Fehlt etwas, fragt KAI nach | Einen fehlenden Kontakt, eine unklare Person oder einen mehrdeutigen Raum erfindet KAI nicht. Er schlägt vor oder fragt mit Auswahlknöpfen. | RECHTE#01 §7 · KONTAKTE#08 kontakt-finden |
persona-apply-correction sind noch nicht vollziehbar (Schema ≠ Endpunkt bzw. kein Endpunkt).JCs Anspruch, wörtlich (03.09.): „KAI kann über alles reden und hat richtige fundierte Antworten." Beides zugleich ist der Kern: über alles reden heißt, kein Thema ist vorab ausgeschlossen — Bestand, Wissen, TGV selbst, die Welt. Richtig fundiert heißt, jede Aussage hat einen benennbaren Bezug, und was keinen hat, wird als Deutung erkennbar. Der erste gemessene Bruch dieses Anspruchs war kein Verbot, sondern ein Abtippen: am 14.09. las KAI ein Werkzeug-Ergebnis und schrieb es als Tabelle neu — mit drei erfundenen Belegen (Register #1905). Werkzeugtreue ist deshalb die Regel, die §7 (was in der Antwort steht) und §7b (was nie darin steht) ergänzt: Strukturdaten kommen aus dem Werkzeug oder gar nicht; Prosa ist Lead-in und Deutung, nie die Abschrift.
Die vier Bezugsarten — worauf sich eine KAI-Aussage stützen kann. Jede hat ihren Wirkort, ihre Schranke und ihre Wand; „über alles reden" ist die Summe der vier, nicht ein fünfter, freier Kanal.
| Bezugsart | Woher die Aussage kommt | Wirkort (Code) | Schranke | Mandantenwand |
|---|---|---|---|---|
| ① Bestand | Belege, Buchungen, Verträge, Konten, Klärungen — gelesen durch ein read-Werkzeug. Die Zeilen reisen als Block (card · chart · table), nie als Prosa. | 08_OPS/chat_engine/blocks.py (BLOCK_KINDS prose · card · chart · table) · Executoren executors_core/rka/klaerung/steuer.py · agent_loop._collect_cards sammelt werkzeugtreu ein | Jede Zahl trägt ihre basis (Filter · Sortierung · Scope · Gesamtzahl, W2-Muster); ohne Basis kein Etikett — „745" war die Scope-Gesamtzahl, kein Zeitraum. | Die Reader laufen mit Service-Key, also OHNE RLS-Netz: _entity_filter (Mitgliedschaft, fail-closed) und _conf_or_filter (Decke) sind in JEDEM Executor Pflicht (Sicherheit, #150 / #286). |
| ② Wissen | Inhalte aus Dokumenten und Notizen über die zwei Suchwege (§9e) — Wort und Vektor. | executors_rag.py · search_knowledge_scoped · Quellen-Karten mit retrieval.geprueft | Geprüfte Treffer werden Karte, ungeprüfte Wortsuche nur Quellen-Zeile (#848/#1292); die Prosa nennt, was sie verwendet hat. | Gilt auch beim SUCHEN, nicht erst beim Zeigen: Scope und Decke schneiden die Trefferliste, nicht die Anzeige (§9b, #334). |
| ③ Selbstauskunft | Was KAI darf, sieht und kann: Zeitraum, gepinnter Beleg, Decke, Tagesdatum, Fähigkeiten. | grounding.py — period_directive · doc_directive · rechte_directive · today_directive · upload_directive · id_directive; capabilities() | Jede Direktive NENNT einen Zustand, sie erfindet keinen: fehlt die Periode, steht keine; das Tagesdatum ist unbedingt (#1904 ②). Die Decke wird ohne Anzahl genannt — eine Zahl verdeckter Treffer wäre ein Orakel über das Geschützte (#1260). | Direktiven kommen aus dem SCOPE (serverseitig), nie vom Client, nie vom Modell — sonst könnte eine Antwort eine Decke nennen, unter der sie nicht lief. |
| ④ Allgemeinwissen | Was das Modell aus sich heraus weiß — Steuerlogik, Vertragsrecht, Fachbegriffe, die Welt. | Persona + Grounding-Fragment (prompt_config, §4) — kein Werkzeug | Erlaubt und gewollt („über alles reden"). Aber: keine Zahl aus dem Bestand darf hier entstehen. Eine Aussage dieser Art ist Deutung und muss als solche lesbar sein. | Trägt keinen Bestand — die Wand steht vor dem Modell (Scope-Gate), nicht dahinter. Die Frage selbst wird gesäubert (sanitize_question, 4 Schichten). |
_WERKZEUG_LISTE), und das Werkzeug sagt es im Ergebnis selbst (dargestellt) — der Hinweis steht dort, wo die Zeilen liegen. Aber der Prompt ist Bitte, der Block ist Garantie: der Fabrikations-Schutz hängt am Block, nicht am Wortlaut (Positivkontrolle in 07_TESTS/agent_chat01/test_table_block_contract.py F).envelope.steps = [{tool, ok, ms, args}] — „woher kam die Zahl" steht in der Hülle, nicht im Nachrechnen; das Ergebnis selbst bleibt draußen (es trägt Daten).{count, sample, basis} ohne Blocktable-Block, zweite Instanz desselben Musters. ⭐ Und sie brauchte einen EIGENEN Bauer, keinen Parameter am Beleg-Bauer: txn_master trägt weder confidentiality_level noch einen test_run-Filter — ein kopierter Basis-Satz hätte eine Schranke genannt, die auf dieser Tabelle nicht wirkt, also ein Fabrikat im Etikett statt in den Zeilen. Gemeinsam ist nur, was wirklich gemeinsam ist (_scope_satz/_treffer_satz). Guard: 07_TESTS/agent_chat01/test_buchung_block_contract.py.evidence — er wird für Bezugsart ④ nicht gefüllt. Entscheid offen: Kennzeichnung im Text („nach allgemeiner Regel") oder als Block.renderSpur zeichnet die Spur als aufklappbare Fußzeile am Turn — Werkzeug, Argumente, Dauer, und ein fehlgeschlagener Schritt wird genannt statt verschwiegen. ⭐ Die Form ist gemessen, nicht gewählt: an 244 echten Turns haben 69 % genau EINEN Werkzeugschritt, deshalb ist es keine K-33-Tabelle (das wäre die Ein-Zeilen-Tabelle, die E-42.4 verbietet), sondern die Bauart des Quellen-Blocks mit dem Vokabular des Dokumentwegs. Guard: 07_TESTS/agent_chat01/test_spur_auf_der_flaeche.py.trace.0a192642ebc6ec468bddef1f5cfbcef11a5402a4:jcbinetti, 198 Belege): Tür A 0 für alle drei, KAI-Angriff des viewers abgewiesen. Stufe innerhalb C3: viewer sieht 347 von 507, 0 über der Decke (Tür E live; der Prädikat-Weg derselben Probe endet in HTTP 400 = Enthaltung).POST /api/skill/system-graph, ansicht=prozess): je Konto 8 Stationen, das Postfach stammt aus der EIGENEN Entitätszeile (viewer/C3 → c3-convis-gmbh, C4 → c4-convis-sarl). ⛔ Mengenvergleich über die Adresse trägt nicht: c3, c4 und privat haben dieselbe intake_mail.entity="alle" wird als Filter durchgereicht: die Shell setzt den Chip-Default alle (core_shell.html:4342), scope_gate.py:417 legt ihn in die Werkzeug-Argumente, und zwei Werkzeuge behandeln ihn als Entität: executors_graph.py (erbeten = {"entity": args.get("entity")}) ⇒ Endpunkt 403 „Entität 'alle' … nicht erlaubt“ ⇒ KAI sagt jedem Nutzer, er dürfe den Prozessgraphen nicht sehen; executors_core.py:1349 (get_ustva_report) ⇒ der Rückfall „eine erlaubte Entität“ greift nie, Karte „erbeten: alle“. A/B live: entity weggelassen oder eigene Entität ⇒ 200, alle ⇒ 403; mit Chip c3-convis-gmbh antwortet KAI die 8 Stationen (B-26 grün nur mit gesetztem Chip).intern: beide Verwalter tragen im Envelope confidentiality_max: intern, obwohl Tür A ihnen alles zeigt. Gelesene Ursache: _owner_conf_bypass(auth, _mandant_slug) (app.py:3993) bekommt den Mandanten aus dem Instanz-Deskriptor (core: None, übrige: jcbinetti), nicht aus der Mitgliedschaft — dieselbe Klasse wie #3296. Folge: get_ustva_report verweigert C4 seine eigene Steuer-Liste; die Positivkontrolle von KAI-Q0151 kann so nie treffen ⇒ kein Urteil (die Negativrichtung C4→C3 ist dicht). Kein Leck — eine Verengung.ok=false. Dazu der Rechtsname: KAI nennt C3 mit dem Namen eines KUNDEN statt mit dem eigenen Rechtsnamen (Klasse von #3818, Tor 33).md_accounts, das inaktive Konto ist ausgelassen — aber „Stand 04.04.2026“ pauschal, ein Konto trägt 04.05.2026 ⇒ Nachweis ⑤ teilweise. Rollen-Negativprobe (viewer fragt Gesamtzahl inkl. vertraulich): keine Zahl, Decke benannt ⇒ dicht.deliverables/kai_review_feedback.json — die Datei fehlt, und kein *.py/*.js/*.html im Repo liest sie; korpus_abdeckung.py:185–192 nimmt den Status nur aus report.json. Ansicht und Quelle sind getrennt, aber der Kanal fehlt. Deshalb lief Stufe 2 (Welle über 47) nicht — Auftragsregel: bricht der Durchstich, läuft die Welle nicht.user_mandant_roles führt für c4-convis nur mandant_admin und super_admin. Q0153 braucht ein strukturiertes Zielentitätsfeld im Korpus — in wall_cases.json nicht vorhanden. Beide bleiben ohne Urteil. ⭐ Nachtrag 11.10. (#3982, PR #3405 gemergt): Q0153 trägt jetzt context.ziel_entitaet (Vertrag → build_wall.py → bindungspruefung.py ⑩, Abbruch vor dem Modell mit BindungMandantFremd); Q0152 braucht kein C4-Konto mehr — die Decke hängt an der Rolle, nicht am Mandanten: Negativ c3-viewer (keine Summe, Absage nennt die Decke), Positiv C3-Verwalter; ohne Urteil bis #3552 (Verwalter-Decke im Chat).provision_qa_session.py --erneuere, ohne JC./finanzen/bank und der Bereich „Steuern & Abgaben“ fürs Fahrtenbuch (richtig: Reisen & Fahrten › Fahrtenbuch).menue-weg liefert Wege nur daraus; die Regel grounding.wege_directive steht unbedingt im Prompt; der Antwort-Prüfer misst mit (Klasse N pfad_ohne_kanon, markiert, ersetzt nicht — JC-Entscheid (a) offen).funktion_abgesprochen (markiert): „gibt es nicht“ über eine Fläche oder Fähigkeit ohne gemessene Reichweite, „nur über Support“, oder eine Haus-Adresse ohne Quelle — gemessen im Bestand 8 Funde in 7 von 730 Antworten, live 4 von 16.SPRACH_GRENZEN trägt je Werkzeug den Gegenstand (Muster über das Ergebnis, die Bedingung steckt darin) und das verbotene Wort (Muster über den Absatz der Antwort); Klasse L grenze_gebrochen misst mit (markiert). Erster Eintrag: get_domains_expiring — verlängert sich eine Domain automatisch, ist ihr Datum kein Ablauf. Gemessen im Bestand: das Werkzeug lief in 2 Antworten, 1 bricht die Grenze (beide Domains, „Läuft am … aus“ auf der Folgezeile des Aufzählungspunkts).JC-Kanon-Klärung vom 03.08. (E-44, Register #292), ausgelöst von einem Live-Befund: auf /abgleich beantwortete KAI die Frage „Worum geht es hier gerade?" mit dem Monatsstand Januar 2026 C3 — dem Konversations-Fokus von vorher — und nicht mit dem Zahlungs-Abgleich, auf dem der User stand. Die äußeren Ebenen überschrieben still den Screen. Nach dieser Klärung ist das teils richtig (der Scope hält, das ist seine Aufgabe) und teils falsch (der Screen ist gar keine eigene Kontext-Ebene): die Verdrahtung muss die drei Ebenen sauber staffeln, statt dass eine die andere verdeckt.
| Ebene | Was sie ist | Wie sie gesetzt wird | Baustein |
|---|---|---|---|
| ① SCOPE / Betrachtungsraum | Die äußerste Ebene: „nur Projekt XXX", „nur C3 · Januar 2026". Gilt über Screens hinweg. | Bewusst vom User — über den Scope-Frame ODER per Satz an KAI. | K-11 scope-frame · session_overrides |
| ② KONVERSATIONS-KONTEXT | Der Thread. Liegt INNERHALB einer Scope-Definition. | Durch Öffnen/Fortführen eines Threads. | chat_sessions · K-16 Composer-Bindung |
| ③ KARTEN-KONTEXT | Der fokussierte Einzelfall. Eine Karte ist immer Bestandteil einer Konversation. | Durch Fokussieren einer Karte (Klärung, Beleg, Buchung). | Karten-Bindung des Composers (eckige obere Ecken, kantengleich) |
⛔ Wann eine „Ab hier"-Systemzeile erscheint — und wann NICHT. JA: wenn der User den Scope bewusst ändert (eingegeben oder mit KAI geändert) · wenn der Thread-Fokus wechselt (z. B. „Diese Konversation fortführen" im Verlauf, E-46). NEIN: wenn es für den User selbstverständlich ist — wer eine Karte beantwortet, erwartet selbstredend, dass es um diese Karte und deren Kontext geht; dafür braucht es keine Meldung. Der Auslöser-Befund (E-43): auf „Wir betrachten jetzt nur Januar C3" antwortete KAI korrekt „Ich bleibe in diesem Kontext" — die sichtbare Zeile „Ab hier: C3 · Januar 2026" fehlte.
Warum das kein UI-Detail ist (E-43, Register #289): derselbe Fokus soll den RAG-/Quellen-Bereich eingrenzen — für vertiefte Suche UND für Token-Optimierung des Inputs. Referenz-Satz: „Bitte im Projekt XXX liste mir alle Quittungen zur Veranstaltung YYY und schreibe mir einen Text zur Erklärung dazu für den Buchhalter." EIN Scope-Objekt (Mandant · Entität · Zeitraum · Projekt · gepinntes Dokument) wirkt an drei Stellen gleichzeitig: Anzeige (K-10-Zustandszeile) · Werkzeuge (apply_scope_to_tool_args, #287) · Wissenssuche (Chunk-Auswahl, §9). Welche Achse heute an welcher Stelle wirklich ankommt, steht gemessen in §8b — Kurzfassung: die Werkzeuge tragen vier von fünf Achsen, die Wissenssuche nur zwei, den Zeitraum trägt niemand. Weniger Chunks im Prompt = weniger Input-Token und präziser — der Scope ist damit ein Kosten- und Qualitätshebel. Bestand, kein Neubau: session_overrides (#276) · K-11 · K-10/E-15.3 · Scope-Gate (#287) · search_knowledge_scoped (Mig 221); rag_chunks.project_code existiert und ist ungenutzt, ein Zeitraum-Filter auf Chunk-Ebene fehlt.
§8 sagt, dass ein Scope-Objekt an drei Stellen gleichzeitig wirken muss. Hier steht, welche Achsen es hat und wo jede von ihnen heute tatsächlich ankommt — gemessen am Code, nicht behauptet. Der Satz „wirkt heute nur bei den Werkzeugen, teilweise" war zu unscharf, um danach zu bauen; diese Matrix ersetzt ihn. Quelle der Achsen: JC-Referenzsatz E-43 („im Projekt XXX alle Quittungen zur Veranstaltung YYY"), Register #289.
| Achse | ① Gesetzt wo | ② Anzeige | ③ Werkzeuge scope_gate.py | ④ Wissenssuche Mig 221 |
|---|---|---|---|---|
| Mandant | Instanz-Default; eine Session darf ihn nicht wechseln (ScopeError, fail-closed) | K-11 / Rollen-Menü | ✅ hart Pflichtfeld, ohne ihn läuft kein Tool | ✅ p_mandant_ids = harte Grenze |
| Entität | session_overrides.entity — Session darf verengen, nie ausweiten | K-11 Scope-Frame | ✅ out["entity"] | ✅ p_entity |
| Projekt | session_overrides.project_code | K-11, dritte Achse (AR-238) | ✅ project_code und project_id (Doppel-Vokabular seit #286) | ⛔ NEIN — die RPC kennt keinen Projekt-Parameter |
| Zeitraum | ⛔ existiert im Scope-Objekt GAR NICHT — kein Feld in Scope | K-11-Popover bietet „von–bis" an | ⛔ | ⛔ |
| Gepinntes Dokument | session_overrides.context_doc_id (Chip, Nutzer-Entscheidung — #276) | Chip über dem Composer | ✅ beide Richtungen gepinnt → injizieren, nicht gepinnt → ein vom LLM erfundenes Feld entfernen | — (verengt die Tools, nicht die Chunk-Auswahl) |
rag.search_chunks(… p_project_code …) (028:58-63, WHERE :84). Die heute benutzte RPC public.search_knowledge_scoped hat keinen solchen Parameter (Mig 221:44-51 — nur query_embedding · mandant_ids · entity · confidentiality_max · source_types · top_k). ⇒ Beim Sprung auf den scoped/Mistral-Raum ist die Projekt-Verengung mitgegangen. Das ist besonders bitter, weil der Scope-Vertrag sie ausdrücklich zusagt: „mandant_id, entity, project_code, confidentiality_level sind dieselben Schlüssel, die die RAG-Suche filtert" (02_SCOPE_MODELL_SPEC.md:37,44). Klasse: R-174 auf den Kopf gestellt — nicht ein Leck blieb in der Schwester-Funktion zurück, sondern eine Fähigkeit.
rag_chunks.project_code ist 99 von 1298 belegt (7,6 %) — und zwar ausschließlich im Gabriel-Memoir-Korpus; im TGV-Korpus 0. Die Ursache liegt aber eine Stufe früher: auch in der Quelle documents.project_code sind 0 von 219 Kandidaten belegt. Der Ingest kopiert das Feld korrekt durch — es steht nur nichts zum Kopieren da. ⇒ Richtig ist: „niemand setzt documents.project_code bei TGV-Dokumenten". Der fehlende Projekt-Parameter in der RPC bleibt davon unberührt und gilt weiter — beide Lücken sind echt, aber es sind zwei verschiedene, und nur die zweite ist eine verlorene Fähigkeit.Scope-Objekt hat die Felder mandant_id · entity · project_code · confidentiality_max · source · context_doc_id · is_owner · allowed_entities · allowed_mandanten — kein Zeitfeld (scope_gate.py Scope-Dataclass, 0 Treffer für period/zeitraum). Die Oberfläche verspricht ihn trotzdem: K-11 ist als „2-Achsen-Popover (Entität UND Zeitraum von–bis)" definiert, und Register #300 hat unabhängig gemessen, dass „Benutzerdefiniert" eine tote Option ist (renderPeriodMenu setzt nur key, kein Von/Bis). ⇒ Die Zeit-Achse ist durchgängig unverdrahtet — Wähler, Scope-Objekt, Werkzeuge, Wissenssuche. Wer sie baut, baut sie an vier Stellen oder an keiner.Scope-Objekt aufnehmen — solange sie dort fehlt, kann keine der drei Wirkstellen sie bekommen, und der K-11-Wähler bleibt ein Versprechen. ② Die Projekt-Verengung in der RPC wiederherstellen (Parameter + WHERE), damit der Scope-Vertrag wieder stimmt. ③ Erst danach ist JCs Referenzsatz überhaupt beantwortbar. ⚠️ Und dabei gilt die JC-Warnung aus §8: die Verengung muss dem Nutzer dienen — greift ein Filter, wird er genannt (R-177), sonst wird aus Präzision eine Leer-Antwort.JC-Direktive 03.08. (Register #288: „Das gehoert im KONZEPT MASTER!!"). Die geltenden Index-Regeln standen bis dahin nur im n8n-Workflow „RAG Ingest v0.2" und im P5-Integrationsplan — nicht im Kompendium. Was und wann indexiert wird, samt der drei unsichtbaren Filter, steht jetzt an seinem Platz in der Ablage-Seite: Dokumente §3b — Was in den Index kommt. Hier steht die Lese-Seite: wer den Index befragen darf und was dabei fail-closed abgeschnitten wird.
| Leseweg | Was geprüft wird | Verhalten |
|---|---|---|
executors_rag → RPC search_knowledge_scoped | mandant_id hart · Entitäts-Verengung · Vertraulichkeits-Decke | Mig 221 APPLIED fail-closed: fehlender Wert (NULL) gilt als „streng" |
| Scope-Verengung aus dem Dialog | Projekt/Zeitraum aus dem Scope-Objekt (§8) | Ziel rag_chunks.project_code existiert, ungenutzt; Zeitraum fehlt als Chunk-Filter |
| Lexikalischer Fallback | FTS de+fr, nur wenn die Vektorsuche NICHTS liefert — nicht zusätzlich, nicht gemischt | LIVE seit 08.08. Mig 232 appliziert, lexical_caller gesetzt (app.py), Service 0.50.0. Details: §9e |
confidentiality_level, language und mandant_id beim Schreiben nicht (P5-Plan (d)). Das Lese-Gate stuft fehlende Werte als „streng" ein — korrekt vorsichtig, aber KAI findet möglicherweise nicht, was es finden dürfte. Das ist kein Sicherheitsmerkmal, sondern eine unbewiesene Sperre (R-173). Dazu: kein Auto-Purge stale Chunks bei Dubletten (P5 (c)).JC-Auftrag 08.08.: „Die gesamte Kontext, Intention, Methode, Begründung, Beispiele rund um Anwendung der Lexikal- und LLM-Methode … in MASTER KONZEPT gut erklären!!!!" — ausgelöst von seiner eigenen Frage, die zwei Agenten in einem Satz vermischte. Das war kein Missverständnis, sondern der Beleg, dass diese Seite die Trennung nicht hergab.
doc_type · entity · provider · skill_plan und seit 08.08. auch die Vertraulichkeitsstufe. Er sieht den Volltext des Dokuments.
| Weg | Stärke | ⛔ Blind bei |
|---|---|---|
| Vektor (Bedeutung) | findet „PKW", wenn du „Fahrzeug" fragst; überbrückt Sprachen (deutsche Frage → französischer Beleg) | mittelt über die GANZE Frage. In „sage mir mehr über meine Hiscox Versicherung" zieht ein Fachwort gegen fünf Füllwörter — der Satz landet unter der Relevanzschwelle |
| Wortsuche (Zeichen) | unschlagbar bei Eigennamen, Nummern, Aktenzeichen — content_tsv + content_tsv_french, OR-verknüpft (Mig 232) | versteht nichts. Kein Synonym, keine Paraphrase; „Fahrzeug" findet kein „PKW" |
Vektor: JCs Frage 0.709 < „Rezept für Lasagne" 0.771 < Schwelle 0.78
ts_rank: JCs Frage 0.03908 < „Wetter in Tokio" 0.04906
BM25/idf: JCs Frage 11.80 < „Wetter in Tokio" 15.25
idf misst Seltenheit im Korpus — und in einem Rechnungs-Bestand sind meine (idf 4.28) und morgen (6.17) selten. Die Umgangssprache sieht dort aus wie ein Fachbegriff. Auch nach Postgres' eigener Stoppwortfilterung bleibt es überlappt (9.34 vs 9.53).
retrieval: {weg:"wortsuche", geprueft:false} + validation_status:"ai_unverified" — an der Karte, nicht im Fließtext. Eine Kennzeichnung im Text überlebt den nächsten Prompt-Umbau nicht; und validation_status wandert mit, damit auch ein Renderer, der retrieval nicht kennt, die Karte nicht für geprüft hält.
weg:"wortsuche" · geprueft:false · ai_unverified, KAI antwortet inhaltlich mit Beleg-Link. 3/3 stabil.
idf, kein Floor).
doc 7241 stand am 26.07. auf streng_vertraulich, app.py-Versionsverlauf 0.46.0). Für den Owner greift die Decke nicht (Owner-Bypass) — dort ist die wahrscheinlichere Ursache eine fehlende mandant_id: die RPC filtert mandant_id = ANY(...), und ein Chunk ohne Mandant ist für jeden unsichtbar, auch für seinen Eigentümer. Der damalige Zustand ist heute nicht mehr messbar — das wird hier gesagt und nicht überspielt.JC 08.08., wörtlich: „Was sind die konkreten, für mich lesbaren Festlegungen, die TGV jetzt hat? Welches LLM-Modell, welche Chunk-Anzahl…? Und WELCHER AGENT nimmt diese BALANCING-Entscheidung wann vor, damit die Antworten gut sind?"
⭐ Die kurze Antwort auf den zweiten Teil steht in der letzten Spalte — und sie lautet fünfmal „nie".
| Wert | steht auf | lebt in | gesetzt von / wann | zuletzt GEMESSEN |
|---|---|---|---|---|
| LLM-Modell | claude-haiku-4-5-20251001 | DB prompt_config.llm_params — und als Vorgabe im Code (taxonomy_config.py:103) | ORCH-Strangler, 02.08. | nie ⛔ der Mistral-Entscheid ist hier nicht vollzogen |
| max_tokens | 1400 | DB llm_params | Tower#33, 08.08. | nie |
| ⛔ dieselbe Zahl im Code | 900 | taxonomy_config.py:104 | — | ⛔ weicht ab — greift nur, wenn die DB unlesbar ist, und niemand würde es merken |
| CHUNK_CHARS | 3200 | Code ingest_core_privat.py:70 | unbekannt | 08.08. — §9g, erste Messung überhaupt |
| CHUNK_OVERLAP | 400 | Code ingest_core_privat.py:71 | unbekannt | nie — in §9g lief er proportional mit, nie eigenständig |
| Treffer für KAI | 10 (6 Vektor + bis 4 Wortsuche) | Code executors_rag.py GESAMT_MAX | Tower#26 / A″ 08.08. | nie — begründet, nicht gemessen |
| p_min_score | 0.78 trägt seit dem 17.07. das Wort INTERIM | DB, Mig 227 (Vorgabewert der RPC) | 17.07. | 17.07. p6_relevance_guard: Unsinn 0.705–0.771, echt 0.743–0.814 ⇒ Überlappung, keine Trennung |
| p_top_k | 6 | DB, Mig 227 | 17.07. | nie |
0.78 ist ein Interim vom 17.07., und die Messung von damals sagt selbst, dass er nicht trennt: Unsinn und echte Treffer überlappen. JCs eigene Frage lag bei cos 0.709 — unter dem Riegel.
3200 tauscht Fälle gegeneinander: von acht gemessenen Fragen werden bei feinerem Schnitt zwei besser und sechs schlechter (§9g). Es gibt kein Optimum — nur andere Gewinner.
top_k=6 entscheidet, was KAI überhaupt weiss. Bei „Wer ist Christian Müller" bekam er einen von 28 Belegen — und gab die Rolle aus der Signatur einer fremden Firma wieder (HRB269886B statt HRB 102141 B).
Ganzer Korpus (475 Dokumente, 2,7 Mio Zeichen) in je einer Größe geschnitten, 5 Größen, 10 Fragen aus 5 Klassen + 6 Unsinn-Fragen. Klassen von Tower#33, Fragen von der Lane — beides eine Wahl und keine Messung. Werkzeug: 08_OPS/rag01/messe_chunk_groesse.py, Erwartungen vor dem Lauf: ERWARTUNG_chunk_groesse.md.
| Frage (Rang des richtigen Chunks) | 600 | 800 | 1200 | 1600 | 3200 = IST |
|---|---|---|---|---|---|
| ① „meine Hiscox Versicherung" | 8 | 9 | 6 | 3 | 2 |
| ① „Steuerberater Ruef" | 5 | 5 | 14 | 5 | 4 |
| ② „Wann Schlüssel abgeben?" (tief, 79 %) | 1212 | 877 | 691 | 707 | 698 |
| ② „Grundlage für das Honorar?" (tief, 85 %) | 3 | 3 | 3 | 5 | 18 |
| ③ „welche Versicherung läuft Ende August aus?" | 2562 | 2014 | 1480 | 1157 | 630 |
| ③ „ab wann bekomme ich meine Rente?" | 6 | 12 | 8 | 7 | 2 |
| ④ „wie hoch war die IONOS-Rechnung im Mai?" | 555 | 246 | 393 | 677 | 458 |
| ④ „Jahresbeitrag Lebensversicherung?" | 3 | 3 | 3 | 2 | 11 |
| ⑥ „Wer ist Christian Müller?" | 31 | 569 | 122 | 288 | 164 |
| ⑥ ⛔ der Störer #8076 dazu | 6 | 13 | 5 | 4 | 10 |
| ⑥ „Wer sind die Geschäftsführer der Convis GmbH?" | 1 | 2 | 1 | 2 | 1 |
| Chunks im Raum | 5579 | 4291 | 3023 | 2312 | 1267 |
„Echéance : 31 août" nicht (630–2562). Beträge tragen keine Bedeutung (246–677). ⇒ Die Lücke sitzt bei Datums-Semantik und Zahlen, nicht bei der Größe.
JC-Gesamt-Intention 04.08. (E-49.3, Register #302): Datei(en) in den Composer ziehen · dazu schreiben, was damit passieren soll · als Block in der Konversation sichtbar · Zuordnungs-Prüfung je nach Rechten · ggf. Freistellung für den engsten Kreis. Dazu JCs Reuse-Auflage: „In Gabriel ist das FERTIG — wir haben alles, nur nicht verdrahtet" ⇒ ernten, nicht neu bauen. Dieser Abschnitt ist die Ernte-Messung und der Bau-Vorschlag; er ist Workshop-Stoff, noch nicht beschlossen.
| JC-Anforderung | Gabriel-Bestand (gemessen 04.08.) | Erntbar? |
|---|---|---|
| ① Datei(en) ziehen | Fertig, und reicher als erwartet: Dropzone direkt am Chat-Feld (gabriel.html:1509-1511), Mehrfach-Auswahl, Audio/Nicht-Audio-Weiche (accueilDrop:5484), Warteschlange mit Status je Datei, Einzel-Entfernen, Dubletten-Erkennung („déjà là"), Kamera-Namensfix für generische image.jpg (_enqueueScanFiles:5875) | ✅ 1:1 als Muster |
| ② Hinweis dazuschreiben | ⛔ Gibt es NICHT. Gabriel fragt strukturierte Felder ab (Typ, Quelle, Rechte) — kein Freitext „was soll damit passieren". Das Chat-Feld (:1501) liegt daneben und ist an den Drop nicht gebunden | ❌ neu — und genau das ist der TGV-Kern |
| ③ Block in der Konversation | ⛔ Genau umgekehrt: der Drop verlässt die Konversation — accueilDrop ruft openScanInput() und schaltet auf die Formular-Ansicht „Déposer une source" (:5494). ⚠️ E-51.2: nicht das Formular ist der Fehler, sondern der Weg hinaus | ❌ verschachteln statt ersetzen |
| ④ Rechte-Prüfung | Reich vorhanden, aber als Formular: rights_verbatim_ok · rights_model_release („Personen auf dem Foto") · rights_post_mortem · rights_copyright_holder, mit typ-abhängigem Wortlaut (updateRightsWording:6041 · setDocMeta:6059 · Mig 169) | ✅ Fragen ja, Darreichung nein |
| ⑤ Freistellung engster Kreis | Achse vorhanden: confidentiality_level am Dokument (:3895) + zeitliche Freigabe „Visible à partir de quand ?" (:3981) | ✅ Achse da |
Warum das kein Detail ist: Ein Drop, der den Nutzer aus dem Gespräch hinauswirft, ist die Gegenthese zu diesem Konzept — er führt ihn genau dorthin, wo er nach §3 nicht hin soll. Die Ernte muss deshalb am Bauteil stattfinden (Dropzone-Mechanik, Warteschlange, Dubletten-Erkennung, Rechte-Felder) und nicht am Ablauf.
save_raw ZUERST (Datenfluss, Fluss-Invariante 1). Der Drop ist also ein Eingang in die bestehende Kette — Roh-Layer, dann Triage, dann Fach-Agent (R-178), der Typ, Vertraulichkeit und Zuordnung fachlich setzt. Neu ist nur eines: der Nutzer-Hinweis wird ein Eingabewert der Kette („das gehört zu Projekt X", „das ist die Schlussrechnung dazu") — ein Intent-Feld, das die Triage heute nicht kennt.class: n8n-Einträge mit n8n_wf_id und bundled:false — das ist das vorhandene Muster für „Vertrag da, Bau woanders" (_registry.yaml:322-345); und für „referenziert, noch nicht gebaut" hat sie einen eigenen Kommentar-Block. Beides wird benutzt, statt eine dritte Form zu erfinden — mit der offen benannten Nebenwirkung, dass Kommentar-Einträge von beiden Parsern ignoriert werden und ein Guard sie nicht sieht (KI-First §4b).rights_post_mortem und rights_model_release sind Biografie-Fragen (Gabriel), nicht Buchhaltungs-Fragen; rights_copyright_holder dagegen trifft TGV sehr wohl (fremde Dokumente, Verträge Dritter). Das ist eine Geschäfts-Entscheidung, keine Bau-Entscheidung — sie gehört in den Workshop, nicht in diesen Text.JC-Befund: Frage auf Deutsch nach der Hiscox-Police (französisch) ⇒ 0 Treffer über fünf Formulierungen; dieselbe Frage auf Französisch ⇒ 0.821 Similarity, sofort da. Der Auftrag lautete: miss die Wurzel, schließe sie nicht. ⛔ Gemessen sind es zwei Wände, nicht eine — und welche trägt, hängt davon ab, WER fragt.
| Bestand (1309 Chunks, live 05.08.) | Zahl |
|---|---|
| Sprache | 576 de · 502 fr · 53 en · 175 NULL ⇒ nicht-deutsch 555 = 42,4 % |
| Vertraulichkeit | 1065 streng_vertraulich · 121 intern · 24 vertraulich · 99 Gabriel-Stufen · 0 NULL |
⛔ Erreichbar mit der Standard-Decke intern | 121 von 1309 = 9,2 % |
Erreichbar für den Owner (Decke streng) | 100 % erlaubt — dann entscheidet die Sprache über 42,4 % |
streng, Bypass) ist die Sprache die Wand: 42,4 % des Korpus sind nicht-deutsch. Für jeden anderen Nutzer (Decke intern) ist die Vertraulichkeit die Wand: er sieht 9,2 % — und von 502 französischen Chunks liegen ganze 9 unter seiner Decke. Wer nur eine der beiden Wände einreißt, ändert für die andere Nutzergruppe nichts.language=NULL ⇒ falsche FTS-Konfiguration" — NEIN. Beide Volltext-Spalten sind bedingungslos generiert: content_tsv GENERATED ALWAYS AS to_tsvector('german', content) (05_SCHEMA/072:55) und content_tsv_french analog mit 'french' (125:22) — für JEDEN Chunk, unabhängig von language. Und kein Retrieval-Pfad liest die Spalte language überhaupt (0 Treffer in Mig 221, Mig 232, executors_rag.py). ⇒ language=NULL ist für die Auffindbarkeit folgenlos.hiscox matcht 18 Chunks in BEIDEN FTS-Konfigurationen, mit identischen IDs — die deutsche Spalte findet das französische Dokument genauso, weil ein Eigenname nicht gestemmt wird. ⇒ Der lexikalische Pfad erreicht Hiscox sprachunabhängig. Er ist nur abgeschaltet. Und alle Hiscox-Chunks sind streng_vertraulich — für jeden Nicht-Owner wäre auch der lexikalische Treffer gesperrt.embedding_mistral (1024). Eine deutsche Frage landet nicht neben französischem Text — JCs 0 vs. 0.821 ist genau diese Messung. Betrifft 555 Chunks.language ist Metadatum, kein Filter.rag_conf_rank bildet genau diese vier Namen ab — oeffentlich 0 · intern 1 · vertraulich 2 · streng 3 — und fällt ELSE auf 3. ⛔ Damit stehen DREI Vokabulare nebeneinander: JCs Wortreihe aus E4/JE-157 (fünf Wörter), das DB-Enum aus Mig 181 (vier: oeffentlich/intern/vertraulich/streng_vertraulich — ⛔ hier stand bis 08.08. die englische Wortreihe, die es im Constraint nie gab) und die Gabriel-Skala (fünf weitere Namen). E-5 hat für genau diese Kollision eine Auflage: „wenn 5 Wörter ↔ 4 Enum-Stufen kollidieren → Mini-Arbitrage im Dossier-Format an JC, nicht still entscheiden" — sie ist offen, und die 99 Gabriel-Chunks sind ihr erster messbarer Schaden. Die 99 Gabriel-Stufen (publishable_book, private_gabriel_jc, family_close, editorial_team, excluded) stehen nicht in der Leiter ⇒ sie fallen alle fail-closed auf streng. Klasse R-183: eine Werteliste, die im Schema steht, aber nicht in der Regel, die sie auswertet.search_knowledge_lexical (Mig 232, appliziert) ist bereits zweisprachig — websearch_to_tsquery in german UND french, OR-Semantik — und trägt ein identisches Scope-Gate wie search_knowledge_scoped (mandant_ids hart, conf-Decke fail-closed, SECURITY DEFINER).lexical_caller=None, app.py:177-183) — Rollback Tower#26, weil sie bei JCs Frage 6 falsche Dokumente lieferte: ts_rank kennt kein idf, häufige Terme verdrängen den Eigennamen. Der Satz dort: „Glaubwürdiger Müll > ehrliche Leere = NEIN."rag_hybrid_search.py hat BM25 + RRF-Fusion — also genau das fehlende idf — ist aber DRY-RUN ohne Gate und ohne Relevanz-Floor.ts_rank und ist genau deshalb das Argument für BM25: ein idf-gewichteter Score macht eine Schwelle erst interpretierbar, und ein relativer Floor (Anteil am Top-Score) ist nicht willkürlich, sondern selbstnormierend. Der Floor ist das, was „ehrliche Leere" wieder möglich macht.rag_hybrid_search.py bleibt, wofür es gebaut wurde: ein Messinstrument.Es bleiben zwei Wege — und das ist die eigentliche Arbitrage:
| (a) Frage übersetzen vor dem Einbetten | (c) Mehrsprachige Embeddings | |
|---|---|---|
| Erfüllt E-61.3? gleiche Frage DE+FR ⇒ DASSELBE Dokument | ⚠️ nur wenn die Zielsprache richtig geraten wird. Der Korpus hat de+fr+en — man müsste in alle übersetzen und einbetten, oder raten. Vor dem Retrieval weiß niemand, in welcher Sprache die Antwort liegt. | ✅ bauartbedingt. Ein Raum, jede Frage landet neben Inhalt jeder Sprache — gleiche Frage, gleiche Nachbarschaft, gleiches Dokument |
Die 175 language=NULL | ⚠️ blinder Fleck — ohne Sprach-Label ist kein Übersetzungsziel bestimmbar | ✅ irrelevant — das Modell braucht das Label nicht |
| Kosten | laufend, je Frage: 1 Übersetzungs-Call + N Einbettungen | einmalig: 1333 Chunks · 2,93 Mio Zeichen ≈ 814 000 Tokens — bei Embedding-Tarifen Cent-Bereich |
| Latenz | +1 LLM-Runde bei jeder Frage | ±0 zur Laufzeit |
| Aufwand | klein im Bau, dauerhaft in der Rechnung | einmalige Migration — und das Muster existiert bereits (s. u.) |
| Risiko | Übersetzung ist verlustbehaftet; Fehlerquelle bleibt für immer im heißen Pfad | Modellwahl muss EU-Stack bleiben; Qualität ist vor dem Umstieg zu messen |
rag_chunks trägt heute zwei Vektorräume gleichzeitig, embedding (OpenAI 1536) und embedding_mistral (1024) — der letzte Modellwechsel lief exakt so: parallele Spalte, Backfill, Leser umstellen. ⇒ Das Migrationsrisiko von (c) ist deutlich kleiner als vermutet, weil der Weg schon einmal gegangen wurde. Und die Auflage bleibt: ein Weg, der nur für neue Dokumente gilt, löst JCs Problem nicht — seine Police liegt bereits da; der Backfill über alle 1333 Chunks ist Teil des Wegs, nicht Zugabe.mistral-embed cross-lingual schwach ist, stützt sich bisher auf eine Query (0 Treffer DE vs. 0.821 FR). Das ist ein Indiz, keine Modell-Bewertung. Bevor ein Modellwechsel beschlossen wird, gehört ein A/B über ~20 bekannte DE↔FR-Paare gefahren: dasselbe Dokument, Frage in beiden Sprachen, Similarity je Modell. Kostet einen Tag und macht aus einer Vermutung eine Entscheidung — genau die Reihenfolge, die uns diese Woche zweimal vor einem falschen Fix bewahrt hat.de · 502 fr · 53 en · 175 NULL · 5 unknown) ⇒ E-62 ist eine Anforderung an die BAUART, kein Befund über den Bestand. Der Sprachraum ist offen; ein Entwurf, der ihn schließt, ist am Tag der ersten russischen Rechnung falsch.Damit fallen zwei der drei Wege endgültig — und zwar aus Struktur, nicht aus Geschmack: (a) Query-Übersetzung muss die Zielsprache raten; bei drei Sprachen kann man fächern, bei offener Menge nicht. (b) Volltext braucht in Postgres je Sprache eine eigene Konfiguration und Spalte — german und french sind schon zwei; jede weitere Sprache wäre eine weitere Spalte, Migration und Verzweigung. ⇒ (c) bleibt als einzige Bauart, die mit einer offenen Sprachmenge überhaupt vereinbar ist, weil das Modell kein Label liest, sondern Bedeutung.
| Paar | Rolle im Test | Warum es nicht fehlen darf |
|---|---|---|
| DE → FR | Der reale Fall | 502 Chunks, JCs Hiscox-Police. Der Fall, der den Auftrag ausgelöst hat — wenn er nicht besteht, ist alles andere gleichgültig |
| DE → EN | Kontrolle | 53 Chunks. Englisch ist in jedem Modell am besten gestützt ⇒ ein Modell, das hier scheitert, scheitert überall. Fällt die Kontrolle, ist der Testaufbau kaputt, nicht das Modell (R-188②) |
| DE → RU | Der offene Raum (E-62) | 0 Chunks im Bestand — deshalb wird das Paar aus bekannten deutschen Passagen für den Test übersetzt. Prüft die Eigenschaft, nicht den Korpus. Und es bringt eine zweite Schrift (Kyrillisch) ins Spiel |
| DE → IT | Die ungenannte Sprache | Niemand hat sie beauftragt — genau deshalb. Ein Modell, das FR, EN und RU kann, weil wir sie besprochen haben, aber IT nicht, ist nicht mehrsprachig, sondern auf unsere Beispiele passend. Das ist der Test gegen unsere eigene Auswahl |
| FR → DE | Die Gegenrichtung | ⛔ Cross-linguales Retrieval ist nicht symmetrisch. Ein Modell kann DE→FR gut und FR→DE schlecht abbilden. Kostet fast nichts — und trifft genau den ersten externen Mandanten: Anna ist französisch und wird deutsche Dokumente suchen |
source_document_id wie die Frage in der Sprache des Dokuments? Ja/Nein je Paar.search_knowledge_scoped und §8.2 (Zeile 1 von 0 → ≥1 und Zeilen 4+5 bleiben 0) — genau der Fehler, den wir schon einmal gemacht haben, als die Baseline über den falschen Pfad lief.rag_hybrid_search.py trägt seine Queries als eingefrorene Liste im Code — R-168. Für ein A/B, das wiederholt und erweitert wird, ist das die falsche Bauart.Reuse statt Neubau: Das Gerüst existiert — rag_hybrid_search.py lädt den Korpus, bettet ein, rechnet Cosine, schreibt einen Markdown-Bericht mit Rollup. Fehlen nur drei Dinge: ein zweites Modell, die Ground-Truth-Dokument-IDs je Paar und das Identitäts-Maß. Der Aufwand liegt nicht im Lauf (Minuten), sondern im Bau des Paar-Satzes — realistisch ein halber Tag, weil jede Frage an einem Dokument hängen muss, dessen Inhalt wir kennen.