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>
This commit is contained in:
Sergio
2026-05-08 18:24:50 +00:00
parent b85700c538
commit 85886b7a3c
4 changed files with 123 additions and 1 deletions
+54
View File
@@ -6,6 +6,60 @@ ratio/diff ver `git show <sha>`.
## 2026-05-08 ## 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 <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) ### 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 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 los procesos. Una Mónada Nouser y un Ente Brahman son ambos "entidades
Generated
+2
View File
@@ -6060,6 +6060,8 @@ dependencies = [
name = "nouser-core" name = "nouser-core"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"brahman-card",
"brahman-sidecar",
"nouser-card", "nouser-card",
"serde", "serde",
"serde_json", "serde_json",
+2
View File
@@ -10,6 +10,8 @@ description = "Nouser — explorador de Mónadas: scanner, clustering determinis
[dependencies] [dependencies]
nouser-card = { path = "../card" } nouser-card = { path = "../card" }
brahman-card = { path = "../../../core/brahman-card" }
brahman-sidecar = { path = "../../../shared/brahman-sidecar" }
serde = { workspace = true } serde = { workspace = true }
serde_json = { workspace = true } serde_json = { workspace = true }
thiserror = { workspace = true } thiserror = { workspace = true }
@@ -34,6 +34,7 @@ fn main() -> ExitCode {
"scan" => cmd_scan(rest), "scan" => cmd_scan(rest),
"show" => cmd_show(rest), "show" => cmd_show(rest),
"json" => cmd_json(rest), "json" => cmd_json(rest),
"daemon" => cmd_daemon(rest),
"--help" | "-h" | "help" => { "--help" | "-h" | "help" => {
print_usage(&prog); print_usage(&prog);
return ExitCode::SUCCESS; return ExitCode::SUCCESS;
@@ -61,9 +62,11 @@ fn print_usage(prog: &str) {
eprintln!(" scan <dir> recorre un directorio y lista las Mónadas detectadas"); eprintln!(" scan <dir> recorre un directorio y lista las Mónadas detectadas");
eprintln!(" show <dir> <prefix> scan + detalle de la Mónada cuyo ID empieza con <prefix>"); eprintln!(" show <dir> <prefix> scan + detalle de la Mónada cuyo ID empieza con <prefix>");
eprintln!(" json <dir> scan + dump JSON de todos los manifests"); eprintln!(" json <dir> scan + dump JSON de todos los manifests");
eprintln!(" daemon <dir> scan + sidecarea cada Mónada al Init brahman");
eprintln!(); eprintln!();
eprintln!("env:"); 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<dyn std::error::Error>>; type Cmd = Result<(), Box<dyn std::error::Error>>;
@@ -155,3 +158,64 @@ fn cmd_json(args: &[String]) -> Cmd {
println!("{}", serde_json::to_string_pretty(&manifests)?); println!("{}", serde_json::to_string_pretty(&manifests)?);
Ok(()) 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()
}
}