Files
brahman/CHANGELOG.md
T
Sergio 006640057a feat(sidecar): API reusable de discovery via broker
Promueve el patron ad-hoc discover_producer_socket que vivia inline en
'nouser attract --remote' a un modulo publico brahman_sidecar::discovery.
Cualquier consumer ahora puede preguntar al broker "quien provee este
TypeRef?" sin reimplementar el patron a mano.

API:
- build_consumer_card(label, flow_name, type_name) construye una Card
  minima (Ente, Oneshot, Virtual) con un input flow. Asigna Ulid::new()
  real (no nil), evitando colisiones en el broker.
- await_provider(card, timeout) async: conecta al init, espera
  MatchEvent::Available, devuelve producer_service_socket, manda
  Farewell. Ignora eventos Lost durante el await.
- await_provider_blocking(card, timeout) wrapper para mundos no-async
  (CLIs, std-thread loops). Crea su propio runtime current_thread.
- ConsumerError tipado: Connect{socket,source}, NoProvider{flow,type_ref,
  timeout}, Client(ClientError), Runtime(String). Adios al Box<dyn Error>.

Refactor en nouser daemon: discover_producer_socket inline (60 LOC) ->
5 LOC delegando en el helper. remote_embed ya no construye su propio
runtime.

Tests: 4 unitarios (id no-nil, id unico por llamada, formateo de Wit
TypeRef, fallback sin input). Build verde para sidecar y nouser-core.
2026-05-09 02:30:48 +00:00

48 KiB
Raw Blame History

Changelog

Registro cronológico de cambios sustantivos en el monorepo Brahman. Cada entrada lista las acciones concretas tras un commit; para detalles de ratio/diff ver git show <sha>.

2026-05-09

feat(sidecar): API reusable de discovery vía broker

Promueve el patrón ad-hoc discover_producer_socket (que vivía inline en nouser attract --remote) a un módulo público brahman_sidecar::discovery. Cualquier consumer puede ahora preguntar al broker "¿quién provee este TypeRef?" con dos llamadas:

// Construir un consumer Card mínimo (Ente, Oneshot, Virtual) let card = brahman_sidecar::build_consumer_card( "mi-cli", "embed-result", // flow.input.name "json", // TypeRef::Primitive { name } );

// Bloqueante (CLIs, std-thread loops): let socket: PathBuf = brahman_sidecar::await_provider_blocking( card, Duration::from_secs(3), )?; // O async (módulos con runtime tokio propio): let socket = brahman_sidecar::await_provider(card, timeout).await?;

API:

  • build_consumer_card(label, flow_name, type_name) -> Card abstrae la verbosidad del struct-literal repetido en cada caller. Genera un id: Ulid::new() real (no nil → seguro contra colisiones en el broker).
  • await_provider(card, timeout) -> Result<PathBuf, ConsumerError> conecta al init, espera MatchEvent::Available, devuelve producer_service_socket, manda Farewell. Ignora eventos Lost durante el await (no aplican al arranque).
  • await_provider_blocking(card, timeout) arma su propio runtime current_thread para mundos no-async.
  • ConsumerError con variantes tipadas: Connect { socket, source }, NoProvider { flow, type_ref, timeout }, Client(ClientError), Runtime(String). Adiós al Box<dyn Error> de antes.

Refactor en nouser daemon:

  • discover_producer_socket (60 LOC inline en bin/nouser.rs) → 5 líneas que delegan en el helper.
  • remote_embed ya no construye su propio runtime tokio.

Próximo consumer natural: nouser-explorer. Hoy renderea StatusSnapshot vía socket admin (introspección pura). El día que quiera interactuar con un Ente — p. ej., disparar un re-embed desde la UI — usa este helper para resolver el socket del provider sin hardcodear paths.

Nota sobre identidad: este commit fuerza Ulid::new() para los consumer Cards generados, evitando la trampa documentada del Card::default() que devuelve Ulid::nil(). La fijación global de Default queda como cleanup separado (requiere auditar que ningún caller dependa del determinismo de nil).

Tests: 4 unitarios nuevos en discovery::tests (id no-nil, id único por llamada, formateo de TypeRef::Wit, fallback sin input). Workspace verde.

feat(nouser+sidecar): watcher con debounce + re-publish al broker

Cierra las dos limitaciones del watcher previo: ya no spamea N veces por una sola edición, y el broker ve los cambios estructurales en lugar de quedarse con manifests congelados al arranque.

$ nouser daemon /tmp/x & $ touch /tmp/x/src/a.rs /tmp/x/src/b.rs /tmp/x/src/c.rs

daemon log (un solo batch, no 9 reacciones):

[watcher] ⚙ batch: 6 path(s) coalescidos → re-scan [watcher] ✦ x/src nace (3 miembros, lens=Code) [watcher] ⌃ delta: 1 nuevas, 0 refrescadas, 0 cerradas — 3 sesiones vivas

Mecánica del debounce (150ms):

  • spawn_fs_watcher arma dos threads: dispatcher filtra eventos notify Create/Modify/Remove a un canal de paths; coordinator mantiene HashMap<PathBuf, Instant> y dispara batch sólo cuando todos los paths llevan ≥150ms quietos.
  • Un :w típico de vim (~5 eventos por archivo) colapsa a 1 batch.

Mecánica del re-publish:

  • SidecarPool ahora trackea HashMap<Ulid, AbortHandle> indexado por Card.id. Llamar pool.spawn(card) con un id ya presente aborta la sesión previa y abre una nueva — spawn se vuelve idempotente: re-publicar una Mónada cuya composición cambió refresca su sesión en el broker sin dejar zombies.
  • Nueva API pool.drop_session(id) para cerrar una sesión explícitamente cuando una Mónada desaparece (directorio quedó bajo min_files o se borró).
  • pool.live_sessions() para introspección/logs.
  • process_change_batch re-scanea + re-clusteriza con hidratación, diffea contra prior_monads, y para cada Mónada decide:
    • removida → drop_session
    • nueva → spawn con ✦
    • composición cambió (members o centroid distintos) → spawn con ↻
    • idéntica → no-op

