From 85886b7a3c5a997e35b52881560a765c74262733 Mon Sep 17 00:00:00 2001 From: Sergio Date: Fri, 8 May 2026 18:24:50 +0000 Subject: [PATCH] =?UTF-8?q?feat(nouser):=20Phase=20B-2=20=E2=80=94=20daemo?= =?UTF-8?q?n=20que=20publica=20M=C3=B3nadas=20al=20Init=20brahman?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 . 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) --- CHANGELOG.md | 54 ++++++++++++++++ Cargo.lock | 2 + crates/modules/nouser/core/Cargo.toml | 2 + crates/modules/nouser/core/src/bin/nouser.rs | 66 +++++++++++++++++++- 4 files changed, 123 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 24feb81..0f4e4c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,60 @@ ratio/diff ver `git show `. ## 2026-05-08 +### 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 `. + - 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 diff --git a/Cargo.lock b/Cargo.lock index baaf039..9bd57dc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6060,6 +6060,8 @@ dependencies = [ name = "nouser-core" version = "0.1.0" dependencies = [ + "brahman-card", + "brahman-sidecar", "nouser-card", "serde", "serde_json", diff --git a/crates/modules/nouser/core/Cargo.toml b/crates/modules/nouser/core/Cargo.toml index 023d764..edfad77 100644 --- a/crates/modules/nouser/core/Cargo.toml +++ b/crates/modules/nouser/core/Cargo.toml @@ -10,6 +10,8 @@ description = "Nouser — explorador de Mónadas: scanner, clustering determinis [dependencies] nouser-card = { path = "../card" } +brahman-card = { path = "../../../core/brahman-card" } +brahman-sidecar = { path = "../../../shared/brahman-sidecar" } serde = { workspace = true } serde_json = { workspace = true } thiserror = { workspace = true } diff --git a/crates/modules/nouser/core/src/bin/nouser.rs b/crates/modules/nouser/core/src/bin/nouser.rs index 03fa85d..f073033 100644 --- a/crates/modules/nouser/core/src/bin/nouser.rs +++ b/crates/modules/nouser/core/src/bin/nouser.rs @@ -34,6 +34,7 @@ fn main() -> ExitCode { "scan" => cmd_scan(rest), "show" => cmd_show(rest), "json" => cmd_json(rest), + "daemon" => cmd_daemon(rest), "--help" | "-h" | "help" => { print_usage(&prog); return ExitCode::SUCCESS; @@ -61,9 +62,11 @@ fn print_usage(prog: &str) { eprintln!(" scan recorre un directorio y lista las Mónadas detectadas"); eprintln!(" show scan + detalle de la Mónada cuyo ID empieza con "); eprintln!(" json scan + dump JSON de todos los manifests"); + eprintln!(" daemon scan + sidecarea cada Mónada al Init brahman"); eprintln!(); eprintln!("env:"); - eprintln!(" NOUSER_MIN_FILES mínimo de archivos por Mónada (default: 3)"); + eprintln!(" NOUSER_MIN_FILES mínimo de archivos por Mónada (default: 3)"); + eprintln!(" BRAHMAN_INIT_SOCKET socket del Init (heredado de brahman-handshake)"); } type Cmd = Result<(), Box>; @@ -155,3 +158,64 @@ fn cmd_json(args: &[String]) -> Cmd { println!("{}", serde_json::to_string_pretty(&manifests)?); Ok(()) } + +fn cmd_daemon(args: &[String]) -> Cmd { + let dir = require_dir(args)?; + + // 1. El propio engine se presenta como Ente. + let engine_card = build_engine_card(); + eprintln!( + "nouser daemon: publicando engine '{}' (kind=Ente)", + engine_card.label + ); + brahman_sidecar::spawn(engine_card); + + // 2. Scan y cluster. + let (db, n_files) = run_scan(&dir)?; + eprintln!( + "nouser daemon: {} archivos en {}, {} mónadas detectadas", + n_files, + dir.display(), + db.monad_count() + ); + + // 3. Cada Mónada se presenta como Card de tipo Data. + let mut handles = Vec::with_capacity(db.monad_count()); + for monad in db.monads() { + let card = monad.to_brahman_card(); + match brahman_sidecar::spawn_with_handle(brahman_sidecar::SidecarConfig::new(card)) { + Ok(h) => handles.push(h), + Err(e) => eprintln!( + "nouser daemon: falló sidecar para mónada '{}': {e}", + monad.label + ), + } + } + eprintln!( + "nouser daemon: {} sidecars activos (1 ente + {} data). Ctrl-C para terminar.", + handles.len() + 1, + handles.len() + ); + + // 4. Park: el proceso principal queda dormido, los sidecars siguen + // pingueando en sus threads. + std::thread::park(); + Ok(()) +} + +/// Card del propio engine (kind=Ente). Es el "ser" que produce y +/// administra Mónadas; aparece en brahman-status junto a sus Mónadas. +fn build_engine_card() -> brahman_card::Card { + use brahman_card::{ulid::Ulid, Card, CardKind, Lifecycle, Payload, Priority, Supervision}; + Card { + schema_version: brahman_card::CARD_SCHEMA_VERSION, + id: Ulid::new(), + label: "brahman.nouser_engine".into(), + payload: Payload::Virtual, + supervision: Supervision::Delegate, + lifecycle: Lifecycle::Daemon, + priority: Priority::Normal, + kind: CardKind::Ente, + ..Default::default() + } +}