Skip to content

feat(brain): runtime embedding config + incremental per-symbol embedding #499

Description

@ajianaz

Summary

Add runtime-configurable embedding backend selection and incremental per-symbol re-embedding to Brain Mode.

Currently, the embedding backend (hashing-trick 256d vs nomic pretrained 768d) is selected at compile time via #[cfg(feature = "pretrained-embed")]. This means users cannot switch backends without recompiling. Additionally, embed_project() re-embeds all symbols in a project on every index run, even when only 1 file changed.

Problem

  1. No runtime config — embedding backend is compile-time only, no .cora.yaml control
  2. Full re-embed on every indexembed_project() queries SELECT ... FROM symbols WHERE project_id = ? with no changed-symbol filter
  3. No brain section in .cora.yaml config schema

Proposed Changes

1. BrainConfig in .cora.yaml

brain:
  embedding: auto    # auto | hashing | pretrained
Value Behavior
auto (default) Best available: pretrained (if compiled) → hashing fallback
hashing Force 256d hashing trick (zero dependency)
pretrained Force nomic 768d (requires --features pretrained-embed)

New struct in src/config/schema.rs (~15 lines).

2. Runtime Embedding Dispatch

Refactor src/embed/mod.rs from compile-time #[cfg] to runtime enum dispatch:

pub enum Backend {
    Hashing,       // always available
    Pretrained,    // cfg(feature = "pretrained-embed")
}

pub fn resolve_backend(config: &BrainConfig) -> Backend { ... }

The embed_code_dispatch() function reads the resolved backend instead of compile-time cfg.

3. Incremental Per-Symbol Embedding

Migration v7: Add embed_fingerprint TEXT column to symbols table.

ALTER TABLE symbols ADD COLUMN embed_fingerprint TEXT;

brain.rs: embed_project() only embeds symbols whose name + signature hash differs from stored fingerprint:

// Before: embed ALL symbols
SELECT id, name, kind, signature FROM symbols WHERE project_id = ?1

// After: embed ONLY changed symbols
SELECT id, name, kind, signature FROM symbols
WHERE project_id = ?1 AND (
  embed_fingerprint IS NULL
  OR embed_fingerprint != ?
)

Performance Impact

Scenario Before (full re-embed) After (incremental)
Full index (cold, 1106 symbols) 0.9s 0.9s (same — all new)
1 file changed (~89 symbols) 0.6s ~0.05s
cora watch auto-reindex 0.6s per change ~0.05s per change

Benchmarked on Uteke (1106 symbols, 147 files) with nomic 768d backend.

Files to Change

File Change Est. Lines
src/config/schema.rs Add BrainConfig struct +15
src/config/loader.rs Add brain defaults +5
src/embed/mod.rs Runtime dispatch + Backend enum +50 (refactor)
src/index/schema.rs Migration v7: embed_fingerprint column +15
src/index/brain.rs Incremental embed in embed_project() +30 (refactor)
src/index/mod.rs Pass brain config to embed call +3

Total: ~120 lines changed across 6 files.

Testing

  • cargo test --features tree-sitter — all 903+ tests pass
  • cargo test --features tree-sitter,pretrained-embed — pretrained path tested
  • cargo clippy --all-targets --features tree-sitter -- -D warnings
  • cargo clippy --all-targets --features tree-sitter,pretrained-embed -- -D warnings
  • Manual test on Uteke (1106 symbols) — incremental embed is faster
  • Manual test on Corin (537 symbols) — brain search returns vector signal
  • Config test: .cora.yaml with brain: { embedding: hashing } forces 256d
  • Config test: .cora.yaml with brain: { embedding: pretrained } forces 768d
  • Config test: no brain section → auto default works
  • Migration test: existing DB upgrades cleanly to v7

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions