Phase 2.5 provider generalization

Redis SHA-256 content-hash → vector cache sitting in front of whichever EmbeddingBackend is active. Backend-agnostic; exact-match cache for identical text (same model + dim). Ops target >80% hit rate on repetitive memory embeddings.

Milestone 2.5.G4.M4 — Content-Hash Embedding Cache

Status: Completed (2026-08-24)
Goal: Track D — Pluggable Embedding Service
Phase: 2.5 — Provider Generalization & Foundation
Estimated effort: 1 day


Why This Milestone Exists

Embedding the same text repeatedly is expensive (GPU time or API cost) and unnecessary. A content-hash cache sits in front of whichever EmbeddingBackend is active — backend-agnostic, no logic duplication per backend.

Memory content is often highly repetitive — the same user preference, factual statement, or procedural note gets re-embedded each time it's updated or re-ranked. A >80% hit rate target is an ops SLO (not a CI gate) from Prometheus hit/(hit+miss).


Non-Goals

  • Semantic deduplication (separate concern in Phase 3 memory dedup)
  • Explicit cache flush on model upgrade (model_id + dim in the digest invalidate naturally)
  • Go Redis client in packages/embedder (Python service owns inference + cache)

Advancement vs early sketch: org-scoped keys were originally listed as a non-goal. Shipped code does isolate by org_id because embedding vectors are tenant data (inversion risk) and Redis tenancy rules require {org_id}:… prefixes.


What shipped

  • CachingEmbeddingBackend decorator over any backend; ABC embed(texts) unchanged
  • Digest: sha256(u32be(len(model_id)) || utf8(model_id) || u32be(dim) || u32be(len(text)) || text)
  • Redis key: {org_id}:embed:v1:{hex} (not sketch embed:{model}:{hash})
  • POST /v1/embed requires org_id UUID; ContextVar bridges into the decorator
  • Fail-open on Redis errors; fail-closed startup when cache enabled without Redis / PING fail
  • Env: IBEX_EMBEDDING_CACHE_* (default enabled=false), REDIS_URL fallback
  • Metrics: ibex_embedder_cache_requests_total, GET /metrics
  • Unit + real Redis CI (redis:7 on embedder-test); cross-tenant covered

Package layout: services/embedder/app/cache/{keys,redis_store,backend,metrics,env,context}.py

Success signals

  • Decorator wraps any EmbeddingBackend, transparent to callers of embed(texts)
  • Cache key uses SHA-256 content address (model + dim + text); no text in keys
  • Cache hit: no call to underlying backend
  • Cache miss: underlying backend called, result stored with TTL
  • Batch requests: mixed hit/miss via single MGET + one pipeline SET EX
  • TTL via IBEX_EMBEDDING_CACHE_TTL_SECONDS (default 86400)
  • Prometheus hit/miss counters (labels: backend, result=hit|miss)
  • Unit + real Redis tests; Redis failure does not fail the embedding request
  • Org isolation tested (org B does not hit org A’s keys)

Prerequisites

  • 2.5.G4.M1 (Embedder interface) — EmbeddingBackend ABC
  • 2.5.G4.M2 or 2.5.G4.M3 — at least one real backend to wrap
Edit on GitHub

Last updated on

On this page

0%