Commit Graph

6 Commits

Author SHA1 Message Date
Sergio b3feaf667c feat: Crossreferencia — Card.references como grafo del fractal
Las Cards ahora declaran sus relaciones con otras Cards. El Engine
posee Mónadas; las Mónadas declaran que son poseídas por el Engine.

- brahman-card:
  - RelationshipKind { Owns, OwnedBy, Processes, ProcessedBy, Sibling }
  - CardReference { kind, target_id: Ulid, target_label: String }.
    target_label es cache para que la UI renderee sin resolver.
  - Card.references: Vec<CardReference> + espejo en WireCard.
    Conversiones From propagan.
- brahman-broker::BrokeredCard propaga references.
- brahman-status imprime "ref OwnedBy → label (id)" por sesión.
- nouser daemon: cada Mónada publicada añade OwnedBy apuntando al
  engine. Declaración unilateral — el engine no necesita conocer
  Mónada IDs de antemano.

Validación end-to-end:
  $ ente-zero & nouser daemon crates/core
  $ brahman-status
  Sessions (6):
    [ente]  brahman.nouser_engine
    [data]  brahman-handshake/src
        ref OwnedBy  →  brahman.nouser_engine  (01K...)
    [data]  ente-brain/src
        ref OwnedBy  →  brahman.nouser_engine  (01K...)
    ...

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 19:44:47 +00:00
Sergio 794884a90f refactor(nouser): labels de Mónada con 2 componentes del path
Resuelve la fricción visual de monorepos donde múltiples Mónadas
quedaban con label "src" (ambiguo). Nueva función label_from_path
toma los últimos hasta 2 componentes normales del path:

  $ nouser scan crates/core
    [01K..] brahman-admin/src      card=5
    [01K..] brahman-handshake/src  card=6
    [01K..] ente-brain/src         card=11
    [01K..] ente-kernel/src        card=4

Tests añadidos: label_from_root_only_one_component,
label_from_deep_path_takes_last_two. Tests existentes actualizados
con los nuevos labels (proj/src en lugar de src).

22 tests en nouser-core (era 20, +2).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 19:28:19 +00:00
Sergio b3c3c00cf2 feat(nouser): Phase D — proveedor Nous mock + cliente remoto
Cierra el patrón "Nous como módulo aparte intercambiable": el contrato
del proveedor de embeddings vive en su crate, el mock determinístico
implementa ese contrato sirviéndolo por Unix socket, y nouser-core
sabe consumirlo remotamente. El switch mock↔real (futuro) será vía
priority_contexts en el broker.

Crates nuevos:

- crates/modules/nouser/nous: contrato compartido.
  - EmbedRequest { kind: { EmbedFile | EmbedText | Ping }, payload }.
  - EmbedFilePayload (path, ext, size, mtime), EmbedTextPayload.
  - EmbedResponse (embedding, model, elapsed_ms), PingResponse,
    ErrorResponse.
  - Wire: line-delimited JSON sobre Unix socket, single-shot.
  - Constants FLOW_EMBED_REQUEST, FLOW_EMBED_RESULT, FLOW_TYPE_NAME.
  - transport::default_socket_path con env NOUSER_NOUS_SOCKET.

- crates/modules/nouser/nous-mock: bin nouser-nous-mock.
  - Sidecarea a brahman-init con Card kind=Ente declarando los flows
    embed-request/embed-result + priority_contexts.test = +1.
  - Bind del socket Nous + accept loop tokio.
  - EmbedFile delega a nouser_core::embed::embed (Phase C).
  - Modelo: "mock-pseudo-32d".

Cambios:

- nouser-core: dep nueva nouser-nous. Subcomando attract --remote
  abre un UnixStream blocking, envía EmbedRequest, lee response.
  Imprime "embed: local|remote" para ver cuál ruta corrió.

Bug encontrado y corregido:
- ContextBias tenía #[serde(skip_serializing_if = ...)] en sus campos.
  Postcard NO soporta skip-condicional en formatos no self-describing:
  el serializer omitía bytes que el deserializer esperaba, rompiendo
  la wire de cualquier Card con priority_contexts poblada.
  Síntoma: "postcard decode: Hit the end of buffer" en el server,
  "early eof" en el cliente.
- Fix: removidos los skip_serializing_if de ContextBias. JSON pretty
  ahora emite {"pin_to": null, "priority_offset": 0} pero el wire
  funciona. Trade-off aceptado.
- Test wirecard_postcard_with_priority_contexts en brahman-card que
  ejercita el roundtrip postcard con biases poblados.

Validación end-to-end:
  $ ente-zero & nouser-nous-mock & nouser daemon crates/core
  $ brahman-status
  Sessions (7):
    [ente] nouser.nous_mock      flows: embed-request, embed-result
    [ente] brahman.nouser_engine
    [data] src   summary: 6 archivos en crates/core/brahman-handshake/src
    [data] graph summary: 7 archivos en crates/core/ente-zero/src/graph
    ...
  $ nouser attract --remote crates/core <archivo>.rs
    embed: remote
    🧲  0.9058  src  ...
  (mock log: embed_file path=...)

Tests: 75. cargo check --workspace: 0 errores, 0 warnings.

