brainy/docs/augmentations/DEVELOPER-GUIDE.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

13 KiB

🛠️ Brainy Augmentation Developer Guide

How to create, test, and use augmentations in Brainy v4.0.0

⚠️ v4.0.0 Update: This guide has been updated with breaking changes for metadata structure and type system improvements.

v4.0.0 Migration Guide

What Changed?

  1. Metadata Structure: All metadata now requires type fields (noun or verb)
  2. Property Rename: verb.typeverb.verb for relationships
  3. Two-File Storage: Vectors and metadata stored separately for performance
  4. Return Types: Storage methods distinguish between internal (pure) and public (WithMetadata) returns

Migration Checklist

  • Update metadata creation to include required noun field
  • Change verb.type to verb.verb in all relationship code
  • Update storage adapter methods to follow internal/public pattern
  • Ensure metadata access uses correct structure

Quick Migration Example

// ❌ v3.x
const verb = {
  type: 'relatedTo',
  sourceId: 'a',
  targetId: 'b'
}
if (verb.type === 'relatedTo') { ... }

// ✅ v4.0.0
const verb = {
  verb: 'relatedTo',
  sourceId: 'a',
  targetId: 'b'
}
const metadata: VerbMetadata = {
  verb: 'relatedTo',
  sourceId: 'a',
  targetId: 'b'
}
if (verb.verb === 'relatedTo') { ... }

Quick Start: Your First Augmentation

import { BaseAugmentation, BrainyAugmentation, AugmentationContext } from '@soulcraft/brainy'

export class MyFirstAugmentation extends BaseAugmentation {
  readonly name = 'my-first-augmentation'
  readonly timing = 'after' as const       // When to run: before | after | both
  readonly operations = ['add'] as const  // Which operations to hook
  readonly priority = 10                    // Execution order (lower = first)

  protected async onInit(): Promise<void> {
    // Initialize your augmentation
    console.log('MyFirstAugmentation initialized!')
  }

  async execute<T = any>(
    operation: string,
    params: any,
    context?: AugmentationContext
  ): Promise<T | void> {
    // Your augmentation logic
    if (operation === 'add') {
      console.log('Noun added:', params.noun)

      // v4.0.0: Access metadata correctly
      if (params.noun?.metadata) {
        console.log('Noun type:', params.noun.metadata.noun)  // Required field
      }

      // You can access the brain instance
      const stats = await context?.brain.getStats()
      console.log('Total nouns:', stats.totalNouns)
    }
  }

  protected async onShutdown(): Promise<void> {
    // Cleanup
    console.log('MyFirstAugmentation shutting down')
  }
}

Using Your Augmentation

import { Brainy } from '@soulcraft/brainy'
import { MyFirstAugmentation } from './my-first-augmentation'

const brain = new Brainy()

// Register before init()
brain.augmentations.register(new MyFirstAugmentation())

await brain.init()

// Now your augmentation runs automatically!
await brain.add('Hello World')
// Console: "Noun added: { id: '...', vector: [...], metadata: {} }"

Augmentation Lifecycle

1. Registration Phase

const aug = new MyAugmentation()
brain.augmentations.register(aug)  // Before brain.init()!

2. Initialization Phase

await brain.init()  // Calls aug.initialize() internally
// Your onInit() method runs here

3. Execution Phase

await brain.add('data')  // Your execute() method runs

4. Shutdown Phase

await brain.shutdown()  // Your onShutdown() method runs

Timing Options

before - Modify Input

class ValidationAugmentation extends BaseAugmentation {
  readonly timing = 'before' as const
  
  async execute<T>(operation: string, params: any): Promise<any> {
    if (operation === 'add') {
      // Validate and/or modify params
      if (!params.content) {
        throw new Error('Content required')
      }
      // Return modified params
      return { ...params, validated: true }
    }
  }
}

after - React to Results

class LoggingAugmentation extends BaseAugmentation {
  readonly timing = 'after' as const
  
