Current state: - Unified augmentation system to BrainyAugmentation interface - Changed methods to specific noun/verb naming (addNoun, getNoun, etc) - Made old methods private - Combined getNouns into single unified method - Neural API exists and is complete - Triple Intelligence uses correct Brainy operators (not MongoDB) Issues identified: - Documentation incorrectly shows MongoDB operators (code is correct) - Need to ensure all features are properly exposed - Need to verify nothing was lost in simplification This commit serves as a rollback point before applying fixes.
7.6 KiB
Storage Unification Plan: Everything as Augmentations
Executive Summary
Unify storage adapters and memory augmentations into a single augmentation-based system while maintaining 100% backward compatibility and zero-config philosophy.
Current State Analysis
Two Parallel Systems
-
Storage Adapters (
src/storage/adapters/)- Direct implementation of StorageAdapter interface
- Selected via
createStorage()during initialization - 67 direct calls to
this.storagethroughout BrainyData
-
Memory Augmentations (
src/augmentations/memoryAugmentations.ts)- Wrap storage adapters as augmentations
- Use
timing: 'replace'for storage operations - Redundant with storage adapters
Initialization Order Problem
// Current flow in BrainyData.init()
1. Create/initialize storage (line 1463-1503)
2. Initialize augmentations with storage context (line 1508)
3. Storage passed to augmentations via context (line 782)
Problem: Augmentations need storage in context, but we want augmentations to provide storage!
Proposed Solution: Two-Phase Initialization
Phase 1: Pre-Registration (No Context)
// Early in init(), before storage creation
this.registerDefaultAugmentations() // Register but don't initialize
Phase 2: Storage Resolution
// Check for storage augmentations
const storageAug = this.augmentations.findByOperation('storage')
if (storageAug) {
// Get storage from augmentation
this.storage = await storageAug.provideStorage()
} else if (this.config.storageAdapter) {
// Use provided adapter (backward compat)
this.storage = this.config.storageAdapter
} else {
// Zero-config: create and wrap in augmentation
this.storage = await createStorage(this.storageConfig)
// Auto-register as augmentation for consistency
const autoAug = new DynamicStorageAugmentation(this.storage)
this.augmentations.register(autoAug)
}
await this.storage.init()
Phase 3: Full Augmentation Initialization
// Now initialize all augmentations with context
const context = {
brain: this,
storage: this.storage,
config: this.config,
log: this.log
}
await this.augmentations.initializeAll(context)
Implementation Steps
Step 1: Create DynamicStorageAugmentation
// Wraps any storage adapter as an augmentation
class DynamicStorageAugmentation extends BaseAugmentation {
constructor(private adapter: StorageAdapter) {
super()
this.name = `${adapter.constructor.name}Augmentation`
this.timing = 'replace'
this.operations = ['storage']
this.priority = 100
}
async provideStorage(): Promise<StorageAdapter> {
return this.adapter
}
async execute(op, params, next) {
if (op === 'storage') {
return this.adapter
}
return next()
}
}
Step 2: Modify AugmentationRegistry
class AugmentationRegistry {
// Add method to find augmentations before initialization
findByOperation(operation: string): BrainyAugmentation | null {
return this.augmentations.find(aug =>
aug.operations.includes(operation) ||
aug.operations.includes('all')
) || null
}
// Split registration from initialization
register(augmentation: BrainyAugmentation): void {
this.augmentations.push(augmentation)
// Don't initialize yet
}
async initializeAll(context: AugmentationContext): Promise<void> {
for (const aug of this.augmentations) {
if (aug.initialize) {
await aug.initialize(context)
}
}
}
}
Step 3: Update BrainyData.init()
async init(): Promise<void> {
// ... existing validation ...
// Step 1: Register default augmentations (no init)
this.registerDefaultAugmentations()
// Step 2: Resolve storage
await this.resolveStorage()
// Step 3: Initialize augmentations with context
await this.initializeAugmentations()
// ... rest of init ...
}
private async resolveStorage(): Promise<void> {
// Check for storage augmentation
const storageAug = this.augmentations.findByOperation('storage')
if (storageAug && storageAug.provideStorage) {
// Get storage from augmentation
this.storage = await storageAug.provideStorage()
} else if (!this.storage) {
// No storage augmentation and no provided adapter
// Use zero-config
const storageOptions = this.buildStorageOptions()
this.storage = await createStorage(storageOptions)
// Wrap in augmentation for consistency
const wrapper = new DynamicStorageAugmentation(this.storage)
this.augmentations.register(wrapper)
}
// Initialize storage
await this.storage!.init()
}
Usage Examples
Zero-Config (No Change)
const brain = new BrainyData()
await brain.init()
// Automatically selects best storage for environment
Explicit Storage Adapter (Backward Compatible)
const brain = new BrainyData({
storageAdapter: new S3Storage(config)
})
await brain.init()
Storage via Augmentation (New)
const brain = new BrainyData()
brain.augmentations.register(new S3StorageAugmentation(config))
await brain.init()
Storage Config (Backward Compatible)
const brain = new BrainyData({
storage: {
s3Storage: {
bucketName: 'my-bucket',
accessKeyId: 'xxx',
secretAccessKey: 'yyy'
}
}
})
await brain.init()
Benefits
- Unified Architecture: Everything is an augmentation
- Backward Compatible: All existing code continues to work
- Zero-Config Maintained: Intelligent selection still works
- Extensible: Easy to add new storage types as augmentations
- Middleware Capable: Storage operations can be intercepted
- Premium Ready: Premium storage augmentations can be added to marketplace
Migration Path
Phase 1: Implement Infrastructure (No Breaking Changes)
- Add DynamicStorageAugmentation
- Update AugmentationRegistry with new methods
- Modify BrainyData.init() to support both paths
Phase 2: Deprecate Direct Storage Config
- Mark
storageAdapterconfig as deprecated - Encourage augmentation approach in docs
- Keep working for 2-3 major versions
Phase 3: Remove Legacy Code
- Remove
storageAdapterfrom config - Remove
createStorage()direct calls - All storage through augmentations
Testing Strategy
-
Backward Compatibility Tests
- Ensure all existing storage config methods work
- Test zero-config in different environments
- Verify no breaking changes
-
New Functionality Tests
- Test storage augmentation registration
- Test override behavior
- Test middleware capabilities
-
Performance Tests
- Ensure no performance regression
- Measure augmentation overhead
Risk Mitigation
-
Risk: Circular dependency between storage and augmentations Mitigation: Two-phase initialization breaks the cycle
-
Risk: Breaking existing code Mitigation: Keep
this.storageand all direct calls unchanged -
Risk: Performance overhead Mitigation: Storage augmentation is registered once, minimal overhead
-
Risk: Confusion about which approach to use Mitigation: Clear documentation, deprecation warnings, migration guide
Timeline
- Week 1: Implement core infrastructure
- Week 2: Update documentation and examples
- Week 3: Testing and optimization
- Week 4: Release as minor version (non-breaking)
Conclusion
This unification maintains all existing behaviors while providing a cleaner, more extensible architecture. The augmentation approach aligns with Brainy's philosophy and enables future enhancements without breaking changes.