MnemoCore / REVIEW_v4.5.0-beta.md
Granis87's picture
Upload folder using huggingface_hub
7c8b011 verified

MnemoCore v4.5.0-beta — Code Review

Reviewer: Omega (GLM-5)
Datum: 2026-02-20 07:45 CET
Scope: Full kodbas, fokus på query/store-flödet


🚨 KRITISKA PROBLEMER (Blockers)

1. Query Returnerar 0 Resultat 🔴 BLOCKER

Symptom: POST /query returnerar tom lista även efter framgångsrik POST /store

Root Cause Analysis:

1.1 HNSW Index Manager — Position Mapping Bug

Fil: hnsw_index.py:221-236

def _position_to_node_id(self, position: int) -> Optional[str]:
    """Map HNSW sequential position back to node_id."""
    if not hasattr(self, "_position_map"):
        object.__setattr__(self, "_position_map", {})
    pm: Dict[int, str] = self._position_map

    # Rebuild position map if needed (after index rebuild)
    if len(pm) < len(self._id_map):
        pm.clear()
        for pos, (fid, nid) in enumerate(
            sorted(self._id_map.items(), key=lambda x: x[0])
        ):
            pm[pos] = nid

    return pm.get(position)

PROBLEM: Position map bygger på sorted(_id_map.items(), key=lambda x: x[0]) vilket sorterar efter FAISS ID (int), inte efter insättningsordning. HNSW returnerar positioner baserat på insättningsordning, men mappningen är inkonsekvent.

Fix:

# Behåll insättningsordning separat
def add(self, node_id: str, hdv_data: np.ndarray) -> None:
    # ... existing code ...
    self._insertion_order.append(node_id)  # NY

def _position_to_node_id(self, position: int) -> Optional[str]:
    if position < len(self._insertion_order):
        return self._insertion_order[position]
    return None

1.2 TextEncoder — Token Normalization Inkonsekvens

Fil: binary_hdv.py:339-342

def encode(self, text: str) -> BinaryHDV:
    tokens = text.lower().split()  # <-- BARA whitespace split
    if not tokens:
        return BinaryHDV.random(self.dimension)

PROBLEM: Query-text vs lagrad text kan ha olika tokenisering:

  • "Hello World" → tokens: ["hello", "world"]
  • "Hello, World!" → tokens: ["hello,", "world!"] ← olika token!

Fix:

import re

def encode(self, text: str) -> BinaryHDV:
    # Konsekvent tokenisering
    tokens = re.findall(r'\b\w+\b', text.lower())
    if not tokens:
        return BinaryHDV.random(self.dimension)

1.3 HNSW Upgrade Threshold Race Condition

Fil: hnsw_index.py:87-117

def _maybe_upgrade_to_hnsw(self) -> None:
    if len(self._id_map) < FLAT_THRESHOLD:  # 256
        return

    # ... existing code ...
    existing: List[Tuple[int, np.ndarray]] = []
    for fid, node_id in self._id_map.items():
        if node_id in self._vector_cache:
            existing.append((fid, self._vector_cache[node_id]))

PROBLEM: _vector_cache används bara vid HNSW-upgrade, men vid normal flat-index-användning cachas inte vektorer. Vid upgrade saknas data.

Fix: Alltid cacha vektorer:

def add(self, node_id: str, hdv_data: np.ndarray) -> None:
    # ... existing code ...
    self._vector_cache[node_id] = hdv_data.copy()  # ALLTID, inte bara HNSW

2. Qdrant Vector Unpacking Mismatch 🔴 HIGH

Fil: tier_manager.py:387-392 + qdrant_store.py

# Vid save till Qdrant (tier_manager.py):
bits = np.unpackbits(node.hdv.data)
vector = bits.astype(float).tolist()  # 16,384 floats

# Vid search från Qdrant (qdrant_store.py):
arr = np.array(vec_data) > 0.5
packed = np.packbits(arr.astype(np.uint8))

PROBLEM: Qdrant använder COSINE distance för HOT och MANHATTAN för WARM, men BinaryHDV använder HAMMING distance. Similarity scores kan vara inkompatibla.

Konfiguration (config.yaml):

qdrant:
  collection_hot:
    distance: COSINE  # ← Fel för binary vectors!
  collection_warm:
    distance: MANHATTAN  # ← Också suboptimalt

Fix: Använd Distance.DOT för binary vectors med normaliserad similarity.


