brainy/docs/CREATING-AUGMENTATIONS.md
David Snelling 92c96246fb feat(v4.0.0): Complete metadata/vector separation architecture with Azure support
This commit completes the core v4.0.0 architecture changes for billion-scale
performance with metadata/vector separation. NO RELEASE YET - remaining optimizations
and testing required before production release.

## Core v4.0.0 Architecture Changes

### Type System Updates
- Fixed all TypeScript compilation errors (zero errors achieved)
- Updated HNSWNoun/HNSWVerb to separate core fields from metadata
- Implemented HNSWNounWithMetadata/HNSWVerbWithMetadata for API boundaries
- Added required 'noun' field to NounMetadata for semantic structure
- Renamed verb.type to verb.verb for consistency

### Storage Adapter Updates
**All adapters updated for v4.0.0 two-file storage pattern:**
- memoryStorage: Proper metadata/vector separation
- fileSystemStorage: Two-file pattern with sharding
- opfsStorage: Browser persistent storage updated
- s3CompatibleStorage: AWS/MinIO/DigitalOcean support
- r2Storage: Cloudflare R2 optimization
- gcsStorage: Google Cloud with ADC support
- **azureBlobStorage: NEW - Full Azure Blob Storage support**

### Storage Features
- BaseStorage: Internal vs public method separation (_getNoun vs getNoun)
- Two-file storage: Vectors in one file, metadata in another
- Change tracking: getChangesSince return type updated
- Pagination: getNounsWithPagination returns WithMetadata types

### Azure Blob Storage Integration (NEW)
- Native @azure/storage-blob SDK integration
- Four authentication methods:
  * DefaultAzureCredential (Managed Identity) - recommended
  * Connection String - simplest setup
  * Account Name + Key - traditional auth
  * SAS Token - delegated access
- High-volume mode with write buffering
- Adaptive backpressure for throttling
- UUID-based sharding for billion-scale
- Full HNSW support with graph persistence

### Utility Updates
- EmbeddingManager: Updated to accept Record<string, unknown>
- LSMTree: Wrapped data in NounMetadata structure with 'noun' field
- EntityIdMapper: Fixed nested metadata.data structure access
- MetadataIndex: Fixed field type inference integration
- PeriodicCleanup: Updated for new metadata structure

### Core API Updates
- Brainy: Updated verb property access from v.type to v.verb
- ConfigAPI: Fixed NounMetadata access patterns
- DataAPI: Updated metadata handling

### Documentation Updates
- CREATING-AUGMENTATIONS.md: v4.0.0 breaking changes guide
- DEVELOPER-GUIDE.md: Migration checklist and examples
- COMPLETE-REFERENCE.md: v4.0.0 architecture improvements
- **finite-type-system.md: NEW - Revolutionary type system benefits**

### Build & Dependencies
- Zero TypeScript compilation errors
- Added @azure/storage-blob and @azure/identity
- 591 tests passing (23 timeout in long-running neural tests)

## What's NOT in This Release
This is a work-in-progress commit. Before v4.0.0 release we need:
- Storage adapter optimizations (batch operations, compression)
- Azure blob tier management (Hot/Cool/Archive)
- Cost optimization implementations
- Additional performance testing at billion-scale
- Migration guides for v3.x users

## Testing
- Clean build: 
- Type checking:  (zero errors)
- Test suite:  (591/614 passing, timeouts in neural tests only)

🔐 Generated with Claude Code
https://claude.com/claude-code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-17 12:29:27 -07:00

11 KiB

Creating Augmentations for Brainy

Updated for v4.0.0 - Includes metadata structure changes and type system improvements

The BrainyAugmentation Interface

Every augmentation implements this simple yet powerful interface:

interface BrainyAugmentation {
  // Identification
  name: string                    // Unique name for your augmentation

  // Execution control
  timing: 'before' | 'after' | 'around' | 'replace'  // When to execute
  operations: string[]            // Which operations to intercept
  priority: number                 // Execution order (higher = first)

  // Lifecycle methods
  initialize(context: AugmentationContext): Promise<void>
  execute<T>(operation: string, params: any, next: () => Promise<T>): Promise<T>
  shutdown?(): Promise<void>      // Optional cleanup
}

v4.0.0 Breaking Changes for Augmentation Developers

1. Metadata Structure Separation

v4.0.0 introduces strict metadata/vector separation for billion-scale performance:

// ✅ v4.0.0: Metadata has required type field
interface NounMetadata {
  noun: NounType               // Required! Must be a valid noun type
  [key: string]: any          // Your custom metadata
}

interface VerbMetadata {
  verb: VerbType               // Required! Must be a valid verb type
  sourceId: string
  targetId: string
  [key: string]: any
}