Trade-off aceptado: re-scan global por batch (no incremental). Es O(N archivos) por evento y para árboles típicos (<10k) cae en <100ms. Optimizar a re-cluster parcial cuando duela.

Tests: workspace completo verde.

feat(nouser): notify watcher — el sistema reacciona en tiempo real

El daemon ahora monta un notify::recommended_watcher recursivo sobre el directorio. Cada Create/Modify de archivo regular dispara: embedding del archivo, filtro por centroid_model, ranking contra centroides existentes, log con marker 🧲 / · según supere el umbral de atracción.

$ nouser daemon /tmp/x &

en otra terminal:

$ vim /tmp/x/src/nuevo.rs

daemon log:

[watcher] 🧲 /tmp/x/src/nuevo.rs → x/src (0.7470)

$ echo "edit" >> /tmp/x/docs/n1.md [watcher] 🧲 /tmp/x/docs/n1.md → x/docs (0.8169)

Mecánica:

  • DB pasa a Arc<Mutex<MonadDb>> para sharing con el thread del watcher.
  • Watcher en thread dedicado (nouser-watcher); reacciona sólo a Create/Modify, ignora Access/Metadata-only.
  • react_to_change(path, metadata, db) computa embedding, filtra por centroid_model, busca best attraction.
  • No re-publica al broker ni muta DB — sólo observa y narra. La invalidación selectiva (re-cluster + replace_monads + diff publish) queda como work futuro.

Limitación conocida: notify emite múltiples eventos por una sola edición (Create + Modify, etc.). Sin debounce, el watcher reporta varias veces. Aceptable para demo; production conviene debounce ~100ms por path.

Tests: 7 (card) + 24 (core) verdes, 0 errores, 0 warnings.

feat(nouser): hidratación del daemon vía sled + path_hint

El daemon ya no recomputa ciegamente al arrancar. Si la DB tiene Mónadas previas con centroid_model válido, las publica instantáneo y el re-scan reusa sus IDs vía path_hint.

Schema:

  • MonadManifest.path_hint: Option<String> — identidad estable derivada del origen (para by_directory, el parent dir canónico). Permite reusar ULID across re-scans.

Algoritmo (cluster):

  • Nueva fn cluster::by_directory_hydrated(files, min_files, prior: Option<&MonadDb>). Cuando hay prior, busca Mónada con mismo path_hint Y mismo centroid_model; si la encuentra, reusa id, lineage y created_at_ms.
  • by_directory queda como wrapper sin hidratación (back-compat).

Daemon (cmd_daemon):

  1. Open sled si NOUSER_DB_PATH existe.
  2. Publica las Mónadas previas con centroid_model válido (las inválidas se descartan con log explícito).
  3. Re-scan + by_directory_hydrated(prior=&db).
  4. Sólo spawnea sidecars para Mónadas con id que NO estaba en la hidratación inicial. Los path_hints existentes preservan identidad, evitando duplicados en el broker.
  5. Persiste el set actualizado.

Validación end-to-end:

$ NOUSER_DB_PATH=/tmp/h.sled nouser daemon crates/core

arranque 1: DB vacía

re-scan 102 archivos → 5 mónadas
1 ente + 5 mónadas vivas (5 nuevas vs hidratación)

$ NOUSER_DB_PATH=/tmp/h.sled nouser daemon crates/core

arranque 2: DB poblada

hidratadas 5 mónadas previas en O(1)
re-scan 102 archivos → 5 mónadas
1 ente + 5 mónadas vivas (0 nuevas vs hidratación)

Costo del arranque 2: ~0.06s user CPU. Antes (sin hidratación) era re-scan + cluster + spawn x N — segundos enteros para árboles grandes.

Tests: 7 (card) + 24 (core) verdes.

feat(nouser): centroid_model — versionado de embeddings

Protege contra el bug silencioso de mezclar centroides de modelos distintos (mock 32-d vs real 384-d), que daba scores sin sentido.

  • MonadManifest.centroid_model: Option<String> taggea qué modelo produjo el centroid. None = legacy pre-versioning.
  • nouser_core::embed::MODEL_ID = "nouser-pseudo-32d". El cluster lo setea en cada Mónada que genera.
  • nouser-nous-mock reusa la misma constante (use nouser_core::embed::MODEL_ID); produce vectores idénticos al cluster local, así que reportar el mismo ID es honesto.
  • nouser-nous-real reporta "real-fastembed-allMiniLML6V2-384d" (dim distinta, semántica distinta).
  • cmd_attract ahora:
    • Captura el model_id del embedding del target (local o remote).
    • Filtra Mónadas cuyo centroid_model no matchee.
    • Reporta embed: <source> (<model>) y skipped: N mónadas con centroid_model distinto cuando descarta.

Resultado operativo: cambiar de mock a real (vía BRAHMAN_BROKER_CONTEXT=prod) hace que attract filtre las Mónadas viejas con cero score en lugar de fingir que las puede comparar.

2026-05-08

chore: profile.dev slim — target/ ~50% más liviano

Cambios en [profile.dev] raíz para que builds futuras no desborden disco. Decisiones:

  • debug = "line-tables-only": stack traces correctos, drop del resto de symbols. Sin pérdida real para nuestro flujo.
  • split-debuginfo = "unpacked": relink más rápido, debuginfo en archivos aparte.
  • codegen-units = 256: paralelismo + builds incrementales chicas.
  • Override [profile.dev.package.X] para los pesados (gpui, ort, fastembed, tokenizers, image): opt-level = 1, debug = false. No los debuggeamos línea por línea, no necesitan info pesada.

Resultado: binarios ~3× más livianos. ente-zero 125→47 MB; mock-nous ~50→22 MB.

feat(nouser): dynamic binding — consumer descubre el provider vía broker

Cierra el bucle prometido por priority_contexts: el cliente ya no hardcodea el socket del provider de embeddings. En su lugar:

  1. Si NOUSER_NOUS_SOCKET está set, lo usa directo (atajo explícito).
  2. Si no, abre brahman_handshake::client::Client al brahman-init, anuncia un consumer Card mínimo con flow.input = embed-result:json, espera 3s por el primer MatchEvent::Available, y usa el producer_service_socket que viaja en el evento.

