Docs/AI & agentsv1.0.0-rc.1

AIOptimizer

Overview

AIOptimizer collects runtime performance data — action execution latencies, selector call counts, and state update frequency — and produces prioritized OptimizationSuggestion objects. AutoOptimizer wraps AIOptimizer and runs analysis on a configurable interval, notifying subscribers when new suggestions appear.

Both classes work entirely locally; no external AI service is required. Suggestions are rule-based: high latency triggers batching recommendations, high dispatch frequency triggers debounce recommendations, and slow actions trigger normalization suggestions.

Key Concepts

  • Action latency recording — recordAction(type, latencyMs) accumulates latency samples per action type. Suggestions appear once a minimum sample size is reached.
  • Selector tracking — recordSelector(name) counts how many times each selector is called. Heavily called selectors without memoization get a SELECTOR_MEMOIZATION suggestion.
  • Optimization types — each suggestion has a typed OptimizationType enum value indicating the category of improvement.
  • Priority — suggestions are sorted by priority (1–10, descending) so you can act on the most impactful ones first.

API Reference

OptimizationType

enum OptimizationType {
  SELECTOR_MEMOIZATION = 'SELECTOR_MEMOIZATION',
  ACTION_BATCHING = 'ACTION_BATCHING',
  STATE_NORMALIZATION = 'STATE_NORMALIZATION',
  LAZY_LOADING = 'LAZY_LOADING',
  DERIVED_STATE = 'DERIVED_STATE',
  IMMUTABLE_UPDATE = 'IMMUTABLE_UPDATE',
  DEBOUNCE_THROTTLE = 'DEBOUNCE_THROTTLE',
  VIRTUALIZATION = 'VIRTUALIZATION'
}

OptimizationSuggestion

interface OptimizationSuggestion {
  id: string;
  type: OptimizationType;
  title: string;
  description: string;
  impact: 'low' | 'medium' | 'high';
  effort: 'low' | 'medium' | 'high';
  priority: number;           // 1–10
  codeExample?: string;       // TypeScript snippet illustrating the fix
  estimatedGain?: string;     // human-readable improvement estimate
}

OptimizerConfig

interface OptimizerConfig {
  enableAutoOptimization?: boolean;   // default true
  latencyThreshold?: number;          // ms, default 16 (60fps budget)
  frequencyThreshold?: number;        // actions/second, default 10
  minSampleSize?: number;             // minimum recordings before suggesting, default 10
}

PerformanceProfile

interface PerformanceProfile {
  actionLatencies: Map<string, number[]>;
  selectorExecutions: Map<string, number>;
  stateUpdateFrequency: number;
  averageUpdateTime: number;
  slowActions: string[];
  frequentActions: string[];
}

AIOptimizer

class AIOptimizer {
  constructor(config?: OptimizerConfig)

  optimize(profile: Partial<PerformanceProfile>): OptimizationSuggestion[]
  recordAction(actionType: string, latency: number): void
  recordSelector(selectorName: string): void
  getSuggestions(): OptimizationSuggestion[]
  getSuggestion(id: string): OptimizationSuggestion | undefined
  clearSuggestions(): void
  getPerformanceSummary(): {
    slowestAction: { type: string; avgLatency: number } | null;
    mostFrequentAction: { type: string; count: number } | null;
    mostCalledSelector: { name: string; executions: number } | null;
    totalActions: number;
    totalSelectors: number;
  }
}

function createOptimizer(config?: OptimizerConfig): AIOptimizer

AutoOptimizer

class AutoOptimizer {
  constructor(config?: OptimizerConfig & { analysisIntervalMs?: number })

  start(): void
  stop(): void
  subscribe(listener: (suggestions: OptimizationSuggestion[]) => void): () => void
  getOptimizer(): AIOptimizer
}

Basic Usage

import { AIOptimizer, OptimizationType } from '@tagna/udiot';

const optimizer = new AIOptimizer({ latencyThreshold: 16, minSampleSize: 5 });