2. Storage Adapter Return Types

Storage adapters now return different types at different boundaries:

// Internal methods: Pure structures (no metadata)
abstract _getNoun(id: string): Promise<HNSWNoun | null>

// Public API: WithMetadata structures
abstract getNoun(id: string): Promise<HNSWNounWithMetadata | null>

3. Verb Property Renamed

The verb relationship field changed from type to verb:

// ❌ v3.x
verb.type === 'relatedTo'

// ✅ v4.0.0
verb.verb === 'relatedTo'

Creating a Storage Augmentation

Storage augmentations are special - they provide the storage backend for Brainy.

Important: v4.0.0 Storage Requirements

Your storage adapter MUST:

  1. Wrap metadata with required noun/verb fields
  2. Return pure structures from internal _methods
  3. Return WithMetadata types from public methods
import { StorageAugmentation } from 'brainy/augmentations'
import { BaseStorageAdapter, HNSWNoun, HNSWNounWithMetadata, NounMetadata } from 'brainy'

export class MyCustomStorage extends BaseStorageAdapter {
  // Internal method: Returns pure structure
  async _getNoun(id: string): Promise<HNSWNoun | null> {
    const data = await this.fetchFromDatabase(id)
    return data ? {
      id: data.id,
      vector: data.vector,
      nounType: data.type
    } : null
  }

  // Public method: Returns WithMetadata structure
  async getNoun(id: string): Promise<HNSWNounWithMetadata | null> {
    const noun = await this._getNoun(id)
    if (!noun) return null

    // Fetch metadata separately (v4.0.0 pattern)
    const metadata = await this.getNounMetadata(id)

    return {
      ...noun,
      metadata: metadata || { noun: noun.nounType || 'thing' }
    }
  }

  // CRITICAL: Always save with proper metadata structure
  async saveNoun(noun: HNSWNoun, metadata?: NounMetadata): Promise<void> {
    // Validate metadata has required 'noun' field
    if (!metadata?.noun) {
      throw new Error('v4.0.0: NounMetadata requires "noun" field')
    }

    await this.database.save({
      id: noun.id,
      vector: noun.vector,
      nounType: noun.nounType,
      metadata: metadata  // Stored separately in v4.0.0
    })
  }
}

export class MyStorageAugmentation extends StorageAugmentation {
  private config: MyStorageConfig

  constructor(config: MyStorageConfig) {
    super()
    this.name = 'my-custom-storage'
    this.config = config
  }

  // Called during storage resolution phase
  async provideStorage(): Promise<StorageAdapter> {
    const storage = new MyCustomStorage(this.config)
    this.storageAdapter = storage
    return storage
  }

  // Called during augmentation initialization
  protected async onInitialize(): Promise<void> {
    await this.storageAdapter!.init()
    this.log(`Custom storage initialized`)
  }
}

Using Your Storage Augmentation

// Register before brain.init()
const brain = new Brainy()
brain.augmentations.register(new MyStorageAugmentation({
  connectionString: 'redis://localhost:6379'
}))
await brain.init()  // Will use your storage!

Creating a Feature Augmentation

Here's a complete example of a caching augmentation:

import { BaseAugmentation, BrainyAugmentation } from 'brainy/augmentations'

export class CachingAugmentation extends BaseAugmentation {
  private cache = new Map<string, any>()
  
  constructor() {
    super()
    this.name = 'smart-cache'
    this.timing = 'around'        // Wrap operations
    this.operations = ['search']   // Only cache searches
    this.priority = 50             // Mid-priority
  }
  
  async execute<T>(operation: string, params: any, next: () => Promise<T>): Promise<T> {
    if (operation === 'search') {
      // Check cache
      const cacheKey = JSON.stringify(params)
      if (this.cache.has(cacheKey)) {
        this.log('Cache hit!')
        return this.cache.get(cacheKey)
      }
      
      // Execute and cache
      const result = await next()
      this.cache.set(cacheKey, result)
      return result
    }
    
    // Pass through other operations
    return next()
  }
  
  protected async onInitialize(): Promise<void> {
    this.log('Cache initialized')
  }
  
  async shutdown(): Promise<void> {
    this.cache.clear()
    await super.shutdown()
  }
}

The Four Timing Modes

1. before - Pre-processing

timing = 'before'
async execute(op, params, next) {
  // Validate/transform input
  const validated = await validate(params)
  return next(validated)  // Pass modified params
}

2. after - Post-processing

timing = 'after'
async execute(op, params, next) {
  const result = await next()
  // Log, analyze, or modify result
  console.log(`Operation ${op} returned:`, result)
  return result
}

3. around - Wrapping (middleware)