Esto activa el swap automático mock↔real:

  • BRAHMAN_BROKER_CONTEXT=test: el bias +1 en test del mock lo hace ganar; consumer recibe el socket del mock.
  • BRAHMAN_BROKER_CONTEXT=prod: el bias del real lo hace ganar.
  • Sin contexto: empate alfabético entre los presentes.

Validación end-to-end:

$ ente-zero & nouser-nous-mock & $ # Sin NOUSER_NOUS_SOCKET: $ nouser attract --remote crates/core archivo.rs embed: remote 🧲 0.9058 ente-brain/src ... (mock log confirma "embed_file path=...")

Cambios:

  • nouser-core Cargo.toml: deps directas brahman-handshake + tokio.
  • cmd_attract resuelve el socket por discovery antes de llamar a embed_via(&path, file) (mini-runtime tokio current_thread inline).

Bug que se descubrió en el camino: la "flakiness" reportada de cargo test --workspace era disco lleno (24 GB en target/), no condición de carrera. Con cargo clean + profile slim, todos los tests pasan deterministas.

feat(nouser): yahweh widget — nouser-explorer panel GPUI

Bin GPUI standalone que consulta brahman-admin cada 2s y renderea todas las sesiones del Init como cards. Cierra el círculo visual del ecosistema brahman.

  • Crate nuevo crates/apps/nouser-explorer (deps: brahman-admin, brahman-card, gpui).
  • Ventana 900×640 con header del estado del Init, banner de error cuando no conecta, y lista de cards (una por sesión).
  • Cada card muestra: kind + label + lifecycle, ULID corto, summary (si data), keywords, lens hint, service_socket si está, y refs (RelationshipKind → target_label). El borde izquierdo coloreado diferencia ente (azul) de data (lavanda).
  • cx.spawn(async move |this, cx| { … }) corre el loop de refresh en el GPUI executor; query_blocking se usa porque GPUI no provee un runtime tokio.
  • Nuevo helper en brahman-admin: client::query_blocking(path) — versión sync de query(), para callers con su propio executor.

Uso:

$ ente-zero & nouser daemon crates/core & $ cargo run -p nouser-explorer

ventana muestra ~6 cards en vivo, refrescando cada 2s.

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

feat(nouser): persistencia sled write-through del MonadDb

MonadDb ahora soporta backend dual:

  • MonadDb::new() → memoria pura (default, back-compat).
  • MonadDb::open(path) → sled-backed con cache en memoria. Carga contenido existente al abrir; cada insert_* hace write-through (cache + sled).

Diseño:

  • 2 trees sled: files y monads.
  • Wire format: serde_json (ergonomía + inspectability con sled-cli; los manifests son chicos, JSON gana sobre postcard aquí).
  • Reads SIEMPRE desde la cache — sled se consulta sólo al abrir.
  • replace_monads() purga el tree de sled antes de escribir.

Bin nouser: nueva env var NOUSER_DB_PATH. Si está set, persiste en esa ruta; si no, in-memory:

$ NOUSER_DB_PATH=/tmp/monads.sled nouser scan crates/core scan: 102 archivos en crates/core, 5 mónadas $ ls /tmp/monads.sled blobs conf $ NOUSER_DB_PATH=/tmp/monads.sled nouser scan crates/core

segunda corrida re-escribe la DB con el nuevo scan

Tests nuevos en db.rs:

  • persistence_roundtrip — escribe, cierra, reabre, datos están.
  • replace_monads_purges_persistent_tree — replace limpia el tree.

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

feat(sidecar): Phase B-3 — SidecarPool consolida en un runtime

Antes: cada spawn(card) creaba un thread + tokio runtime propio. Para módulos que publican muchas sesiones (nouser daemon con 50+ Mónadas) eso es 50 threads + 50 runtimes. Ahora: un thread + un runtime tokio current_thread que hostea N tasks de sidecar.

API nueva (aditiva, no rompe spawn/spawn_with_handle):

let pool = SidecarPool::new()?; pool.spawn(card1); pool.spawn(card2); pool.spawn_conscious(card_wit, wit); pool.spawn_with_config(SidecarConfig::new(c).with_wit(w)); // pool drop = todas las sesiones cierran.

run_client se hace pública para que el pool pueda enqueuar tasks externos al runtime con handle.spawn(run_client(config)).

nouser daemon migrado al pool. Verificación con ps -L:

$ ps -L -p $(pidof nouser) LWP CMD 28817 nouser # main thread 28819 brahman-sidecar # pool thread (todas las sesiones)

Antes serían 6+ LWP (1 main + N sesiones); ahora 2 fijos sin importar cuántas Mónadas se publiquen.

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. La UI puede cruzar el grafo sin discovery especial.

  • brahman-card:
    • RelationshipKind { Owns, OwnedBy, Processes, ProcessedBy, Sibling }.
    • CardReference { kind, target_id, target_label }target_label es cache del label en el momento de declarar (la UI puede pintar sin resolver).
    • Card.references: Vec<CardReference> y espejo en WireCard. Conversiones From propagan.
  • brahman-broker::BrokeredCard propaga references.
  • brahman-status imprime cada referencia: ref OwnedBy → label (id).
  • nouser daemon: cada Mónada que publica añade RelationshipKind::OwnedBy apuntando al engine. La declaración es unilateral; el engine no necesita conocer las 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...) summary: 6 archivos... [data] ... ente-brain/src ref OwnedBy → brahman.nouser_engine (01K...) ...

feat: Phase D-3 + D-4 — service_socket en Card, providers coexisten

