Multi-tenancy¶
Eagle-RAG isolates data on two axes:
| Axis | Identifier | Mechanism |
|---|---|---|
| Domain | plugin_namespace |
Physical Milvus Database + PostgreSQL repository filter (deploy-time) |
| Knowledge base | kb_name |
Scalar filter inside one domain Database (request-time) |
A single process binds one domain (settings.plugins.default_namespace). Within that domain, many KBs (finance, patent, pharma, …) share base collections and are separated by kb_name. Cross-domain retrieval uses multiple instances, not Core fan-out.
Canonical detail: Plugin architecture. Decisions: ADR-001, ADR-002.
Theory and foundations¶
Multi-tenant RAG patterns¶
Gao et al., 2023 identifies tenant isolation as a production concern: retrieval must never return another tenant's chunks.
| Pattern | Isolation mechanism | Eagle-RAG |
|---|---|---|
| Separate vector Database per domain | Physical Milvus DB | Chosen for plugin_namespace |
| Separate collection per KB | Logical sprawl | Not used for KBs |
| Shared collection + metadata filter | kb_name scalar on every ANN |
Chosen for KBs inside a domain DB |
| Separate deployment per domain | Full stack duplication | Chosen for multi-industry (one instance each) |
Milvus scalar filtering with inverted indexes (Milvus filtering) pushdown kb_name / document_id during ANN. Domain boundaries are stronger: clients are constructed with db_name= via MilvusClientPool — no per-request using_database.
Why not global file dedup?¶
The same physical file (SHA-256) may belong in multiple knowledge bases — e.g. a regulatory PDF in both tax_law and compliance. Dedup PK is (sha256, kb_name, plugin_namespace) so the same bytes can exist across KBs/domains while preventing re-upload within one KB of one domain.
Two-layer model¶
flowchart TB
subgraph deploy [Deploy_instance]
NS["plugin_namespace = biomed<br/>default_namespace fixed"]
end
subgraph milvus [Milvus_Database_biomed]
ET[eagle_text]
EV[eagle_visual]
SPEC[specialized_collections]
end
subgraph kbs [kb_name_scalar_filter]
A[kb: clinical]
B[kb: chemistry]
end
NS --> milvus
A & B --> ET & EV & SPEC
| Term | Meaning | Mutable at runtime? |
|---|---|---|
plugin_namespace |
Domain binding (= Milvus DB name mapping) | No — deploy config / profile |
kb_name |
KB id, ^[a-z0-9_]+$, PK with namespace |
Created/deleted via KB API; immutable rename |
Do not call kb_name a “namespace” in UI/API copy — that word is reserved for plugin_namespace.
Default KB: default (KB_NAME / settings.kb_name).
Default domain: core (PLUGIN_NAMESPACE / settings.plugins.default_namespace, or EAGLE_RAG_PROFILE).
Eagle-RAG implementation¶
Isolation mechanisms¶
| Layer | Mechanism | Code |
|---|---|---|
| Domain resolve | Trust default_namespace; mismatch → 403 |
eagle_rag/db/namespace.py |
| Dedup | PK (sha256, kb_name, plugin_namespace) |
eagle_rag/storage/dedup.py |
| Milvus | MilvusClientPool.get(db_name) per domain |
eagle_rag/index/milvus_pool.py |
| Milvus text/visual | kb_name (+ scope) scalar filters |
milvus_text_store.py / milvus_visual_store.py |
| Specialized collections | Same DB; domain plugin schemas | e.g. eagle_text_biomed |
| PostgreSQL | Repository injects plugin_namespace |
eagle_rag/db/repositories/ |
| MinIO / MCP cache | Keys include plugin_namespace |
storage + mcp_cache |
| API / MCP | kb_name on write/query; domain from settings |
routers + core_* tools |
| Celery | kb_name (+ namespace from settings) in kwargs |
ingest tasks |
| Sessions / tags | Namespace-scoped rows | repositories + tag_catalog |
Domain binding (G19)¶
# eagle_rag/db/namespace.py — conceptual
def resolve_namespace(requested: str | None = None) -> str:
default = settings.plugins.default_namespace
if not requested:
return default
if settings.plugins.allow_namespace_override: # tests only
return requested
if requested != default:
raise HTTPException(403, ...) # production
return default
Dedup flow¶
# PK: (sha256, kb_name, plugin_namespace)
check_duplicate(sha256, kb_name, plugin_namespace=...)
register(sha256, document_id, kb_name=..., plugin_namespace=...)
Failed parse tasks do not register dedup — same file can be re-uploaded.
Milvus filtering (inside one DB)¶
KnowhereGraphRetriever uses LlamaIndex MetadataFilters; visual store builds expr in _build_search_expr. Inverted index on kb_name in ensure_collection().
Ingest propagation¶
Every vector row carries kb_name. Domain is implicit from the process-bound Milvus Database (and PG plugin_namespace column). Effective KB:
On successful ingest, collections_used is recorded on the document and unioned into the KB catalog (ADR-006).
Scope filter (query-time)¶
Beyond a single kb_name, QueryRequest.scope_filter accepts:
Union (OR) semantics — any matching KB, explicit document ID, or tag-resolved document includes chunks. Tags resolve via resolve_tags_to_document_ids(plugin_namespace, tags, cap=...).
When scope is active, retrievers use kb_names + document_ids lists pushed to Milvus in predicates.
Scope-aware collection plans: if scoped KBs/documents/tags catalog includes specialized collections, RetrieverOrchestrator forces those collection plans even when Core’s default classifier would only hit eagle_text / eagle_visual (ADR-006).
Persisted in sessions.scope_filter for conversation continuity (sessions are namespace-scoped).
Per-KB configuration¶
| Field | Purpose | Code |
|---|---|---|
display_name, description, theme, icon |
Frontend KB module | eagle_rag/kb/registry.py |
collections_used |
Catalog of collections written by successful ingest | knowledge_bases + repositories |
pdf_text_page_ratio |
Overrides global PDF probe | get_pdf_ratio_sync(kb_name) |
KB primary key is (kb_name, plugin_namespace).
KB lifecycle¶
| Operation | Module | Behavior |
|---|---|---|
| Create / validate | eagle_rag/kb/registry.py |
Regex validate kb_name; bound to instance namespace |
| Delete (cascade) | eagle_rag/kb/lifecycle.py |
Milvus delete in domain DB → documents → images → dedup → tasks → KB row |
| Rebuild | Admin API | Clears collections_used, re-ingests, recomputes catalog |
| Health / stats | kb/health.py, kb/stats.py |
Fan-out specialized collections from plugin manifest |
API: knowledge bases. Internals: kb management.
Design tensions and tuning¶
| Tension | Consequence |
|---|---|
| Domain vs KB confusion | Wrong mental model → agents pass “namespace” as kb_name or expect runtime domain switch |
| Filter pushdown vs client scope | Missing kb_name / scope on one MCP path can leak across KBs inside the domain DB |
| Storage duplication vs dedup key | Same bytes in two KBs = two index copies; expected |
| Tag union breadth | Cap max_scope_documents silently truncates |
Immutable kb_name |
Rename requires re-ingest, not SQL UPDATE |
| Multi-industry | Run one instance per EAGLE_RAG_PROFILE; do not fan-out across Milvus DBs in one query |
Security note¶
Eagle-RAG has no auth by default (auth.enabled: false). Isolation is a data organization layer, not cryptographic multi-tenancy. Expose only on trusted networks or add API key / reverse-proxy auth. Domain 403 prevents accidental cross-namespace API override — it is not a substitute for network auth.
Configuration¶
| Key | Effect |
|---|---|
EAGLE_RAG_PROFILE |
Overlay profiles.<name> → default_namespace + milvus.db_name |
PLUGIN_NAMESPACE / plugins.default_namespace |
Instance domain binding |
plugins.allow_namespace_override |
Tests only — allow request override |
KB_NAME / kb_name |
Default KB when API omits value |
router.max_scope_documents |
Cap tag → document_id resolution |
EAGLE_RAG_PROFILE=biomed task be:api # domain = biomed Milvus DB
KB_NAME=clinical # default KB inside that domain
Failure modes and operations¶
| Failure | Impact | Mitigation |
|---|---|---|
Missing kb_name on API |
Falls back to settings.kb_name |
Agents should pass explicit kb_name |
Mismatched plugin_namespace |
403 | Align client with instance profile |
| Wrong profile | Empty ANN / wrong specialized collections | Check /health/plugins default_namespace |
| Tag resolution error | Tags ignored; other scope dims apply | Check document_keywords for namespace |
| KB delete partial | Orphan vectors possible | Re-run cascade in the correct Milvus DB |
| Wrong KB in MCP args | Cross-KB leak within domain | Validate agent tool inputs |
Audit queries¶
-- Documents per KB within a domain
SELECT kb_name, count(*) FROM documents
WHERE plugin_namespace = 'biomed' GROUP BY kb_name;
-- KB collection catalog
SELECT kb_name, collections_used FROM knowledge_bases
WHERE plugin_namespace = 'biomed';
Two identifiers
Deploy binds domain (plugin_namespace). Requests select KB (kb_name). MCP Core tools: core_ingest, core_query, core_retrieve_text, core_retrieve_visual.