Files
pv-agent/.agents/MEMORY.md
T

561 lines
37 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Agent-Memory — pv-agent
Rollender Übergabe-Log für agent-Threads. Workflow: `.agents/SKILL.md`
(agent-memory-Skill). Ergänzen, nicht überschreiben.
## Current focus
D24 validiert (500-Euro-Fragen verifiziert, think-Fix, Offline-Eval Recall@8
0,95). D25-Planung steht (planung.md Abschnitt 14): Odoo orchestriert und
rechnet (System of Record), Agent prüft Plausibilität, kein Rückpfad;
Privacy-Regel 8 wird erst per Feature-Flag `PV_REVIEW_MODE` im M4 aufgeweicht.
Modul-Review abgeschlossen (`.oddo-module/`), KV-Varianten-Mapping gebaut
(`tools/catalogs/kv_variant_map.json`, 439/614 abgedeckt, Seeds SI-2203/
SI-2748 getestet). Als Nächstes: D25-Umsetzung — Agent-`review`-Modus (M4.2)
und Odoo-Modul `l10n_at_payroll_agent` (M4.1).
## Completed (2026-09-16, Modul-Review/D25-Planung)
- **`.oddo-module/` verlinkt** (Symlink → ../odoo-at-payroll/addons, in
`.gitignore` aufgenommen). Review gegen M4: Odoo 19.0 (Kern 19.0.10.0.0 auf
`hr_payroll`, Privat 19.0.17.0.0, Gemeinde-Bgld, Dokumente), LGPL-3,
~22k Zeilen Python / ~7,5k XML / ~25 Testdateien.
**Kein HTTP-/Controller-/Agenten-Code vorhanden** — M4 startet bei null.
Tenant-Isolation via `ir.rule` (company_ids), Feldgruppen
`group_hr_payroll_user`. Rechenkern: sozialversicherung.py (§-49-Ausschal-
tungen mit Jahres-Kumulative `_l10n_at_sv49_ytd`, WF-Satzvektor Bundesland
×Jahr, DAG), lohnsteuer.py (§ 66/67/68 mit YTD-Freibetragsverbrauch),
payslip_private (KommSt/DZ/FLAF-DB/SZ), sachbezuege/reisekosten. Parameter
aus ÖGK-TASY-Export gegen offiziellen Report gespiegelt; 2027-Rahmen.
KV-Katalog `l10n.at.payroll.kv` mit versionierten `kv.wert`, Gruppen/Stufen
und CSV-Import-Wizard (Sprungwarnung >10 %); `library_variant_id` verlinkt
auf die KV-Library (SI-2203/SI-2748). D25-Planung steht in `planung.md`
Abschnitt 14: neues Modul `l10n_at_payroll_agent`, ir.config_parameter plus
eigene Gruppe, serverseitiger Client, hart kodierte Kontext-Whitelist
(facts key/value/note, keine Personendaten), Pilot-Workflow
Plausibilitätsprüfung einer geplanten Auszahlung (Odoo rechnet auf dem
Draft-Payslip, Agent liefert strukturiertes Verdict), Agent-M4.2:
`mode=review` plus context-Schema, drei Beweisklassen, Injection-Abgrenzung
und Feature-Flag `PV_REVIEW_MODE` (default aus). Nicht-Ziele fixiert.
- **KV-Varianten-Mapping (Odoo ↔ KB, 2026-09-16):** Die KV-Library führt
bereits `wko/match-report.json` (wko_slug → oegb_variant_id; 407 matched /
32 low / 175 unmatched). `tools/build_kv_variant_map.py` erzeugt daraus
`tools/catalogs/kv_variant_map.json`: 105 Varianten (98 matched, 7 low),
439/614 KB-Einträge abgedeckt; je Variante `docs` mit kv_kvt_id + slug +
doctype als Paare, Low-Confidence markiert, Rest in `unmatched_kb_entries`
(WKO-aktuelle Dokumente ohne ÖGB-Gegenstück, z. B. KV-Abschluss-News).
Odoo kann pro `library_variant_id` die zugehörigen KB-Einträge auflösen;
Seed-Abdeckung SI-2203/SI-2748 testet
`tests/test_kv_variant_map.py`. Tests gesamt **98 grün**.
## Completed (2026-09-14)
- **Planung** (`planung.md`): Architektur, Grounding-Regeln, Modellfeld,
Meilensteine M1M4; **Skill** `.agents/skills/pv-rag-agent/SKILL.md`.
- **Commits**: `25eb285` (Wissensbasis-Import), `bf8191b` (Planung +
Skills), `2cba72a` (M1+M2-Implementierung, 41 Tests).
- **Implementierung M1+M2** (`agent/`): kb/ingest/retrieve/generate/
ollama_client/api/cli/eval, Goldset 31 Fragen, Test-Chat.
- **Ollama-Anbindung verifiziert** (Ziel-Instanz `http://100.103.83.12:11435`,
Ollama 0.32.13): `bge-m3` per API gepullt; `qwen3.8:27b` war bereits
installiert. Hybrid-Index: 3.005 Chunks, alle eingebettet (144 s).
- **M1-Akzeptanz erreicht**: Retrieval Hybrid Hit-Rate 0,968 ·
Recall@8 **0,952** (>0,9 ✓) · MRR 0,690 (BM25-only: 0,871/0,855/0,476).
- **M2-Antwortmodus-Eval** (qwen3.8:27b, Thinking aus): Zitier-Präzision
**100 %** (4 Regenerierungen, alle geheilt) · Verweigerung korrekt
94,3 % · erwartete Quelle zitiert 80,6 % · Latenz mean 32 s / p95 53 s.
Report: `data/eval-qwen38.json`.
- Der ATZ-Konfliktfall (28,5 vs. 27,5 %) wird korrekt mit ⚠ und beiden
IDs beantwortet; harte Verweigerung (UStVA, Retrieval nicht leer)
funktioniert.
- **Topologie geklärt**: `100.103.83.12` ist die **Remote-GPU-Maschine**
(Entwicklungsumgebung, R9700, Tailscale); die Dev-Maschine selbst hat
einen eigenen, fast leeren Ollama auf localhost:11434 (dorthin ging der
erste bge-m3-Pull — auf die GPU-Maschine neu gepullt). Ziel-Instanz ist
ausschließlich `http://100.103.83.12:11435`.
- **Bake-off M3 — abgeschlossen (D7)**: alle 6 Kandidaten gemessen
(Tabelle in `planung.md` Abschnitt 13). **`qwen3.8:27b` gewinnt**
(100 % Zitier-Präzision, 94,3 % Verweigerung korrekt, 80,6 % erwartete
Quelle, 32 s mean). `gemma4:26b` = dokumentierter Latenz-Kandidat
(7,9 s mean, 100 % Zitier-Präzision, aber 67,7 % Quellentreue, 3
Fehlverweigerungen). mistral-small3.1: 71,4 % Verweigerungskorrektheit
— Deutsch-Hypothese praktisch widerlegt. **muse-glimmer:latest**
(nachgereicht): 100 % Zitier-Präzision bei nur 1 Regenerierung
(diszipliniertestes Modell), aber 8/35 falsche Verweigerungen
(Überverweigerung teils trotz vorhandener Zitate) und 37,5 s mean —
Rang 5 von 6, schlägt qwen3.8 in keiner Kennzahl. qwen3.6:27B: 2
dauerhafte Zitierverletzungen, 10 Regenerierungen. gemma4:12B:
2 Verletzungen.
Reports: `data/eval-*.json`. q-008/q-031 verweigern alle
Top-Kandidaten — Prompt-/Retrieval-Tuning (nicht modellspezifisch).
- **Remote-GPU-Maschine**: nach `systemctl restart ollama` (User-Aktion)
liefen alle Läufe stabil; zwischen Kandidaten wurde per `keep_alive: 0`
entladen (Crash-Ursache war der Modell-Swap-Stress beim gemma4-Lauf).
## Completed (2026-09-15)
- **KB-Erweiterung um WKO-Kollektivverträge + RIS-Gesetze** (User-Auftrag):
`.firecrawl/kv-portal/wko-kv/docs/` (614 KV-Dokumente: Lohn-/Gehaltsordnungen mit
Lohntabellen, KV-Abschlüsse, Erläuterungen, Zusatz-KVs) und `.firecrawl/ris/gesetze/` (59
Gesetze, nur die im Lexis360-Bestand zitierten §-Ausschnitte, Stand
2026-09-13) → **1 274 Layer-2-Einträge** (`kv-kvt-001…614`,
`ris-<prefix>-nn`), 14 984 Chunks, Reindex 8,5 min (11 947 neue
Embeddings, bge-m3 auf :11435).
- **D9 (2026-09-15):** kv/ris-Einträge sind **quellentreu generiert**
(keine Eigene-Worte-Kuratierung — Gesetze sind amtliche Werke, KV-
Lohntabellen zahlenexakt; Konvention 2 der Wissensbasis gilt nur für
die lizenzierten Quellen lb/wk). Tool-Output: `tools/ingest_sources.py`
+ `tools/build_registry.py` (Registry in diesem Repo neu gebaut — das
Lexis-Tool `build_lexis_kb.py` lebt im Schwesterprojekt), eingefrorene
Kataloge `tools/catalogs/*.json` (nur Metadaten, versioniert).
- **ID-Räume:** `kv-kvt-<nnn>` (ein Cluster `kollektivvertraege`,
Branche als Tag) und `ris-<cluster-prefix>-<nn>` auf der bestehenden
Cluster-Map + 7 neue Cluster (kvt/zvr/avr/agg/lst/abo/nso);
`LAW_MAP` in `tools/kb_common.py` dokumentiert die 59 Zuordnungen.
- **D10 (Retrieval-Kalibrierung, Goldset-Sweep 2026-09-15):** Korpus-
verdopplung drückte Recall@8 auf 0,851 — kalibriert auf
`dense_weight=2.0` (Dense-Liste relativ zu BM25 gewichtet; BM25 ist
durch KV-§-Titel-Matches inflationiert), `rrf_k=20`,
`candidate_pool=150`**Recall@8 0,923 (>0,9 ✓)**, Hit-Rate 0,973
(36/37), MRR 0,667. ENV: `PV_DENSE_WEIGHT`/`PV_RRF_K`/
`PV_CANDIDATE_POOL`. Entry-RRF-Variante getestet: kein Mehrwert.
- **Antwortmodus-Eval (qwen3.8:27b, 42 Fragen):** Zitier-Präzision
95,2 % (2 Verletzungen), Verweigerung korrekt 90,5 % (4 Fehlver-
weigerungen, u.a. q-022/q-029), erwartete Quelle zitiert 83,8 %,
Latenz mean 31 s / p95 53 s, 9 Regenerierungen — **unter den M3-
Gates** (100 % / 94,3 %): Korpusverdopplung macht Grounding härter;
das laufende Fehlverweigerungs-Tuning (uncommittete Änderungen in
generate.py/retrieve.py/goldset.yaml, unangetastet) ist weiteres
Thema. Report: `data/eval-qwen38-kvris.json`.
- **Goldset:** +7 Fragen (q-101106 KV-Preise/§-Fragen, r-005 Branchen-
Refusal) — append-only. q-021 rekalibriert: erwartete ID jetzt
`lb-kar-04` („Karenz - Anspruch, Beginn und Dauer“, Top-1, deckt
Beginn/Dauer vollständig; lb-kar-01 nur Nachbarschaft — dokumentiert
im Goldset-Note).
- **Tests:** 41 → 49, alle grün (neue ID-Räume, html-Source, Konverter,
LAW_MAP-Vollabdeckung, Korpus-Integrationszahl 1 274).
- **D15 (Rechtsprechungs-Intake, 2026-09-15):** `.rechtsprechung/` → neuer
ID-Raum `rj-rjs-*`, gemeinsamer Cluster `rechtsprechung`: 320 RIS-OGD-
Entscheidungen/Rechtssätze, 23 zitierte RIS-Normauszüge und 5 als
nichtamtlich markierte EuGH-lexetius-Textwiedergaben. Parser:
`tools/ingest_sources.py --source rj`; lange Volltexte werden an
Absatzgrenzen in H2-Chunks geteilt. `rj_catalog.json` friert die IDs ein;
Registry/Index und Agent-Zitatvalidierung kennen `rj-*`. Korpus 1.274 →
**1.622** Einträge / 77 Cluster; Reindex: 16.480 Chunks, 1.496 neue
Embeddings in 75 s. Retrieval-Goldset erweitert um q-120123:
Hit-Rate 0,956 · Recall@8 **0,922** (Gate ✓) · MRR 0,661, alle vier
neuen Fälle gefunden. Gezielte Antwortläufe q-120123: alle zitiergültig,
nicht verweigert, keine Regenerierung; Systemprompt verlangt für als
nicht amtlich markierte Quellen die gleiche Einschränkung in der Antwort
(q-123 verifiziert). Voller Antwortmodus-Eval (50 Fragen): **100 %
Zitier-Präzision**, **100 % Verweigerung korrekt**, erwartete Quelle
93,3 %, mean 39,4 s / p95 82,2 s, 5 Regenerierungen; Report
`data/eval-qwen38-rj.json` (lokal/unversioniert). Offline-Tests: 65
grün. **Nicht als generelle
Commit-Freigabe lesen:** Die gelieferten OGH/VwGH/VfGH-Dateien nennen
überwiegend RIS-OGD, der User sprach jedoch von Lexis/opendataloader;
die Publikations-/Lizenzfreigabe der quellentreuen Volltexte ist vor
Commit ausdrücklich zu prüfen. EuGH-Wiedergaben sind als nichtamtlich
markiert und verweisen auf EUR-Lex.
## Completed (2026-09-16)
- **D16 (Gestaltungsfragen / Retrieval-Tuning):** Goldset um q-124126
erweitert (500-Euro-Zusatzzahlung, Alternativen, Mitarbeiterprämie 2026).
`agent/query_planner.py` erkennt Decision-Support deterministisch und zerlegt
in drei gesetzlich gescopte Queries: Mitarbeiterprämie samt §-49-Konflikt,
Zukunftssicherung sowie Sach-/zweckgebundene Leistungen. Decision-Support
läuft als `specific`, nicht als Survey/Map-Reduce. `Retriever.search()` nutzt
denselben Plan im Offline-Eval; `search_multi()` behält Scope/Jahr auch bei
einer einzelnen Query. Ergebnis (53 Goldsetfragen / 48 Retrieval-Fälle):
q-124/q-125/q-126 jeweils Recall@8 **1,00**, gesamt Recall@8 **0,9271**
(Gate >0,9), Hit-Rate 0,9583, MRR 0,6158. Bekannte Altfehler q-015/q-113
bleiben unverändert.
- **Decision-Support-Grounding:** Systemprompt trennt Barzahlung von
zweckgebundenen Leistungen, verbietet pauschale Sieger ohne Kontext,
verlangt vergleichbare Abgabendimensionen und genau eine Rückfrage. Der
bekannte Konflikt Mitarbeiterprämie 2026 wird offen dargestellt:
`wk-akt-04` nennt SV/BV/DB/DZ/KommSt-Pflicht, `lb-sva-03` ordnet die Prämie
systematisch in den taxativen Katalog beitragsfreier Bezüge ein. Zusätzlich
erzwingt ein semantischer Post-Validation-Gate ⚠ + beide IDs + korrekte
Rollen und verhindert interne Regelverweise bzw. Quellenpriorisierung.
Gezielte Real-Läufe: q-124/q-125 nicht verweigert, zitiergültig; q-126 war
bereits sauber. Tests **72 grün**; `git diff --check` sauber.
- **D16 Voll-Eval (2026-09-16):** 53 Fragen mit `qwen3.8:27b`; Report
`data/eval-qwen38-decision-support.json` (lokal/gitignored). Retrieval:
Hit-Rate 0,9583, Recall@8 **0,9271**, MRR 0,6266. Antworten:
Zitier-Präzision **98,11 %**, Verweigerung korrekt **98,11 %**, erwartete
Quelle 91,67 %, mean 40,3 s / p95 98,4 s, 4 Regenerierungen. Einziger
Gate-Fehler: q-125 eskalierte nach zwei semantisch unzureichenden Antworten
zu `UNCERTAIN` (`verified=false`). Direkte Wiederholung von q-125 war mit
einer Regenerierung vollständig, zitiergültig und stellte den Konflikt
korrekt dar — verbleibend ist stochastische Gate-/Regenerierungs-Flakiness,
kein Retrieval-Fehler. q-124 und q-126 bestanden im Voll-Eval.
- **D16 Stabilisierung/Bestätigung (2026-09-16):** Bei vollständig
ausgelassenem, aber retrieved Mitarbeiterprämien-Konflikt ergänzt die
Pipeline deterministisch eine feste, ausschließlich aus `lb-sva-03` und
`wk-akt-04` formulierte ⚠-Notiz. Eigene Konfliktversuche bleiben ungeändert
und werden weiter auf Rollenfehler/Priorisierung geprüft. Der semantische
Gate akzeptiert gleichwertige Verben (`ordnet`, `listet`, `führt`) statt
fragiler exakter Wortwahl. Sonden: q-124 3/3 ohne Regenerierung, q-125 3/3
verifiziert, q-126 verifiziert. Voller Bestätigungslauf (53 Fragen), Report
`data/eval-qwen38-decision-support-confirm.json`: Zitier-Präzision **100 %**,
erwartete Quelle 93,75 %, mean 40,3 s / p95 69,6 s, 3 Regenerierungen;
q-124126 alle bestanden. Verweigerungskorrektheit 98,11 % nur wegen der
bekannten stochastischen Survey-Fehlverweigerung q-029; direkte q-029-
Wiederholung antwortete verifiziert (aber sehr langsam, 280 s).
- **q-015 fachlich rekalibriert:** Eine allgemeine Tagesgeldfrage mischt zwei
Ebenen: Der KV regelt vorrangig den arbeitsrechtlichen Anspruch, das EStG den
steuerfreien Satz. Branchen-KV-Treffer sind deshalb korrekt; q-015 hat keine
künstliche Einzel-Pflicht-ID mehr. Neue q-127 prüft ausdrücklich nur die
steuerliche Höhe und findet `lb-rei-09`. Goldset 54 Fragen; Offline-Retrieval
nun Hit-Rate 0,9792, Recall@8 **0,9479**, MRR 0,6653. Tests **75 grün**.
- **llama.cpp-LLM-Eval (2026-09-16):** Lokaler/privater OpenAI-kompatibler
Server aus `.env` getestet (Zugangsdaten/URLs nicht dokumentiert oder
versioniert); nur das LLM wurde gewechselt, Retrieval und bge-m3-Index
blieben unverändert. llama.cpp benötigt
`chat_template_kwargs.enable_thinking=false`; Chatmodell meldet sich als
`qwen3.8`. 54-Fragen-Report: `data/eval-llamacpp-qwen38.json`
(lokal/gitignored). Zitier-Präzision **100 %**, Verweigerung korrekt 96,3 %,
erwartete Quelle 91,67 %, mean 54,9 s / p95 104,8 s, 2 Regenerierungen.
q-124126 bestanden alle ohne Regenerierung. Fehlverweigerungen: q-029
(bekannte Survey-Flakiness, 357 s) und q-102. q-102 ist kein LLM-Defizit:
Mit Planner lieferte der KV-Scope viele ähnlich benannte KVs und nur einen
irrelevanten Abschnitt von kv-kvt-197; ohne Planner beantwortete dasselbe
llama.cpp-Modell die Frage in 14,8 s korrekt aus kv-kvt-056. Schluss:
llama.cpp ist grounding-stabil, aber langsamer als die Ollama-Basislinie;
q-102 erfordert später Planner-/Retrieval-Tuning, nicht Modell-Tuning.
- **llama.cpp-Rerun nach Server-Tuning (2026-09-16):** Report
`data/eval-llamacpp-qwen38-rerun.json` (lokal/gitignored), gleicher Aufbau
wie zuvor: nur LLM über llama.cpp, Retrieval/bge-m3 unverändert. Ergebnis:
Zitier-Präzision **100 %**, Verweigerung korrekt **98,15 %**, erwartete
Quelle **95,83 %**, mean 53,5 s / p95 105,4 s, 6 Regenerierungen. Gegenüber
dem ersten Lauf: q-029 geheilt, erwartete Quelle +4,16 Prozentpunkte,
mean ca. 1,3 s schneller; q-102 bleibt als einzige Fehlverweigerung
reproduzierbar (Planner-/Kontextproblem). q-124126 bestehen weiterhin;
q-125/q-126 benötigten diesmal je eine Regenerierung. Das Server-Tuning ist
damit qualitativ besser, erhöht aber die Regenerierungen (2 → 6); Latenz
bleibt deutlich über der Ollama-Basislinie.
- **llama.cpp Full-Stack (Chat + Embeddings, 2026-09-16):** llama.cpp-
Embeddings sind trotz gleicher Dimension 1024 inkompatibel zu Ollama
`bge-m3` (Same-Text-Cosinus nur ca. -0,004 bis 0,017); deshalb separater,
lokaler Index `data/index-llamacpp.db`. Vollständig 16.480 Chunks; der
Embedding-Server lehnt einzelne Texte > ca. 9.000 Zeichen ab, daher wurden
nur für diesen Test längere Embedding-Inputs deterministisch auf 9.000
Zeichen gekürzt (KB und Produktionsindex unverändert). Indexbau ca. 17,4 min.
Retrieval ohne Neukalibrierung: Hit-Rate 0,9583, Recall@8 **0,9323**,
MRR 0,5977 (Gate >0,9 erfüllt). Vollreport
`data/eval-llamacpp-fullstack.json` (lokal/gitignored): Zitier-Präzision
**100 %**, Verweigerung korrekt 94,44 %, erwartete Quelle 87,5 %, mean
**44,6 s**, p95 130,8 s, 3 Regenerierungen. Full-Stack ist schneller als
llama.cpp-LLM + bge-m3 (53,5 s), aber fachlich schwächer: Fehlverweigerungen
q-029, q-102, q-111. q-102: korrektes Dokument kv-kvt-056, aber falscher
repräsentativer Abschnitt; q-111: Embedding-Retrieval verfehlt kv-kvt-001
komplett und liefert fremde Lehrlingstabellen. Decision-Support q-124126
besteht weiter. Fazit: Full-Stack ist testfähig und erfüllt das Retrieval-
Recall-Gate, ersetzt bge-m3 aber noch nicht; zuerst eigene RRF-/Dense-
Kalibrierung und KV-Metadaten-/Representative-Section-Tuning nötig.
- **llama.cpp Full-Stack Rerun (2026-09-16):** identischer separater Index und
unveränderte Parameter; Report `data/eval-llamacpp-fullstack-rerun.json`.
Zitier-Präzision **100 %**, Verweigerung korrekt **98,15 %**, erwartete
Quelle **91,67 %**, mean 49,5 s / p95 101,2 s, nur 1 Regenerierung. Gegenüber
erstem Full-Stack-Lauf: q-029 und q-111 geheilt, Fehlverweigerungen 3 → 1,
erwartete Quelle +4,17 Prozentpunkte, Regenerierungen 3 → 1; Latenz dagegen
44,6 → 49,5 s. Einziger reproduzierbarer Fehler bleibt q-102 (korrektes
Dokument, falscher Representative-Section-Kontext). q-124126 alle ohne
Regenerierung bestanden. Full-Stack erreicht damit dieselbe Verweigerungs-
korrektheit wie der aktualisierte llama.cpp-LLM+bge-m3-Lauf, bleibt aber bei
erwarteter Quelle darunter (91,67 % vs. 95,83 %).
- **D17 (Runtime-Entscheidung, 2026-09-16):** Nach den llama.cpp-LLM- und
Full-Stack-Vergleichen bleibt der Agent bei **Ollama** mit
`qwen3.8:27b` + `bge-m3`. Gründe: beste bzw. stabilere Gesamtbalance aus
erwarteter Quellentreue, Verweigerungskorrektheit und Latenz; llama.cpp-
Full-Stack zeigte embedding-spezifische KV-/Representative-Section-Fehler
und höhere Flakiness. Der letzte angeforderte Full-Stack-Rerun wurde nach
drei Fragen vom User abgebrochen und nicht gewertet. Lokale llama.cpp-
Indizes/Reports und `.env` bleiben unversioniert; keine Integration in die
Produktionskonfiguration.
- **Finaler Ollama-54-Eval (2026-09-16):** Nach Rückkehr zum Ziel-Stack
`qwen3.8:27b` + `bge-m3`, Report `data/eval-qwen38-final-54.json`
(lokal/gitignored). Retrieval: Hit-Rate 0,9792, Recall@8 **0,9479**,
MRR 0,6378. Antworten: Zitier-Präzision **100 %**, Verweigerung korrekt
**100 %**, erwartete Quelle 95,83 %, mean **38,8 s**, p95 67,2 s,
2 Regenerierungen. q-029 sowie q-124127 bestanden; damit beide Haupt-Gates
erstmals im finalen 54er-Set vollständig erfüllt. Zwei Quellentreue-
Abweichungen ohne Grounding-Verstoß: q-127 zitiert alternative Reisekosten-
Quellen (`lb-rei-01/03`, `wk-rei-01` plus KVs) statt erwarteter
`lb-rei-09`; q-025 zitiert Betriebsübergangs-/Insolvenzquellen statt der
bisherigen Expected-ID. Optional später Goldset-Expected-IDs fachlich
rekalibrieren, aber kein Release-Blocker.
- **D18 (Rechtsprechungsnutzung, 2026-09-16, User-Freigabe):** Auf
gerichtliche Entscheidungen darf in Antworten verwiesen werden. Ihr Inhalt
darf in eigenen Worten wiedergegeben und stellenweise zitiert werden. Die
Agentenantwort muss weiterhin die retrieved `rj-*`-/RIS-KB-ID nennen,
nichtamtliche Wiedergaben ausdrücklich kennzeichnen und darf keine nicht
retrieved Entscheidung ergänzen. Diese Freigabe umfasst Referenz,
Zusammenfassung und auszugsweise Zitate; sie ist keine pauschale Freigabe
zur Veröffentlichung vollständiger quellentreuer Entscheidungstexte.
## Open issues / blockers
- **Push**: Remote localhost:3003 aus der Sandbox nicht erreichbar — User
pusht vom Host.
- **Fehlverweigerungen — Stand nach Prompt v2:** q-008 und q-031 sind
**behoben** (Section-Priorität + Regel 8). Verbleibend: q-029
(„Welche Neuerungen behandelt WIKU Personal aktuell 2026?“) — breite
Survey-Frage über 12 Hefte; Kontext enthält echte Zusammenfassungen,
Modell verweigert trotzdem (sicherer Fehlermodus). Mögliche spätere
Hebel: Survey-Rule im Prompt, mehr Kontextblöcke oder Map-Reduce-artige
Zusammenfassung. Weitere Iterationen erst mit neuen Evaluationsdaten.
- **Latenz**: mean 32 s/ Antwort ist hoch (Dense 27B + große Prompts).
Hebel: weniger Kontextblöcke (aktuell 8+6), schnellere Kandidaten.
- **Cloud-Modelle** (`*:cloud` auf der Ollama-Instanz) sind für Antworten
tabu (Anforderung: lokal). Nicht versehentlich konfigurieren.
- **Bake-off M3 — erledigt** (siehe Completed; Entscheidung D7 in
`planung.md`). Nach Tuning von Prompt/Retrieval: erneuter kurzer
Bestätigungslauf nur mit dem Sieger.
- **M4 Odoo**: native LLM-Module des konkreten Odoo-19-Stands verifizieren
(keine API-Annahmen); Option A (dünnes Custom-Modul + Service-API) ist
Default.
- **Nach KV/RIS-Erweiterung (2026-09-15)**: Antwortmodus vor dem Tuning
(D10-Follow-up) unter den Gates (Zitier-Präzision 95,2 % — 2 Verlet-
zungen; Verweigerung 90,5 % — 4 Fehlverweigerungen, u.a. q-022/q-029;
erwartete Quelle 83,8 %) — Report `data/eval-qwen38-kvris.json`.
**D11-Fix behoben die 4 Fehlverweigerungen**: Antwortmodus jetzt
Zitier-Präzision 97,6 %, Verweigerung **97,6 %** (>94,3 %-Gate ✓),
erwartete Quelle 91,9 %, Retrieval Recall@8 0,946 — Report
`data/eval-qwen38-kvris-tuned.json`. Verbleibend: q-015 (Branchen-
Noise) und q-024 als transiente Flakiness (leerer Draft — Think-Ghost-
Verdacht bei 32k-Kontext; beobachten).
- **q-015 (Tagesgelder Dienstreisen)** — Branchen-Noise: 8 Branchen-KV-
Einträge verdrängen lb-rei-01/09 (EStG-Taggeld-Abrechnung) aus Top-8;
generische Branchen-Frage ist mehrdeutig geworden. Hebel: Frage
präzisieren (Branche/Kontext) oder KV-§-Titel-Matches dämpfen.
## Decisions & conventions
- **D1 (Planung):** Schlanke Eigen-Pipeline statt LangChain/LlamaIndex —
Grounding-Kontrolle schlägt Framework-Komfort bei 601 Dokumenten.
- **D2:** Retrieval-Korpus ist **nur Layer 2**; Layer 1 bleibt aus Prompts
(Lizenz); Antworten zitieren `[kb-id]` + `(Stand YYYY-MM)`.
- **D3:** Umlaut-Folding für FTS (NFKD, ß→ss) — gilt konsistent für Index
und Query; ASCII-Slug-Konvention der Wissensbasis bleibt davon unberührt.
- **D4:** Post-Validierung strikt: zitierte IDs ⊆ Retrieved-Set (Block-Kopf-
IDs); Fließtext-Verweis-IDs sind KEINE Belege (Systemprompt-Regel 2) —
Verstoß → 1× Regenerierung → Verweigerung (UNCERTAIN_MESSAGE).
- **D5:** Vektoren-Tabelle ist Cache (Content-Hash × Modell), Rebuild
löscht sie nicht; `--no-embed` setzt `embed_off` im Config-Copy.
- **D6:** Leeres Retrieval → deterministische Verweigerung ohne LLM-Call.
- **D7 (Bake-off 2026-09-14):** `qwen3.8:27b` ist das fixierte Antwortmodell
(100 % Zitier-Präzision, 94,3 % Verweigerung korrekt). `gemma4:26b` als
dokumentierter Latenz-Kandidat; ein Modellwechsel läuft nur erneut über
das dokumentierte Protokoll (Skill).
- **D8 (Prompt-Tuning 2026-09-14):** Prompt v2 + Kontext-Section-
Priorität. (a) Regel 4 erlaubt Teilantworten bei unvollständiger Deckung;
Regel 8 verlangt Prämisse-Korrektur statt Verweigerung (Muster-Beispiel
„Mindestlohngesetz“). (b) `_representative_chunk` wählt pro Eintrag die
beste **Inhaltssektion** (Zusammenfassung > Kernwerte > Rechtsgrundlagen
> Payroll > sonstige; „Verweise“ nur als letzter Rückgriff — BM25
rangiert die dünnen Navigations-Chunks bevorzugt, q-008-Ursache).
Ergebnis v1→v2: Zitier-Präzision 100 % gehalten; q-008 + q-031 behoben;
erwartete Quelle 80,6 → 83,9 %; Verweigerung 94,3 % (Fehler getauscht:
neu q-029 — breite Survey-Frage über 12 Hefte, sicherer Fehlermodus).
Report `data/eval-qwen38-v2.json`.
- **D9 (KV/RIS-Erweiterung, 2026-09-15):** kv-/ris-Einträge sind
**quellentreu generiert** (keine Eigene-Worte-Kuratierung — Gesetze
sind amtliche Werke, KV-Lohntabellen zahlenexakt; Konvention 2 der
Wissensbasis gilt nur für die lizenzierten Quellen lb/wk).
Tool-Output: `tools/ingest_sources.py` + `tools/build_registry.py`
(Registry in diesem Repo neu gebaut — das Lexis-Tool
`build_lexis_kb.py` lebt im Schwesterprojekt), eingefrorene Kataloge
`tools/catalogs/*.json` (nur Metadaten, versioniert). IDs:
`kv-kvt-<nnn>` (ein Cluster `kollektivvertraege`, Branche als Tag),
`ris-<cluster-prefix>-<nn>` auf der bestehenden Cluster-Map + 7 neue
Cluster; `LAW_MAP` in `tools/kb_common.py` (59 Gesetze).
- **D10 (Retrieval-Kalibrierung, 2026-09-15):** nach KV/RIS-Erweiterung
(3.005 → 14 984 Chunks) dense_weight=2.0 (Dense relativ zu BM25
gewichtet — BM25 durch KV-§-Titel-Matches inflationiert), rrf_k=20,
candidate_pool=150 → Recall@8 0,851 → 0,923 (>0,9 ✓), Hit-Rate
0,973, MRR 0,667. ENV: PV_DENSE_WEIGHT / PV_RRF_K /
PV_CANDIDATE_POOL. Entry-RRF-Variante getestet: kein Mehrwert.
- **D11 (Prompt-Budget + Kontext-Abdeckung, 2026-09-15):** lange KV-Chunks
ueberlieferten num_ctx=16384 (q-022: 62,7 KB Prompt ≈ 18k Tokens) —
Ollama trunciert den Systemprompt vorn, das Modell verliert die
Zitierregeln („Block 8“-Zitate statt IDs → Regenerierung → Verweigerung).
Fix: num_ctx 32 768, `trim_results` (max_context_chars=90 000, Tail-
Bloecke ganz weg statt trunciert, Mindestbestand 6) und
cross_ref_expand 3 → 6 (die Top-3 sind oft Branchen-KV-Bloecke mit
leeren cross_refs — kuratierte Nachbarn kamen sonst nie nach).
Ergebnis: alle 4 Fehlverweigerungs-Faelle (q-001/q-019/q-022/q-029)
geheilt, 10/10 Bestaetigungslaeufe OK. Eval: Zitier-Praezision 97,6 %,
Verweigerung 97,6 % (> 94,3 %-Gate ✓), erwartete Quelle 91,9 %
(vorher 83,8 %), Retrieval Recall@8 **0,946** (cross_ref-Extras
bringen Expected-IDs nach), MRR 0,671. Latenz mean 33 s.
Report `data/eval-qwen38-kvris-tuned.json`. Restrisiko: q-024
flaky (1/3 Laeufe — transienter leerer Draft, Think-Ghost-Verdacht);
q-015 Branchen-Noise bleibt offen.
- **D12 (Komplexe-Fragen-Stufe 1 / M6, 2026-09-15):** Query-Planer vor dem
Retrieval: Heuristik-Gate (Jahreszahl, Vergleichs-/Aggregationsmarker,
Laenge) entscheidet, ob ein kleiner LLM-Call die Frage in 1-3 Sub-Queries
zerlegt (JSON, temp 0, num_predict 220; Fehler -> Original als Einzel-
Query). Multi-Query-Retrieval: je Sub-Query BM25+Dense, Beitraege
summieren; **Per-Query-Slots** (2 je Sub-Query) sichern jeden Frage-
aspekt im Kontext — ohne sie dominieren Eintraege, die in mehreren
Sub-Queries mittelgut matchen (q-113: 5 KV-Urlaubs-SS schlugen
lb-url-05 (Rang 6) und ris-url-01 (>Top-12) ueber beide Sub-Queries).
**Scope-Filter** je Sub-Query: Planer markiert "gesetz" (nur Lexis/WIKU/
RIS-Chunks) bzw. "kv" (nur Branchen-KV) — q-113 geheilt (ris-url-01/
lb-url-05 + KV-Ss im selben Kontext). Scope-Fallback: gefilterte Suche
leer -> unscoped. Temporal-Intent: stand_year je Sub-Query, kv-Eintraege
im Geltungsjahr erhalten temporal_boost (default 0 — FTS-Tag-Signal
`jahr-YYYY` reichte in der Messung). Grounding unveraendert: ein
Kontext aus der Union, Post-Validierung ueber die gesamte Retrieved-
Menge, Verweigerungspflicht unveraendert.
Eval (46 Fragen): Zitier-Praezision 97,8 %, Verweigerung 97,8 %
(Gate > 94,3 % erfuellt), erwartete Quelle 90,2 %, Latenz mean 33,5 s.
Temporal-Fragen laufen sogar ohne Planer gut (Jahr-Toknen matchen
`jahr-YYYY`-Tags). Neue komplexe Fragen im Goldset: q-110 (Temporal 2023,
nach Korrektur — eine 2024er-Friseur-Lohnordnung existiert im Korpus
NICHT: kv-kvt-161 ist der Mantelvertrag ohne Lohntabelle; korrekt
verweigert), q-111 (Temporal 2025), q-112 (Abfertigung Vergleich),
q-113 (Gesetz+KV Multi-Source). Tests 50 -> 57.
Offen: q-029 Survey (Stufe-2-Hebel: Map-Reduce), q-113 Planer-
Scope flaky (1/2 Laeufe ohne gesetz-Marker).
- **D13 (Antworttyp-Routing / Stufe 2, 2026-09-15):** (a) Planer liefert
jetzt `type: survey|specific`; Survey-Fragen („Welche Neuerungen …“)
laufen ueber Map-Reduce: breiteres Retrieval (`survey_blocks`=16,
PV_SURVEY_BLOCKS), ein Map-Call destilliert JE Block als Stichpunkte
mit seiner KB-ID (MAP_SYSTEM_PROMPT), ein Reduce-Call synthetisiert
daraus die Antwort mit dem normalen Grounding-Prompt; Zitier-
Validierung weiterhin strikt ueber die Retrieved-Union, leerer
Map-Output -> Fallback Einzelantwort. (b) Systemprompt-Regel 9: bei
wesentlicher Kontextabhaengigkeit (Branche, Bundesland, Zeitraum)
belegte allgemeine Aussage + EINE Rueckfrage statt Verweigerung
(API-first; Odoo-Chat kann die Rueckfrage als Follow-up nutzen).
Ergebnis: **q-029 geheilt** (vorher jahrelange Fehlverweigerung;
jetzt Teilantwort mit 5 belegten Heften, 95 s — Map-Reduce-Latenz
nur bei Survey-Fragen), **q-015** antwortet mit expliziter
KV-Abhaengigkeit + Rueckfrage statt Branchen-Noise. Eval (46 Fragen):
Zitier-Praezision 97,8 %, Verweigerung 97,8 % (Gate erfuellt),
erwartete Quelle 90,2 %, Latenz mean 34,2 s. Tests 57 -> 59.
Report `data/eval-qwen38-stage2.json`. Verbleibend: q-024 transiente
Flakiness (Think-Ghost-Verdacht, 2/3 Laeufe sauber).
- **D14 (Output-Budget + length-Retry, 2026-09-15):** q-024-Flakiness
Ursache gefunden — NICHT Thinking (Hypothese widerlegt: thinking-Feld
leer, keine Tags), sondern `num_predict=1024`: lange belegte Antworten
wurden bei `done_reason=length` abgschnitten (q-024: 3/3 Sondenlaeufe)
→ unvollstaendige Zitationen → CITE_RE matcht partielle IDs →
Verletzung → Regen-Eskalation → UNCERTAIN. Fix: (a) num_predict
1024 → 2048; (b) `OllamaClient.chat_full()` liefert (content,
done_reason); (c) `chat_with_length_retry`: ein technischer Retry
mit 2× num_predict bei length (zaehlt nicht als Regel-Regenerierung,
laeuft auch im Map- und im Regenerierungspfad).
**Voll-Eval: Zitier-Praezision 100 %, Verweigerung korrekt 100 %**
(46/46 — alle M3-Gates erfuellt, erstmals ueber Basislinie),
erwartete Quelle 92,7 %, Latenz mean 39,6 s / p95 78 s (+5 s:
vollstaendige statt abgeschnittener Antworten). q-024: 4/4 stabil.
Tests 59 → 60. Report `data/eval-qwen38-lengthfix.json`.
- **D19 (API-first-Vertrag, 2026-09-16):** `/v1/ask`, `/v1/health` und
`/v1/reindex` sind die stabilen Endpunkte; alte Pfade bleiben deprecated.
Der Ask-Response enthält Status, Quellen, belegte Konflikte, extrahierte
Rückfrage, Planungs- und Grounding-Metadaten sowie eine Request-ID. Optionaler
Bearer-Schutz über `PV_API_KEY`; Reindex kann mit `PV_ADMIN_API_KEY` getrennt
werden. Leere Keys sind nur lokaler Entwicklungsmodus. Health und öffentliche
Fehler nennen keine internen Hosts/Exceptions. v1 ist zustandslos,
`knowledge_base_only`, lehnt unbekannte JSON-Felder ab und akzeptiert keine
Payroll-/Mitarbeiterobjekte. Odoo-Lohndaten erfordern später einen separaten
tenant-autorisierten Vertrag mit Datensatzregeln, Datenminimierung, Audit und
Cross-Tenant-Tests. Vertrag: `docs/API.md`. Der gemeinsame SQLite-Retriever
ist durch `check_same_thread=False` plus `RLock` worker-thread-sicher. Neue
Nicht-lokaler CLI-Bind ohne `PV_API_KEY` wird fail-closed abgelehnt. Neue
API-/Concurrency-Tests erhöhen die Offline-Suite von 75 auf **82 grüne Tests**.
- **D20 (Test-Frontend, 2026-09-16):** FastAPI liefert unter `/` ein
dependency-freies, responsives Frontend aus `web/` aus. Es nutzt same-origin
`/v1/ask` und `/v1/health`, zeigt Status, Quellen, Konflikte, Rückfragen,
Suchplan und Grounding-Metadaten und hält den Verlauf nur im DOM. Ein
optional gemerkter Service-Key liegt ausschließlich im `sessionStorage` des
Tabs; kein Secret wird in die Assets injiziert. Modelltext wird ohne
`innerHTML` gerendert. CSP gilt gezielt für UI/Assets, damit `/docs` nutzbar
bleibt; globale Header setzen `nosniff`, `DENY` und `no-referrer`. Betrieb
auf dem Tailscale-Interface: `python -m agent.cli serve --host
100.103.83.12` mit gesetztem `PV_API_KEY`; UI auf Port 8080. Tests 82 → 84.
- **D21 (Docker-Compose-Deployment, 2026-09-16):** `compose.yaml` baut einen
gemeinsamen UI-/API-Container, bindet Port 8080 nur an `100.103.83.12` und
trat zunächst dem externen Netz `ollama-default` bei; D22 korrigiert den
tatsächlichen Namen auf `ollama_default`. Ollama wird nicht dupliziert;
`OLLAMA_URL` ist konfigurierbar (Beispiel `http://ollama:11434`, Alias auf
Zielhost noch zu verifizieren). `data/` ist schreibbar, `wissensbasis/`
read-only; Root-FS read-only, Capabilities entfernt, no-new-privileges,
unprivilegierter `PUID:PGID`, verpflichtender `PV_API_KEY`. `.dockerignore`
hält `.env`, Index, Layer-1-Korpora und KB aus dem Image; Runtime-Image nutzt
`requirements-runtime.txt`. Compose `config --quiet`, Image-Build und
read-only Import-/Asset-Smoke-Test bestanden. Deployment: `docs/DOCKER.md`.
Statische Deploymenttests erhöhen die Suite von 84 auf **87 Tests**.
- **D22 (Bootstrap, Audit und Bewertungen, 2026-09-16):** Das Container-CMD
führt vor FastAPI `agent.bootstrap` aus. Ein Index gilt nur bei vorhandenen
Chunks und vollständiger Vektorabdeckung für `PV_EMBED_MODEL` als bereit;
andernfalls wird er im externen Netz `ollama_default` automatisch aufgebaut.
Embedding-Fehler stoppen den Container statt BM25-only in Produktion zu
starten. `agent.audit` protokolliert Interaktionen und Bewertungen in
`data/audit.db` mit konfigurierbarer 30-Tage-Aufbewahrung und optionalen
`AUDIT`-JSON-Zeilen nach stdout. `PV_AUDIT_LOG_CONTENT=false` entfernt alle
Freitexte inklusive indirekter Inhalte in Suchplan/Quellen/Konflikten. Die
authentisierte API `/v1/ratings` speichert `up|down` plus optionalen Kommentar;
das Frontend zeigt die Funktion nur bei aktiviertem Audit. Keys/Header werden
nie geloggt. Docker-JSON-Logs rotieren bei 50 MB × 5. Letzte Einträge:
`python -m agent.cli audit --limit 20`. Offline-Suite **93 Tests**; Compose-
Validierung, Image-Rebuild und unprivilegierter read-only Bootstrap-Smoke-Test
gegen den bestehenden vollständigen Index bestanden.
- **D23 (Antwortkommentare, 2026-09-16):** Zusätzlich zu `up|down` besitzt
jede protokollierte Antwort eine immer sichtbare Kommentarbox. Der
authentisierte Endpunkt `/v1/comments` speichert mehrere, jeweils datierte
Kommentare pro Request-ID in der separaten Audit-Tabelle `comments`; leere
Texte werden abgewiesen, Maximallänge 2.000 Zeichen. `agent.cli audit` gibt
Kommentare gemeinsam mit Interaktion und Bewertung aus. Bei deaktivierter
Inhaltsprotokollierung bleiben Kommentarereignis und ID sichtbar, der Text
wird weder in SQLite noch stdout gespeichert.
- **D24 (Kostenaufstellung + think-Fix, 2026-09-16):** (a)
`is_decision_support` matcht auf `normalize_text(question)`; die Regex deckt
neben NFKD-Folding (gunstig/losung) auch ue-Schreibweisen (guenstigste
loesung) ab — vorher liefen solche Fragen in den generischen LLM-Planer und
wurden verweigert. (b) Neue Trigger: "einmalig ... auszah", "bar auszah",
"wieviel/wie viel ... kostet", "kostet mich", "mitarbeiterpra(e)m". (c)
COST_INTENT: deterministischer Plan ergänzt `cost_tax` (einmalige Bezüge,
Jahressechstel 6 %, 620 — trifft lb-son-04) und `cost_lnk` (Beitragssätze DN
+ Dienstgeberanteil, DB/DZ, KommSt — trifft lb-sva-06, lb-lnk-02/07); bei
Kostennnabsicht fällt Zukunftssicherung weg (Slots), Cap 4 Queries. (d) Regel
11: konkrete Eckdaten → Arbeitgeberkosten Schritt für Schritt aus belegten
Sätzen rechnen, Annahmen nennen, Rückfrage nur bei wesentlichem Fehlen.
(e) generate.chat(): OllamaError bei think=true (leerer content, Antwort nur
im thinking-Feld) → einmaliger Retry ohne thinking. (f) Goldset q-128/q-129
(beide recall=1,00). Offline-Eval 56 Fragen: Hit-Rate 0,98 · Recall@8 0,95 ·
MRR 0,67. Real-Läufe: Q1 (günstigste) verified, 1 Regen; Q2 (Kosten) verified
mit AG-SV-Rechnung; PV_THINK=true verified (281 s).
- **Bake-off-Protokoll** (Skill): Modellwechsel nur über dokumentierten
Goldset-Vergleich; Kriterium: Zitier-Präzision > Verweigerungs-
korrektheit > Latenz.
## Files that matter right now
- `planung.md` — Plan + Entscheidungspunkte (Abschnitt 12) + Stand.
- `.agents/skills/pv-rag-agent/SKILL.md` — verbindliche Regeln.
- `agent/README.md` — Betrieb, Konfiguration, Host-Schritte.
- `.oddo-module/` (Symlink) — Odoo-19-Addon-Bestand; `planung.md` Abschnitt 14
— M4/D25-Planung; `l10n_at_hr_payroll_private/models/kollektivvertrag.py`
KV-Katalog mit `library_variant_id` (Brücke zu KB).
- `docs/API.md` — v1-Vertrag, Authentisierung, Privacy-Grenze und Odoo-Aufruf.
- `agent/api.py` — versionierte Service-Oberfläche, UI-Auslieferung und Sicherheitsgrenzen.
- `web/` — dependency-freies Test-Frontend.
- `compose.yaml`, `Dockerfile`, `.env.example`, `.dockerignore` — Zielhost-Stack und automatischer Bootstrap.
- `agent/bootstrap.py`, `agent/audit.py` — Indexbereitschaft, Q&A-Log und Bewertungen.
- `docs/DOCKER.md` — Deployment, Indexaufbau und Smoke-Tests.
- `agent/eval/goldset.yaml` — Goldset (IDs gegen kb.json verifiziert).
- `agent/generate.py` — Grounding-Kern (Prompt, Post-Validierung).
- `wissensbasis/README.md` — Layer-2-Schema + Rechtsprechungs-Provenance.
- `tools/ingest_sources.py` / `tools/catalogs/rj_catalog.json` — rj-Intake und gefrorene IDs.
- `agent/eval/goldset.yaml` — q-120123; als Nächstes Antwortmodus-Eval.