Cierra el ciclo del swap automático de Nous (mock↔real):

  • Schema (brahman-card): Card.service_socket: Option<PathBuf> y espejo en WireCard. Conversiones From propagan. Es el path del data plane (distinto del socket del Init); cualquier consumer que matchee con esta Card puede conectar directo sin discovery adicional.
  • Broker (brahman-broker): BrokeredCard propaga service_socket desde la Card. Sin participación en el matching — sólo metadata para los observadores.
  • MatchEvent (brahman-handshake): nuevo campo producer_service_socket: Option<PathBuf>. Cuando el server emite Available, busca la BrokeredCard del productor en el broker y copia su service_socket. El consumer recibe la ruta completa para conectar.
  • Transport (nouser-nous): provider_socket_path(provider: &str) devuelve nouser-nous-{provider}.sock por default — mock y real coexisten en sockets distintos (Phase D-4). default_socket_path() conserva el comportamiento single-provider.
  • Providers: mock declara service_socket = /run/user/X/nouser-nous-mock.sock; real declara nouser-nous-real.sock. La Card se construye DESPUÉS del bind para que el path declarado sea el real.
  • Status: brahman-status imprime socket: por sesión cuando está presente.

Validación end-to-end:

$ ente-zero & nouser-nous-mock & nouser-nous-real & $ ls /run/user/1001/nouser-nous-*.sock nouser-nous-mock.sock nouser-nous-real.sock

$ brahman-status Sessions (2): [ente] ... nouser.nous_real socket: /run/user/1001/nouser-nous-real.sock in embed-request: Primitive { name: "json" } out embed-result: Primitive { name: "json" } [ente] ... nouser.nous_mock socket: /run/user/1001/nouser-nous-mock.sock in embed-request, out embed-result

Pendientes para futuro (no críticos):

  • nouser-core attract --remote todavía usa NOUSER_NOUS_SOCKET hardcoded o default_socket_path(). El siguiente paso es subscribirse al MatchEvent del broker y usar producer_service_socket directo — con eso BRAHMAN_BROKER_CONTEXT=test/prod swapea provider sin tocar al consumer.

refactor(nouser): labels de Mónada con 2 componentes del path

Resuelve la fricción visual de monorepos donde múltiples Mónadas se llamaban "src". Nueva función label_from_path toma los últimos hasta 2 componentes normales del path y los une con /.

$ 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.

feat(nouser): Phase D-2 — proveedor Nous real (LLM) detrás de feature flag

Cierra el ciclo del módulo Nous: existe un proveedor que produce embeddings reales con un modelo LLM, mientras que cargo build sin features sigue siendo liviano (no descarga ni compila ML deps).

Crate nuevo:

  • crates/modules/nouser/nous-real: bin con dos modos según feature.

    • Sin feature (default): stub. Bin compila en ~10s, arranca, sidecarea a brahman-init declarando la Card de real-nous, escucha en el socket Nous, y rechaza toda request con ErrorResponse { error: "compilado sin la feature embeddings. Rebuild con cargo build -p nouser-nous-real --features embeddings" }. cargo build --workspace sigue siendo limpio.
    • Con --features embeddings: pulls fastembed = "4". Ese crate arrastra ort 2.0.0-rc.9 (ONNX Runtime con binarios descargados por Cargo) + tokenizers 0.21 + ~30 deps más. Compila en ~50s. Modelo default: all-MiniLM-L6-v2 (384-d, descargado a ~/.cache/fastembed la primera vez).
    • EmbedText: pasa el texto al modelo, devuelve vector 384-d.
    • EmbedFile: lee primeros 8KiB con UTF-8 lossy, embed como texto. Para binarios el resultado no es semánticamente útil — caller decide.
    • Ping: devuelve model_id y embed_dim reales.
  • Card de real-nous:

    • label nouser.nous_real (distinto del mock para coexistir).
    • priority_contexts.prod = { priority_offset: +1 }. En contexto prod gana sobre el mock; en test el mock gana por su propio +1. Sin contexto activo, empate alfabético entre ambos.

Validación end-to-end con modelo real:

$ cargo build -p nouser-nous-real --features embeddings # ~50s $ ente-zero & nouser-nous-real & $ # probe vía python al socket Unix: $ echo '{"kind":"embed_text","payload":{"text":"hello brahman"}}'
| python3 -c "..." | head model: real-fastembed-allMiniLML6V2-384d elapsed_ms: 8 embed_dim: 384 first 5 values: [0.0034, -0.0036, 0.0078, -0.0218, -0.0162]

Tradeoff conocido: las dimensiones del mock (32-d) y real (384-d) son incompatibles. Cambiar de proveedor invalida los centroides cacheados de Mónadas. Documentar como "limpiar DB al cambiar proveedor".

Workspace state:

  • cargo build --workspace sigue limpio sin features (no ML).
  • cargo build -p nouser-nous-real --features embeddings funciona.
  • 0 errores, 0 warnings en ambos modos.

Pendientes para D-3 / futuro:

  • Discovery de socket: hoy el consumer hardcodea NOUSER_NOUS_SOCKET. Para que el broker brahman elija real vs mock per-contexto, falta inyectar el socket del provider electo en el MatchEvent o exponer un broker query "dame el socket de la sesión X".
  • Coexistencia: hoy los dos providers compiten por el mismo socket path por default. Habría que parametrizarlos a sockets distintos cuando coexistan.

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 entre mock y real (futuro) se hará vía priority_contexts en el broker.

Crates nuevos:

  • crates/modules/nouser/nous: contrato compartido. Tipos EmbedRequest, RequestKind { EmbedFile, EmbedText, Ping }, EmbedFilePayload, EmbedTextPayload, EmbedResponse, PingResponse, ErrorResponse. Wire format: line-delimited JSON por Unix socket, single-shot per conexión. Constants para los nombres de flow (embed-request/embed-result) y el tipo (json). Helper transport::default_socket_path() con env var 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:json/embed-result:json y un priority_contexts.test = { priority_offset: +1 } (gana sobre cualquier real-nous en contexto test). Bind del socket Nous, accept loop, despacha por RequestKind. EmbedFile usa nouser_core::embed::embed (los pseudo-embeddings de Phase C). Modelo: mock-pseudo-32d.

Cambios:

  • nouser-core: dep nueva nouser-nous. Subcomando attract ahora acepta --remote que abre un socket UnixStream blocking, envía un EmbedRequest y lee la response. Imprime embed: local|remote para que se vea cuál ruta corrió.

Validación end-to-end (un solo terminal, varios procesos):

