# Brain v2 Extension — Design Doc Status: DRAFT 2026-04-30 Owner: tehnic@finesynergy.eu Companion doc: HIL_MODERATION_DESIGN.md (queue + UI — separat) --- ## Goal Extindem didi-brain să cache-uiască rezultate de inferență pentru **techniques** și **ai_tampered** (la fel ca face deja pentru claims), cu 3 tier-uri de încredere (gold/silver/bronze) și invalidare la schimbare prompt/framework. Atomi gold provin din corecții moderator (vezi HIL doc); silver din LLM cache regular; bronze NU se servește. **Out of scope**: workflow moderare, UI, queue management — vezi HIL_MODERATION_DESIGN.md. --- ## Decizii agreate | # | Decizie | |---|---| | 1 | 1 atom per (componentă × content_hash) — `techniques` separat de `ai_tampered` separat de `claims` | | 2 | Embedding model = ce folosește brain deja (NU adăugăm infra nouă). Brain are deja semantic search + cross-encoder reranking | | 3 | 3 tier-uri: `gold` (human_validated), `silver` (LLM cache neverificat), `bronze` (pending review, nu se servește) | | 4 | Write trigger: silver pe LLM run (fire-and-forget); gold pe PATCH cu human_validated=true; bronze pe LLM run cu confidence < 60 | | 5 | Confidence threshold pe write — sub 60, NU se scrie silver (lasă miss → re-try cu poate model premium) | | 6 | Prompt versioning: gold supraviețuiește la schimbare prompt; silver e invalidat | --- ## Architecture ``` ┌──────────────────────────────────────────────────────────────────┐ │ agent-v3 (techniques/ai-tampered/claims executors) │ │ │ │ 1. Pre-LLM: │ │ POST http://10.11.10.13:8090/v1/analysis_atom/lookup │ │ body: { content_hash, content?, component, tier, │ │ prompt_hash, framework_version } │ │ │ │ 2. Brain răspunde: │ │ { │ │ hit: true | false, │ │ atom: { tier, result_processed, ... } | null, │ │ staleness: 'fresh' | 'stale_prompt' | 'stale_framework', │ │ hit_count_incremented: true │ │ } │ │ │ │ 3. Branch logic: │ │ - tier=gold + fresh → return cached, skip LLM │ │ - tier=silver + fresh → return cached, skip LLM │ │ - tier=silver + stale_prompt → run LLM (prompt changed) │ │ - miss / bronze → run LLM normal │ │ │ │ 4. Post-LLM (miss path, fire-and-forget): │ │ POST /v1/analysis_atom │ │ body: { ..., tier: 'silver' | 'bronze' (if conf<60) } │ │ │ │ 5. Post-moderation (HIL flow): │ │ PATCH /v1/analysis_atom/:atom_id │ │ body: { tier: 'gold', human_validated: true, │ │ human_corrections: {...}, validator_user_id } │ └──────────────────────────────────────────────────────────────────┘ ``` --- ## Data Model (în PG-ul brain, NU în DIDI cluster) ### Tabel nou `analysis_atom` ```sql -- Migration brain-side: analysis_atom_v1.sql CREATE EXTENSION IF NOT EXISTS vector; CREATE TABLE analysis_atom ( atom_id BIGSERIAL PRIMARY KEY, content_hash TEXT NOT NULL, content_embedding vector(1024), -- ajustat la dimensiunea modelului brain (poate fi 768/1024/3072) content_preview TEXT, -- first 200 chars pentru debug, NU full text component TEXT NOT NULL CHECK (component IN ('techniques', 'ai_tampered', 'claims')), tier TEXT NOT NULL CHECK (tier IN ('free', 'premium')), prompt_hash TEXT NOT NULL, framework_version TEXT NOT NULL, model_used TEXT, -- ex: 'qwen35:Qwen3.5-397B-A17B' sau 'openrouter:google/gemini-3-flash-preview' result_processed JSONB NOT NULL, -- mapped la canonical types din agent-v3 (TechniquesResult, AITamperedResult etc.) result_raw JSONB, -- raw LLM output (pentru re-mapping ulterior dacă schimba schema) cache_tier TEXT NOT NULL DEFAULT 'silver' CHECK (cache_tier IN ('gold', 'silver', 'bronze')), human_validated BOOLEAN NOT NULL DEFAULT false, human_corrections JSONB, -- diff din moderation (vezi HIL doc) validator_user_id TEXT, validated_at TIMESTAMP, hit_count INTEGER NOT NULL DEFAULT 0, last_hit_at TIMESTAMP, llm_confidence NUMERIC, -- 0-100, folosit pentru bronze decision created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, expires_at TIMESTAMP, -- TTL 90 zile pentru silver/bronze, NULL pentru gold UNIQUE (content_hash, component, tier, prompt_hash) ); -- Indexuri CREATE INDEX idx_analysis_atom_lookup ON analysis_atom(content_hash, component, tier); CREATE INDEX idx_analysis_atom_embedding ON analysis_atom USING ivfflat (content_embedding vector_cosine_ops) WITH (lists = 100); CREATE INDEX idx_analysis_atom_gold ON analysis_atom(component, cache_tier) WHERE cache_tier = 'gold'; CREATE INDEX idx_analysis_atom_expires ON analysis_atom(expires_at) WHERE expires_at IS NOT NULL; -- Trigger pentru updated_at CREATE TRIGGER trigger_analysis_atom_updated_at BEFORE UPDATE ON analysis_atom FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); -- TTL job (pg_cron sau external cleanup zilnic) -- DELETE FROM analysis_atom WHERE expires_at < NOW() AND cache_tier != 'gold'; ``` ### `result_processed` shapes — exemple per component **Pentru `component='techniques'`** (schema TechniquesResult din agent-v3): ```json { "manipulation_score": 65, "total_severity": 23.4, "dimensions_affected": ["emotional_appeal", "loaded_language"], "techniques_count": 4, "techniques_detected": [ { "code": "loaded_language", "intensity": 3, "evidence": "...", "severity": 7.2 } ], "coupling_context": null, "screening_duration_ms": 1200, "deep_analysis_duration_ms": 3400 } ``` **Pentru `component='ai_tampered'`**: ```json { "ai_probability": 78, "verdict": "LIKELY_AI", "risk_score": 65, "categories_affected": ["text_patterns"], "indicators_count": 6, "disclosure_detected": false, "indicators_detected": [...] } ``` **Pentru `component='claims'`** (păstrăm compatibilitatea cu schema existentă verification_cache): - Minimal: 1 row în `analysis_atom` cu component='claims' acoperă **TOATE claim-urile** din analiza acelui content (rezultatul agregat) - SAU: continuăm pe schema veche `verification_cache` per claim individual + un `analysis_atom` agregator - **Recomandare**: păstrăm `verification_cache` per-claim cum e (atomar, granular) + adăugăm `analysis_atom` ca agregator opțional pentru "claims output" (rezultat final post-LLM-extraction) --- ## Cache Tier Logic ### Write rules ``` Trigger: post LLM run în executor Input: { result, component, tier, content, prompt_hash, framework_version, llm_confidence } if (component === 'claims') { // Claims continuă să folosească verification_cache existent (nu schimbăm) // Plus adăugăm un atom agregator pentru "claims output as a whole" } confidence = result.confidence ?? estimateConfidence(result); if (confidence < 60) { cache_tier = 'bronze'; // se scrie pentru audit/debug, NU se servește } else { cache_tier = 'silver'; // se scrie și se servește } POST /v1/analysis_atom { ..., cache_tier, human_validated: false } ``` ### Lookup rules (priority order) ``` 1. Match exact pe (content_hash, component, tier, prompt_hash) → if found: check tier - gold + fresh → SERVE - silver + fresh → SERVE - bronze → IGNORE (treat as miss) - silver + stale (prompt_hash diferă față de current) → IGNORE pe stale_prompt 2. Match semantic pe content_embedding cu cosine_distance < 0.08 AND component=X AND tier=Y AND cache_tier='gold' AND human_validated=true → if found: SERVE (gold supraviețuiește prompt change pentru că răspunsul corect nu depinde de prompt) 3. Match semantic pe embedding < 0.08 AND cache_tier='silver' AND prompt_hash matches → if found: SERVE (silver semantic, dar doar pe prompt actual) 4. Miss → return { hit: false } ``` ### Promotion rules ``` silver → gold: când moderator face PATCH cu human_validated=true + human_corrections=null (mod a aprobat fără modificări) SAU human_corrections!=null (mod a corectat → result_processed actualizat) bronze → silver: dacă același content e re-analizat și produce confidence ≥ 60 → atom-ul vechi bronze e șters, se scrie silver nou gold → expires never: gold rămâne forever (sau până prompt schimbă fundamental schema, atunci marcaj manual) ``` --- ## API Endpoints (brain side) ### `POST /v1/analysis_atom/lookup` Request: ```json { "content": "text full optional pentru semantic search", "content_hash": "sha256:abc123...", "component": "techniques", "tier": "free", "prompt_hash": "p_v3_2026_03_15", "framework_version": "fw_v1_2026_03", "allow_semantic_match": true, "semantic_threshold": 0.08 } ``` Response: ```json { "hit": true, "atom": { "atom_id": 4242, "cache_tier": "gold", "human_validated": true, "result_processed": { ... }, "model_used": "qwen35:Qwen3.5-397B-A17B", "validator_user_id": "admin-123", "validated_at": "2026-04-15T12:34:56Z", "hit_count": 17 }, "staleness": "fresh", "match_type": "exact" // sau "semantic" } ``` Sau pe miss: ```json { "hit": false, "atom": null, "staleness": null } ``` ### `POST /v1/analysis_atom` Request: ```json { "content": "full text pentru embedding compute", "content_hash": "sha256:abc123...", "content_preview": "first 200 chars...", "component": "techniques", "tier": "free", "prompt_hash": "p_v3_2026_03_15", "framework_version": "fw_v1_2026_03", "model_used": "qwen35:Qwen3.5-397B-A17B", "result_processed": { ... }, "result_raw": { ... }, "llm_confidence": 78, "cache_tier": "silver" // calculat client-side din confidence } ``` Response: ```json { "success": true, "atom_id": 4243, "cache_tier": "silver" } ``` Idempotent: dacă există atom pentru același (content_hash, component, tier, prompt_hash), face UPDATE (sau ignoră dacă cache_tier=gold — nu suprascriu gold cu silver). ### `PATCH /v1/analysis_atom/:atom_id` Request: ```json { "human_validated": true, "human_corrections": { "verdict": {...}, "techniques": {...} }, "validator_user_id": "admin-123", "result_processed": { ... corrected version ... }, "cache_tier": "gold" } ``` Response: ```json { "success": true, "atom_id": 4242, "cache_tier": "gold", "expires_at": null } ``` ### `GET /v1/analysis_atom/stats` Response: ```json { "total_atoms": 12345, "by_tier": { "gold": 234, "silver": 11000, "bronze": 1111 }, "by_component": { "techniques": 4500, "ai_tampered": 4200, "claims": 3645 }, "hit_rate_24h": 0.42, "writes_24h": 850, "promotions_24h": { "silver_to_gold": 12, "bronze_to_silver": 3 } } ``` Folosit de admin dashboard pentru tracking. --- ## Modificări în agent-v3 ### 1. `shared/brain/client.ts` (extindere) ```typescript // Pseudo-cod, nu pentru implementare directă // Existent: gatherFromBrain, writeVerificationCacheAsync (pentru claims) // NOU: export interface AnalysisAtomLookupResult { hit: boolean; atom: { atom_id: number; cache_tier: 'gold' | 'silver' | 'bronze'; human_validated: boolean; result_processed: any; hit_count: number; } | null; staleness: 'fresh' | 'stale_prompt' | 'stale_framework' | null; } export async function lookupAnalysisAtom(params: { content: string; contentHash: string; component: 'techniques' | 'ai_tampered' | 'claims'; tier: 'free' | 'premium'; promptHash: string; frameworkVersion: string; }): Promise { if (!process.env.DIDI_BRAIN_URL) return null; try { const res = await fetch(`${process.env.DIDI_BRAIN_URL}/v1/analysis_atom/lookup`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({...}), signal: AbortSignal.timeout(2000), // brain MUST respond fast }); return await res.json(); } catch { return null; // fail open — fallback la LLM } } export function writeAnalysisAtomAsync(params: { content: string; contentHash: string; component: 'techniques' | 'ai_tampered' | 'claims'; tier: 'free' | 'premium'; promptHash: string; frameworkVersion: string; modelUsed: string; resultProcessed: any; resultRaw: any; llmConfidence: number; }) { // Fire-and-forget, nu așteaptă răspuns const cacheTier = params.llmConfidence < 60 ? 'bronze' : 'silver'; fetch(`${process.env.DIDI_BRAIN_URL}/v1/analysis_atom`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...params, cacheTier }), signal: AbortSignal.timeout(5000), }).catch(() => {}); // log warn, ignore } export async function patchAnalysisAtomGold(params: { atomId: number; validatorUserId: string; humanCorrections: any | null; resultProcessed: any; }) { // Apelat din moderation/queue-manager.ts după resolve ... } ``` ### 2. `components/techniques/executor.ts` (modificare) ```typescript // La începutul execute(): const promptHash = computePromptHash(systemPrompt, userTemplate); const frameworkVersion = computeFrameworkVersion(); const contentHash = sha256(input.text).slice(0, 16); const lookup = await lookupAnalysisAtom({ content: input.text, contentHash, component: 'techniques', tier: searchTier, promptHash, frameworkVersion, }); if (lookup?.hit && lookup.atom) { if (lookup.atom.cache_tier === 'gold' || (lookup.atom.cache_tier === 'silver' && lookup.staleness === 'fresh')) { // Return cached, skip LLM return { ...lookup.atom.result_processed, _cache_hit: true, _cache_tier: lookup.atom.cache_tier, }; } } // Miss path — run LLM normal: const result = await runLLMScreeningAndDeepAnalysis(...); // Fire-and-forget write back writeAnalysisAtomAsync({ content: input.text, contentHash, component: 'techniques', tier: searchTier, promptHash, frameworkVersion, modelUsed: result.llm_screening, resultProcessed: result, resultRaw: result._raw, llmConfidence: estimateConfidence(result), }); return result; ``` ### 3. `components/ai-tampered/executor.ts` (modificare) Identic cu techniques. Schimbă `component: 'ai_tampered'`. ### 4. `components/claims/executor.ts` (NU modifica) Claims continuă pe `verification_cache` existent (granular per-claim). Eventual adăugăm `analysis_atom` agregator în Faza 2 doar dacă măsurăm și hit rate scăzut pe claims output ca whole. ### 5. `api/moderation-routes.ts` resolve handler (din Doc A, dar dependent de brain) ```typescript // În handler-ul PUT /api/v3/moderation/queue/:queueId/resolve: if (action === 'corrected') { // ... salvăm corecții în analysis_session ... // Pentru fiecare componentă afectată în human_corrections, găsim atom-ul corespunzător și PATCH gold for (const component of ['techniques', 'ai_tampered', 'claims']) { if (corrections[component]) { const atomLookup = await lookupAnalysisAtom({...}); if (atomLookup?.atom) { await patchAnalysisAtomGold({ atomId: atomLookup.atom.atom_id, validatorUserId: userId, humanCorrections: corrections[component], resultProcessed: applyCorrections(atomLookup.atom.result_processed, corrections[component]), }); } } } } if (action === 'approved') { // Userul a aprobat fără modificări → ridicăm silver la gold // PATCH cu human_corrections=null ... } ``` --- ## Bootstrap Strategy Brain pornește gol pe atomi. Pentru a accelera hit rate: ### Opțiunea A — re-rulare istoric (recomandată) Avem ~thousands de sesiuni completate în `bos_analysis.analysis_session`. Putem face un script one-time: ``` 1. SELECT session_id, input_text, techniques (JSONB), ai_tampered (JSONB) FROM analysis_session WHERE status='completed' AND created_at > '2026-01-01' 2. Pentru fiecare row, calculează content_hash + apel POST /v1/analysis_atom cu cache_tier='silver', human_validated=false 3. Skip dacă confidence < 60 (sau marcaj bronze) ``` Hit rate jump: 0% → ~25-35% peste noapte. ### Opțiunea B — start gol, learn organic Hit rate creste lent (5% săpt 1 → 25% săpt 4). Mai puțin risc de a propaga erori vechi. **Recomandare**: Opțiunea A doar pentru sesiuni completate ≥ 90 zile (probabilitate mare că nu au erori), iar pentru ultimele 90 zile — start gol și lasă organic. --- ## Embedding Model Brain folosește deja embedding pentru semantic search (vezi `/v1/search` cu cross-encoder reranking). NU schimbăm modelul. Dimensiunea vector field în PG (`vector(1024)` în schema) trebuie aliniată cu modelul actual al brain. **Action item**: Înainte de migration, query brain `SELECT vector_dims(embedding) FROM atom LIMIT 1` (sau echivalent) ca să confirmăm dimensiunea exactă. --- ## Cost & Latency Impact ### Cost Cu hit rate 50% după 3 luni, cost LLM scade ~40-50%. Pentru analiză text simplă premium: - Înainte: $0.05/analiză (techniques + ai + claims + verdict) - După (50% hits): $0.025-0.030/analiză amortizat Brain operating cost neglijabil — postgres + pgvector pe mașina existentă (10.11.10.13). Singura cost suplimentar: storage atomi (~ 5KB per atom, 20K atomi/lună = 100MB). ### Latency | Scenariu | Înainte | După | |---|---|---| | Cache HIT exact | n/a | ~80-150ms (brain lookup + serialize) | | Cache HIT semantic | n/a | ~120-250ms (vector search adaugat) | | Cache MISS | 5-15s | 5-15s + 100ms (lookup overhead pe miss) | Lookup overhead pe miss path = ~100ms cost net. Acceptabil dacă hit rate ≥ 30%. --- ## Rollout Plan ### Faza 1 — Brain schema + endpoints (2 zile) - [ ] Migration brain-side (`analysis_atom` tabel + indexuri) - [ ] Endpoint-uri `/v1/analysis_atom/lookup`, POST, PATCH, GET stats - [ ] Test cu Postman: write silver, read back, write gold, lookup match exact + semantic - [ ] Deploy brain v2 pe staging-ul brain (separate environment dacă există) ### Faza 2 — agent-v3 client integration (2 zile) - [ ] Extindere `shared/brain/client.ts` cu `lookupAnalysisAtom`, `writeAnalysisAtomAsync`, `patchAnalysisAtomGold` - [ ] Modificare `techniques/executor.ts` cu lookup + write back - [ ] Modificare `ai-tampered/executor.ts` la fel - [ ] Feature flag: `BRAIN_ATOMS_ENABLED=false` default. Activate per env var pentru testing - [ ] Test E2E: rulează 2x aceeași analiză → a doua vine din cache silver ### Faza 3 — HIL integration (depinde de Doc A) - [ ] Modificare `moderation-routes.ts` resolve handler să cheme `patchAnalysisAtomGold` - [ ] UI badge "Verified by analyst" în extensie + dashboard pentru sesiuni cu atom gold ### Faza 4 — Bootstrap (1 zi) - [ ] Script `scripts/bootstrap-brain-from-history.ts` pentru re-import sesiuni vechi - [ ] Run pe sesiuni completate înainte de `now() - 90 days` - [ ] Monitor hit rate creste ### Faza 5 — Monitoring + tuning (continuous) - [ ] Dashboard Grafana: hit rate per component, gold/silver ratio, drift detection - [ ] Alertă: hit rate scade brusc → cineva a schimbat prompt - [ ] Alertă: bronze ratio crește → modelul produce confidence scăzut, problemă de calitate --- ## Open Questions 1. **Repo brain location**: unde e fizic codul brain? Pe ce mașină rulează? Cum se face deploy? (necesar pentru Faza 1) 2. **Brain stack**: e FastAPI + Python? Folosește SQLAlchemy/asyncpg? Pgvector deja instalat? (necesar pentru migration) 3. **Embedding model exact**: Qwen embed local sau alt model? Dimensiune exactă vector? (necesar pentru `vector(N)` în schema) 4. **Cross-encoder reranking**: brain face deja reranking pe `/v1/search`. Aplicăm același reranking și pe `/v1/analysis_atom/lookup` semantic match? Sau e overkill (lookup-ul trebuie să fie fast)? 5. **Atom expiration**: TTL 90 zile pentru silver/bronze. Gold = forever. Acceptabil sau prea generos? Putem pune 30 zile silver pentru a forța regenerare cu modele mai noi. --- ## Risk Register | Risc | Probabilitate | Impact | Mitigare | |---|---|---|---| | Brain endpoint slow (>500ms) → analize globale lente | Medie | Mare | AbortSignal.timeout(2000) pe lookup. Dacă brain nu răspunde în 2s, fail open la LLM | | Embedding drift (model schimbat în brain) | Mică | Mare | Versionare strict embedding_model_id în atom; la schimbare → invalidate tot | | Storage atomi explodează | Mică | Mediu | TTL 90 zile + cleanup zilnic; gold păstrat forever (volum mic) | | Race condition write atom (2 agenți scriu același content_hash) | Medie | Mic | UPSERT pe UNIQUE constraint (content_hash, component, tier, prompt_hash) | | Mock human_corrections invalid (mod scrie diff care nu se aplică) | Mică | Mare | Validare schema diff în resolve handler; dacă invalid, aprobă fără gold write | | Brain v2 endpoint break compat cu brain v1 (claims) | Mică | Mare | NU modificăm `verification_cache` table sau `/v1/gather` endpoint. analysis_atom e separat | --- ## Metrice cheie - `brain_lookup_total{component, tier, hit}` — counter - `brain_lookup_latency_ms{component, hit}` — histogram - `brain_atoms_count{component, tier, cache_tier}` — gauge (din /v1/analysis_atom/stats) - `brain_writes_total{component, cache_tier}` — counter - `brain_promotions_total{from, to}` — counter - `analyses_with_cache_hit_ratio` — derived: hit_total / lookup_total - `analyses_with_gold_ratio` — derived: gold_hits / hit_total Toate scrise via /metrics endpoint pe agent-v3 + brain. --- ## Dependencies cu Doc A Brain v2 funcționează STANDALONE pe path-ul silver (LLM cache) FĂRĂ HIL. Beneficiile inițiale (cost reduction 30%) vin de aici, nu din gold. Gold path (HIL → brain) e benefic doar după ce moderatorii produc atomi gold (~200/lună la 1-2 mod). Beneficiul gold = quality lift + badge "Verified", nu cost reduction. **Concluzie**: putem deploy Brain v2 înainte de a avea HIL complet. HIL doar adaugă layer gold deasupra.