Próximo natural: Phase D-2 — real-nous con ONNX/Llama text-embedding.
Declara la misma Card con priority_contexts.prod = +1 y el swap es
transparente para el consumer.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 18:49:25 +00:00
Sergio 77faf12e82 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>
2026-05-08 18:31:04 +00:00
Sergio 85886b7a3c feat(nouser): Phase B-2 — daemon que publica Mónadas al Init brahman
Cierra la unificación ontológica de B-1: el nouser daemon se
sidecarea como Ente y publica cada Mónada como su propia sesión Data.
Un solo brahman-status muestra procesos y datos en la misma lista.

Cambios:

- nouser-core gana deps brahman-card + brahman-sidecar.
- bin nouser nuevo subcomando: daemon <dir>.
  1. Spawna sidecar para el engine (brahman.nouser_engine, kind=Ente):
     el "ser" que produce y administra Mónadas.
  2. Scan + cluster del directorio.
  3. Para cada Mónada, monad.to_brahman_card() + sidecar (kind=Data).
     Cada Mónada es una sesión brahman propia, con su ULID estable.
  4. Park del main thread; los sidecars siguen pingueando.

Validación end-to-end:

  $ ente-zero &
  $ NOUSER_MIN_FILES=5 nouser daemon crates/core &
  $ brahman-status

  Sessions (6):
    [ente]  ... brahman.nouser_engine  lifecycle=Daemon
    [data]  ... src    summary: 5 archivos en crates/core/brahman-admin/src
                       members: 5 (dispersion=0.00)  lens hint: code
    [data]  ... src    summary: 11 archivos en crates/core/ente-brain/src
    [data]  ... graph  summary: 7 archivos en crates/core/ente-zero/src/graph
    ...

La función de presentarse es la misma para procesos y datos. UI ve
una lista uniforme y discrimina por `kind` cuando le importa.

Costo conocido: cada Mónada consume thread + tokio runtime
current_thread (legacy del sidecar API). Para escalar a >50 Mónadas
conviene consolidar en un único runtime con N tasks. Defer a B-3.

Pendientes propuestos (en CHANGELOG):
- B-3: consolidar sidecars en un solo runtime.
- C: pseudo-embeddings + atracción por centroide.
- D: módulo nouser-nous para LLM, swappable por priority_contexts.
- Polish: labels con 2-3 componentes de path.
- Crossreferencia: Ente anuncia "procesando Mónada X", Mónada anuncia
  "siendo procesada por Ente Y".

cargo check --workspace: 0 errores, 0 warnings.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 18:24:50 +00:00
Sergio 7bdc26e61a feat(nouser): Phase A — mecanismo determinista de Mónadas
Primer trozo de Nouser/Kairos: explorador de Mónadas como agrupaciones
semánticas sobre el filesystem, sin tocar IA todavía. Cubre el 90% de
los casos con heurísticas puras.

Crates nuevos:

crates/modules/nouser/card:
- MonadManifest: la Tarjeta de Presentación de una Mónada. Espejo
  conceptual de brahman::Card pero para datos: id (Ulid), label,
  summary, centroid (vacío en Phase A), keywords, cardinality, entropy
  [0,1], dominant_lens (Grid|Code|Gallery|Database|Markdown|Tree),
  pins, members, timestamps, extensions (forward-compat).
- Diferencia explícita en docs: brahman::Card describe entidades
  runtime con payload/soma/supervision; MonadManifest describe una
  agrupación de datos sin proceso atrás.
- Validación: schema_version, label no vacío, entropy en rango,
  cardinality consistente con members.len().
- 6 tests (validación + JSON roundtrip).

crates/modules/nouser/core:
- scanner::scan_directory: walkdir → Vec<FileEntry> con metadatos.
  Skipea hidden por default; configurable max_depth y follow_links.
- cluster::by_directory: agrupa archivos por parent dir, mínimo 3
  para promover a Mónada (configurable). Computa keywords (top-N
  extensiones por freq + alfabético), elige Lens dominante por
  extensión más frecuente, entropía de Shannon normalizada.
- db::MonadDb: store en memoria con índices BTreeMap.
  resolve_members filtra IDs huérfanos.
- bin nouser con subcomandos scan, show, json. Env var
  NOUSER_MIN_FILES para el threshold.
- 13 tests (4 scanner + 6 cluster + 3 db).

Demo end-to-end:

  $ nouser scan crates
  scan: 255 archivos en crates, 19 mónadas (min_files=3)
    [01KR4C13] src       card=12  ent=0.00  lens=Code  keywords: rs
    [01KR4C13] tests     card=14  ent=0.00  lens=Code  keywords: rs
    [01KR4C13] fixtures  card=5   ent=0.00  lens=Grid  keywords: rhai

Pendientes (anotados en CHANGELOG, no urgentes):
- Phase B: bin nouser daemon que sidecarea a brahman-init.
- Phase C: pseudo-embeddings de metadatos + atracción por centroide.
- Phase D: módulo nouser-nous para el LLM real, swappable por
  priority_contexts (mock-nous en test, real-nous en prod).
- Polish: labels con 2-3 componentes del path.

cargo check --workspace: 0 errores, 0 warnings.
Tests acumulados: 58.

CHANGELOG.md actualizado.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 18:03:49 +00:00