TGV · Master-Konzept · Selbstverständnis · Stand 2026-07-13

Chatbot First

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").

§1Was KAI ist

„KAI ist dein eigenes ‚ChatGPT hinter der Firewall' — und es spart dir Zeit, weil du redest statt klickst."— KAI_INTENTION_SSOT, Kern-Satz. Statt Menüs und starrer Abläufe: eigene Worte; KAI passt sich an. Deine Fragen verlassen nie das Haus.

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.

§2Die 9 Facetten — mit ehrlichem Stand

Was KAI kann (Intention, SSOT-Wortlaut verkürzt) — und wo die Umsetzung heute WIRKLICH steht (Code/Lane-Wahrheit, nicht Behauptung):

FacetteBedeutungAnker im KanonStand heute (ehrlich)
🇪🇺 Souverän & anonymEU-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 liegenDB · Dokumente · Internet → strukturiert, mit Quellen; belegt, nie geratenR-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 redetausfü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 ScopeEntität · Projekt · Dokument · DB-TeilR-40 R-43 scope-frameFundament scope-frame kanonisch; Auto-Scope nur verengen (RLS)
🎙️ Sprich & dirigierereden/diktieren über den Composer, mit Einordnungs-HinweisenR-62 Diktier-Standardteils TTS/STT gebaut (AUDIO#01 ✅), CORE-Verdrahtung JC-gated; Häppchen-Diktat Katalog-Karte offen
🌍 In deiner Sprachedie 7 TGV-Spracheni18n-Kanon (Seitenbau-Querschnitt)teils Shell 7 Sprachen ✅; Screens/Docs de-lastig — i18n-Gate DESIGN#04
🔒 Respektiert GrenzenMandanten-Datenraum (RLS) + Geheimhaltungsstufe — auch beim SUCHENSicherheit §3/§4 Owner-Bypass, fail-closedteils Governance GELOCKT + RLS-Fundament live; rag_chunks-Retrieval-Gate = Pflicht VOR Mehr-Nutzer (Lücken-Landkarte)
🧾 Prüfbar & compliance-fähigjede Antwort belegt, jede Aktion freigegeben + protokolliert (GoBD/Audit)R-91 · approval_card (R-94) · AuditMuster live Approval-before-Write + record_decision; Audit-Vollausbau lane-weise
🧠 Lernt · ✋ Du gibst freilernt aus Freigaben; schlägt vor und fragt nach statt zu ratenVorschlagswesen (JE-092) · Lernschleife (Seitenbau)teils Freigabe-Prinzip verankert; Lern-Loop (learning_events → Regeln) im Aufbau
Stadium-Ehrlichkeit (Guard): Die Facetten sind die Garantie-Erzählung an Mandanten — deshalb steht der US-LLM-Bruch hier offen markiert (wie im EU-Stack der Sicherheits-Seite): „Ziel EU, heute US" — Due-Diligence (Mistral-DPA, No-Training zitierfähig) VOR der Mandanten-Garantie. Nicht beschönigen.

§3Chatbot-first ≠ Menü abschaffen — die zwei Gesichter

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.

SituationErstes GesichtWarum
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)

§4Der System-Prompt — 3 Schichten, EINE Verwaltung

SchichtInhaltWer ändertWo verwaltet
① Kern (TGV-weit, FEST)Selbstverständnis + 9 Facetten + Verhaltensregeln: belegen · nachfragen statt raten · Freigabe-Prinzip · Scope/RLS/Vertraulichkeit strikt · EU/anonymniemand 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äferenzOwner/mandant_admin je Mandantprompt_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.

§5Warum TGV sich abhebt (Markt)

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.

§6TGV Workbench — Chat plus lebende Screens

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-ZoneRolle im ProduktKorrespondenz am MarktTemplate-/Taxonomie-Folge
Linkes Menue / RailOrientierung, 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.
HeaderMarke, Login, Mandant/Entitaet, Sicherheitsstatus, globale Aktionen.Enterprise-Workspaces zeigen Konto, Workspace und Controls immer oben.workbench-zonen.header · nie screen-lokal nachbauen.
Context-FensterHero, 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.
ComposerImmer 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 / ErgebnisflaecheLebender 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.

§7KAI-Antwortvertrag — Chat-Bloecke und rechtes Panel

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.

BlockKommt in der KAI-AntwortCollapse-RegelPanel-Regel
long_text / summaryZusammenhaengender Langtext oder kurze Antwort, je nach Nutzerbedarf; Stand und naechster sinnvoller Schritt bleiben erkennbar.Hauptantwort offen; Zusatzteile koennen einklappbar sein.Kein Panel noetig.
clarificationNur wenn KAI fuer eine sichere Aktion eine Rueckfrage braucht.Offen, wenn blockierend; sonst einklappbar.Kein Panel, ausser Auswahl/Matrix wird gross.
evidenceQuellen, Datenstand, Annahmen, Sicherheits-/RLS-Hinweise.Standardmaessig einklappbar.Panel bei Quellenliste, Dossier oder Audit-Trail.
actionsButtons fuer Freigabe, Export, Reparatur, neuen Screen, Termin, Nachricht.Offen, wenn Handlung erwartet wird.Panel fuer Vorschau vor Schreibaktion.
panel_offerKarte/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_contractBei 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.

§7b · Die Wahrhaftigkeits-Klausel — KAI sagt nie, was er nicht getan hat AGENT-CHAT#01 · 15.08.2026 · Register #449 / #454-B4

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.