timing = 'around'
async execute(op, params, next) {
  console.log('Starting', op)
  try {
    const result = await next()
    console.log('Success', op)
    return result
  } catch (error) {
    console.log('Failed', op, error)
    throw error
  }
}

4. replace - Complete replacement

timing = 'replace'
async execute(op, params, next) {
  // Don't call next() - replace entirely!
  return myCustomImplementation(params)
}

Operations You Can Intercept

Common operations in Brainy:

  • 'storage' - Storage resolution (special)
  • 'add' - Adding data
  • 'search', 'similar' - Searching
  • 'update', 'delete' - Modifications
  • 'saveNoun', 'saveVerb' - Storage operations
  • 'all' - Intercept everything

Context Available to Augmentations

interface AugmentationContext {
  brain: Brainy         // The brain instance
  storage: StorageAdapter   // Storage backend
  config: BrainyConfig  // Configuration
  log: (message: string, level?: 'info' | 'warn' | 'error') => void
}

Real-World Examples

1. Redis Storage Augmentation

export class RedisStorageAugmentation extends StorageAugmentation {
  async provideStorage(): Promise<StorageAdapter> {
    return new RedisAdapter({
      host: 'localhost',
      port: 6379,
      // Implement full StorageAdapter interface
    })
  }
}

2. Audit Trail Augmentation

export class AuditAugmentation extends BaseAugmentation {
  timing = 'after'
  operations = ['add', 'update', 'delete']
  
  async execute(op, params, next) {
    const result = await next()
    
    // Log to audit trail
    await this.logAudit({
      operation: op,
      params,
      result,
      timestamp: new Date(),
      user: this.context.config.currentUser
    })
    
    return result
  }
}

3. Rate Limiting Augmentation

export class RateLimitAugmentation extends BaseAugmentation {
  timing = 'before'
  operations = ['search']
  private limiter = new RateLimiter({ rps: 100 })
  
  async execute(op, params, next) {
    await this.limiter.acquire()  // Wait if rate limited
    return next()
  }
}

Publishing to Brain Cloud Marketplace

Future capability for premium augmentations:

// package.json
{
  "name": "@brain-cloud/redis-storage",
  "brainy": {
    "type": "augmentation",
    "category": "storage",
    "premium": true
  }
}

// Users can install via:
// brainy augment install redis-storage

Best Practices

General Practices

  1. Use BaseAugmentation - Provides common functionality
  2. Set appropriate priority - Storage (100), System (80-99), Features (10-50)
  3. Be selective with operations - Don't use 'all' unless necessary
  4. Handle errors gracefully - Don't break the chain
  5. Clean up in shutdown() - Release resources
  6. Log appropriately - Use context.log() for consistent output
  7. Document your augmentation - Include examples

v4.0.0 Specific Best Practices

  1. Always include noun field when creating/modifying NounMetadata:

    const metadata: NounMetadata = {
      noun: 'thing',  // REQUIRED!
      yourField: 'value'
    }
    
  2. Use verb property not type when working with relationships:

    // ✅ Correct
    if (verb.verb === 'relatedTo') { ... }
    
    // ❌ Wrong (v3.x pattern)
    if (verb.type === 'relatedTo') { ... }
    
  3. Access metadata correctly from storage:

    // ✅ Correct - metadata is already structured
    const nounType = noun.metadata.noun
    
    // ⚠️ Fallback pattern for robustness
    const nounType = noun.metadata?.noun || 'thing'
    
  4. Respect the two-file storage pattern - Don't mix vector and metadata operations:

    // ✅ Good - Separate concerns
    await storage.saveNoun(noun)
    await storage.saveMetadata(noun.id, metadata)
    
    // ❌ Bad - Mixing concerns
    await storage.saveNounWithEverything(combinedData)
    

Testing Your Augmentation

import { Brainy } from 'brainy'
import { MyAugmentation } from './my-augmentation'

describe('MyAugmentation', () => {
  let brain: Brainy
  
  beforeEach(async () => {
    brain = new Brainy()
    brain.augmentations.register(new MyAugmentation())
    await brain.init()
  })
  
  afterEach(async () => {
    await brain.destroy()
  })
  
  it('should enhance searches', async () => {
    // Test your augmentation's effect
    const results = await brain.search('test')
    expect(results).toHaveProperty('enhanced', true)
  })
})

Summary

Augmentations are Brainy's extension system. They can:

  • Replace storage backends
  • Add caching layers
  • Implement audit trails
  • Add rate limiting
  • Sync with external systems
  • Transform data
  • And much more!

The unified BrainyAugmentation interface makes it easy to create powerful extensions while maintaining consistency across the entire system.