feat(nouser): Phase C — pseudo-embeddings + atracción por centroide
El "imán semántico" matemático del diseño Kairos, sin LLM. Cada
archivo se proyecta a un vector 32-d determinista derivado de sus
metadatos; cada Mónada calcula su centroide; archivos nuevos se
asignan por cosine similarity contra los centroides existentes.
Cambios:
- nouser-core dep nueva: blake3 (hash determinista de strings).
- crates/modules/nouser/core/src/embed.rs nuevo:
- EMBED_DIM = 32. Vector:
* dims 0..8: blake3(extension)
* dims 8..16: blake3(parent_dir)
* dims 16..24: blake3(file_stem)
* dims 24..28: tamaño (log + flags)
* dims 28..32: mtime (escala día + features cíclicas)
- Tip clave: hash bytes se centran a [-1, 1] (no [0, 1]). Sin
centrar, dos hashes random tendrían cosine ~0.75 espurio.
Centrados, expectativa ≈ 0 entre no-relacionados.
- APIs: embed, cosine_similarity, centroid, cohesion,
attraction_score, best_attraction. DEFAULT_ATTRACTION_THRESHOLD = 0.7.
- cluster::by_directory ahora computa el centroide de cada Mónada
y lo guarda en MonadManifest.centroid. El centroide viaja al
brahman-status vía DataFacet.centroid.
- bin nouser nuevo subcomando: attract <dir> <file>.
- Scan del dir, embedding del archivo objetivo, ranking de afinidad
contra Mónadas con centroide.
- 🧲 si la mejor supera umbral, · si es mejor pero debajo.
Validación end-to-end:
$ nouser attract crates/core crates/modules/nouser/core/src/embed.rs
🧲 0.9058 [01K..] src (ente-brain/src)
0.8984 [01K..] src (brahman-handshake/src)
...
$ nouser attract crates/core crates/modules/nouser/core/Cargo.toml
0.3427 [01K..] graph (ente-zero/src/graph)
(mejor score 0.3427 < umbral 0.7000 — no se 'pega')
7 tests nuevos en embed (determinismo, normalización, similitud
mismo-dir/mismo-ext, baja entre no-relacionados, centroide
unidad+coherente, attraction picks correctly, vacío skipeado).
Tests acumulados: 73. cargo check --workspace: 0 errores, 0 warnings.
Próximo: Phase D — nouser-nous, módulo aparte para LLM real.
Mock-nous determinista (basado en estos pseudo-embeddings) en
BRAHMAN_BROKER_CONTEXT=test; real-nous en prod. El switch lo hace
el broker via priority_contexts.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -17,6 +17,8 @@ use std::path::PathBuf;
|
||||
|
||||
use nouser_card::{FileEntry, Lens, MonadManifest};
|
||||
|
||||
use crate::embed;
|
||||
|
||||
/// Mínimo de archivos para que un directorio sea promovido a Mónada.
|
||||
/// Por debajo de eso, los archivos quedan "huérfanos" (no asignados).
|
||||
pub const DEFAULT_MIN_FILES_PER_MONAD: usize = 3;
|
||||
@@ -56,11 +58,18 @@ fn build_monad(parent: &std::path::Path, group: &[&FileEntry]) -> MonadManifest
|
||||
|
||||
let summary = build_summary(parent, group, &keywords);
|
||||
|
||||
// Centroide vectorial: promedio de los embeddings de los miembros.
|
||||
// Esto es lo que permite "atracción" determinista de archivos
|
||||
// nuevos sin tocar Nous.
|
||||
let member_vecs: Vec<Vec<f32>> = group.iter().map(|f| embed::embed(f).to_vec()).collect();
|
||||
let centroid = embed::centroid(&member_vecs);
|
||||
|
||||
let mut m = MonadManifest::new(label);
|
||||
m.summary = summary;
|
||||
m.keywords = keywords;
|
||||
m.dominant_lens = lens;
|
||||
m.entropy = entropy;
|
||||
m.centroid = centroid;
|
||||
m.members = group.iter().map(|f| f.id).collect();
|
||||
m.touch();
|
||||
m
|
||||
|
||||
Reference in New Issue
Block a user