// Simulate recording action performance (normally done by @AIPowered)
for (let i = 0; i < 10; i++) {
  optimizer.recordAction('FETCH_PRODUCTS', 45);  // consistently slow (> 16ms)
}
for (let i = 0; i < 15; i++) {
  optimizer.recordAction('SET_FILTER', 2);        // fast but very frequent
}
for (let i = 0; i < 60; i++) {
  optimizer.recordSelector('filteredProducts');    // called many times
}

// Run analysis
const suggestions = optimizer.optimize({
  stateUpdateFrequency: 12,  // 12 updates/second — above threshold
});

suggestions.forEach((s) => {
  console.log(`[${s.priority}] ${s.title}`);
  console.log(`  Impact: ${s.impact}, Effort: ${s.effort}`);
  console.log(`  ${s.description}`);
  if (s.codeExample) console.log('  Example:\n', s.codeExample);
});

Common Patterns

Auto-optimizer with subscriber

import { AutoOptimizer, OptimizationSuggestion } from '@tagna/udiot';

const auto = new AutoOptimizer({
  latencyThreshold: 16,
  analysisIntervalMs: 10_000,  // check every 10 seconds
});

const unsubscribe = auto.subscribe((suggestions: OptimizationSuggestion[]) => {
  const highPriority = suggestions.filter(s => s.priority >= 8);
  if (highPriority.length > 0) {
    console.warn('[AutoOptimizer] High-priority suggestions:', highPriority.map(s => s.title));
  }
});

auto.start();

// Later, when your application shuts down or the component unmounts:
auto.stop();
unsubscribe();

Accessing the optimizer attached by @AIPowered

import {
  ReactiveStore, AIPowered, Store, Mutation,
  getOptimizer
} from '@tagna/udiot';

@ReactiveStore({ name: 'ProductStore' })
@AIPowered({ autoOptimize: true })
class ProductStore extends Store<ProductState> {
  constructor(state: ProductState) { super(state); }

  @Mutation()
  setProducts(products: Product[]): void {
    this.dispatch({ type: 'SET_PRODUCTS', payload: products });
  }
}

const store = new ProductStore({ products: [], filter: '' });

// Dispatch a few actions...
store.setProducts([/* ... */]);

// Then inspect suggestions
const optimizer = getOptimizer(store);
const summary = optimizer?.getPerformanceSummary();
console.log('Slowest action:', summary?.slowestAction);

const suggestions = optimizer?.getSuggestions() ?? [];
suggestions.forEach(s => console.log(`${s.type}: ${s.title}`));

Applying a suggestion: memoizing a selector

Given the suggestion SELECTOR_MEMOIZATION for filteredProducts:

import { Store, Selector } from '@tagna/udiot';

class ProductStore extends Store<ProductState> {
  // Before: called directly without memoization
  // getFilteredProducts() { return this.getState().products.filter(...); }

  // After: @Selector wraps with memoization
  @Selector()
  getFilteredProducts(): Product[] {
    return this.getState().products.filter(p => p.active);
  }
}

Integration Notes

  • @AIPowered with autoOptimize: true calls optimizer.recordAction(type, duration) on every dispatch, and calls optimizer.optimize(...) with the current profile. No manual wiring required.
  • optimize() clears and rebuilds the suggestion map each time it is called, so stale suggestions from previous runs do not accumulate.
  • AutoOptimizer creates its own AIOptimizer internally. If you need to feed custom performance data (e.g., from a profiling tool), use AIOptimizer directly and call recordAction / recordSelector yourself.
  • AutoOptimizer uses setInterval internally. In Node.js test environments, it calls .unref() on the timer so it does not prevent the process from exiting.

Pitfalls

  • Suggestions appear only after minSampleSize recordings for a given action type (default 10). In development with few dispatches, the optimizer may produce no output — this is expected.
  • optimize() returns suggestions sorted by priority but does not apply them. All suggestions are advisory; you decide which ones to implement.
  • The latencyThreshold default of 16ms (one 60fps frame) is aggressive for most back-end operations. For Node.js services, raise it to 50–100ms to avoid noise.
  • Calling clearSuggestions() does not clear the performance profile (recorded latencies and selector counts). To reset everything, create a new AIOptimizer instance.
From the canonical UDIOT public documentation.Try it in the playground ↗