  async execute<T>(operation: string, params: any): Promise<void> {
    if (operation === 'search') {
      console.log(`Search for "${params.query}" returned ${params.result.length} results`)
    }
    // Don't return anything - just observe
  }
}

both - Before AND After

class TimingAugmentation extends BaseAugmentation {
  readonly timing = 'both' as const
  private startTime?: number
  
  async execute<T>(operation: string, params: any, context?: AugmentationContext): Promise<void> {
    if (!this.startTime) {
      // Before execution
      this.startTime = Date.now()
    } else {
      // After execution
      const duration = Date.now() - this.startTime
      console.log(`${operation} took ${duration}ms`)
      this.startTime = undefined
    }
  }
}

Operation Hooks

Core Operations You Can Hook

readonly operations = [
  'add',            // Adding data
  'update',         // Updating data
  'delete',         // Deleting data
  'get',            // Retrieving data
  'search',         // Searching
  'find',           // Triple Intelligence queries
  'relate',         // Adding relationships
  'unrelate',       // Removing relationships
  'clear',          // Clearing data
  'all'            // Hook ALL operations
] as const

Example: Multi-Operation Hook

class AuditAugmentation extends BaseAugmentation {
  readonly operations = ['add', 'update', 'delete'] as const
  
  async execute<T>(operation: string, params: any): Promise<void> {
    // Log all data modifications
    await this.logToAuditTrail(operation, params)
  }
}

Accessing Brain Context

class ContextAwareAugmentation extends BaseAugmentation {
  async execute<T>(
    operation: string,
    params: any,
    context?: AugmentationContext
  ): Promise<void> {
    // Access the brain instance
    const brain = context?.brain
    if (!brain) return
    
    // Use any brain method
    const stats = await brain.getStats()
    const size = await brain.size()
    const results = await brain.search('query')
    
    // Access other augmentations
    const cache = brain.augmentations.get('cache')
    if (cache) {
      await cache.clear()
    }
  }
}

Real-World Examples

1. Backup Augmentation

class BackupAugmentation extends BaseAugmentation {
  readonly name = 'backup'
  readonly timing = 'after' as const
  readonly operations = ['add', 'update', 'delete'] as const
  readonly priority = 5
  
  private changes = 0
  private readonly backupThreshold = 100
  
  async execute<T>(operation: string, params: any, context?: AugmentationContext): Promise<void> {
    this.changes++
    
    if (this.changes >= this.backupThreshold) {
      await this.performBackup(context?.brain)
      this.changes = 0
    }
  }
  
  private async performBackup(brain?: any): Promise<void> {
    if (!brain) return
    const backup = await brain.backup()
    await this.saveToCloud(backup)
    console.log('Automatic backup completed')
  }
}

2. Rate Limiting Augmentation

class RateLimitAugmentation extends BaseAugmentation {
  readonly name = 'rate-limit'
  readonly timing = 'before' as const
  readonly operations = ['search', 'find'] as const
  readonly priority = 100  // High priority - run first
  
  private requests = new Map<string, number[]>()
  private readonly limit = 100  // 100 requests
  private readonly window = 60000  // per minute
  
  async execute<T>(operation: string, params: any): Promise<void> {
    const now = Date.now()
    const key = params.userId || 'anonymous'
    
    // Get request timestamps
    const timestamps = this.requests.get(key) || []
    
    // Remove old timestamps
    const recent = timestamps.filter(t => now - t < this.window)
    
    // Check limit
    if (recent.length >= this.limit) {
      throw new Error('Rate limit exceeded')
    }
    
    // Add current request
    recent.push(now)
    this.requests.set(key, recent)
  }
}

3. Encryption Augmentation

class EncryptionAugmentation extends BaseAugmentation {
  readonly name = 'encryption'
  readonly timing = 'both' as const
  readonly operations = ['add', 'get'] as const
  readonly priority = 90  // Run early
  
