diff --git a/CONTEXT_HANDOVER_20260830.md b/CONTEXT_HANDOVER_20260830.md deleted file mode 100644 index 04fd311..0000000 --- a/CONTEXT_HANDOVER_20260830.md +++ /dev/null @@ -1,198 +0,0 @@ -# CONTEXT HANDOVER — Punte Discord → Claude Code (LXC 171) - -**Data:** 2026-08-30 13:00 UTC · **Stare:** LIVRATA, in productie, pe `master` (impins) -**Commit curent:** `c8a5d41` · **Repo:** romfast/ROMFASTSQL · **Branch:** `master` - -Nu e un task intrerupt. Puntea functioneaza si e folosita. Documentul asta exista ca o -sesiune noua sa poata prelua operarea si continuarea fara sa redescopere totul. - ---- - -## 1. Ce e - -Bot Discord care comanda LXC 171 (si, prin el, infrastructura) dintr-un canal privat, de pe -telefon. Bot subtire `discord.py` peste CLI-ul `claude`, cu **proces persistent per fir** -alimentat pe stdin cu `--input-format stream-json` — de aceea un mesaj trimis in timpul unui -tur functioneaza ca **steering**, nu deschide tur nou. - -Plan sursa: `~/.gstack/projects/romfast-ROMFASTSQL/claude-master-plan-discord-bridge-20260830.md` -(15 taskuri, toate facute). Executat cu 5 agenti in 3 lane-uri paralele. - -**Cod:** `proxmox/lxc171-claude-agent/discord-bridge/` -**Documentatie:** `discord-bridge/README.md` (operare completa), `discord-bridge/INTERFACES.md` -(contractul intre module — de citit inainte de orice modificare), `discord-bridge/security/README.md`. - ---- - -## 2. Stare de rulare (verificata la 12:58 UTC) - -| | | -|---|---| -| Serviciu | `systemctl --user status claude-discord` → **active**, `NRestarts=0` | -| Bot | `ClaudeAgent#7891`, app id `1543576449624186880` | -| Guild | Romfast `1430476698264145922` | -| Canal | **privat** `#claude-agent` `1543581063496859679` | -| User permis | `949388626146517022` (marius.mutu) | -| Director de lucru | `/workspace/claude-agent/` (repo git propriu) | -| Model | `sonnet` (default), `/model opus` per fir | -| Cost ziua curenta | $1.5456 (plafon `COST_CAP_USD_DAY=5.00`) | -| Comenzi slash | 7, inregistrate pe guild | - -**Stare pe disc:** `~/.claude-discord/` — `env` (0600, token+allowlist), `state.json`, -`bot-settings.json` (hook), `approvals/`, `logs/`, `venv/`. -Cod versionat in repo; secretele NU. - -### Comenzi (slash, nu prefix `!` — migrat) -`/new [fork]` · `/cd ` · `/model ` · `/status` · `/stop` · `/cleanup [force]` · `/help` - -Mesajele obisnuite (fara comanda) merg la Claude ca prompt. - ---- - -## 3. Ce s-a verificat EMPIRIC in productie (nu doar teste) - -Astea sunt afirmatii cu dovada, nu presupuneri: - -| Ce | Dovada | -|---|---| -| Steering mid-tur | mesaj la 6s intr-un tool call de 25s a schimbat raspunsul final (ALFA→BETA), CLI 2.1.251 | -| Comenzi slash | 7 citite inapoi din API-ul Discord, nu doar din log | -| Confirmare operatiuni ireversibile | `12:24:25 PENDING` → butoane in canal → `12:24:39 ALLOW` → `/tmp/test-punte` sters | -| Fail-closed | acelasi `rm -rf` fara aprobare → `DENY` la timeout, directorul intact | -| Proces persistent | pid viu la 2 min dupa incheierea turului (reaper la 20 min) | -| `--resume` peste restart | aceeasi sesiune reluata dupa ce restartul a omorat procesul | -| `KillMode=control-group` | restart mid-sesiune, zero orfani | -| Parser tolerant | `rate_limit_event` aparut real in stream, ignorat cu un singur log | -| `/cleanup` dupa fix | rulare seaca pe container: „Niciun proces orfan" | -| Suita | **322 passed** cu discord.py, **319 passed + 3 skipped** fara | - -Rulare teste: `cd proxmox/lxc171-claude-agent/discord-bridge && python3 -m pytest` -E2E (CLI real, ~40s, costa): `pytest -m e2e` - ---- - -## 4. Capcane gasite pe parcurs — NU le reintroduce - -Astea au costat timp si unele erau periculoase. Sunt fixate; sunt scrise aici ca sa nu -fie „simplificate" inapoi. - -1. **`/cleanup` omora sesiunile de lucru.** Definitia initiala („orice proces `claude` - absent din `state.json`") prindea sesiunile Claude interactive de pe container: - rularea seaca propunea 25 de procese / 3864 MB, inclusiv sesiunea din care ar fi fost - data comanda. **Apartenenta la cgroup-ul `claude-discord.service` e conditie NECESARA - pentru toate familiile**, fail-closed la cgroup necitibil. Sesiunile de lucru stau in - `tmux-spawn-*.scope`. Exista test de regresie pe instantaneul real; verificat prin - mutant ca musca (fara filtru pica 3 teste). - -2. **`pid_start_time` e in SECUNDE**, nu ticks (`session_store.py` converteste; `state.json` - viu contine secunde). Un consumator care compara ticks nu se potriveste niciodata — si - cum acea comparatie protejeaza turul in desfasurare, esecul e tacut si face eligibil - pentru omorare exact ce trebuia protejat. Documentat in `INTERFACES.md`. - -3. **`/proc/uptime` e virtualizat de lxcfs, `starttime` nu.** Calculul naiv al varstei dadea - `4123168064` pentru un proces de 10 minute. Se foloseste `btime` din `/proc/stat`. - -4. **Utilizatorul containerului se numeste `claude`**, deci potrivirea pe subsirul `/claude` - marca orice proces din home-ul lui. Potrivire pe basename per argument. - -5. **Log dublat:** unitul redirecteaza stdout in `bot.log` (`StandardOutput=append:`) SI - codul avea `FileHandler` pe acelasi fisier. `FileHandler` ramane doar la rulare manuala - (`INVOCATION_ID` absent). - -6. **`View Channel` lipsea** din permisiunile de invitatie — fara ea botul nu vede canalul, - oricat de permis ar fi in allowlist. Link corect (`permissions=309237763136`): - `https://discord.com/oauth2/authorize?client_id=1543576449624186880&scope=bot%20applications.commands&permissions=309237763136` - -7. **Comenzile slash au nevoie de scope `applications.commands`** (nu doar `bot`) si de - **sync pe guild** (instantaneu; global ~1h). Fiecare comanda face `defer()` — altfel - Discord marcheaza interactiunea esuata dupa 3s desi comanda a rulat. - -8. **`~/bin` trebuie sa fie in PATH-ul unitului**, altfel wrapperul `infra` e prezent pe - disc dar negasibil de bot (stratul 4 de securitate inert). - -9. **Raportul `/cleanup`** depasea 2000 de caractere cu rezultatele atasate → mesaj respins - de Discord exact la `force:True`. Buget de caractere `MAX_REPORT_CHARS=1800`. - -10. **Discord API respinge User-Agent-ul implicit al `urllib`** cu `error code: 1010` - (Cloudflare). Arata identic cu un refuz de permisiuni. Pune un User-Agent real. - ---- - -## 5. Decizii de arhitectura — luate deliberat, NU le redeschide - -Sunt in plan si au fost reconfirmate de utilizator. Un agent nou care le „descopera" ca -probleme pierde timp si risca sa strice lucruri. - -- **`--permission-mode bypassPermissions` e intentionat.** Regulile `deny` sunt strat - cosmetic, NU bariera — verificat: `/usr/bin/ssh -V` si `bash -c "ssh -V"` trec pe langa. - Bariera reala e hook-ul PreToolUse. -- **Accesul larg la infrastructura si la `/workspace` e FUNCTIONALITATE ceruta**, nu bug. - De aceea: fara user separat `cdbot`, fara allowlist de proiecte pentru `/cd`. -- **Fara audit append-only** (stratul 3), respins constient. **Consecinta asumata:** la o - problema nu exista jurnal independent; logurile pot fi sterse de chiar procesul care le - scrie. Hook-ul si botul ruleaza sub acelasi user, deci un agent isi poate scrie singur - `"status":"allow"`. -- **Fara reluare automata a turului pierdut** — risc de dubla executie sub bypassPermissions. -- Fara Agent SDK, fara dashboard web, fara user separat, fara test golden pe JSONL inregistrat. - ---- - -## 6. CE A RAMAS DE FACUT - -### 6.1 Token Proxmox cu ACL (singurul lucru neterminat) - -**Nu e conditie de functionare.** Puntea merge acum folosind cheile SSH root deja prezente -pe container — adica orice tur are drepturi depline pe cluster. Tokenul restrange asta. - -Comenzile exacte sunt in `discord-bridge/security/README.md`. Pe scurt, ca root pe `pvemini`: - -``` -pveum user add claude-bridge@pve -pveum role add ClaudeBridge -privs "Datastore.Audit,Sys.Audit,Sys.Console,Sys.Syslog,VM.Audit,VM.Monitor,VM.Console,VM.PowerMgmt" -pveum acl modify / -pveum user token add claude-bridge@pve discord --privsep 1 -pveum acl modify / --tokens 'claude-bridge@pve!discord' -``` - -`VM.Allocate` (creare/distrugere guest) e lasat afara INTENTIONAT. - -Apoi: secretul (afisat o singura data) in `~/.claude-discord/env` ca `PVE_TOKEN_ID` / -`PVE_TOKEN_SECRET`, `chmod 600`. De decis daca ACL ramane pe `/` sau se restrange la un -subset de guest-uri. - -### 6.2 Idei ramase, neprioritizate -- Atasamente Discord → Claude (download in /tmp + cale in prompt) — era v1.1 in plan. -- Rate limit-ul Discord la render e singura degradare tacuta ramasa: are tratare (interval - adaptiv 1s→5s), nu are test. Acceptat, fiindca esecul e intarziere, nu pierdere. -- `/cleanup` nu prinde procese iesite complet din cgroup — pret deliberat, ca sa nu atinga - niciodata sesiunile interactive. - ---- - -## 7. Fisiere cheie - -| Fisier | De ce conteaza | -|---|---| -| `discord-bridge/INTERFACES.md` | contractul intre module + proprietatea pe fisiere. **De citit primul.** | -| `discord-bridge/README.md` | operare, instalare, depanare, limitari oneste | -| `discord-bridge/runner.py` | proces persistent, reaper, respawn `--resume` | -| `discord-bridge/session_store.py` | state.json atomic, lock per fir, PID reuse | -| `discord-bridge/cleanup.py` | vezi capcana #1 — cea mai periculoasa zona | -| `discord-bridge/security/confirm_hook.py` | hook PreToolUse, fail-closed | -| `discord-bridge/security/infra` | wrapper cu lista explicita de hosturi (exit 3 la host necunoscut) | -| `discord-bridge/ops/claude-discord.service` | `KillMode=control-group`, MemoryHigh 3G / Max 6G, PATH | -| `~/.gstack/.../claude-master-plan-discord-bridge-20260830.md` | planul original, cu justificarile | - -## 8. Comenzi de operare - -```bash -systemctl --user status claude-discord -journalctl --user -u claude-discord -n 200 -tail -f ~/.claude-discord/logs/bot.log -tail -f ~/.claude-discord/logs/confirm_hook.log # cine a cerut/primit aprobare -cd proxmox/lxc171-claude-agent/discord-bridge && python3 -m pytest -python3 cleanup.py # rulare seaca, sigura -``` - -**Memorie:** un fir costa ~440 MB (claude 300 + servere MCP 137). Plafon 4 procese → ~1,8 GB, -sub `MemoryHigh=3G`. Containerul are 16 GB. diff --git a/CONTEXT_HANDOVER_20260831.md b/CONTEXT_HANDOVER_20260831.md deleted file mode 100644 index 1f7ab77..0000000 --- a/CONTEXT_HANDOVER_20260831.md +++ /dev/null @@ -1,127 +0,0 @@ -# Context handover — 2026-08-31 - -Sesiune lungă, patru fire de lucru terminate și unul în curs. Totul e comis și împins. - -> **Infrastructura e deja documentată în acest repo** — nu o repet aici. Punctele de -> intrare: [`CLAUDE.md`](CLAUDE.md), [`proxmox/README.md`](proxmox/README.md) și, pentru -> chatboți, [`docs/chatboti-si-punti.md`](docs/chatboti-si-punti.md) (scris azi). -> Documentul ăsta e doar despre **ce s-a schimbat azi și ce a rămas**. - ---- - -## 1. Ce rulează acum (starea la finalul sesiunii) - -| | Stare | -|---|---| -| **Maria (WhatsApp + RAG)** | **LXC 171**, asociată la +40723197939, funcțională. 11 documente, sincronizare Drive la 10 min. | -| Prototipul Maria de pe LXC 104 | **oprit și dezactivat**. `llama-qwen35.service` (LLM-ul, port 8091) și `flowise.service` rămân pornite acolo. | -| Puntea Discord | activă, cu atașamente (imagini + fișiere text). | -| Reindexare RAG | **era în curs la finalul sesiunii** — vezi „De verificat întâi" mai jos. | - -## 2. De verificat întâi, la reluare - -Reindexarea pornită după corectarea `efactura-erori-rag.xml` s-ar putea să nu fi terminat. - -```bash -pgrep -af "sync.py" # gol = gata -ls -la ~/.maria-bridge/rag_index.json # mtime recent = reconstruit -python3 -c " -import json,collections -e=json.load(open('/home/claude/.maria-bridge/rag_index.json')) -c=collections.Counter(x['source'] for x in e) -print(len(e),'chunk-uri,',len(c),'documente') -print(c.get('efactura-erori-rag.xml'),'din efactura-erori-rag.xml (8 = structurat corect, ~41 = inca text simplu)') -" -``` - -O reindexare completă durează **~18–21 de minute** (embedding CPU-only, ~7,3 s per chunk). -Nu e blocată — chiar durează atât. Dacă a eșuat, se reia cu: -`cd proxmox/lxc171-claude-agent/maria-whatsapp-bridge/rag && MARIA_BRIDGE_DIR=$HOME/.maria-bridge ~/.maria-bridge/venv/bin/python sync.py --force` - -## 3. Ce s-a făcut azi - -### Git și Gitea -- **`romfast/workspace` dădea 500** de pe 2026-04-06: patru gitlink-uri fără `.gitmodules` - făceau Gitea 1.24.3 să dea panic pe listarea rădăcinii. Scoase din index. **Nu era vina - botului**, cum se bănuia inițial. -- **`/workspace/claude-agent` e acum repo propriu** (`git@gitea.romfast.ro:romfast/claude-agent.git`), - scos din repo-ul `workspace` unde fusese comis din greșeală. - -### Memorie și mod de lucru al botului Discord -- `~/.claude/projects/-workspace-claude-agent/memory` e **symlink** către memoria lui - `-workspace-romfastsql`. Firele Discord porneau amnezice fiindcă memoria e per director - de proiect. Copia veche: `memory.bak-2026-08-31`. -- **Regula nouă:** orice cerere de dezvoltare → citește `claude-agent/TODO.md`, planifică - cu `Agent(subagent_type: "Plan", model: "opus")`, execută pe Sonnet, actualizează - `TODO.md`. Scrisă în `claude-agent/CLAUDE.md` + `claude-agent/docs/flux-dezvoltare.md`. - Testată pe sesiuni reale: prima versiune era prea blândă și agentul sărea peste plan la - sarcini mici — întărită explicit cu „mărimea nu e criteriu". -- **`claude-agent/TODO.md`** e registrul viu al funcționalităților. Nimic blocat acum. - -### Puntea Discord — atașamente -Imagini (png/jpeg/gif/webp, max 4, max 3,5 MB brut) → blocuri `image`; fișiere text → -inserate în prompt (max 4, trunchiate la 100 KB); restul doar numite. Merge și mid-tur ca -steering. 31 de teste noi; suita: 426 pass. - -### Maria — mutare completă pe LXC 171 -- **Sincronizare Drive** prin `rclone authorize` (fără cont de serviciu). Procedura - completă: [`docs/rclone-google-drive-headless.md`](docs/rclone-google-drive-headless.md). - Script: `maria-whatsapp-bridge/ops/setup-drive.sh`. -- **`.xml` acceptat și preferat peste `.md`** la același nume de bază; chunking per - problemă (un `` = un chunk, cu mesajul și rezolvarea împreună). -- **Lacăt `flock` între reindexări** + scriere atomică a indexului. Descoperit când - timer-ul a pornit peste o rulare manuală și două procese scriau același fișier. -- **Fix LID**: după asociere nu răspundea nimic. WhatsApp livrează self-chat-ul ca - `51947713372214@lid`, iar filtrul compara doar cu `@s.whatsapp.net` — arunca tot, - tăcut. Reparat; mesajele respinse se loghează acum cu motivul. -- 26 de teste noi (prima suită a proiectului Maria). - -### Documentație -- **[`docs/chatboti-si-punti.md`](docs/chatboti-si-punti.md)** — care chatbot e care. - Trei lucruri numite „Maria", doi boți pe Discord, două punți WhatsApp pe **același - număr**. A costat o oră de diagnostic azi: Maria răspundea de pe LXC 104, iar toate - verificările se făceau pe 171. -- **[`proxmox/lxc301-docker-template/README.md`](proxmox/lxc301-docker-template/README.md)** — - singurul guest nedocumentat. E un template Proxmox, nu un container oprit. - -## 4. Ce a rămas de făcut - -### a) LXC 301 — nimic urgent (corectat 2026-08-31) -Versiunea inițială a acestei secțiuni descria un conflict de IP activ cu VM 109. **Greșit:** -301 e marcat `template: 1`, deci nu poate fi pornit deloc și `onboot: 1` e ignorat. Nu -revendică 10.0.20.37 la reboot-ul lui pveelite, deci `pct set 301 -onboot 0` nu e necesar. - -Verificat tot atunci: `basevol-301-disk-0@__base__` nu are niciun clon, deci `pct destroy 301` -ar fi sigur (~916 MB pe local-zfs). Opțional, la decizia utilizatorului — nu e urgent. - -Singurul caz în care IP-ul redevine o problemă: dacă cineva convertește template-ul înapoi -în container. Atunci `-onboot 0` sau alt IP **înainte** de pornire. - -### b) Auditul de infrastructură, dacă se dorește -Coverage-ul e bun — LXC 301 era singurul guest fără pagină proprie. Ce **nu** s-a făcut: -verificarea sistematică a fiecărui README existent față de starea live. Divergențele -găsite azi (prototipul Maria pe 104, LLM-ul pe 8091, Gitea rupt de 5 luni) au ieșit la -iveală accidental, nu dintr-un audit. - -### c) Model de răspuns al Mariei -Rămâne `Qwen3.5-2B-Q4` pe LXC 104 — decizia utilizatorului a fost „păstrez deocamdată, cu -RAG-ul complet". Răspunsurile factuale (e-Factura) sunt acum corecte și ancorate în -documente; cele explicative sunt încă vagi. De reevaluat după câteva întrebări reale. - -## 5. Capcane de reținut - -- **Verifică întâi pe ce container ești.** `8099` și `11434` există și pe 104 și pe 171, - cu conținut diferit. Nu repeta ora pierdută azi. -- **Nu reporni prototipul de pe 104.** Ar răspunde în paralel, pe același număr, cu un - index de 24 de chunk-uri. -- **`rclone sync` e distructiv pe destinație.** Depozitul Mariei e oglindă a Drive-ului; - un document adăugat manual din dashboard dispare la următoarea sincronizare. -- **Maria nu are voie la infrastructură** — e chatbot pentru clienții ERP ROA. Nimic din - `proxmox/`, niciun IP sau credențial în depozitul ei. Regula e în `claude-agent/CLAUDE.md`. -- **Nu tăia ieșirea comenzilor de diagnostic cu `head`.** Concluzia „nu ascultă nimic pe - 8091" a fost greșită exact din cauza asta și a trimis diagnosticul pe pistă falsă. - -## 6. Comiteri - -`ROMFASTSQL`: `b29b9f2..824b215` (14 comiteri) · `claude-agent`: `6d0bfe1..c97ba0c` (9 comiteri) -· `workspace`: `d24dca3`. Toate împinse pe gitea.romfast.ro. diff --git a/CONTEXT_HANDOVER_20260831_reindexare.md b/CONTEXT_HANDOVER_20260831_reindexare.md deleted file mode 100644 index 7e86158..0000000 --- a/CONTEXT_HANDOVER_20260831_reindexare.md +++ /dev/null @@ -1,164 +0,0 @@ -# Context handover — 2026-08-31, seara - -Continuarea sesiunii din [`CONTEXT_HANDOVER_20260831.md`](CONTEXT_HANDOVER_20260831.md). -Tot ce e mai jos e comis și împins (`ROMFASTSQL`: `b447251..c514818`, `claude-agent`: două -comiteri noi). - ---- - -## 1. Întrebarea care a declanșat handover-ul - -> *„nu vreau ca reindexarea să refacă toate embeddings-urile de fiecare dată. de ce le face?"* - -**Nu le mai face.** Reparat în `c514818` (parte din commit-ul `ca6b53e`), verificat pe viu: - -``` -[indexer] 12 documente, 169 chunk-uri (0 embeddings noi, 169 refolosite) -real 0.15s # înainte: ~20 de minute -``` - -### De ce le refăcea - -`rag/indexer.py:build()` era scris ca o reconstrucție totală: pentru fiecare document, pentru -fiecare chunk, `embed(chunk)` — necondiționat. Nu exista nicio noțiune de „chunk-ul ăsta e -neschimbat". Indexul vechi era pur și simplu suprascris. - -Costul: un embedding pe CPU (nomic-embed-text, fără GPU pe LXC 171) durează **~7 secunde**. -La 169 de chunk-uri asta înseamnă ~20 de minute — plătiți integral chiar și când se schimba -un singur document. Adăugarea dicționarului de erori Oracle (29 de chunk-uri noi) ar fi -costat 20 de minute în loc de 3. - -Nu era o scăpare evidentă la citirea codului: `sync.py` avea deja o optimizare care *arăta* -ca și cum ar rezolva problema — amprenta pe nume+mtime+mărime, care sărea reindexarea când -nimic nu se schimbase. Dar aia decide **dacă** se reindexează, nu **cât** se reindexează. -Odată ce un singur fișier se atingea, se plătea totul. - -### Cum se rezolvă acum - -`rag/indexer.py:_vectori_existenti()` citește indexul precedent și îl transformă într-un -dicționar `text_chunk -> embedding`. La reconstrucție, fiecare chunk se caută întâi acolo. - -Cheia e **textul chunk-ului**, nu numele documentului și nu mtime-ul. Motivul: dacă nu s-a -schimbat niciun caracter în chunk, vectorul e identic prin definiție. Un document reformatat -în Drive, ale cărui chunk-uri ies la fel, nu costă nimic. Un document în care s-a schimbat un -rând costă doar chunk-urile atinse. - -**Sursa e chiar `rag_index.json`, nu un al doilea fișier de cache.** Deliberat: nu există un -al doilea fișier care să se poată desincroniza, iar dacă indexul lipsește se recalculează tot, -exact ca înainte. Logul spune de fiecare dată câte au fost calculate și câte refolosite. - -### Când tot se recalculează (și e corect așa) - -| Situație | Ce se întâmplă | -|---|---| -| `rag_index.json` lipsește sau e corupt | se recalculează tot — nu are de unde refolosi | -| Textul unui chunk s-a schimbat, oricât de puțin | acel chunk se re-embedează (corect: alt text = alt vector) | -| Se schimbă **strategia de chunking** (`indexer.py`) | toate chunk-urile ies altfel → se recalculează tot | -| Se schimbă `EMBED_MODEL` în env | **capcană: NU se recalculează**, deși ar trebui | - -Ultima linie e singura problemă rămasă și e reală: cheia de cache e doar textul, fără modelul -care l-a produs. Dacă cineva schimbă `EMBED_MODEL`, indexul rămâne un amestec de vectori din -două modele, iar căutarea dă rezultate aiurea fără niciun mesaj de eroare. Nu s-a întâmplat -(modelul e `nomic-embed-text` de la început), dar merită reparat — vezi „De făcut" mai jos. - -**Fișiere:** `rag/indexer.py` (`_vectori_existenti`, `build`), teste în -`tests/test_store_si_chunking.py` (`test_vectorii_din_indexul_vechi_se_refolosesc`, -`test_textul_modificat_se_reembedeaza`). - ---- - -## 2. Ce s-a mai făcut în sesiune - -Toate în `proxmox/lxc171-claude-agent/`. Documentația completă e în -[`maria-whatsapp-bridge/README.md`](proxmox/lxc171-claude-agent/maria-whatsapp-bridge/README.md) — -aici doar ce nu se vede din cod. - -### Asociere WhatsApp prin cod, nu doar QR -`/pair` exista dar nu era folosibil: fără descriptor `browser` explicit WhatsApp refuză codul, -iar după introducerea lui corectă serverul cere un restart (515) care consuma din bugetul de -reîncercări și putea opri puntea la a cincea asociere. Cod valabil ~3 minute, cu marcarea -expirării în dashboard. Folosit pe bune pentru re-legarea lui Echo. - -### Capturi de ecran cu erori (OCR) -tesseract `ron+eng`; modelul de răspuns e strict text, deci OCR nu e o opțiune de calitate. -**Căutarea în index folosește doar liniile care arată a eroare**, nu toată fereastra — un ecran -de meniuri diluează embedding-ul. Modelul primește captura întreagă, marcată ca OCR. - -### Rank hibrid (`rag/rank.py`) -Embeddings + BM25, fuzionate prin RRF. Motivul, măsurat: intervalele de cosinus **se suprapun** -— întrebări bune 0,600–0,816, străine 0,534–0,685. Codurile (`ORA-01722`, `D406`, `CIF`) sunt -exact ce ratează căutarea semantică. Praguri calibrate cu `ops/calibrate-rank.py`, **19/19**. - -### Escaladare la suport -Când nu există acoperire, modelul **nu mai e întrebat deloc**. Referință (`M-260831-FC34`), -confirmare onestă („am trimis" doar dacă notificarea chiar a plecat), captura se retrimite ca -imagine. Jurnal în `~/.maria-bridge/escalations/`, vizibil în dashboard. - -### Dicționar de 29 de erori Oracle -`maria-whatsapp-bridge/knowledge/oracle-erori-uzuale.xml`, scris pentru clienți — fără nume de -servere sau pași de administrare. - -### Două bug-uri găsite în log -- Fiecare mesaj din self-chat sosea **de două ori** (`@s.whatsapp.net` și `@lid`) → Maria - răspundea dublu. Dedup pe `key.id` în punte. -- Documentele adăugate din dashboard ajungeau în oglinda Drive și **dispăreau la următorul - `rclone sync`**. Acum există `~/.maria-bridge/documents-local/`. - -### Echo re-legat la WhatsApp -Dispozitivul `:14`. S-a lămurit de ce nu răspund ambele punți în chatul „Eu": Echo aruncă tot -ce e `fromMe && !isGroup`, iar în self-chat tot ce scrii e `fromMe`. **Echilibrul e accidental** — -detalii în [`docs/chatboti-si-punti.md`](docs/chatboti-si-punti.md). - ---- - -## 3. De făcut - -### a) `EMBED_MODEL` în cheia de cache (mic, real) -Vezi tabelul de mai sus. Cea mai simplă variantă: pune modelul în fiecare intrare de index -(`"model": config.get("EMBED_MODEL")`) și refolosește doar intrările cu modelul curent. -Intrările vechi, fără câmp, se tratează ca „model necunoscut" → se recalculează o dată. - -### b) `SUPPORT_JID` — decizia utilizatorului -E setat pe **propriul număr** (`40723197939@s.whatsapp.net`), pentru testare. De schimbat cu -numărul sau grupul echipei de suport, în `~/.maria-bridge/env`. - -### c) Dicționarul Oracle trăiește doar pe container -E în `documents-local/`, deci supraviețuiește sincronizărilor, dar **nu-l vede nimeni altcineva**. -Locul lui pe termen lung e `document_store` din Drive. Când ajunge acolo, șterge copia locală — -altfel rămân două versiuni și câștigă tăcut cea din Drive. - -### d) Recalibrare când se schimbă documentele -Pragurile din `env` sunt măsurate pe indexul de **acum**. La schimbări mari de conținut: -```bash -cd proxmox/lxc171-claude-agent/maria-whatsapp-bridge -MARIA_BRIDGE_DIR=$HOME/.maria-bridge ~/.maria-bridge/venv/bin/python ops/calibrate-rank.py -``` -Adaugă în `CAZURI` (în capul scriptului) întrebările reale la care Maria a greșit — e singurul -mod în care calibrarea rămâne onestă. - -### e) Rămase din handover-ul precedent -LXC 301 (nimic urgent), auditul de infrastructură, modelul de răspuns al Mariei. - ---- - -## 4. Stare la finalul sesiunii - -| | | -|---|---| -| Index RAG | 169 chunk-uri, 12 documente (din care `oracle-erori-uzuale.xml` e local) | -| Servicii | `maria-whatsapp`, `maria-rag`, `claude-discord-dashboard` — active | -| WhatsApp | Maria conectată (`40723197939`), Echo re-legat ca dispozitiv `:14` | -| Teste | **73 pass** (26 la începutul zilei) | -| `SUPPORT_JID` | propriul număr — de schimbat | - -## 5. Capcane (în plus față de handover-ul precedent) - -- **`systemctl` pe LXC 110 are nevoie de `--user`.** Serviciile lui Echo sunt unități de - utilizator (`moltbot`, uid 1000). Ca root, `systemctl is-active echo-core` răspunde - `inactive` deși botul rulează. -- **Nu adăuga documente în `~/.maria-bridge/documents/`.** E oglinda Drive-ului; `rclone sync` - le șterge. Folosește `documents-local/`. -- **Nu ridica pragurile de rank „ca să fie sigur".** Escaladarea prea agresivă e la fel de - proastă ca răspunsul generic — rulează calibrarea, nu intuiția. -- **Fiecare embedding costă ~7 secunde.** Orice schimbare în strategia de chunking înseamnă - recalcularea întregului index; nu e o schimbare de făcut în grabă. diff --git a/CONTEXT_HANDOVER_20260901_termeni_distinctivi.md b/CONTEXT_HANDOVER_20260901_termeni_distinctivi.md deleted file mode 100644 index 495d1f8..0000000 --- a/CONTEXT_HANDOVER_20260901_termeni_distinctivi.md +++ /dev/null @@ -1,154 +0,0 @@ -# Context handover — 2026-09-01, seara - -> **REZOLVAT in `653f5cb`** (2026-09-01, noaptea). Pasul (1) facut: setul are 31 de -> cazuri. Pasul (2) facut, dar cauza s-a dovedit ALTA decat se banuia aici: -> politetea din ancora nu era ce strica verdictul, ci faptul ca termenii se -> comparau pe **cuvantul intreg** — documentul scrie „token"/„tokenuri", omul scrie -> „tokenul". Reparat prin potrivire pe primele 5 litere (codurile raman intregi) + -> varianta (a), `rank.assess(dovada=...)`. Varianta (b), lista de formule de -> politete, a fost scrisa si apoi **scoasa**: masuratoarea n-a justificat-o. -> 31/31 cu reparatia, 30/31 fara. Restul documentului se pastreaza ca istoric — -> §7 (eroarea „CUI cumparator incorect" lipsa din documente) e inca deschis. - -**Ce e de făcut:** două lucruri legate între ele, în `proxmox/lxc171-claude-agent/maria-whatsapp-bridge/`: - -1. adaugă **cazuri de continuare** în `ops/calibrate-rank.py` (azi setul n-are decât două, ambele cu cod de eroare); -2. repară **termenii distinctivi**: `rank.rare_terms()` judecă acoperirea pe *interogarea combinată* (ancora firului + mesajul nou), nu pe ce a întrebat omul acum. - -(1) există ca să poți face (2) fără să ghicești. **Nu începe cu (2).** - -Tot ce e mai jos e comis și împins: `ROMFASTSQL` `5e60394..af04d97`. - ---- - -## 1. De unde vine - -Sesiunea de azi a plecat de la: *„am trimis 2 mesaje pe grupul maria test whatsapp și ambele răspunsuri sunt eronate"*. Cauza reală s-a dovedit dublă și e reparată (vezi §5), dar reparația a scos la iveală problema care rămâne deschisă. - -Din firul de test real (escaladarea `M-260901-06EB`, `~/.maria-bridge/escalations/1788291399-M-260901-06EB.json`): - -``` -[consumer] rank: efactura_knowledge.md(0.836), … | cosinus 0.836, iar termenii -distinctivi (buna, luna, august, trimitere, auto) nu apar in documente -``` - -**„buna" e tratat drept termen distinctiv.** E rar în corpusul de ERP, deci `rare_terms()` îl consideră discriminant, deși e o formulă de salut. - -De ce contează acum și nu conta înainte: de azi, la o continuare se caută după **ancoră + mesajul nou** (`consumer.py:574` → `fir.interogare()`), iar ancora e primul mesaj al omului, cu tot cu „Buna . … Poti te rog sa ma ajuti?". Ancora nu se șterge cât trăiește firul (2h), deci **fiecare continuare de pe firul ăla e judecată și după „buna/luna/august"**, cuvinte care nu vor apărea niciodată în documente. - -### Efectul - -`assess()` cere ca cel puțin `RANK_MIN_RARE_RATIO` (0,5) din termenii distinctivi să apară chiar în chunk-uri. Cu 3 cuvinte de politețe care nu apar niciodată, raportul scade artificial la fiecare replică → **escaladare în plus**. - -Direcția e sigură (un om vede întrebarea, nu primește un răspuns inventat), deci **nu e urgent**. Dar face Maria inutilă exact pe discuțiile lungi, unde ar trebui să fie cea mai bună. - -În cazul testat n-a stricat nimic — răspunsul chiar nu era în documente, escaladarea a fost corectă. Deci **nu ai încă nicio dovadă că regresia se produce în practică**; asta e chiar ce trebuie măsurat la pasul (1). - ---- - -## 2. Reparația propusă (nu implementată) - -**Separă cele două roluri ale interogării:** - -| Rol | Ce folosește azi | Ce ar trebui | -|---|---|---| -| **regăsire** (ce chunk-uri aduc) | ancoră + mesaj nou | rămâne așa — ancora chiar ajută, „da, mă blochează" singur nu găsește nimic | -| **acoperire** (răspund sau escaladez) | ancoră + mesaj nou | doar mesajul nou (+ OCR-ul capturii), adică ce a întrebat omul acum | - -Concret, în `consumer.py:131 search()`, `rank.assess()` (`consumer.py:144`) primește azi același `query` ca și regăsirea. Ar trebui să primească separat interogarea de dovadă. - -**Capcana, și motivul pentru care n-am făcut-o azi:** o continuare scurtă („da, mă blochează complet", „eram la salvarea unei facturi") **n-are termeni distinctivi proprii**. Judecată singură, escaladează — exact regresia pe care `fir.py` a fost scris ca s-o prevină (vezi tabelul din docstring-ul lui `rag/fir.py`, cazurile măsurate). Deci reparația poate strica mai mult decât repară, și nu se poate valida pe setul actual de cazuri. - -Variante de luat în calcul, în ordinea preferinței: -- **a)** acoperirea se judecă pe mesajul nou, dar dacă acesta n-are termeni distinctivi proprii se cade înapoi pe interogarea combinată (continuările scurte se comportă ca azi); -- **b)** `rare_terms()` ignoră formulele de politețe — listă mică de stopwords („buna", „salut", "buna ziua", „multumesc", „va rog", „poti", „ajuti"). Whack-a-mole, dar o linie și zero risc; -- **c)** ancora se curăță la creare: se păstrează doar liniile care arată a eroare, cum face deja `ocr.retrieval_query()` pentru capturi (`rag/ocr.py`). - -(b) e cel mai ieftin și poate fi făcut singur, imediat. (a) e cel corect. Nu le confunda: (b) ascunde simptomul măsurat azi, (a) rezolvă clasa. - ---- - -## 3. Ce trebuie adăugat în `ops/calibrate-rank.py` — pasul (1) - -Setul (`CAZURI`, o listă de `(intrebare, are_raspuns_in_documente)`) are azi **26 de cazuri, 26/26 corecte**. Continuări are doar două, și **ambele conțin un cod de eroare**, deci trec pe regula codurilor și nu spun nimic despre termenii distinctivi: - -```python -("ORA-06550: line 1, column 7 PLS-00906 object invalid\nda, ma blocheaza complet", True), -("ORA-12154 TNS could not resolve the connect identifier\neram la salvarea unei facturi", True), -``` - -**Lipsesc continuările fără cod de eroare** — exact cazul în care termenii distinctivi decid. De adăugat, în forma pe care o produce `fir.interogare()` (ancoră `\n` mesaj nou): - -- ancoră politicoasă + continuare scurtă, cu răspuns în documente → `True` - (ex. `"Buna ziua . Am o problema la trimiterea in SPV . Puteti sa ma ajutati?\nimi zice ca tokenul e expirat"`) -- ancoră politicoasă + continuare scurtă, fără răspuns în documente → `False` -- aceeași continuare **fără** formula de salut în ancoră → același verdict ca varianta cu salut - (dacă verdictele diferă, ai măsurat exact regresia) -- ancoră + a treia, a patra replică (fir lung) → verdictul nu trebuie să se degradeze cu lungimea -- continuare pe un fir deschis de o **captură** (ancora = OCR, plin de zgomot: „Total", „Lit", „Selecteaz!") - -Scrie-le pornind de la fire reale, nu inventate: `~/.maria-bridge/escalations/*.json` are câmpul `fir` cu schimburile, iar `~/.maria-bridge/conversations/*.json` are ancora exactă. - -**Atenție:** fiecare caz nou costă un embedding (~7s pe CPU, fără GPU pe LXC 171). Se pun în cache automat în `~/.maria-bridge/calibrare-cache.json`, deci doar prima rulare e lentă. - -### Cum se rulează - -```bash -cd /workspace/romfastsql/proxmox/lxc171-claude-agent/maria-whatsapp-bridge -MARIA_BRIDGE_DIR=$HOME/.maria-bridge ~/.maria-bridge/venv/bin/python ops/calibrate-rank.py -MARIA_BRIDGE_DIR=$HOME/.maria-bridge ~/.maria-bridge/venv/bin/python ops/calibrate-rank.py --sweep -python3 -m pytest -q # 103 teste; venv-ul n-are pytest, se ruleaza cu python3 de sistem -``` - -`--sweep` mătură `RANK_WEAK_COSINE` × `RANK_MIN_RARE_RATIO` (nu mai există prag „strong", vezi §5). - -**Criteriu de acceptare pentru (2):** setul extins rămâne 100% corect *și* cu reparația, *și* fără ea nu e — altfel n-ai dovedit că reparația face ceva. - ---- - -## 4. Fișiere care contează - -| Fișier | De ce | -|---|---| -| `rag/rank.py:168 rare_terms()` | inima problemei: `len(t) >= 4 and df <= 25% din corpus` → „buna" trece | -| `rag/rank.py:212 assess()` | decizia acoperirii; docstring-ul explică de ce nu mai există prag pe cosinus | -| `rag/consumer.py:131 search()` | aici `assess()` primește același query ca regăsirea — punctul de separat | -| `rag/consumer.py:574` | `search_query = fir_mod.interogare(fir, search_query)` — unde se combină | -| `rag/fir.py:140 interogare()` | ancoră + mesaj nou | -| `rag/fir.py:126 extinde_ancora()` | ancora crește cu OCR-ul capturii (adăugat azi) | -| `rag/fir.py` docstring | tabelul cu răspunsurile greșite care justifică existența ancorei — citește-l înainte să scoți ancora din ceva | -| `ops/calibrate-rank.py` | setul de cazuri + `--sweep` | -| `tests/test_rank.py`, `tests/test_fir.py` | testele care prind regresiile | -| `README.md` §„Cum se ordonează rezultatele (rank)" | documentația de actualizat la final | - ---- - -## 5. Ce s-a reparat azi (nu reface) - -Trei comiteri, toate împinse, serviciile repornite, verificate pe viu în grupul „Maria Test". - -**`45b3659`** — două răspunsuri inventate: mesaj vag (cosinus 0,768) și captură cu `errorMessage="CUI cumparator incorect"` (0,778, eroare inexistentă în documente). Cauza: `RANK_STRONG_COSINE=0,70` însemna „cosinusul singur e destul", dar toate chunk-urile eFactura seamănă între ele. Plus `LLM_TEMPERATURE=0` (llama.cpp folosea 0,8). - -**`47110d7`** — două probleme de fond: -- gardul „mesaj prea vag" (`triaj.prea_vag`) cerea text sub 12 cuvinte; mesajul real avea 14. Acum nu mai numără cuvinte și se aplică **după căutare, doar când nu există acoperire**, o singură dată pe fir (`fir["detalii_cerute"]`); -- `fir.este_continuare()` rupea firul la **orice** imagine. Cazul frecvent e tocmai text + captură trimise una după alta. Acum o captură în `FIR_IMAGINE_MIN` (5 min) continuă firul, OCR-ul intră în ancoră, iar pe o escaladare deschisă captura pleacă la aceeași referință; -- **`RANK_STRONG_COSINE` a dispărut de tot.** Motivul e important pentru (2): *cosinusul crește cu lungimea interogării*. Aceeași captură: 0,778 singură, **0,836** împreună cu mesajul de dinainte. Orice prag fix de sus e trecut de o discuție destul de lungă. Acoperirea se sprijină acum exclusiv pe dovada lexicală — adică pe chiar `rare_terms()`, ceea ce face (2) mai important decât părea. - -**`af04d97`** — găsite citind logul: `requests` nu ridică excepție la 4xx/5xx, deci escaladarea se scria `notified: true` chiar când puntea răspundea 503; puntea nu loga nimic la trimitere; mesajele primite se logau tăiate la 80 de caractere fără semn (m-a dus la o concluzie greșită în diagnostic). - ---- - -## 6. Constrângeri de mediu - -- **Modelul de chat e Qwen3.5-2B Q4** pe LXC 104 (`LLM_URL=http://10.0.20.161:8091`). Inventează dacă îl lași. Poarta de acoperire e singura apărare reală — de asta merită reparată bine. -- **Nu încerca un model mai mare pe LXC 171.** Testat azi: `qwen3:4b-instruct` prin Ollama, 4 nuclee, fără GPU → 600s timeout fără niciun token; un prompt de 15 tokeni a scos 6 tokeni în 10 min 20s. Modelul a fost șters. Fără GPU sau API extern (vezi `docs/supliment-glm-zai.md`, neimplementat) nu e altă variantă. -- **Ollama pe 171 are doar `nomic-embed-text`**, pentru embeddings. Un embedding ≈ 7s. -- Servicii: `systemctl --user restart maria-rag maria-whatsapp`; loguri în `~/.maria-bridge/logs/{rag,whatsapp}.log`. -- Grup de test: `120363409761730101@g.us` („Maria Test"), `SUPPORT_JID` e chiar numărul propriu, deci escaladările se văd. -- **Șterge firele vechi înainte de a testa** (`rm -f ~/.maria-bridge/conversations/*.json`), altfel primul mesaj e luat drept continuare a discuției precedente — TTL 120 min. - ---- - -## 7. De reținut, separat de task - -**„CUI cumparator incorect" nu există în niciun document** (`grep -ri "cumparator incorect" ~/.maria-bridge/documents*` → zero). Maria escaladează corect, dar ar putea răspunde. E o eroare eFactura reală și frecventă. N-am scris textul — nu inventez procedură ROA. De pus în `document_store` (Google Drive), de către cineva care știe procedura.