3. FAISS Binary HNSW — Inte Fullt Implementerat 🔴 HIGH

Fil: hnsw_index.py:59-66

def _build_hnsw_index(self, existing_nodes: Optional[List[Tuple[int, np.ndarray]]] = None) -> None:
    hnsw = faiss.IndexBinaryHNSW(self.dimension, self.m)
    hnsw.hnsw.efConstruction = self.ef_construction
    hnsw.hnsw.efSearch = self.ef_search

PROBLEM: IndexBinaryHNSW saknar IndexIDMap-stöd. Koden försöker hantera detta med _position_map, men detta är skört vid:

  • Delete + re-add
  • Concurrent access
  • Index rebuilds

Risk: Position mapping kan bli desynkroniserad → query returnerar fel IDs eller inga resultat.


⚠️ HÖGA RISKER (High Priority)

4. Demotion Race Condition 🟠

Fil: tier_manager.py:175-220

async def get_memory(self, node_id: str) -> Optional[MemoryNode]:
    demote_candidate = None
    result_node = None

    async with self.lock:
        if node_id in self.hot:
            node = self.hot[node_id]
            node.access()
            
            if self._should_demote(node):
                node.tier = "warm"  # Markerar som warm
                demote_candidate = node
            
            result_node = node

    # I/O OUTSIDE LOCK — gap där annan tråd kan försöka access
    if demote_candidate:
        await self._save_to_warm(demote_candidate)  # Kan misslyckas
        
        async with self.lock:
            if demote_candidate.id in self.hot:
                del self.hot[demote_candidate.id]  # Nu borta

PROBLEM: Tidsfönster mellan "mark as warm" och "delete from hot" där:

  • get_memory() kan returnera samma node twice
  • Query kan missa noden under övergången

5. Subconscious AI — Infinite Loop Risk 🟠

Fil: subconscious_ai.py (inte granskad fullt, men config visar risk)

subconscious_ai:
  enabled: false  # BETA - bra att den är avstängd
  pulse_interval_seconds: 120
  rate_limit_per_hour: 50
  max_memories_per_cycle: 10

Risk: Om micro_self_improvement_enabled: true kan systemet gå in i självförbättringsspiraler.


6. Memory Leak i _vector_cache 🟠

Fil: hnsw_index.py:107

@property
def _vector_cache(self) -> Dict[str, np.ndarray]:
    if not hasattr(self, "_vcache"):
        object.__setattr__(self, "_vcache", {})
    return self._vcache

PROBLEM: _vector_cache växer obegränsat. Ingen cleanup vid delete eller consolidation.

Fix:

def remove(self, node_id: str) -> None:
    # ... existing code ...
    self._vector_cache.pop(node_id, None)  # Finns redan, men verifiera

📊 PRESTANDA & SKALBARHET

7. O(N) Linear Search Fallback 🟡

Fil: tier_manager.py:902+

När HNSW inte är tillgängligt (FAISS ej installerat), faller systemet tillbaka till:

def _linear_search_hot(self, query_vec: BinaryHDV, top_k: int) -> List[Tuple[str, float]]:
    # Inte visad i filen, men nämnd som fallback

Prestandaimpakt:

  • 2,000 memories (HOT max): ~4ms
  • 10,000 memories: ~20ms
  • 100,000 memories: ~200ms ← Ej acceptabelt för real-time query

8. Qdrant Batch Operations Saknas 🟡

Fil: qdrant_store.py

async def upsert(self, collection: str, points: List[models.PointStruct]):
    await qdrant_breaker.call(
        self.client.upsert, collection_name=collection, points=points
    )

PROBLEM: Consolidation (consolidate_warm_to_cold) gör en-at-a-time deletes istället för batch:

# tier_manager.py:750
if ids_to_delete:
    await self.qdrant.delete(collection, ids_to_delete)  # Bra!

Men list_warm() och search() saknar pagination-optimering.


🏗️ ARKITEKTUR & DESIGN

9. Dependency Injection — Halvvägs 🟡

Status: Singeltons borttagna, men inte fullt DI

Gott:

  • HAIMEngine(config=..., tier_manager=...) stöder injection
  • Container pattern i container.py

Dåligt:

  • get_config() är fortfarande global
  • BinaryHDV.random() använder global np.random

Rekommendation:

class BinaryHDV:
    def __init__(self, data: np.ndarray, dimension: int, rng: Optional[np.random.Generator] = None):
        self._rng = rng or np.random.default_rng()