$ ente-zero & $ nouser-nous-mock & $ NOUSER_MIN_FILES=5 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=crates/modules/nouser/core/src/embed.rs"

Bug encontrado y corregido en el camino:

  • ContextBias tenía #[serde(skip_serializing_if = ...)] en sus campos. Postcard NO soporta skip-condicional (formato no self-describing): el serializer omitía bytes que el deserializer esperaba, rompiendo la wire de cualquier Card con priority_contexts poblada.
  • Fix: removidos los skip_serializing_if de ContextBias. JSON pretty ahora emite {"pin_to": null, "priority_offset": 0} en lugar de objeto vacío. Trade-off aceptado por compatibilidad de wire.
  • Test nuevo en brahman-card: wirecard_postcard_with_priority_contexts que ejercita el roundtrip completo postcard.

Tests acumulados: 75 (card 12 +1 nuevo, broker 15, handshake 9, card-wit 4, admin 0, nouser-card 7, nouser-core 20, nouser-nous 2). cargo check --workspace: 0 errores, 0 warnings.

Próximo natural: Phase D-2 — real-nous con un modelo ONNX/Llama de text-embedding. La infraestructura ya está lista: declara la misma Card con priority_contexts.prod = { priority_offset: +1 } y el swap es transparente para el consumer.

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 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:
    • EMBED_DIM = 32. Estructura del vector:
      • dims 0..8: blake3(extension) → identidad de tipo
      • dims 8..16: blake3(parent_dir) → identidad de contenedor
      • dims 16..24: blake3(file_stem) → identidad léxica
      • dims 24..28: tamaño (log + flags)
      • dims 28..32: mtime (escala día + cíclicas)
    • Tip clave: bytes del hash se centran a [-1, 1] (no [0, 1]). Sin centrar, dos vectores hash random tendrían cosine ~0.75 espuria; 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 (promedio de embeddings de los miembros, L2-normalizado) y lo guarda en MonadManifest.centroid. El centroide viaja al brahman-status vía DataFacet.centroid → ahora se ven los Vec reales por cada Mónada.
  • bin nouser nuevo subcomando: attract <dir> <file>.
    • Escanea el dir, embeda el archivo objetivo, ranking de afinidad contra todas las Mónadas con centroide.
    • Marca 🧲 si la mejor supera el umbral, · si es la mejor pero debajo, espacio en blanco para el resto.

Validación end-to-end:

$ nouser attract crates/core crates/modules/nouser/core/src/embed.rs ranking de atracción (cosine similarity): 🧲 0.9058 [01K..] src (11 archivos en crates/core/ente-brain/src) 0.8984 [01K..] src (6 archivos en crates/core/brahman-handshake/src) 0.8918 [01K..] src (5 archivos en crates/core/ente-zero/src) ...

$ nouser attract crates/core crates/modules/nouser/core/Cargo.toml ranking: 0.3427 [01K..] graph (7 archivos en crates/core/ente-zero/src/graph) ... (mejor score 0.3427 < umbral 0.7000 — el archivo no se 'pega')

Tests: 20 en nouser-core (era 13, +7 de embed). Total acumulado: 73 (card 11, broker 15, handshake codec+tr 2 + integ 7, card-wit 4, admin 0, nouser-card 7, nouser-core 20, ente-card 0). cargo check --workspace: 0 errores, 0 warnings.

Próximo: Phase Dnouser-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 sin tocar nada más.

feat(nouser): Phase B-2 — daemon que publica Mónadas al Init

Cierra la unificación: 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, exactamente como buscaba el diseño.

Cambios:

  • crates/modules/nouser/core/Cargo.toml: deps nuevas brahman-card y brahman-sidecar.
  • crates/modules/nouser/core/src/bin/nouser.rs: subcomando daemon <dir>.
    • Spawna un sidecar para el "engine" (brahman.nouser_engine, kind=Ente) — el ser que produce y administra Mónadas.
    • Scan + cluster del dir.
    • Para cada Mónada, llama monad.to_brahman_card() y spawnea un sidecar (kind=Data). Cada Mónada es una sesión brahman propia con su ULID estable.
    • Park del thread principal: 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

El protocolo de presentación es uno solo: la Card. La función — anunciar identidad, exponer metadata, ser descubierto — es idéntica para procesos vivos y agrupaciones de datos. La UI lo ve como una lista uniforme.

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

Pendientes propuestos:

  • B-3: consolidar todos los sidecars en un único runtime tokio para no spawnear N threads.
  • C: pseudo-embeddings + atracción por centroide.
  • D: módulo nouser-nous para LLM, swappable por priority_contexts.
  • Polish: labels con 2-3 componentes del path.
  • Crossreferencia: que un Ente pueda anunciar "estoy procesando la Mónada X" y la Mónada anuncie "Ente Y me está procesando".

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

feat: Phase B-1 — unificación ontológica de Cards (Ente ↔ Data)

La Card es el protocolo de presentación del ecosistema, no sólo de los procesos. Una Mónada Nouser y un Ente Brahman son ambos "entidades que se presentan"; el consumidor (UI, broker, admin) discrimina por kind cuando importa, pero todos hablan el mismo idioma.

Cambios:

  • brahman-card:

    • CardKind { Ente (default), Data }. Conserva back-compat: Cards existentes son Ente por default.
    • DataFacet { summary, keywords, centroid, member_count, dispersion, presentation_hint }. Liviano para el wire — listas grandes (members, embeddings completos) se consultan al daemon dueño bajo demanda.
    • Card.kind y Card.data: Option<DataFacet> agregados. WireCard espeja, conversiones From propagan.
    • Default impl actualizado.
  • brahman-broker::BrokeredCard: propaga kind y data desde la Card registrada. No afecta el matching (sigue siendo por TypeRef + priority + pin_to); permite a observadores discriminar sin re-query.

  • nouser-card: depende ahora de brahman-card. Nuevo método MonadManifest::to_brahman_card() que proyecta:

    • id, label, lineage → directos.
    • payload Virtual, supervision Delegate, lifecycle Daemon (placeholder semántico — la Mónada no se ejecuta).
    • kind = Data.
    • data = Some(DataFacet) con summary, keywords, centroide, member_count, entropy → dispersion, y un presentation_hint derivado del Lens (Code"code", Gallery"gallery", etc.).
    • Test nuevo: projects_to_brahman_card.
  • brahman-status: cada sesión muestra ahora [ente] o [data] como prefijo. Para sesiones data, render adicional con summary, members

    • dispersion, keywords y lens hint.

