ruvnet/ruflo · error

HNSW pattern limit reached

Error message

HNSW pattern limit reached (${HNSW_MAX_SAFE_PATTERNS}).

What it means

The HNSW router wrapper enforces a hard safety cap of HNSW_MAX_SAFE_PATTERNS = 1024 patterns (exported at ruvllm-wasm.ts:145), independent of the maxPatterns you pass to the constructor. addPattern() throws once the count of successfully added patterns reaches 1024; the cap exists to bound wasm-side memory since HNSW index memory grows with pattern count.

Solutions

  1. Prune before adding: evict the oldest or lowest-value patterns to stay under 1024
  2. Shard: partition patterns across multiple HNSW routers (by tenant, topic, or bucket) and merge route() results
  3. Dedupe by pattern.name before re-adding on restart so counts do not grow monotonically
  4. Track the count yourself and stop or rotate before hitting the cap (the wrapper exposes no count getter)

Example fix

// before
for (const p of allPatterns) router.addPattern(p); // throws at pattern #1025
// after
import { HNSW_MAX_SAFE_PATTERNS } from './ruvllm-wasm';
let added = 0;
for (const p of allPatterns) {
  if (added >= HNSW_MAX_SAFE_PATTERNS) throw new Error('pattern budget exhausted — shard the router');
  if (router.addPattern(p)) added++;
}
Defensive patterns

Strategy: validation

Validate before calling

import { HNSW_MAX_SAFE_PATTERNS } from './ruvllm-wasm';
function budgetRemaining(added: number): number {
  return HNSW_MAX_SAFE_PATTERNS - added; // check before every batch add

Type guard

const hasPatternBudget = (added: number): boolean => added < HNSW_MAX_SAFE_PATTERNS;

Try / catch

try {
  ok = router.addPattern(p);
} catch (e) {
  if (e instanceof Error && e.message.startsWith('HNSW pattern limit reached')) {
    evictOldestPattern(); ok = router.addPattern(p); // make room, retry once
  } else throw e;
}

Prevention

When it happens

Trigger: Bulk-loading more than 1024 embeddings into one router; accumulating patterns across a long-running process without eviction; replaying an event log into the index on every restart, incrementing past the cap; concurrent producers adding patterns between your count checks.

Common situations: Rebuilding a routing index from history on boot; memory/knowledge ingestion pipelines with no dedupe; multi-tenant designs funneling all tenants' patterns into a single router.

Related errors


AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18). Data as JSON: /api/errors/305f866b4e5e0293. Report an issue: GitHub.

Appendix: source

Thrown at v3/@claude-flow/cli/src/ruvector/ruvllm-wasm.ts:173

  route: (query: Float32Array, k?: number) => HnswRouteResult[];
  clear: () => void;
  patternCount: () => number;
  toJson: () => string;
}> {
  await initRuvllmWasm();
  const mod = await import('@ruvector/ruvllm-wasm');

  const router = new mod.HnswRouterWasm(config.dimensions, config.maxPatterns);
  if (config.efSearch) {
    router.setEfSearch(config.efSearch);
  }

  let count = 0;

  return {
    addPattern(pattern: HnswPattern): boolean {
      if (count >= HNSW_MAX_SAFE_PATTERNS) {
        throw new Error(
          `HNSW pattern limit reached (${HNSW_MAX_SAFE_PATTERNS}).`
        );
      }
      const metadataJson = JSON.stringify(pattern.metadata ?? {});
      // addPattern requires 3 args: (embedding, name, metadata_json)
      const ok = router.addPattern(pattern.embedding, pattern.name, metadataJson);
      if (ok) count++;
      return ok;
    },
    route(query: Float32Array, k = 3): HnswRouteResult[] {
      const raw = router.route(query, k);
      return Array.from(raw).map((r: any) => ({
        name: r.name ?? r.pattern_name ?? '',
        score: r.score ?? r.distance ?? 0,
        metadata: r.metadata ? (typeof r.metadata === 'string' ? JSON.parse(r.metadata) : r.metadata) : undefined,
      }));
    },
    clear(): void {

View on GitHub (pinned to fa13ee4ad6)