10. Error Handling — Inkonsekvent 🟡

Filer: Spridda

Vissa funktioner returnerar None:

async def get_memory(self, node_id: str) -> Optional[MemoryNode]:
    # Returnerar None om ej hittad

Andra kastar exceptions:

async def delete_memory(self, node_id: str):
    if not node:
        raise MemoryNotFoundError(node_id)

Rekommendation: Konsekvent mönster:

  • get_* → return Optional[T] (None = not found)
  • *_or_raise → raise exception
  • delete_* → return bool (deleted or not)

🔒 SÄKERHET & ROBUSTHET

11. API Key i Env Var — Bra

Fil: api/main.py:81

security = config.security if config else None
expected_key = (security.api_key if security else None) or os.getenv("HAIM_API_KEY", "")

Gott: API key måste sättas explicit, fallback till env var.


12. Rate Limiting — Implementerat

Fil: api/middleware.py

class QueryRateLimiter(RateLimiter):
    def __init__(self):
        super().__init__(requests=500, window_seconds=60)  # 500/min

Gott: Separate limits för store/query/concept/analogy.


13. Input Validation — Svag 🟡

Fil: api/models.py

class StoreRequest(BaseModel):
    content: str = Field(..., min_length=1, max_length=100000)
    metadata: Optional[Dict[str, Any]] = None

PROBLEM: Ingen validering av metadata-innehåll. Kan innehålla:

  • Ogiltiga UTF-8 characters
  • Recursive structures
  • Sensitive data leaks

Fix:

from pydantic import field_validator

class StoreRequest(BaseModel):
    @field_validator('metadata')
    @classmethod
    def validate_metadata(cls, v):
        if v and len(str(v)) > 10000:  # Max 10KB metadata
            raise ValueError('Metadata too large')
        return v

📝 KODKVALITET

14. Test Coverage — 39 Passing

Fil: test_regression_output.txt

39 passed, 5 warnings in 3.47s

Gott: Alla tester passerar. Men:

  • Inga tester för HNSW upgrade path
  • Inga tester för concurrent access
  • Inga tester för Qdrant integration (kräver live Qdrant)

15. Documentation — Komplett

Filer: README.md (43KB), CHANGELOG.md, inline docs

Gott: Dokumentation är omfattande och uppdaterad.


🎯 PRIORITERAD FIX-LISTA

Prioritet Problem Fil Estimerad tid
🔴 P0 Position mapping bug hnsw_index.py 2h
🔴 P0 Token normalization binary_hdv.py 30min
🔴 P0 Vector cache vid upgrade hnsw_index.py 1h
🟠 P1 Qdrant distance mismatch config.yaml + qdrant_store.py 2h
🟠 P1 Demotion race condition tier_manager.py 3h
🟡 P2 Linear search fallback tier_manager.py 4h
🟡 P2 Memory leak _vector_cache hnsw_index.py 30min

🔧 REKOMMENDERAD ACTION PLAN

Fas 1: Query Fix (Dag 1)

  1. Fixa _position_to_node_id() — Använd insättningsordning istället för sorted IDs
  2. Fixa TextEncoder.encode() — Konsekvent tokenisering med regex
  3. Alltid cacha vektorer — Ta bort conditional _vector_cache

Fas 2: Qdrant Alignment (Dag 2)

  1. Ändra distance metricDistance.DOT för binary vectors
  2. Verifiera vector unpacking — Säkerställ 16,384 → 2,048 byte mapping

Fas 3: Hardening (Dag 3)

  1. Lägg till HNSW upgrade tester
  2. Fixa demotion race condition
  3. Input validation för metadata

📋 SUMMARY

Total kod: ~25,000 LOC (src/) Tester: 39 passing Kritiska buggar: 3 Höga risker: 4 Medel risker: 5

Verdict: v4.5.0-beta är inte production-ready. Query-flödet har 3 kritiska buggar som förhindrar korrekt retrieval. Arkitekturen är solid, men implementationen av HNSW/index-mapping behöver omskrivas.

Rekommendation:

  1. Omedelbart fixa P0-issues (4-5 timmars arbete)
  2. Kör regression tests
  3. Deploy till staging för validering
  4. Sätt Opus 4.6 + Gemini 3.1 på Fas 1-3

Review genererad av Omega (GLM-5) för Robin Granberg Senast uppdaterad: 2026-02-20 07:45 CET