  async execute<T>(operation: string, params: any): Promise<any> {
    if (operation === 'add') {
      // Encrypt before storing
      if (params.metadata?.sensitive) {
        params.content = await this.encrypt(params.content)
        params.encrypted = true
      }
      return params
    }

    if (operation === 'get' && params.result?.encrypted) {
      // Decrypt after retrieval
      params.result.content = await this.decrypt(params.result.content)
      delete params.result.encrypted
      return params.result
    }
  }
}

Testing Your Augmentation

import { describe, it, expect } from 'vitest'
import { Brainy } from '@soulcraft/brainy'
import { MyAugmentation } from './my-augmentation'

describe('MyAugmentation', () => {
  it('should hook into addNoun', async () => {
    const brain = new Brainy({ storage: 'memory' })
    const aug = new MyAugmentation()
    
    // Spy on the execute method
    const executeSpy = vi.spyOn(aug, 'execute')
    
    brain.augmentations.register(aug)
    await brain.init()
    
    // Trigger the augmentation
    await brain.add('test data')

    // Verify it was called
    expect(executeSpy).toHaveBeenCalledWith(
      'add',
      expect.objectContaining({ content: 'test data' }),
      expect.any(Object)
    )
  })
})

Best Practices

1. Use Proper Timing

  • before: Validation, modification, rate limiting
  • after: Logging, metrics, side effects
  • both: Timing, tracing, wrapping

2. Set Appropriate Priority

// Priority guidelines
100: Critical (auth, rate limiting)
50:  Important (validation, transformation)
10:  Normal (logging, metrics)
1:   Optional (debugging, tracing)

3. Handle Errors Gracefully

async execute<T>(operation: string, params: any): Promise<void> {
  try {
    await this.riskyOperation()
  } catch (error) {
    // Log but don't break the main operation
    console.error(`Augmentation error in ${this.name}:`, error)
    // Optionally report to monitoring
    this.reportError(error)
  }
}

4. Be Performance Conscious

class CachedAugmentation extends BaseAugmentation {
  private cache = new Map<string, any>()
  
  async execute<T>(operation: string, params: any): Promise<any> {
    const key = this.getCacheKey(params)
    
    // Check cache first
    if (this.cache.has(key)) {
      return this.cache.get(key)
    }
    
    // Expensive operation
    const result = await this.expensiveOperation(params)
    this.cache.set(key, result)
    
    return result
  }
}

5. Clean Up Resources

protected async onShutdown(): Promise<void> {
  // Close connections
  await this.connection?.close()
  
  // Clear intervals
  clearInterval(this.interval)
  
  // Flush buffers
  await this.flush()
  
  // Clear caches
  this.cache.clear()
}

Publishing Your Augmentation (Future)

Package Structure

my-augmentation/
├── src/
│   └── index.ts          # Your augmentation
├── dist/                 # Built output
├── tests/
│   └── augmentation.test.ts
├── package.json
├── tsconfig.json
└── README.md

package.json

{
  "name": "@mycompany/brainy-custom-augmentation",
  "version": "1.0.0",
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "keywords": ["brainy-augmentation"],
  "peerDependencies": {
    "@soulcraft/brainy": ">=2.0.0"
  },
  "brainy": {
    "type": "augmentation",
    "class": "CustomAugmentation",
    "timing": "after",
    "operations": ["add"],
    "priority": 10
  }
}

Future: Brain Cloud Registry

# Coming in 2.1+
npm run build
npm test
brainy publish  # Publishes to brain-cloud registry

FAQ

Q: Can I modify the operation result?

A: Yes, if timing: 'before', return modified params. If timing: 'after', you can see but not modify results.

Q: Can augmentations communicate?

A: Yes, through the context: context.brain.augmentations.get('other-augmentation')

Q: What if my augmentation fails?

A: Handle errors internally. Don't break the main operation unless critical.

Q: Can I use async operations?

A: Yes, everything is async-friendly.

Q: How do I access storage directly?

A: Through context: context.brain.storage (but prefer using brain methods)


Get Help


Start building your augmentation today! The marketplace is coming in 2.1 🚀