Resultado: la UI (yahweh, brahman-status, futuro explorer) ve una sola lista uniforme. No tiene que saber si está mirando un proceso o un cúmulo de datos — sólo lee el Card y se adapta por kind.

Tests acumulados: 59 (card 11, broker 15, handshake codec+transport 2 + integ 7, card-wit 4, admin 0, nouser-card 7, nouser-core 13). cargo check --workspace: 0 errores, 0 warnings.

Próximo: Phase B-2 — bin nouser daemon <dir> que sidecarea cada Mónada como una sesión brahman, publicándola al broker. Brahman-status las verá junto a los entes.

feat(nouser): Phase A — mecanismo determinista de Mónadas

Primer trozo del módulo Nouser (Kairos): explorador de Mónadas como "imanes semánticos" sobre el filesystem. Phase A cubre el 90% de los casos sin tocar IA — sólo metadatos y heurísticas.

Crates nuevos:

  • crates/modules/nouser/card: MonadManifest (la Tarjeta de Presentación de una Mónada — espejo conceptual de brahman::Card pero para datos, no para procesos runtime). Campos: id (Ulid), label, summary, centroid (vacío en Phase A), keywords, cardinality, entropy [0,1], dominant_lens, pins, members, timestamps, extensions (forward-compat). 6 tests de validación + JSON roundtrip.
  • crates/modules/nouser/core: pipeline determinista.
    • scanner: walkdir → Vec<FileEntry> con metadatos (path, size, mtime, extension). Skipea hidden por default. Configurable max depth y follow_links.
    • cluster::by_directory: agrupa por parent dir, mínimo 3 archivos para promover a Mónada (configurable). Calcula keywords (top-N extensiones por frecuencia + alfabético), elige Lens dominante (Code/Gallery/Markdown/Database/Grid) según extensión más frecuente, computa entropía de Shannon normalizada [0,1].
    • db: MonadDb en memoria con índices BTreeMap files/monads y resolve_members(monad_id) que filtra IDs huérfanos. Phase B traerá persistencia.
    • bin nouser: subcomandos scan <dir>, show <dir> <prefix>, json <dir>. Env var NOUSER_MIN_FILES para tunear 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 ...

$ nouser show crates 01KR4C Monad 01KR4C1370DVF6NMTW6SECNXAF label: src summary: 4 archivos en crates/modules/nouser/core/src (ext: rs) cardinality: 4 entropy: 0.0000 lens: Code members (4): 4132 bytes crates/modules/nouser/core/src/db.rs ...

Pendientes para próximas fases (anotados, no urgentes):

  • Phase B: bin nouser daemon que sidecarea a brahman-init declarando flows (scan-request:jsonmonad-update:json).
  • Phase C: pseudo-embeddings deterministas (hash de path/ext/size a 32-d) + atracción por centroide via cosine similarity. Implementa el "imán" sin LLM.
  • Phase D: módulo nouser-nous aparte para el LLM real (Llama/ONNX). En priority_contexts.test el Init pinea a mock-nous (embeddings determinísticos); en prod a real-nous.
  • Polish: labels de Mónada incluir 2-3 componentes del path para desambiguar src/ repetidos en monorepo.

Workspace: 0 errores, 0 warnings. Tests acumulados: 58 (card 11, broker 15, handshake codec+transport 2 + integ 7, card-wit 4, admin 0, nouser-card 6, nouser-core 13).

feat(broker): priority contexts — biases per-contexto operativo

  • brahman-card::ContextBias { pin_to: Option<String>, priority_offset: i8 } declara un override per-contexto.
  • Card.priority_contexts: BTreeMap<String, ContextBias> y mismo en WireCard (cruza el wire). Las conversiones From lo propagan.
  • BrokerConfig.current_context: Option<String>. Cuando el broker corre bajo un contexto y una Card declara biases para ese nombre, se aplican:
    • Como consumidor: pin_to sobreescribe el Flow.pin_to estático.
    • Como productor: priority_offset se suma a la priority base (clamp en [Low=0, Critical=3]) para el ranking.
  • BrokeredCard propaga priority_contexts. find_producer_for usa effective_priority(card) y effective_pin(card, input) antes de los tiebreaks.
  • brahman-admin::AdminConfig.current_context + StatusSnapshot.current_context espejan el contexto activo. brahman-status lo imprime como Context: <nombre> justo debajo de Init: ....
  • ente-zero lee BRAHMAN_BROKER_CONTEXT env var y la propaga al broker y al admin. Sin var, biases per-contexto inactivos.
  • 4 tests nuevos en brahman-broker: context_priority_offset_lifts_producer_above_alphabetic_winner, context_pin_to_overrides_static_pin, unknown_context_no_op, priority_offset_clamps_to_critical.
  • Validación end-to-end: BRAHMAN_BROKER_CONTEXT=test ente-zerobrahman-status muestra Context: test.

feat(card): WireCard + extensions — forward-compat sin romper postcard

  • Card.extensions: BTreeMap<String, serde_json::Value> restaurado con #[serde(flatten, default, skip_serializing_if = is_empty)]. Los campos JSON/TOML desconocidos sobreviven el roundtrip de archivos.
  • Nuevo WireCard: proyección postcard-friendly (sin extensions, genesis: Vec<WireCard> recursivo). Conversiones From<Card> y From<WireCard> con descarte/recreación de extensions.
  • brahman-handshake::Hello.card pasa de Card a WireCard. Client hace card.into() antes de enviar; Server hace hello.card.into() para volver a Card antes de validar/registrar.
  • 3 tests nuevos en brahman-card: extensions_preserved_in_json_roundtrip, wire_card_roundtrip_strips_extensions, wire_card_postcard_friendly (postcard encode/decode efectivo).
  • brahman-card gana postcard como dev-dep para el último test.
  • Contrato documentado: extensions = anotaciones locales que NO cruzan al Init; sólo viven en archivos.