⛔ Fall 1 — die Vollzugs-Prosa im action-Turn (JC-Livebeweis Beleg #8210 / #8218, lokal 3/3 reproduziert). KAI schrieb „Ich führe jetzt die Bestätigung aus:" — und tat es nie. Die Wurzel war nicht Bosheit und nicht der Skill: das Modell formuliert die Begleit-Prosa in dem Glauben, sein Werkzeug-Aufruf laufe gleich; der Loop bricht aber bewusst zur ApprovalCard ab (Design #401 — kein Auto-Apply, korrekt). Die Prosa überlebte den Abbruch und wurde wörtlich veröffentlicht.
⭐ Deshalb ist der Fix DETERMINISTISCH und nicht „besseres Prompting": 08_OPS/chat_engine/agent_loop.py:144 _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.
Die Lehre für den Antwortvertrag: Ein Block, der eine Handlung ANKÜNDIGT, muss an den Vollzug gebunden sein — sonst darf er nicht in die Antwort. „Vorschlagen" ja, „vollzogen haben" nur mit Vollzug.
⛔ Fall 2 — die Quittung, die vom Vorschlag stammte statt vom Server (PR #1783). Der Schreibzugriff HIELT (status=approved, learning_event + agent_event vorhanden) — aber die Quittung zeigte die vorformulierte Zusage der Karte („Danke — ich bestätige das jetzt."), während Typ, Betrag und Projekt offen blieben. „Erledigt ✓" las sich als „Zuordnung erledigt", und der Fall verliess die Klärungs-Liste. Nicht gelogen, aber auch nicht wahr: die Quittung sprach über eine Absicht, nicht über einen Zustand.
⭐ Regel daraus — die Quittung ist ein Golden-Read-Back, kein Echo: Der Skill liefert, was NACH dem Schreiben offen bleibt (02_SKILLS/tgv-skill-klaerung/skill.py:627-630 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.
JC-Live-Abnahme 16.08. (Register #458): „Dokument #8204 bestätigt (== DB verifiziert)" — die Quittung sagt jetzt Server-Wahrheit; im Fall #8211 fragt KAI ehrlich nach, statt zu raten. Beide Fixes sind live bestätigt, nicht nur gemergt.
⚠ Ehrlich offen — die Wiedervorlage-Lücke. JCs Sorge hinter #454-B4 ist mit der Quittung nur zur Hälfte beantwortet: „werde ich je wieder gefragt?" Ein Fall, der bestätigt ist, aber offene Felder trägt, verlässt heute die Liste. Eine Wiedervorlage-Mechanik existiert nicht — gesucht in 02_SKILLS/tgv-skill-klaerung, 08_OPS/chat_engine und den Klärungs-Screens. Ball liegt bei Tower (Owner-Zuweisung), nicht in dieser Seite.

§7d · Vom Vorschlag zum Vollzug — die Karte, die handelt RECHTE-UX#01 · 19.09.2026 · Register #2128 (Vorlage, JC „alle 6 OK“) · #2133 (Bau, PR #2809)

§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.

GrundsatzWas er heißtWirkort
Ein Endpunkt, zwei TürenKlick 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 ServerWas 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 vollziehtDer Vollzug trägt den Namen des Menschen, nie KAIs. Die Spur kai.vollzug steht in agent_events.vollzug.von
Einmal, nicht zweimalEin Doppelklick oder zwei Tabs vollziehen einmal. Der zweite Aufruf bekommt das gespeicherte Ergebnis.bedingtes PATCH state=eq.pending
Nur, was belegt passtEin 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 ZustandOffen · 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 nachEinen 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
⚠ Ehrlich offen: Karten aus einem wieder geladenen Gespräch tragen keine Antwort-ID am Knoten und sind darum nicht nachträglich ausführbar. Der Vollzug ist nicht live, bis #2809 deployt ist. Die RKA-Werkzeuge und persona-apply-correction sind noch nicht vollziehbar (Schema ≠ Endpunkt bzw. kein Endpunkt).

§7c · Werkzeugtreue — „KAI kann über alles reden und hat richtige fundierte Antworten" KAI-WERKZEUG#03 · 14.09.2026 · Register #1916 (Konzept) · Belege #1905/#1906 · #1904/#1909 · #1915

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.

Eine Zahl ohne Basis ist keine Antwort. Eine Zeile ohne Werkzeug ist ein Fabrikat. Eine Deutung ohne Kennzeichnung ist eine Behauptung.Die drei Sätze der Werkzeugtreue — gemessen an #1905 (745 ohne Etikett · drei Zeilen ohne Quelle · „soeben nachgeliefert" ohne Vollzug)

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.

BezugsartWoher die Aussage kommtWirkort (Code)SchrankeMandantenwand
① BestandBelege, 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 einJede 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).
② WissenInhalte aus Dokumenten und Notizen über die zwei Suchwege (§9e) — Wort und Vektor.executors_rag.py · search_knowledge_scoped · Quellen-Karten mit retrieval.geprueftGeprü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).
③ SelbstauskunftWas 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.
④ AllgemeinwissenWas das Modell aus sich heraus weiß — Steuerlogik, Vertragsrecht, Fachbegriffe, die Welt.Persona + Grounding-Fragment (prompt_config, §4) — kein WerkzeugErlaubt 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).
⛔ Die Regel, die alle vier bindet — und warum sie deterministisch ist, nicht Prompt-Appell:
· Strukturdaten sind Blöcke. Ein Diagramm (R-171), eine Liste (#1905), eine Karte (S5) entstehen im Executor aus scope-gegateten Zeilen und werden vom Loop eingesammelt. Das Modell kann sie nicht erzeugen — es kann sie nur beschreiben. Was nicht im Block steht, kann im Bild nicht erscheinen.
· Der Prompt sagt es dazu (agent_loop._GENERATIVE_UI_NOTE: TABLE_WHEN · CHART_WHEN · _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).
· Vollzug nur mit Vollzug (§7b): „soeben nachgeliefert" ohne Werkzeug ist dieselbe Klasse wie „ich führe jetzt aus" ohne Ausführung.
· Enthaltung mit Reichweite (CLAUDE.md, Handgriff ③ „nirgends braucht seine Reichweite" — im Regelwerk noch ohne eigene Nummer): „dazu finde ich nichts" nennt, WO gesucht wurde — sonst ist es eine Behauptung über Nicht-Existenz, so groß wie die ungenannte Liste.
· Die Spur trägt die Argumente (#1915): 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).
⚠ Ehrlicher Stand, gemessen 14.09. — was heute NOCH nicht werkzeugtreu ist:
· get_transactions liefert weiter rohes {count, sample, basis} ohne Block — erledigt am 14.09. (Registerzeile #1943): die Buchungsliste reist jetzt ebenfalls als table-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.
· Allgemeinwissen ist nicht gekennzeichnet: eine Deutung sieht in der Prosa aus wie ein Bestandsbefund. Der Vertrag §7 kennt evidence — er wird für Bezugsart ④ nicht gefüllt. Entscheid offen: Kennzeichnung im Text („nach allgemeiner Regel") oder als Block.
· Die Argumente der Spur sind nicht sichtbar — erledigt am 14.09. (Registerzeile #1949, ausgeliefert mit 1.12.144): 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.
· Werkzeugtreue wird nur strukturell bewacht (Kontrakt-Tests am Quelltext, FakeLLM). Ein Golden-Eval, das eine echte Modellantwort gegen den Block prüft („nennt die Prosa eine Zeile, die der Block nicht trägt?"), existiert nicht — Wirkort wäre 07_TESTS/agent_chat01/test_*_golden_regression.py mit trace.
⚠ Live gemessen 11.10.2026, 03:00–03:13 MESZ (KAI-QA#03, Register #3974) — echte Sitzungen viewer · C3-Verwalter · C4-Verwalter, Dienst 1.12.359, master 0a192642ebc6ec468bddef1f5cfbcef11a5402a4:
· Mandanten-Wand hält (08_OPS/mandantenwand_gegenbeweis.py rc=0): alle drei Konten, Tür A (PostgREST/RLS) und Tür B (KAI), jeweils mit Positivkontrolle (eigen 507 · 151 · 347) und Angriff auf den fremden Mandanten — 0 fremde Zeilen. Dazu Privat (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).
· Prozess-Sicht am ausgerollten Dienst (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.
· ⛔ Befund 1 — 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).
· ⛔ Befund 2 — Verwalter bekommen im Chat die Decke 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.
· ⛔ Befund 3 — KAI-QUELLE#01 deckt den Fall „Werkzeug verweigert, Modell erfindet Gründe“ nicht: auf die Karte „keine Steuer-Liste“ bzw. „nur ohne Decke“ schrieb KAI „noch nicht erstellt / keine Buchungen / Datenstand“ bzw. „liegt im System vor“ — beides steht in keiner Karte; der Antwort-Prüfer meldete 0 Befunde, denn Klasse G greift nur bei ok=false. Dazu der Rechtsname: KAI nennt C3 mit dem Namen eines KUNDEN statt mit dem eigenen Rechtsnamen (Klasse von #3818, Tor 33).
· Durchstich KAI-Q0009 (viewer, gespeicherter Kontostand): 5 von 5 aktiven Salden stimmen auf den Cent mit dem unabhängigen Lesepfad 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.
· ⛔ Warum kein Fall grün werden KANN (KAI-150-06): Nachweis ⑥ (JC-Bewertung) hat keinen Weg in den Status. review_server.py schreibt nach 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.
· Q0152 braucht einen C4-viewer — 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).
Rohantworten nur lokal: 08_OPS/credentials/kai_qa_evidence/kai_qa03_20261011/ (nie in git). Sitzungen per provision_qa_session.py --erneuere, ohne JC.
⛔ Wege in TGV kommen aus dem Menü, nie aus dem Modell (KAI-QA#03, Register #4087, 11.10.2026):
· Anlass (ADMIN#09 live): KAI schrieb einem C3-Verwalter „Die vollständige UStVA findest du unter: Finanzen > Vorsteueranmeldung > Juli 2026“ — diese Fläche gibt es nicht.
· Gemessen: 5 von 730 echten Antworten nennen einen Weg, alle 5 unbelegt. Live (Dienst 1.12.390), je acht Fragen „wo finde ich …“: C3 3 erfunden · 1 belegt, Viewer 2 erfunden · 1 belegt — darunter die Route /finanzen/bank und der Bereich „Steuern & Abgaben“ fürs Fahrtenbuch (richtig: Reisen & Fahrten › Fahrtenbuch).
· Wurzel: KAI hatte für Wege keine Quelle. Jetzt: 08_OPS/gen_menue_kanon.py erzeugt chat_engine/menue_kanon.json aus dem gebauten Menü (49 Wege, die CI prüft die Frische); das Werkzeug 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).
· Grenze: Reiter innerhalb eines Screens stehen nicht im Kanon; ⛔ falsche Aussagen OHNE Weg („es gibt keine Profil-Seite“, „nur über Support, support@tgv.fr“ — live 11.10. als C3) fängt keine Prüfer-Klasse, auch X nicht (gemessen an beiden Sätzen: 0 Befunde). Die Regel verbietet sie im Prompt. Seit #4145 misst sie Klasse F 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.
· Sprachgrenzen als Daten (#4150): eine Grenze, die nur in der Werkzeug-Beschreibung steht, kann niemand messen. chat_engine/tools.py 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).

§8Kontext-Hierarchie: SCOPE ⊃ KONVERSATION ⊃ KARTE

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.

EbeneWas sie istWie sie gesetzt wirdBaustein
① SCOPE / BetrachtungsraumDie ä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-KONTEXTDer Thread. Liegt INNERHALB einer Scope-Definition.Durch Öffnen/Fortführen eines Threads.chat_sessions · K-16 Composer-Bindung
③ KARTEN-KONTEXTDer 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)
Verschachtelung, keine Konkurrenz: Karte ⊂ Konversation ⊂ Scope. Die innere Ebene verengt — sie widerspricht nie der äußeren.— E-44, JC 03.08. „Wie immer neu machen und TOKEN verbrennen = VERBOTEN": der Kanon für die Zustandszeilen EXISTIERT (K-10, E-15.3) und wird ANGEWENDET, nicht neu erfunden.

⛔ 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.

⚠️ JC-Warnung, ernst zu nehmen: „Vorsichtig damit umgehen, damit KAI dadurch nicht kaum noch eine Antwort findet." Eine zu enge Verengung erzeugt Leer-Antworten. Die Verengung muss dem Nutzer dienen, nicht ihn aussperren — und wo sie greift, wird sie genannt (R-177).

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.

§8bDas Scope-Objekt — fünf Achsen, drei Wirkorte

§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
MandantInstanz-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ätsession_overrides.entity — Session darf verengen, nie ausweitenK-11 Scope-Frame✅ out["entity"]✅ p_entity
Projektsession_overrides.project_codeK-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 ScopeK-11-Popover bietet „von–bis" an⛔⛔
Gepinntes Dokumentsession_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)
⛔ Befund 1 — die Projekt-Achse ist nicht „ungenutzt", sie ist beim Umstieg VERLOREN GEGANGEN. Die Spalte existiert (05_SCHEMA/028_rag_schema.sql:23) und hat einen eigenen Index (:36), und die Vorgänger-Funktion konnte danach filtern: 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.

⚠️ PRÄZISIERUNG 04.08. (AGENT-ORCH#01, live gemessen) — die Formulierung „das RAG verliert die Projekt-Achse" wäre zu scharf gewesen. Gemessen: 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.
⛔ Befund 2 — den Zeitraum gibt es nirgends, nicht nur „nicht im Chunk-Filter". Das 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.
Was daraus für den Bau folgt (Reihenfolge, nicht Wunschliste): ① Die Achse Zeitraum ins 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.

§9Was KAI im Wissen findet — und was nie

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.

LesewegWas geprüft wirdVerhalten
executors_rag → RPC search_knowledge_scopedmandant_id hart · Entitäts-Verengung · Vertraulichkeits-DeckeMig 221 APPLIED fail-closed: fehlender Wert (NULL) gilt als „streng"
Scope-Verengung aus dem DialogProjekt/Zeitraum aus dem Scope-Objekt (§8)Ziel rag_chunks.project_code existiert, ungenutzt; Zeitraum fehlt als Chunk-Filter
Lexikalischer FallbackFTS de+fr, nur wenn die Vektorsuche NICHTS liefert — nicht zusätzlich, nicht gemischtLIVE seit 08.08. Mig 232 appliziert, lexical_caller gesetzt (app.py), Service 0.50.0. Details: §9e
⛔ Die Lücke, ehrlich benannt (#288): der Live-Ingest setzt 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)).
⛔ Vorbedingung für alles hier (E-42-P1): Solange dem Embedding-Service der API-Key fehlt, ist die Wissenssuche tot — KAI sieht dann nur Metadaten, keinen Vertragstext, keine Police, kein Dokument. Der Code degradiert ehrlich (executors_rag.py:289), der Defekt ist Betrieb/Config auf dem Server. Ohne diesen Key ist der Nordstern „KAI soll perfekte Antworten geben" strukturell unerreichbar — und die RAG-Hälfte der Scope-Kette aus §8 ist nicht messbar.

§9eDie zwei Suchwege — warum es zwei sind, und was jeder NICHT kann

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.

⛔ ZUERST: zwei Agenten, zwei Momente — sie treffen sich nie.
① Beim EINGANG triagiert der ORCH-Agent: doc_type · entity · provider · skill_plan und seit 08.08. auch die Vertraulichkeitsstufe. Er sieht den Volltext des Dokuments.
② Bei der FRAGE sucht KAI. Er sieht eine Suchanfrage, nie das ganze Dokument.
⇒ Der lexikalische Weg gehört ausschließlich zu ②. Er entscheidet nichts über Zuordnung, Einstufung oder Ablage. Wer ihn beim Eingang vermutet, sucht ihn an der falschen Stelle — die Agenten-Rollen stehen in KI-First §4f.

Was jeder Weg kann — und wo er blind ist

WegStä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"
⛔ DIE MESSUNG — sie ist die Begründung, nicht die Illustration (08.08., JCs echte Frage).
Gesucht war ein Schwellwert, der die echte Frage durchlässt und Unsinn blockt. Es gibt ihn nicht — auf drei Ebenen gemessen, alle überlappen:
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
⭐ Und warum auch ein Reranker es NICHT gelöst hätte: 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).
⇒ JCs eigene Frage rankt schlechter als „Rezept für Lasagne". Das ist keine Kalibrierfrage mehr.
⭐ DIE ENTSCHEIDUNG: markiert statt gefiltert. Die Ergebnisse trennen scharf, obwohl die Scores es nicht tun — JCs Frage liefert 10/10 richtige Treffer, „Wetter in Tokio" 20 unbrauchbare. Wir haben die richtige Antwort und kein Kriterium, sie am Score zu erkennen. Also wird nicht gefiltert, sondern gekennzeichnet:
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.
KAI urteilt selbst — das ist der Cross-Encoder, den wir nicht bauen mussten: das Modell liest die Treffer ohnehin und kann eine Police von einer Druckerpapier-Rechnung unterscheiden; es wusste nur nicht, dass es das prüfen soll.
⛔ Das verletzt „glaubwürdiger Müll > ehrliche Leere" NICHT: jener Satz richtet sich gegen unmarkierten Müll, der wie ein Treffer aussieht. Markierter Müll, den KAI verwirft, endet in genau der ehrlichen Leere.
⛔ Und die Grenze: dem Modell wird ein Relevanz-Urteil anvertraut, nie eine Scope-Entscheidung. Mandantenwand und Vertraulichkeits-Decke bleiben Code (§8b, Sicherheit).
⭐ Am Live-System gemessen (08.08., Service 0.50.0, je 3 Läufe):
Echte Frage (Eigenname, konversationell): Vektor 0 Treffer → Fallback 6 → jede Karte trägt weg:"wortsuche" · geprueft:false · ai_unverified, KAI antwortet inhaltlich mit Beleg-Link. 3/3 stabil.
Gegenprobe („bester Wanderweg in den Alpen"): der Fallback liefert 3–6 markierte Treffer — und KAI verwirft sie 3/3: „Die gefundenen Treffer sind vollständig irrelevant – interne Systemdokumentation sowie Google-Cloud-Rechnungen." Durch sein Urteil, nicht durch einen Filter.
⚠️ Offen, ehrlich benannt: die Antworten sind 854–1300 Zeichen lang. JCs Maßstab ist „ein Satz, kein Bericht" — inhaltlich richtig, in der Form zu lang. Ein halber Erfolg, kein voller.
⛔ DIE FEHLDIAGNOSE, die dazugehört (26.07. → 08.08.). Der lexikalische Weg war seit dem 26.07. abgeschaltet, weil er bei JCs Frage sechs falsche Dokumente lieferte. Diagnose damals: der Ranker sei kaputt (kein idf, kein Floor).
⇒ Die Ursache war eine andere: die richtigen Dokumente waren gar nicht im Ergebnisraum. ⭐ Ein Ranker, dem man die richtigen Antworten wegnimmt, sortiert zwangsläufig die falschen nach oben — und sieht dabei aus, als sei er kaputt.
Gesichert ist die Wirkung, nicht der genaue Mechanismus: für Nicht-Owner belegt durch die Einstufung (der Beleg 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.

§9fWas TGV heute eingestellt hat — sieben Zahlen, und wer sie justiert

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".

Wertsteht auflebt ingesetzt von / wannzuletzt GEMESSEN
LLM-Modellclaude-haiku-4-5-20251001DB 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_tokens1400DB llm_paramsTower#33, 08.08.nie
⛔ dieselbe Zahl im Code900taxonomy_config.py:104—⛔ weicht ab — greift nur, wenn die DB unlesbar ist, und niemand würde es merken
CHUNK_CHARS3200Code ingest_core_privat.py:70unbekannt08.08. — §9g, erste Messung überhaupt
CHUNK_OVERLAP400Code ingest_core_privat.py:71unbekanntnie — in §9g lief er proportional mit, nie eigenständig
Treffer für KAI10 (6 Vektor + bis 4 Wortsuche)Code executors_rag.py GESAMT_MAXTower#26 / A″ 08.08.nie — begründet, nicht gemessen
p_min_score0.78 trägt seit dem 17.07. das Wort INTERIMDB, 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_k6DB, Mig 22717.07.nie
⛔ NIEMAND JUSTIERT. Das ist der Befund, nicht die Lücke. JC fragt nach dem Agenten, der die Balance hält. Es gibt ihn nicht — weder für das Retrieval noch für die Chunking-Methode. Sieben Zahlen stehen fest, fünf davon wurden nie gemessen, und keine einzige hat einen Eigentümer, der merkt, wenn sie falsch steht.
⭐ Und das verletzt unseren eigenen Kanon: „Tunable Params gehören in Config, nicht in Hardcode." Drei der sieben stehen als Konstante im Code — eine davon zweimal, mit verschiedenen Werten (siehe Zeile 3).
Was das konkret kostet, an drei belegten Fällen:
· 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).
⛔ Hier steht bewusst kein Vorschlag für einen solchen Agenten. Erst muss benannt sein, dass niemand steuert; der Entwurf ist ein eigener Auftrag und gehört JC vorgelegt. ⭐ Wer die Lücke im selben Atemzug schliesst, in dem er sie benennt, nimmt JC die Entscheidung ab, die ihm gehört.

§9gDie Chunk-Größe — gemessen statt gesetzt, und sie ist NICHT der Hebel

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)600800120016003200 = IST
① „meine Hiscox Versicherung"89632
① „Steuerberater Ruef"551454
② „Wann Schlüssel abgeben?" (tief, 79 %)1212877691707698
② „Grundlage für das Honorar?" (tief, 85 %)333518
③ „welche Versicherung läuft Ende August aus?"2562201414801157630
③ „ab wann bekomme ich meine Rente?"612872
④ „wie hoch war die IONOS-Rechnung im Mai?"555246393677458
④ „Jahresbeitrag Lebensversicherung?"333211
⑥ „Wer ist Christian Müller?"31569122288164
⑥ ⛔ der Störer #8076 dazu6135410
⑥ „Wer sind die Geschäftsführer der Convis GmbH?"12121
Chunks im Raum55794291302323121267
⛔ DIE LEHRE, nicht der Durchbruch (08.08., von dieser Lane selbst widerrufen). In der Nacht sah es aus, als sei die Chunk-Größe der Hebel: dasselbe Dokument (#8214) stand auf Rang 451 und mit 700-Zeichen-Stücken auf Rang 27. Das reproduziert sich nicht. Schneidet man ALLE Dokumente fein, steht es auf 1212 — schlechter als heute.
⭐ Ein Rennen, in dem nur einer neue Schuhe hat, misst nicht die Schuhe. Feine Stücke traten gegen 3200er Nachbarn an; der Vorsprung war der Vergleich, nicht die Methode. Diese Zeile steht hier, damit die 27 nicht als Erkenntnis weiterlebt.
⭐ WAS DIE MESSUNG STATTDESSEN ZEIGT — vier Sätze.
① Feine Chunks helfen, wo die Antwort ein zusammenhängender Absatz ist (#8148: 18 → 3), und schaden, wo das Dokument als Ganzes die Antwort ist (Hiscox 2 → 8). 2 besser, 6 schlechter.
② Drei Klassen sind bei JEDER Größe unerreichbar. „Ende August" findet „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.
③ Der Unsinn steigt NICHT mit (0.712–0.781, ein Chunk über dem Cutoff): die Rangänderungen sind echt, nicht ein globales Lauter-Werden.
④ Die Größe kostet kein Geld, sie kostet Aufrufe. 2,89–3,00 Mio Zeichen über alle Größen — derselbe Text, anders geschnitten. Aber 175 statt 62 Aufrufe und 21 Rate-Limit-Treffer, fast alle unterhalb 1600.
⛔ ⑥ PERSON/ROLLE — der gefährlichste Befund, weil er nicht wie ein Fehler aussieht. JC fragte „Wer ist Christian Müller". KAI antwortete überzeugend und gab die Rolle des Mitgeschäftsführers falsch wieder — aus einem Beleg (#8076), in dem der Name als Mail-Absender steht und daneben die Signatur einer fremden Firma (HRB269886B: „Geschäftsführer: Dr. Matthias Finck, Remus Grolle-Hüging…"). Die richtige Auskunft steht in 21 Belegen, u. a. #7503/#7715: „Geschäftsführer: Jean-Christophe Binetti · Dipl.-Ing. Christian Müller" (HRB 102141 B).
Der Störer gewinnt bei ALLEN fünf Größen (Rang 4–13 gegen 31–569). ⛔ Die Chunk-Größe repariert das nicht.
⭐ Was es repariert, ist die FORM DER FRAGE: dieselbe Tatsache, als „Wer sind die Geschäftsführer der Convis GmbH?" gefragt, steht auf Rang 1. „Wer ist X" ist eine Erwähnungs-Frage — das Embedding belohnt Dokumente, in denen X prominent vorkommt, nicht Dokumente, die X beschreiben.
⇒ Zwei Folgerungen, beide offen: eine Erwähnungs-Frage müsste intern zur Rollen-Frage werden, bevor sie den Index trifft — und ein Beleg mit fremder Handelsregisternummer ist für eine Rollenaussage kein Beleg. Die Nummer steht im Text; heute liest sie niemand. offen

§10Dateien in den Composer ziehen — der Ablage-Dialog Gate-A-Vorlage, noch kein Kanon

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-AnforderungGabriel-Bestand (gemessen 04.08.)Erntbar?
① Datei(en) ziehenFertig, 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üfungReich 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 KreisAchse vorhanden: confidentiality_level am Dokument (:3895) + zeitliche Freigabe „Visible à partir de quand ?" (:3981)✅ Achse da
Gabriel hat den Transport. TGV braucht den Dialog.Der Mechanik-Teil ist fertig und wird geerntet — Dropzone, Warteschlange, Dubletten, Rechte-Felder. Das Interaktions-Modell wird dabei nicht ersetzt, sondern verschachtelt (E-51.2): bei Gabriel führt der Drop AUS der Konversation in ein Formular; in TGV entsteht zuerst ein Block IN der Konversation, und das Formular wird von dort als Panel darüber geöffnet (E-49.3 + „LLM liefert Intent, nie Markup"). ⚠️ Und der Satz ist keine Kritik an Gabriel: was dort fehlt, ist ein Zeitstand, kein Mangel — Gabriel entstand vor der KAI-Prägung, und der Rückfluss ist Auflage (E-51.1).

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.

⛔ PRÄZISIERUNG E-51.2 (JC-Frage „Brauchen wir nicht beide Richtungen?" — Tower-Entscheid, bindend): Der Fehler ist NICHT das Formular. Der Fehler ist, dass Gabriels Drop aus dem Gespräch hinausführt. Beide Richtungen gelten — aber verschachtelt, nicht als Alternativen:
· Der Dialog ist der EINGANG. Jeder Drop erzeugt zuerst einen Block in der Konversation (Datei-Karte + Nutzer-Freitext + KAI-Vorschlag). Das Ereignis gehört ins Gespräch, sonst ist es dort verloren.
· Das Formular ist eine DETAIL-FLÄCHE, die man von dort öffnet — als Panel/Slide-over ÜBER der Konversation (PAN-01/E-27: der Content bleibt sichtbar und wird nie verkleinert oder gesplittet). Das Ergebnis kommt als Block zurück in die Konversation.
· ⛔ Nie ein Weg, der aus dem Gespräch herausführt. Dieselbe Logik wie §8: die innere Ebene verengt, sie widerspricht nie der äußeren.
· Massen-Fall: viele Dateien ⇒ EIN Block mit Liste in der Konversation; die strukturierte Massenbearbeitung ist die K-40-Bulk-Fläche im Panel, nicht 50 Chat-Runden.
· Beide Aktionstypen bleiben visuell unterscheidbar (Prompt-senden ≠ UI-Aktion, JC 07.07.) — nie dasselbe Button-Design.
⛔ ERNTE IST ZWEIRICHTUNG — der Rückfluss wird mitgeplant (E-51.1, JC: „Bitte notieren!"): „Bei Gabriel war der KAI-Einsatz noch nicht so geprägt. Wir sollen zusehen, dass Gabriel auch davon profitiert, wenn wir es kanonisiert haben." ⇒ Was in Gabriel fehlt — Freitext-Hinweis beim Drop, Dialog statt Formular — ist kein Mangel, sondern ein Zeitstand: Gabriel entstand vor der KAI-Prägung. Auflage: bei JEDER Ernte aus Gabriel wird der Rückfluss mitgeplant — der kanonisierte TGV-Baustein muss so geschnitten sein, dass Gabriel ihn übernehmen kann (gemeinsame Komponente/Skill, kein TGV-Sonderweg). Erster konkreter Fall ist genau dieser: der Composer-Drop mit Hinweis. Das ist die Anwendung des bestehenden Rollen-Satzes — Gabriel ist Kompendium für Gabriel und Archiv-Inspiration für TGV, und umgekehrt; gelebt wurde bisher nur die Richtung Gabriel→TGV.
Vorschlag — Komponente und Skill sauber getrennt (zu entscheiden, nicht beschlossen):
Als KOMPONENTE gehört der Drop in den Composer (K-16) — er ist keine eigene Insel: dieselbe Fläche, die tippt und diktiert, nimmt auch Dateien an (+ Anhang existiert bereits als Kanon-Element). Sichtbares Ergebnis ist ein Block in der Konversation: die Datei(en) als Karte, darunter der Freitext des Nutzers („was soll damit passieren"), darunter KAIs Vorschlag zur Zuordnung. Rendern über die vorhandenen Bausteine, kein Neubau — Karte, Badges (3 Achsen), Freigabe-Karte für den Schreibschritt.
Als SKILL ist es kein neuer Intake-Weg: „Chat-Drag&Drop" ist in Dokumente §4 längst als Kanal geführt, und der Kanon sagt 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.
⛔ Die Ziel-Semantik der Zuordnung: „Wissen = sichtbarer ORDNER" (JC-Modell, E-25 B2.2): Statt eines abstrakten „in Wissen" schiebt der Nutzer das Dokument in einen Ordner, dessen Sichtbarkeit er kennt — der Ordner trägt das Sichtbarkeits-Badge, die Datei erbt es (K-41-Vererbung). JCs Begründung als Maßstab: Default-Verständlichkeit vor Magie. Für den Ablage-Dialog heißt das konkret: die Antwort auf „was soll damit passieren" endet nicht bei einer Kategorie, sondern bei einem benannten Ort mit sichtbarer Reichweite — „nach C3 ▸ 2026 ▸ Verträge (intern)" statt „ins Wissen aufgenommen". Damit ist die Zuordnungs-Prüfung aus E-49.3 keine unsichtbare Rechte-Rechnung, sondern eine Aussage, die der Nutzer im Moment des Ablegens lesen und korrigieren kann. Anschluss: Dokumente §5 (Ablage-Struktur) und der Dokumentalist (#256), der die Bewegung ausführt und den Schutzlevel prüft.
Skill-Bedarf, nicht UI-Platzhalter (E-31.4): Jede „in Vorbereitung"-Funktion wird als Skill-Bedarf im Register geführt — nicht als Knopf, der auf ein Nichts zeigt. Für den Ablage-Dialog ist der Bedarf benannt und in 02_SKILLS/_registry.yaml als Bedarfs-Notiz hinterlegt: ein Intent-Feld an der bestehenden Intake-Kette, kein neuer Skill-Baum. ⛔ Reuse statt Zwischenform: die Registry führt bereits 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).
⚠️ Die eine offene Frage, die wirklich entschieden werden muss (Workshop): Wie viel Rechte-Abfrage darf im Dialog stehen? Gabriel stellt bis zu sechs Fragen je Datei (Typ · Quelle · 4× Rechte). Sechs Rückfragen nach einem Drop wären ein Formular mit Sprechblasen — der Fehler, den §3 ausdrücklich vermeidet. Der Kanon gibt die Richtung schon vor: E-50① sagt, die Vertraulichkeit setzt der Fach-Agent fachlich, und die Klär-Schwelle sagt: nur echte Ausnahmen erreichen den Nutzer. Offen bleibt, welche der Gabriel-Rechte-Felder für TGV überhaupt gelten — 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.

§9bDie zwei Wände vor dem Wissen — gemessen 05.08. Register #334

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
Sprache576 de · 502 fr · 53 en · 175 NULL ⇒ nicht-deutsch 555 = 42,4 %
Vertraulichkeit1065 streng_vertraulich · 121 intern · 24 vertraulich · 99 Gabriel-Stufen · 0 NULL
⛔ Erreichbar mit der Standard-Decke intern121 von 1309 = 9,2 %
Erreichbar für den Owner (Decke streng)100 % erlaubt — dann entscheidet die Sprache über 42,4 %
Beide Diagnosen stimmen — aber für verschiedene Nutzer.Für JC als Owner (Decke 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.
⛔ ZWEI ANNAHMEN DES AUFTRAGS SIND WIDERLEGT — gemessen, nicht geschlossen:
① „175× 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 ist ein Sprachproblem" — nur zur Hälfte. Gemessen: 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.
Was wo klemmt — die Wurzel je Ebene:
· Embedding-Ebene (die echte Sprach-Wurzel): EIN Vektorraum, embedding_mistral (1024). Eine deutsche Frage landet nicht neben französischem Text — JCs 0 vs. 0.821 ist genau diese Messung. Betrifft 555 Chunks.
· Query-Ebene: es gibt keine Übersetzung der Frage vor dem Retrieval, nirgends in der Kette.
· Chunk-Ebene: ⛔ nicht die Ursache (s. o.) — language ist Metadatum, kein Filter.
· Und die Wand daneben: 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.
Was bereits GEBAUT ist — und warum es trotzdem nicht hilft (NUXIN-Ernte):
· 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).
· Sie ist abgeschaltet (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.
⇒ Von den drei Bedingungen des Rollbacks (idf · Floor · getreuer DB-Test) ist eine gebaut, eine messbar, und nur der FLOOR ist eine echte Neuentscheidung. Mig 232 hat ihn ausdrücklich abgelehnt: „Ein ts_rank-Schwellwert wäre willkürlich" (232:21) — das stimmt für 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.

§9cDer Weg — nach JCs Ausschluss (E-61)

„Ich erwarte, dass der Chatbot die gleiche Qualität der Antworten gibt und die LLM multilingual nutzt — und nicht auf eine Volltext-Suche zurückstuft!!!!"— JC 05.08. (E-61). Die Klarstellung dahinter ändert die Fragestellung: Das LLM ist nicht das Problem. Es liest ein französisches Dokument und antwortet auf Deutsch in voller Qualität — wenn das Dokument es erreicht. Der Bruch sitzt ausschließlich im Retrieval. ⇒ Es gibt keine „schlechtere Antwort in der anderen Sprache". Es gibt eine Antwort oder gar keine.
⛔ Damit ist Weg (b) „Volltext-Hybrid" als ZIEL gestrichen — nicht weil er schlecht misst, sondern weil er den semantischen Pfad ersetzt statt ihn zu reparieren. JCs Instinkt deckt sich exakt mit der Narbe vom 26.07.: genau dieser Weg lieferte bei genau dieser Hiscox-Frage 6 falsche Dokumente. ⚠️ Aber die Abgrenzung ist die ganze Entscheidung: Volltext darf als zusätzliches Signal in einer Fusion eingehen — nie als Rückfallebene. Und 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
Kostenlaufend, je Frage: 1 Übersetzungs-Call + N Einbettungeneinmalig: 1333 Chunks · 2,93 Mio Zeichen ≈ 814 000 Tokens — bei Embedding-Tarifen Cent-Bereich
Latenz+1 LLM-Runde bei jeder Frage±0 zur Laufzeit
Aufwandklein im Bau, dauerhaft in der Rechnungeinmalige Migration — und das Muster existiert bereits (s. u.)
RisikoÜbersetzung ist verlustbehaftet; Fehlerquelle bleibt für immer im heißen PfadModellwahl muss EU-Stack bleiben; Qualität ist vor dem Umstieg zu messen
⭐ Towers offene Frage ist bereits beantwortet — von der Schema-Historie selbst: „Können alter und neuer Vektorraum nebeneinander leben, während es läuft?" — Ja, und es ist das etablierte Muster. Gemessen: 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.
⛔ Die eine Unsicherheit, die vor dem Umstieg gemessen gehört — und die ich NICHT weggeschlossen habe: Dass 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.

§9dDie A/B-Messung vor dem Modellwechsel — Plan, kein Bau

„Es sind nicht nur FR-DE-EN erwartet, sondern auch alle anderen Sprachen — u. a. RU."— JC 05.08. (E-62). ⛔ Gemessen kommt Russisch heute NICHT vor (1387 Chunks: 652 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.

Welche Sprachpaare — und warum genau diese fünf

PaarRolle im TestWarum es nicht fehlen darf
DE → FRDer reale Fall502 Chunks, JCs Hiscox-Police. Der Fall, der den Auftrag ausgelöst hat — wenn er nicht besteht, ist alles andere gleichgültig
DE → ENKontrolle53 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 → RUDer 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 → ITDie ungenannte SpracheNiemand 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 → DEDie 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

Das Maß — Dokument-Identität, nicht Ähnlichkeit

⛔ Gemessen wird E-61.3 wörtlich, nicht ein Ersatzwert: „Dieselbe Frage auf Deutsch und auf Französisch liefert DASSELBE Dokument."
· Primärmaß: Top-3-Identität. Liefert die Frage in der Fremdsprache dieselbe source_document_id wie die Frage in der Sprache des Dokuments? Ja/Nein je Paar.
· Similarity ist Diagnose, nicht Kriterium. Die 0.821 aus JCs Fall sagen, wie weit es daneben lag — nicht, ob das Ergebnis stimmt.
· ⛔ Vorbedingung je Paar (sonst misst man den Aufbau, nicht das Modell): die gleichsprachige Frage muss ihr eigenes Dokument zuerst treffen. Paare, bei denen schon die Baseline scheitert, werden ausgeschlossen und ausgewiesen — nicht als cross-linguales Versagen gezählt.
· Bestehens-Schwelle, VOR dem Lauf festgelegt (sonst wird sie hinterher passend gemacht): ≥ 80 % Top-3-Identität auf den gültigen Paaren · kein Rückschritt auf den gleichsprachigen Paaren · das Alt-Modell auf demselben Satz gemessen, nicht aus der Erinnerung.
⛔ Zwei Grenzen, die der Plan selbst benennt:
① Der Vergleich läuft ungegatet — er misst eine Modell-Eigenschaft, keinen Produktionspfad. Er ist damit KEIN Abnahme-Beweis. Die Abnahme des späteren Pfades läuft über 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.
② Der Paar-Satz gehört in eine Datendatei, nicht in eine Konstante. Das vorhandene Messinstrument 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.

SSOT & Anschlüsse: 00_LEITDOKUMENT/KAI_INTENTION_SSOT.md (verbindlich; speist System-Prompt · „Frage KAI"-Hero · i18n) · Bibel-Eintrag: TGV_BIBEL_START_HERE §① + Prinzip 6 · Architektur dahinter: 🧠 KI-First (Gehirn·Hände·Oberfläche, Karten-Vertrag, Skills) · Grenzen & Schutz: 🔒 Sicherheit · Entscheide: ⚖ Register. Pflege: Intention ändert sich NUR in der SSOT — diese Seite + Prompt + Hero ziehen nach (KONZEPT#01 wacht).