ADR-013: Feature Flag Architecture
ADR-013: Feature Flag Architecture
Section titled “ADR-013: Feature Flag Architecture”Status
Section titled “Status”Accepted
Context
Section titled “Context”Background and Problem Statement
Section titled “Background and Problem Statement”RLM-RS includes optional heavyweight dependencies that significantly impact binary size and compile time:
- fastembed-embeddings: ONNX runtime + BGE-M3 model (~150MB binary impact)
- usearch-hnsw: HNSW index for vector similarity (~5MB binary impact)
- full-search: All search capabilities combined
Not all use cases require all features. A documentation analysis tool may not need embeddings. A simple grep-based workflow may not need vector search. Users should be able to opt-in to only the features they need.
Size and Performance Considerations
Section titled “Size and Performance Considerations”| Feature | Binary Size Impact | Compile Time Impact |
|---|---|---|
| Base (no features) | ~5MB | ~30s |
| fastembed-embeddings | +150MB | +5min |
| usearch-hnsw | +5MB | +30s |
| full-search | +155MB | +6min |
Decision Drivers
Section titled “Decision Drivers”Primary Decision Drivers
Section titled “Primary Decision Drivers”- Binary size: Enable minimal installations for constrained environments
- Compile time: Speed up development builds by excluding unused features
- Flexibility: Different deployment scenarios need different capabilities
Secondary Decision Drivers
Section titled “Secondary Decision Drivers”- Default experience: Common use cases should work out of the box
- Graceful degradation: Missing features should fail clearly
- Documentation: Feature requirements should be obvious
Considered Options
Section titled “Considered Options”Option 1: Cargo Feature Flags (Chosen)
Section titled “Option 1: Cargo Feature Flags (Chosen)”Standard Rust mechanism for conditional compilation.
Pros:
- Standard, well-understood pattern
- Compile-time feature selection
- Zero runtime cost for disabled features
- Enables optional dependencies
Cons:
- Feature combinations can be complex
- Must test all flag combinations in CI
Option 2: Runtime Configuration
Section titled “Option 2: Runtime Configuration”Load capabilities at runtime based on config.
Pros:
- Single binary for all scenarios
- Dynamic capability discovery
Cons:
- Larger binary always
- Runtime overhead for feature checks
- Complex error handling
Option 3: Multiple Binaries
Section titled “Option 3: Multiple Binaries”Separate binaries for different use cases.
Pros:
- Clear separation
- Optimized per use case
Cons:
- Distribution complexity
- Maintenance burden
- User confusion
Decision
Section titled “Decision”We use Cargo feature flags with the following architecture:
Feature Definitions
Section titled “Feature Definitions”[features]default = ["fastembed-embeddings"]
# Embedding generation with BGE-M3 model via ONNX runtimefastembed-embeddings = ["dep:fastembed"]
# HNSW index for fast approximate nearest neighbor searchusearch-hnsw = ["dep:usearch"]
# All search capabilities (semantic + vector index)full-search = ["fastembed-embeddings", "usearch-hnsw"]Feature Matrix
Section titled “Feature Matrix”| Feature | Semantic Search | BM25 Search | HNSW Index | Embedding Generation |
|---|---|---|---|---|
| (none) | No | Yes | No | No |
| fastembed-embeddings | Yes | Yes | No | Yes |
| usearch-hnsw | No | Yes | Yes | No |
| full-search | Yes | Yes | Yes | Yes |
Default Feature
Section titled “Default Feature”fastembed-embeddings is enabled by default because:
- Most users want semantic search capabilities
- BM25-only search is a specialized use case
- The RLM workflow relies on semantic chunking
Conditional Compilation Patterns
Section titled “Conditional Compilation Patterns”// Optional embedding support#[cfg(feature = "fastembed-embeddings")]mod fastembed_impl;
#[cfg(feature = "fastembed-embeddings")]pub use fastembed_impl::FastEmbedEmbedder;
// Fallback when embeddings disabled#[cfg(not(feature = "fastembed-embeddings"))]pub fn create_embedder() -> impl Embedder { FallbackEmbedder::new() // Returns error on embed attempts}Error Types for Missing Features
Section titled “Error Types for Missing Features”#[derive(Error, Debug)]pub enum StorageError { #[cfg(feature = "fastembed-embeddings")] #[error("embedding error: {0}")] Embedding(#[from] EmbeddingError),
#[cfg(feature = "usearch-hnsw")] #[error("vector search error: {0}")] VectorSearch(String),}Consequences
Section titled “Consequences”Positive Consequences
Section titled “Positive Consequences”- Optimized binaries: Users get only what they need
- Faster CI: Can test core functionality without heavy deps
- Clear capabilities: Feature flags document optional functionality
- Gradual adoption: Start minimal, add features as needed
Negative Consequences
Section titled “Negative Consequences”- CI complexity: Must test multiple feature combinations
- Documentation burden: Must document feature requirements
- User confusion: Feature selection adds decision point
Neutral Consequences
Section titled “Neutral Consequences”- Conditional compilation: Code has
#[cfg(...)]annotations - Feature propagation: Dependencies may enable sub-features
Implementation Notes
Section titled “Implementation Notes”Building with Specific Features
Section titled “Building with Specific Features”# Minimal build (BM25 search only)cargo build --no-default-features
# Default build (with embeddings)cargo build
# Full build (all features)cargo build --all-features
# Specific combinationcargo build --features "fastembed-embeddings,usearch-hnsw"CI Testing Matrix
Section titled “CI Testing Matrix”jobs: test: strategy: matrix: features: - "" # No features - "fastembed-embeddings" # Default - "usearch-hnsw" # HNSW only - "full-search" # All featuresChecking Feature at Runtime
Section titled “Checking Feature at Runtime”For user-facing messages:
pub fn search_capabilities() -> Vec<&'static str> { let mut caps = vec!["bm25"];
#[cfg(feature = "fastembed-embeddings")] caps.push("semantic");
#[cfg(feature = "usearch-hnsw")] caps.push("hnsw");
caps}Adding New Features
Section titled “Adding New Features”- Add feature to
Cargo.tomlwith optional dependency - Gate code with
#[cfg(feature = "...")] - Add error variant for missing feature attempts
- Update documentation and CI matrix
- Consider default inclusion based on use case frequency