9420eae chore: limpia warnings dead-code en arje (commit del usuario)

  • ente-zero/src/events.rs: #![allow(dead_code)] a nivel módulo — es vocabulario de eventos con variantes/campos reservados para flujos no cableados aún (CapabilityRequested, ShutdownReason::Signal, CapabilityGrant::{Granted, Denied, QuotaExceeded}, ExitStatus fields).
  • ente-zero/src/graph/mod.rs: comentado el re-export ahora innecesario de SHUTDOWN_GRACE. DEFAULT_GRANT_TTL con #[allow(dead_code)]
    • nota "reservado para capability granting".
  • ente-zero/src/graph/capabilities.rs: renew_grant con #[allow(dead_code)] (capability renewal pendiente).
  • ente-kernel/src/surface.rs: drop de use anyhow::Context (no se usaba).
  • ente-hostnamed-compat/src/main.rs: drop de Connection (no se usaba).
  • ente-polkit-compat/src/main.rs: PolicyDecision.source con #[allow(dead_code)] (sólo aparece en Debug para logging).
  • cargo check --workspace: 17 warnings → 0.

feat(sidecar): WIT al sidecar — módulos conscientes vivos

  • brahman-card::WitInterface deriva Serialize, Deserialize, PartialEq, Eq para cruzar el wire postcard.
  • brahman-handshake::Hello lleva wit: Option<WitInterface>. Server usa ResolvedCard::from_conscious cuando viene presente, from_agnostic cuando no.
  • brahman-handshake::Client::connect queda como wrapper agnóstico de connect_with(path, card, wit: Option<WitInterface>).
  • brahman-broker::Broker::register ahora toma Option<WitInterface> como tercer arg. BrokeredCard guarda el wit. 25 sitios de tests actualizados con , None.
  • brahman-sidecar::SidecarConfig con campo wit. Helpers nuevos: SidecarConfig::new(card).with_wit(wit) y spawn_conscious(card, wit). El log attached reporta conscious=true|false.
  • brahman-status muestra marker 🧠 + sección wit: (package/world, imports, exports) por sesión consciente.
  • Example nuevo crates/shared/brahman-sidecar/examples/presence-conscious.rs: toma label + path .wit (default shared_wit/protocol.wit), parsea con brahman-card-wit, spawna sidecar consciente.
  • Validado end-to-end:
    $ presence-conscious demo.conscious shared_wit/protocol.wit &
    $ brahman-status
    Sessions (1):
      01K... demo.conscious 🧠  lifecycle=Daemon
          wit: brahman:protocol@0.1.0 / module
               imports: types, handshake, lifecycle
               exports: run
    

feat(core): brahman-card-wit — extractor opcional de contratos WIT

  • Crate nuevo crates/core/brahman-card-wit con wit-parser = "0.230".
  • API: parse_wit(source) y parse_wit_file(path) devuelven Vec<WitInterface> (uno por world declarado).
  • Interfaces importadas/exportadas (no sólo funciones) se resuelven por nombre via resolve.interfaces[id].name.
  • Example crates/core/brahman-card-wit/examples/brahman-wit-info.rs CLI: brahman-wit-info shared_wit/protocol.wit → lista paquete, worlds, imports y exports.
  • 4 tests: inline, archivo real (shared_wit/protocol.wit), parse error, world vacío.
  • Validado contra protocol.wit: detecta worlds module y admin-host con sus imports/exports correctos.

7b589b8 chore: agrega CHANGELOG.md retroactivo

  • CHANGELOG.md en la raíz con los 11 commits previos documentados acción por acción. A partir de este punto, cada cambio sustantivo actualiza también este archivo en el mismo commit.

8a83a26 feat(handshake): notificación push de matches

  • Frame MatchEvent { kind: Available | Lost, ... } añadido al protocolo.
  • Session::run_post_handshake usa tokio::select! para multiplexar reads del cliente y un canal mpsc push del server.
  • Server: SessionTxTable (Arc<Mutex<HashMap<SessionId, Sender>>>) y LastMatches para diff por sesión. broadcast_match_diffs corre tras cada register y unregister, emite sólo los cambios.
  • Capacity del canal push: 32 (ephemeral, try_send non-blocking).
  • Client: VecDeque<MatchEvent> interno, take_event() (non-blocking) y await_event(timeout). ping() ahora drena MatchEvents intermedios hasta encontrar el Pong.
  • Example crates/core/brahman-handshake/examples/subscriber.rs.
  • Test match_event_pushed_on_producer_arrival (handshake integ 6→7).

70a7a0d feat: segundo módulo (nakui) + admin API + brahman-status

  • Crate nuevo crates/shared/brahman-sidecar (DRY del thread + tokio + ping loop). API: spawn(card) / spawn_with_handle(config).
  • nakui cmd_run llama brahman_sidecar::spawn antes de run_server. Card: lifecycle Daemon, supervision Restart, flow command (json) / report (json).
  • Crate nuevo crates/core/brahman-admin con StatusSnapshot JSON line-delim, AdminServer y client::query.
  • ente-zero levanta también el AdminServer en primordial_loop.
  • Example crates/shared/brahman-sidecar/examples/presence.rs (módulo dummy long-lived parametrizable por label).
  • Example crates/core/brahman-admin/examples/brahman-status.rs (CLI que pretty-printa el snapshot).
  • brahman-broker: BrokeredCard ahora incluye lifecycle. Endpoint y Match derivan Serialize/Deserialize. Nuevo Broker::cards() iterador.
  • brahman-card: pub use ::ulid para que módulos no dependan de ulid.
  • yahweh-shell migrado al sidecar compartido (96→53 LOC).

595f68e feat(yahweh-shell): primer módulo brahman vivo

  • yahweh-shell spawnea sidecar antes de Application::new().
  • Card declarada: label brahman.ui_engine, lifecycle Widget, supervision Delegate, payload Virtual, flow input render-data (json) / output user-intent (json).
  • Sidecar en thread aparte con tokio current_thread runtime, desacoplado del runtime GPUI.

df9d10c feat(ente-zero): enchufa el handshake server al Init real

  • ente-zero levanta brahman_handshake::server::Server::bind en primordial_loop después del ente-bus, con degradación grácil si bind falla (mismo patrón que uevents).
  • Nuevo módulo brahman-handshake/src/transport.rs: helper default_socket_path() con resolución BRAHMAN_INIT_SOCKETXDG_RUNTIME_DIRTMPDIR.
  • Example crates/core/brahman-handshake/examples/probe.rs.
  • Validación end-to-end manual: probe contra ente-zero vivo imprime HelloAck: session=... init_attached=true.

07d77a3 feat(handshake): integra el broker con el ciclo de sesiones

  • ServerConfig acepta Option<Arc<Mutex<Broker>>>.
  • register_session indexa la Card en el broker y la SessionRegistry antes de emitir HelloAck.
  • Session::handle refactor a do_handshake → run_post_handshake → cleanup con cleanup unificado (broker + sessions).
  • Tests integ nuevos: broker_registers_and_unregisters_with_session y broker_matches_two_live_modules.
  • Fix colateral: brahman-card::TypeRef pasa de internally-tagged (#[serde(tag = "kind")]) a externally-tagged. Postcard no soporta internally-tagged en formatos no self-describing. JSON cambia de {"kind":"primitive","name":"x"} a {"primitive":{"name":"x"}}.

5091106 feat(core): brahman-broker — matching híbrido

  • Crate nuevo crates/core/brahman-broker.
  • 3 estrategias de matching: Exact, Structural, ExactThenStructural (default). Devuelven Match::via con la estrategia que ganó.
  • Override pin_to: el consumer pide un productor por label; si la pista no resuelve, cae en type-search.
  • Tiebreak por Card.priority desc, luego label asc (estable y determinista).
  • API: register, unregister, find_producer_for, all_matches, cards, sessions, len, is_empty.
  • 11 tests (matching, pin_to, priority, no-self-loops, all-matches).

814390f feat(core): brahman-handshake — protocolo runtime

  • Crate nuevo crates/core/brahman-handshake con server y client Rust↔Rust sobre Unix socket.
  • Frames length-prefixed (4 bytes LE) + cuerpo postcard.
  • Mensajes: Hello, HelloAck, Ping, Pong, Farewell, Error.
  • MAX_FRAME_BYTES = 4 MiB para evitar reservas absurdas.
  • Tradeoff: drop extensions/extra de Card por incompat postcard ↔ serde_json::Value. Forward-compat queda en schema_version + protocol_version negotiation.
  • 4 tests integ + 1 unit en codec.

ed0e973 refactor(arje): migra ente-card a re-export de brahman-card

  • ente-card/src/lib.rs reescrito como crate-shim de re-export (327 LOC → 25 LOC).
  • EntityCardbrahman_card::Card por type alias.
  • ente-card/Cargo.toml: deps reducidas a brahman-card.
  • Card impl Default (Ulid::nil(), label vacío) para que ..Default::default() funcione en struct-literals.
  • 4 sitios en ente-zero/src/seed.rs actualizados con ..Default::default() para los campos aditivos.
  • Los 21 consumidores arje compilan sin tocar fuente.

0feba74 feat(core): brahman-card — Tarjeta canónica híbrida

  • Crate nuevo crates/core/brahman-card.
  • Hereda de arje: id: Ulid, lineage, Capability tipado, Payload::{Wasm, Native, Virtual, Legacy}, SomaSpec (namespaces, cgroups, rlimits, cpu_affinity), Supervision (Restart con backoff, OneShot, Delegate), genesis recursivo.
  • Aditivo brahman: Permissions enumerados (NetworkingPolicy, FsPolicy, IpcPolicy), Lifecycle ortogonal a Supervision, Priority de scheduling, Flows con TypeRef discriminado (Primitive | Wit), pin_to opcional.
  • TrustLevel derivado de Permissions (no declarado).
  • ResolvedCard { card, wit: Option<WitInterface>, trust }.
  • Soporta JSON (canónico) + TOML (auto-detectado por extensión).
  • 8 tests incluido arje_seed_format_compatible que valida que el JSON de arje sigue parseando con defaults para los aditivos.

4d50bfc chore: absorbe nakui (ERP matemático) en modules/nakui

  • ~/nakuicrates/modules/nakui/{core,modules}.
  • core/: el crate nakui-core con 4 bins (nakui, demo, inventory_demo, sales_demo) y tests.
  • modules/{inventory,sales,treasury}/: data declarativa (nsmc.json, schema.k, morphisms/) que el crate consume. No son crates Cargo.
  • Deps directas (no workspace = true): thiserror v1, surrealdb, rhai, petgraph. No conflicto con el resto del workspace.

53dbdf0 chore: monorepo inicial con arje + minga + yahweh absorbidos

  • 45 crates absorbidos en 4 ejes:
    • crates/core/: 24 crates de arje (Init systemd-compatible: ente-card, ente-zero, ente-kernel, ente-bus, ente-cas, ente-soma, ente-wasm, ente-snapshot, ente-brain, ente-echo, ente-policy-provider, + 12 *-compat).
    • crates/modules/semantic_dht/: 5 crates de minga (minga-core con AST/CAS/MST, minga-p2p con libp2p Kad, minga-store, minga-vfs, minga-cli).
    • crates/modules/ui_engine/: 11 crates de yahweh (libs/{core, theme, bus, providers}, widgets/{tree, splitter, tabs, tiled, container_core, text_input}).
    • crates/apps/: 5 crates de yahweh (file_explorer, database_explorer, text_viewer, image_viewer, yahweh-shell).
  • shared_wit/protocol.wit con handshake/lifecycle inicial.
  • Cargo.toml unificado: thiserror bumped a 2 (transparente para arje), tokio "full", paths intra-workspace de yahweh redirigidos.
  • cargo check --workspace: 0 errores (sólo dead-code warnings preexistentes en ente-zero).