gioser

tawasuyu/brahman

Rust 478TOML 190Markdown 50JSON 176650 Definitions

⎇ d7b4886164df5ef048a7c8c0d735fb83555c7990 CHANGELOG.md Markdown · 752 lines · 35.2 KB History raw

feat(sidecar): Phase B-3 — SidecarPool consolida sidecars en un runtime d7b488616 · Sergio ·

1# Changelog
2
3Registro cronológico de cambios sustantivos en el monorepo Brahman. Cada
4entrada lista las acciones concretas tras un commit; para detalles de
5ratio/diff ver `git show <sha>`.
6
7## 2026-05-08
8
9### feat(sidecar): Phase B-3 — SidecarPool consolida en un runtime
10Antes: cada `spawn(card)` creaba un thread + tokio runtime propio.
11Para módulos que publican muchas sesiones (nouser daemon con 50+
12Mónadas) eso es 50 threads + 50 runtimes. Ahora: **un thread + un
13runtime tokio current_thread** que hostea N tasks de sidecar.
14
15API nueva (aditiva, no rompe `spawn`/`spawn_with_handle`):
16
17  let pool = SidecarPool::new()?;
18  pool.spawn(card1);
19  pool.spawn(card2);
20  pool.spawn_conscious(card_wit, wit);
21  pool.spawn_with_config(SidecarConfig::new(c).with_wit(w));
22  // pool drop = todas las sesiones cierran.
23
24`run_client` se hace pública para que el pool pueda enqueuar tasks
25externos al runtime con `handle.spawn(run_client(config))`.
26
27`nouser daemon` migrado al pool. Verificación con `ps -L`:
28
29  $ ps -L -p $(pidof nouser)
30    LWP   CMD
31    28817 nouser              # main thread
32    28819 brahman-sidecar     # pool thread (todas las sesiones)
33
34Antes serían 6+ LWP (1 main + N sesiones); ahora 2 fijos sin importar
35cuántas Mónadas se publiquen.
36
37### feat: Crossreferencia — Card.references como grafo del fractal
38Las Cards ahora declaran sus relaciones con otras Cards. El Engine
39posee Mónadas; las Mónadas declaran que son poseídas por el Engine.
40La UI puede cruzar el grafo sin discovery especial.
41
42- `brahman-card`:
43  - `RelationshipKind { Owns, OwnedBy, Processes, ProcessedBy, Sibling }`.
44  - `CardReference { kind, target_id, target_label }` — `target_label`
45    es cache del label en el momento de declarar (la UI puede pintar
46    sin resolver).
47  - `Card.references: Vec<CardReference>` y espejo en `WireCard`.
48    Conversiones `From` propagan.
49- `brahman-broker::BrokeredCard` propaga `references`.
50- `brahman-status` imprime cada referencia: `ref OwnedBy → label (id)`.
51- **nouser daemon**: cada Mónada que publica añade
52  `RelationshipKind::OwnedBy` apuntando al engine. La declaración es
53  unilateral; el engine no necesita conocer las IDs de antemano.
54
55Validación end-to-end:
56
57  $ ente-zero & nouser daemon crates/core
58  $ brahman-status
59  Sessions (6):
60    [ente]  ... brahman.nouser_engine
61    [data]  ... brahman-handshake/src
62        ref OwnedBy  →  brahman.nouser_engine  (01K...)
63        summary: 6 archivos...
64    [data]  ... ente-brain/src
65        ref OwnedBy  →  brahman.nouser_engine  (01K...)
66    ...
67
68### feat: Phase D-3 + D-4 — service_socket en Card, providers coexisten
69Cierra el ciclo del swap automático de Nous (mock↔real):
70
71- **Schema** (`brahman-card`): `Card.service_socket: Option<PathBuf>` y
72  espejo en `WireCard`. Conversiones `From` propagan. Es el path del
73  **data plane** (distinto del socket del Init); cualquier consumer
74  que matchee con esta Card puede conectar directo sin discovery
75  adicional.
76- **Broker** (`brahman-broker`): `BrokeredCard` propaga
77  `service_socket` desde la Card. Sin participación en el matching —
78  sólo metadata para los observadores.
79- **MatchEvent** (`brahman-handshake`): nuevo campo
80  `producer_service_socket: Option<PathBuf>`. Cuando el server emite
81  `Available`, busca la `BrokeredCard` del productor en el broker y
82  copia su `service_socket`. El consumer recibe la ruta completa para
83  conectar.
84- **Transport** (`nouser-nous`): `provider_socket_path(provider: &str)`
85  devuelve `nouser-nous-{provider}.sock` por default — mock y real
86  coexisten en sockets distintos (Phase D-4). `default_socket_path()`
87  conserva el comportamiento single-provider.
88- **Providers**: mock declara `service_socket =
89  /run/user/X/nouser-nous-mock.sock`; real declara
90  `nouser-nous-real.sock`. La Card se construye DESPUÉS del bind para
91  que el path declarado sea el real.
92- **Status**: `brahman-status` imprime `socket:` por sesión cuando
93  está presente.
94
95Validación end-to-end:
96
97  $ ente-zero & nouser-nous-mock & nouser-nous-real &
98  $ ls /run/user/1001/nouser-nous-*.sock
99    nouser-nous-mock.sock
100    nouser-nous-real.sock
101
102  $ brahman-status
103  Sessions (2):
104    [ente]  ... nouser.nous_real
105        socket:     /run/user/1001/nouser-nous-real.sock
106        in  embed-request: Primitive { name: "json" }
107        out embed-result: Primitive { name: "json" }
108    [ente]  ... nouser.nous_mock
109        socket:     /run/user/1001/nouser-nous-mock.sock
110        in  embed-request, out embed-result
111
112Pendientes para futuro (no críticos):
113- nouser-core attract --remote todavía usa NOUSER_NOUS_SOCKET hardcoded
114  o `default_socket_path()`. El siguiente paso es subscribirse al
115  MatchEvent del broker y usar `producer_service_socket` directo —
116  con eso `BRAHMAN_BROKER_CONTEXT=test/prod` swapea provider sin
117  tocar al consumer.
118
119### refactor(nouser): labels de Mónada con 2 componentes del path
120Resuelve la fricción visual de monorepos donde múltiples Mónadas se
121llamaban "src". Nueva función `label_from_path` toma los últimos hasta
1222 componentes normales del path y los une con `/`.
123
124  $ nouser scan crates/core
125    [01K..] brahman-admin/src       card=5
126    [01K..] brahman-handshake/src   card=6
127    [01K..] ente-brain/src          card=11
128    [01K..] ente-kernel/src         card=4
129    ...
130
131Tests añadidos: `label_from_root_only_one_component`,
132`label_from_deep_path_takes_last_two`. Tests existentes actualizados
133con los nuevos labels.
134
135### feat(nouser): Phase D-2 — proveedor Nous real (LLM) detrás de feature flag
136Cierra el ciclo del módulo Nous: existe un proveedor que produce
137embeddings reales con un modelo LLM, mientras que `cargo build` sin
138features sigue siendo liviano (no descarga ni compila ML deps).
139
140Crate nuevo:
141
142- `crates/modules/nouser/nous-real`: bin con dos modos según feature.
143  - **Sin feature (default)**: stub. Bin compila en ~10s, arranca,
144    sidecarea a brahman-init declarando la Card de real-nous, escucha
145    en el socket Nous, y rechaza toda request con `ErrorResponse {
146    error: "compilado sin la feature embeddings. Rebuild con
147    cargo build -p nouser-nous-real --features embeddings" }`.
148    `cargo build --workspace` sigue siendo limpio.
149  - **Con `--features embeddings`**: pulls `fastembed = "4"`. Ese crate
150    arrastra `ort 2.0.0-rc.9` (ONNX Runtime con binarios descargados
151    por Cargo) + `tokenizers 0.21` + ~30 deps más. Compila en ~50s.
152    Modelo default: `all-MiniLM-L6-v2` (384-d, descargado a
153    `~/.cache/fastembed` la primera vez).
154  - `EmbedText`: pasa el texto al modelo, devuelve vector 384-d.
155  - `EmbedFile`: lee primeros 8KiB con UTF-8 lossy, embed como texto.
156    Para binarios el resultado no es semánticamente útil — caller
157    decide.
158  - `Ping`: devuelve `model_id` y `embed_dim` reales.
159
160- Card de real-nous:
161  - label `nouser.nous_real` (distinto del mock para coexistir).
162  - `priority_contexts.prod = { priority_offset: +1 }`. En contexto
163    prod gana sobre el mock; en `test` el mock gana por su propio
164    `+1`. Sin contexto activo, empate alfabético entre ambos.
165
166Validación end-to-end con modelo real:
167
168  $ cargo build -p nouser-nous-real --features embeddings   # ~50s
169  $ ente-zero & nouser-nous-real &
170  $ # probe vía python al socket Unix:
171  $ echo '{"kind":"embed_text","payload":{"text":"hello brahman"}}' \
172    | python3 -c "..." | head
173    model: real-fastembed-allMiniLML6V2-384d
174    elapsed_ms: 8
175    embed_dim: 384
176    first 5 values: [0.0034, -0.0036, 0.0078, -0.0218, -0.0162]
177
178Tradeoff conocido: las dimensiones del mock (32-d) y real (384-d) son
179incompatibles. Cambiar de proveedor invalida los centroides cacheados
180de Mónadas. Documentar como "limpiar DB al cambiar proveedor".
181
182Workspace state:
183- cargo build --workspace sigue limpio sin features (no ML).
184- cargo build -p nouser-nous-real --features embeddings funciona.
185- 0 errores, 0 warnings en ambos modos.
186
187Pendientes para D-3 / futuro:
188- Discovery de socket: hoy el consumer hardcodea NOUSER_NOUS_SOCKET.
189  Para que el broker brahman elija real vs mock per-contexto, falta
190  inyectar el socket del provider electo en el MatchEvent o exponer
191  un broker query "dame el socket de la sesión X".
192- Coexistencia: hoy los dos providers compiten por el mismo socket
193  path por default. Habría que parametrizarlos a sockets distintos
194  cuando coexistan.
195
196### feat(nouser): Phase D — proveedor Nous mock + cliente remoto
197Cierra el patrón "Nous como módulo aparte intercambiable": el contrato
198del proveedor de embeddings vive en su crate, el mock determinístico
199implementa ese contrato sirviéndolo por Unix socket, y `nouser-core`
200sabe consumirlo remotamente. El switch entre mock y real (futuro) se
201hará vía priority_contexts en el broker.
202
203Crates nuevos:
204
205- `crates/modules/nouser/nous`: contrato compartido. Tipos
206  `EmbedRequest`, `RequestKind { EmbedFile, EmbedText, Ping }`,
207  `EmbedFilePayload`, `EmbedTextPayload`, `EmbedResponse`,
208  `PingResponse`, `ErrorResponse`. Wire format: line-delimited JSON
209  por Unix socket, single-shot per conexión. Constants para los nombres
210  de flow (`embed-request`/`embed-result`) y el tipo (`json`). Helper
211  `transport::default_socket_path()` con env var
212  `NOUSER_NOUS_SOCKET`.
213- `crates/modules/nouser/nous-mock`: bin `nouser-nous-mock`. Sidecarea
214  a brahman-init con Card kind=Ente declarando los flows
215  `embed-request:json`/`embed-result:json` y un
216  `priority_contexts.test = { priority_offset: +1 }` (gana sobre
217  cualquier real-nous en contexto test). Bind del socket Nous, accept
218  loop, despacha por `RequestKind`. EmbedFile usa
219  `nouser_core::embed::embed` (los pseudo-embeddings de Phase C).
220  Modelo: `mock-pseudo-32d`.
221
222Cambios:
223
224- `nouser-core`: dep nueva `nouser-nous`. Subcomando `attract` ahora
225  acepta `--remote` que abre un socket UnixStream blocking, envía un
226  `EmbedRequest` y lee la response. Imprime `embed: local|remote`
227  para que se vea cuál ruta corrió.
228
229Validación end-to-end (un solo terminal, varios procesos):
230
231  $ ente-zero &
232  $ nouser-nous-mock &
233  $ NOUSER_MIN_FILES=5 nouser daemon crates/core &
234  $ brahman-status
235
236  Sessions (7):
237    [ente] nouser.nous_mock      flows: embed-request, embed-result
238    [ente] brahman.nouser_engine
239    [data] src   summary: 6 archivos en crates/core/brahman-handshake/src
240    [data] graph summary: 7 archivos en crates/core/ente-zero/src/graph
241    ...
242
243  $ nouser attract --remote crates/core <archivo.rs>
244    embed: remote
245    🧲  0.9058  src  ...
246
247  Mock log: "embed_file path=crates/modules/nouser/core/src/embed.rs"
248
249Bug encontrado y corregido en el camino:
250- `ContextBias` tenía `#[serde(skip_serializing_if = ...)]` en sus
251  campos. Postcard NO soporta skip-condicional (formato no
252  self-describing): el serializer omitía bytes que el deserializer
253  esperaba, rompiendo la wire de cualquier Card con
254  `priority_contexts` poblada.
255- Fix: removidos los `skip_serializing_if` de `ContextBias`. JSON
256  pretty ahora emite `{"pin_to": null, "priority_offset": 0}` en lugar
257  de objeto vacío. Trade-off aceptado por compatibilidad de wire.
258- Test nuevo en brahman-card: `wirecard_postcard_with_priority_contexts`
259  que ejercita el roundtrip completo postcard.
260
261Tests acumulados: 75 (card 12 +1 nuevo, broker 15, handshake 9,
262card-wit 4, admin 0, nouser-card 7, nouser-core 20, nouser-nous 2).
263cargo check --workspace: 0 errores, 0 warnings.
264
265Próximo natural: Phase D-2 — `real-nous` con un modelo ONNX/Llama de
266text-embedding. La infraestructura ya está lista: declara la misma
267Card con `priority_contexts.prod = { priority_offset: +1 }` y el
268swap es transparente para el consumer.
269
270### feat(nouser): Phase C — pseudo-embeddings + atracción por centroide
271El "imán semántico" matemático del diseño Kairos, sin LLM. Cada
272archivo se proyecta a un vector 32-d derivado de sus metadatos; cada
273Mónada calcula su centroide; archivos nuevos se asignan por cosine
274similarity contra los centroides existentes.
275
276Cambios:
277
278- nouser-core dep nueva: `blake3` (hash determinista de strings).
279- `crates/modules/nouser/core/src/embed.rs`:
280  - `EMBED_DIM = 32`. Estructura del vector:
281    - dims  0..8:  blake3(extension)     → identidad de tipo
282    - dims  8..16: blake3(parent_dir)    → identidad de contenedor
283    - dims 16..24: blake3(file_stem)     → identidad léxica
284    - dims 24..28: tamaño (log + flags)
285    - dims 28..32: mtime (escala día + cíclicas)
286  - **Tip clave**: bytes del hash se centran a `[-1, 1]` (no `[0, 1]`).
287    Sin centrar, dos vectores hash random tendrían cosine ~0.75
288    espuria; centrados, expectativa ≈ 0 entre no-relacionados.
289  - APIs: `embed`, `cosine_similarity`, `centroid`, `cohesion`,
290    `attraction_score`, `best_attraction`. `DEFAULT_ATTRACTION_THRESHOLD = 0.7`.
291- `cluster::by_directory` ahora computa el centroide de cada Mónada
292  (promedio de embeddings de los miembros, L2-normalizado) y lo guarda
293  en `MonadManifest.centroid`. El centroide viaja al brahman-status vía
294  `DataFacet.centroid` → ahora se ven los Vec<f32> reales por cada Mónada.
295- bin nouser nuevo subcomando: `attract <dir> <file>`.
296  - Escanea el dir, embeda el archivo objetivo, ranking de afinidad
297    contra todas las Mónadas con centroide.
298  - Marca 🧲 si la mejor supera el umbral, `·` si es la mejor pero
299    debajo, espacio en blanco para el resto.
300
301Validación end-to-end:
302
303  $ nouser attract crates/core crates/modules/nouser/core/src/embed.rs
304  ranking de atracción (cosine similarity):
305    🧲  0.9058  [01K..] src  (11 archivos en crates/core/ente-brain/src)
306        0.8984  [01K..] src  (6 archivos en crates/core/brahman-handshake/src)
307        0.8918  [01K..] src  (5 archivos en crates/core/ente-zero/src)
308        ...
309
310  $ nouser attract crates/core crates/modules/nouser/core/Cargo.toml
311  ranking:
312        0.3427  [01K..] graph  (7 archivos en crates/core/ente-zero/src/graph)
313        ...
314    (mejor score 0.3427 < umbral 0.7000 — el archivo no se 'pega')
315
316Tests: 20 en nouser-core (era 13, +7 de embed). Total acumulado: 73
317(card 11, broker 15, handshake codec+tr 2 + integ 7, card-wit 4,
318admin 0, nouser-card 7, nouser-core 20, ente-card 0).
319cargo check --workspace: 0 errores, 0 warnings.
320
321Próximo: **Phase D** — `nouser-nous`, módulo aparte para LLM real.
322Mock-nous determinista (basado en estos pseudo-embeddings) en
323`BRAHMAN_BROKER_CONTEXT=test`; real-nous en `prod`. El switch lo hace
324el broker via priority_contexts sin tocar nada más.
325
326### feat(nouser): Phase B-2 — daemon que publica Mónadas al Init
327Cierra la unificación: el `nouser daemon` se sidecarea como Ente y
328publica cada Mónada como su propia sesión Data. Un solo
329`brahman-status` muestra procesos y datos en la misma lista, exactamente
330como buscaba el diseño.
331
332Cambios:
333
334- `crates/modules/nouser/core/Cargo.toml`: deps nuevas `brahman-card`
335  y `brahman-sidecar`.
336- `crates/modules/nouser/core/src/bin/nouser.rs`: subcomando
337  `daemon <dir>`.
338  - Spawna un sidecar para el "engine" (`brahman.nouser_engine`,
339    kind=Ente) — el ser que produce y administra Mónadas.
340  - Scan + cluster del dir.
341  - Para cada Mónada, llama `monad.to_brahman_card()` y spawnea un
342    sidecar (kind=Data). Cada Mónada es una sesión brahman propia
343    con su ULID estable.
344  - Park del thread principal: los sidecars siguen pingueando.
345
346Validación end-to-end:
347
348  $ ente-zero &
349  $ NOUSER_MIN_FILES=5 nouser daemon crates/core &
350  $ brahman-status
351
352  Sessions (6):
353    [ente]  ... brahman.nouser_engine  lifecycle=Daemon
354    [data]  ... src    summary: 5 archivos en crates/core/brahman-admin/src
355                       members: 5 (dispersion=0.00)
356                       lens hint: code
357    [data]  ... src    summary: 11 archivos en crates/core/ente-brain/src
358                       ...
359    [data]  ... graph  summary: 7 archivos en crates/core/ente-zero/src/graph
360
361El protocolo de presentación es uno solo: la Card. La función — anunciar
362identidad, exponer metadata, ser descubierto — es idéntica para procesos
363vivos y agrupaciones de datos. La UI lo ve como una lista uniforme.
364
365Costo conocido: cada Mónada consume un thread + tokio runtime
366current_thread (legacy del sidecar API). Para muchas Mónadas (>50)
367conviene consolidar en un único runtime con N tasks. Defer a Phase B-3.
368
369Pendientes propuestos:
370- **B-3**: consolidar todos los sidecars en un único runtime tokio
371  para no spawnear N threads.
372- **C**: pseudo-embeddings + atracción por centroide.
373- **D**: módulo `nouser-nous` para LLM, swappable por priority_contexts.
374- **Polish**: labels con 2-3 componentes del path.
375- **Crossreferencia**: que un Ente pueda anunciar "estoy procesando la
376  Mónada X" y la Mónada anuncie "Ente Y me está procesando".
377
378cargo check --workspace: 0 errores, 0 warnings.
379
380### feat: Phase B-1 — unificación ontológica de Cards (Ente ↔ Data)
381La Card es **el** protocolo de presentación del ecosistema, no sólo de
382los procesos. Una Mónada Nouser y un Ente Brahman son ambos "entidades
383que se presentan"; el consumidor (UI, broker, admin) discrimina por
384`kind` cuando importa, pero todos hablan el mismo idioma.
385
386Cambios:
387
388- `brahman-card`:
389  - `CardKind { Ente (default), Data }`. Conserva back-compat:
390    Cards existentes son `Ente` por default.
391  - `DataFacet { summary, keywords, centroid, member_count, dispersion,
392    presentation_hint }`. Liviano para el wire — listas grandes
393    (members, embeddings completos) se consultan al daemon dueño bajo
394    demanda.
395  - `Card.kind` y `Card.data: Option<DataFacet>` agregados. WireCard
396    espeja, conversiones `From` propagan.
397  - Default impl actualizado.
398
399- `brahman-broker::BrokeredCard`: propaga `kind` y `data` desde la Card
400  registrada. No afecta el matching (sigue siendo por TypeRef +
401  priority + pin_to); permite a observadores discriminar sin re-query.
402
403- `nouser-card`: depende ahora de `brahman-card`. Nuevo método
404  `MonadManifest::to_brahman_card()` que proyecta:
405  - id, label, lineage → directos.
406  - payload Virtual, supervision Delegate, lifecycle Daemon (placeholder
407    semántico — la Mónada no se ejecuta).
408  - kind = Data.
409  - data = Some(DataFacet) con summary, keywords, centroide,
410    member_count, entropy → dispersion, y un `presentation_hint` derivado
411    del `Lens` (`Code` → `"code"`, `Gallery` → `"gallery"`, etc.).
412  - Test nuevo: `projects_to_brahman_card`.
413
414- `brahman-status`: cada sesión muestra ahora `[ente]` o `[data]` como
415  prefijo. Para sesiones `data`, render adicional con summary, members
416  + dispersion, keywords y lens hint.
417
418Resultado: la UI (yahweh, brahman-status, futuro explorer) ve una sola
419lista uniforme. No tiene que saber si está mirando un proceso o un
420cúmulo de datos — sólo lee el Card y se adapta por `kind`.
421
422Tests acumulados: 59 (card 11, broker 15, handshake codec+transport 2 +
423integ 7, card-wit 4, admin 0, nouser-card 7, nouser-core 13).
424cargo check --workspace: 0 errores, 0 warnings.
425
426Próximo: **Phase B-2** — bin `nouser daemon <dir>` que sidecarea cada
427Mónada como una sesión brahman, publicándola al broker. Brahman-status
428las verá junto a los entes.
429
430### feat(nouser): Phase A — mecanismo determinista de Mónadas
431Primer trozo del módulo Nouser (Kairos): explorador de Mónadas como
432"imanes semánticos" sobre el filesystem. Phase A cubre el 90% de los
433casos sin tocar IA — sólo metadatos y heurísticas.
434
435Crates nuevos:
436
437- `crates/modules/nouser/card`: `MonadManifest` (la Tarjeta de
438  Presentación de una Mónada — espejo conceptual de `brahman::Card`
439  pero para datos, no para procesos runtime). Campos: id (Ulid),
440  label, summary, centroid (vacío en Phase A), keywords, cardinality,
441  entropy [0,1], dominant_lens, pins, members, timestamps,
442  extensions (forward-compat). 6 tests de validación + JSON roundtrip.
443- `crates/modules/nouser/core`: pipeline determinista.
444  - `scanner`: walkdir → `Vec<FileEntry>` con metadatos (path, size,
445    mtime, extension). Skipea hidden por default. Configurable max
446    depth y follow_links.
447  - `cluster::by_directory`: agrupa por parent dir, mínimo 3 archivos
448    para promover a Mónada (configurable). Calcula keywords (top-N
449    extensiones por frecuencia + alfabético), elige `Lens` dominante
450    (Code/Gallery/Markdown/Database/Grid) según extensión más
451    frecuente, computa entropía de Shannon normalizada [0,1].
452  - `db`: `MonadDb` en memoria con índices BTreeMap files/monads y
453    `resolve_members(monad_id)` que filtra IDs huérfanos. Phase B
454    traerá persistencia.
455  - bin `nouser`: subcomandos `scan <dir>`, `show <dir> <prefix>`,
456    `json <dir>`. Env var `NOUSER_MIN_FILES` para tunear el threshold.
457  - 13 tests (4 scanner + 6 cluster + 3 db).
458
459Demo end-to-end:
460
461  $ nouser scan crates
462  scan: 255 archivos en crates, 19 mónadas (min_files=3)
463    [01KR4C13]  src                             card=12  ent=0.00  lens=Code
464             keywords: rs
465    [01KR4C13]  tests                           card=14  ent=0.00  lens=Code
466             keywords: rs
467    [01KR4C13]  fixtures                        card=5   ent=0.00  lens=Grid
468             keywords: rhai
469    ...
470
471  $ nouser show crates 01KR4C
472  Monad 01KR4C1370DVF6NMTW6SECNXAF
473    label:       src
474    summary:     4 archivos en crates/modules/nouser/core/src (ext: rs)
475    cardinality: 4
476    entropy:     0.0000
477    lens:        Code
478    members (4):
479            4132 bytes  crates/modules/nouser/core/src/db.rs
480            ...
481
482Pendientes para próximas fases (anotados, no urgentes):
483- **Phase B**: bin `nouser daemon` que sidecarea a brahman-init
484  declarando flows (`scan-request:json` → `monad-update:json`).
485- **Phase C**: pseudo-embeddings deterministas (hash de path/ext/size
486  a 32-d) + atracción por centroide via cosine similarity. Implementa
487  el "imán" sin LLM.
488- **Phase D**: módulo `nouser-nous` aparte para el LLM real
489  (Llama/ONNX). En `priority_contexts.test` el Init pinea a
490  `mock-nous` (embeddings determinísticos); en `prod` a `real-nous`.
491- **Polish**: labels de Mónada incluir 2-3 componentes del path para
492  desambiguar `src/` repetidos en monorepo.
493
494Workspace: 0 errores, 0 warnings. Tests acumulados: 58
495(card 11, broker 15, handshake codec+transport 2 + integ 7,
496card-wit 4, admin 0, nouser-card 6, nouser-core 13).
497
498### feat(broker): priority contexts — biases per-contexto operativo
499- `brahman-card::ContextBias { pin_to: Option<String>, priority_offset: i8 }`
500  declara un override per-contexto.
501- `Card.priority_contexts: BTreeMap<String, ContextBias>` y mismo en
502  `WireCard` (cruza el wire). Las conversiones `From` lo propagan.
503- `BrokerConfig.current_context: Option<String>`. Cuando el broker corre
504  bajo un contexto y una Card declara biases para ese nombre, se aplican:
505  - Como **consumidor**: `pin_to` sobreescribe el `Flow.pin_to` estático.
506  - Como **productor**: `priority_offset` se suma a la priority base
507    (clamp en `[Low=0, Critical=3]`) para el ranking.
508- `BrokeredCard` propaga `priority_contexts`. `find_producer_for` usa
509  `effective_priority(card)` y `effective_pin(card, input)` antes de
510  los tiebreaks.
511- `brahman-admin::AdminConfig.current_context` + `StatusSnapshot.current_context`
512  espejan el contexto activo. `brahman-status` lo imprime como
513  `Context: <nombre>` justo debajo de `Init: ...`.
514- `ente-zero` lee `BRAHMAN_BROKER_CONTEXT` env var y la propaga al
515  broker y al admin. Sin var, biases per-contexto inactivos.
516- 4 tests nuevos en brahman-broker:
517  `context_priority_offset_lifts_producer_above_alphabetic_winner`,
518  `context_pin_to_overrides_static_pin`, `unknown_context_no_op`,
519  `priority_offset_clamps_to_critical`.
520- Validación end-to-end: `BRAHMAN_BROKER_CONTEXT=test ente-zero` →
521  `brahman-status` muestra `Context: test`.
522
523### feat(card): WireCard + extensions — forward-compat sin romper postcard
524- `Card.extensions: BTreeMap<String, serde_json::Value>` restaurado con
525  `#[serde(flatten, default, skip_serializing_if = is_empty)]`. Los
526  campos JSON/TOML desconocidos sobreviven el roundtrip de archivos.
527- Nuevo `WireCard`: proyección postcard-friendly (sin `extensions`,
528  `genesis: Vec<WireCard>` recursivo). Conversiones `From<Card>` y
529  `From<WireCard>` con descarte/recreación de extensions.
530- `brahman-handshake::Hello.card` pasa de `Card` a `WireCard`. Client
531  hace `card.into()` antes de enviar; Server hace `hello.card.into()`
532  para volver a Card antes de validar/registrar.
533- 3 tests nuevos en brahman-card:
534  `extensions_preserved_in_json_roundtrip`,
535  `wire_card_roundtrip_strips_extensions`,
536  `wire_card_postcard_friendly` (postcard encode/decode efectivo).
537- brahman-card gana `postcard` como dev-dep para el último test.
538- Contrato documentado: extensions = anotaciones locales que NO cruzan
539  al Init; sólo viven en archivos.
540
541### `9420eae` chore: limpia warnings dead-code en arje (commit del usuario)
542- `ente-zero/src/events.rs`: `#![allow(dead_code)]` a nivel módulo —
543  es vocabulario de eventos con variantes/campos reservados para flujos
544  no cableados aún (CapabilityRequested, ShutdownReason::Signal,
545  CapabilityGrant::{Granted, Denied, QuotaExceeded}, ExitStatus
546  fields).
547- `ente-zero/src/graph/mod.rs`: comentado el re-export ahora innecesario
548  de `SHUTDOWN_GRACE`. `DEFAULT_GRANT_TTL` con `#[allow(dead_code)]`
549  + nota "reservado para capability granting".
550- `ente-zero/src/graph/capabilities.rs`: `renew_grant` con
551  `#[allow(dead_code)]` (capability renewal pendiente).
552- `ente-kernel/src/surface.rs`: drop de `use anyhow::Context` (no se
553  usaba).
554- `ente-hostnamed-compat/src/main.rs`: drop de `Connection` (no se
555  usaba).
556- `ente-polkit-compat/src/main.rs`: `PolicyDecision.source` con
557  `#[allow(dead_code)]` (sólo aparece en `Debug` para logging).
558- `cargo check --workspace`: 17 warnings → 0.
559
560### feat(sidecar): WIT al sidecar — módulos conscientes vivos
561- `brahman-card::WitInterface` deriva `Serialize`, `Deserialize`,
562  `PartialEq`, `Eq` para cruzar el wire postcard.
563- `brahman-handshake::Hello` lleva `wit: Option<WitInterface>`. Server
564  usa `ResolvedCard::from_conscious` cuando viene presente, `from_agnostic`
565  cuando no.
566- `brahman-handshake::Client::connect` queda como wrapper agnóstico de
567  `connect_with(path, card, wit: Option<WitInterface>)`.
568- `brahman-broker::Broker::register` ahora toma `Option<WitInterface>`
569  como tercer arg. `BrokeredCard` guarda el wit. 25 sitios de tests
570  actualizados con `, None`.
571- `brahman-sidecar::SidecarConfig` con campo `wit`. Helpers nuevos:
572  `SidecarConfig::new(card).with_wit(wit)` y `spawn_conscious(card, wit)`.
573  El log `attached` reporta `conscious=true|false`.
574- `brahman-status` muestra marker 🧠 + sección `wit:` (package/world,
575  imports, exports) por sesión consciente.
576- Example nuevo `crates/shared/brahman-sidecar/examples/presence-conscious.rs`:
577  toma label + path .wit (default `shared_wit/protocol.wit`), parsea
578  con brahman-card-wit, spawna sidecar consciente.
579- Validado end-to-end:
580  ```
581  $ presence-conscious demo.conscious shared_wit/protocol.wit &
582  $ brahman-status
583  Sessions (1):
584    01K... demo.conscious 🧠  lifecycle=Daemon
585        wit: brahman:protocol@0.1.0 / module
586             imports: types, handshake, lifecycle
587             exports: run
588  ```
589
590### feat(core): brahman-card-wit — extractor opcional de contratos WIT
591- Crate nuevo `crates/core/brahman-card-wit` con `wit-parser = "0.230"`.
592- API: `parse_wit(source)` y `parse_wit_file(path)` devuelven
593  `Vec<WitInterface>` (uno por `world` declarado).
594- Interfaces importadas/exportadas (no sólo funciones) se resuelven
595  por nombre via `resolve.interfaces[id].name`.
596- Example `crates/core/brahman-card-wit/examples/brahman-wit-info.rs`
597  CLI: `brahman-wit-info shared_wit/protocol.wit` → lista paquete,
598  worlds, imports y exports.
599- 4 tests: inline, archivo real (`shared_wit/protocol.wit`), parse
600  error, world vacío.
601- Validado contra `protocol.wit`: detecta worlds `module` y
602  `admin-host` con sus imports/exports correctos.
603
604### `7b589b8` chore: agrega CHANGELOG.md retroactivo
605- `CHANGELOG.md` en la raíz con los 11 commits previos documentados
606  acción por acción. A partir de este punto, cada cambio sustantivo
607  actualiza también este archivo en el mismo commit.
608
609### `8a83a26` feat(handshake): notificación push de matches
610- Frame `MatchEvent { kind: Available | Lost, ... }` añadido al protocolo.
611- `Session::run_post_handshake` usa `tokio::select!` para multiplexar
612  reads del cliente y un canal `mpsc` push del server.
613- Server: `SessionTxTable` (Arc<Mutex<HashMap<SessionId, Sender<Frame>>>>)
614  y `LastMatches` para diff por sesión. `broadcast_match_diffs` corre
615  tras cada `register` y `unregister`, emite sólo los cambios.
616- Capacity del canal push: 32 (ephemeral, `try_send` non-blocking).
617- Client: `VecDeque<MatchEvent>` interno, `take_event()` (non-blocking)
618  y `await_event(timeout)`. `ping()` ahora drena MatchEvents intermedios
619  hasta encontrar el Pong.
620- Example `crates/core/brahman-handshake/examples/subscriber.rs`.
621- Test `match_event_pushed_on_producer_arrival` (handshake integ 6→7).
622
623### `70a7a0d` feat: segundo módulo (nakui) + admin API + brahman-status
624- Crate nuevo `crates/shared/brahman-sidecar` (DRY del thread + tokio +
625  ping loop). API: `spawn(card)` / `spawn_with_handle(config)`.
626- `nakui` cmd_run llama `brahman_sidecar::spawn` antes de `run_server`.
627  Card: lifecycle Daemon, supervision Restart, flow `command` (json) /
628  `report` (json).
629- Crate nuevo `crates/core/brahman-admin` con `StatusSnapshot` JSON
630  line-delim, `AdminServer` y `client::query`.
631- ente-zero levanta también el AdminServer en `primordial_loop`.
632- Example `crates/shared/brahman-sidecar/examples/presence.rs`
633  (módulo dummy long-lived parametrizable por label).
634- Example `crates/core/brahman-admin/examples/brahman-status.rs`
635  (CLI que pretty-printa el snapshot).
636- `brahman-broker`: `BrokeredCard` ahora incluye `lifecycle`. `Endpoint`
637  y `Match` derivan `Serialize`/`Deserialize`. Nuevo `Broker::cards()`
638  iterador.
639- `brahman-card`: `pub use ::ulid` para que módulos no dependan de ulid.
640- yahweh-shell migrado al sidecar compartido (96→53 LOC).
641
642### `595f68e` feat(yahweh-shell): primer módulo brahman vivo
643- yahweh-shell spawnea sidecar antes de `Application::new()`.
644- Card declarada: label `brahman.ui_engine`, lifecycle Widget,
645  supervision Delegate, payload Virtual, flow input `render-data`
646  (json) / output `user-intent` (json).
647- Sidecar en thread aparte con tokio current_thread runtime,
648  desacoplado del runtime GPUI.
649
650### `df9d10c` feat(ente-zero): enchufa el handshake server al Init real
651- ente-zero levanta `brahman_handshake::server::Server::bind` en
652  `primordial_loop` después del ente-bus, con degradación grácil
653  si bind falla (mismo patrón que uevents).
654- Nuevo módulo `brahman-handshake/src/transport.rs`: helper
655  `default_socket_path()` con resolución `BRAHMAN_INIT_SOCKET` →
656  `XDG_RUNTIME_DIR` → `TMPDIR`.
657- Example `crates/core/brahman-handshake/examples/probe.rs`.
658- Validación end-to-end manual: probe contra ente-zero vivo
659  imprime `HelloAck: session=... init_attached=true`.
660
661### `07d77a3` feat(handshake): integra el broker con el ciclo de sesiones
662- `ServerConfig` acepta `Option<Arc<Mutex<Broker>>>`.
663- `register_session` indexa la Card en el broker y la `SessionRegistry`
664  antes de emitir HelloAck.
665- `Session::handle` refactor a `do_handshake → run_post_handshake →
666  cleanup` con cleanup unificado (broker + sessions).
667- Tests integ nuevos: `broker_registers_and_unregisters_with_session`
668  y `broker_matches_two_live_modules`.
669- Fix colateral: `brahman-card::TypeRef` pasa de internally-tagged
670  (`#[serde(tag = "kind")]`) a externally-tagged. Postcard no soporta
671  internally-tagged en formatos no self-describing. JSON cambia de
672  `{"kind":"primitive","name":"x"}` a `{"primitive":{"name":"x"}}`.
673
674### `5091106` feat(core): brahman-broker — matching híbrido
675- Crate nuevo `crates/core/brahman-broker`.
676- 3 estrategias de matching: `Exact`, `Structural`, `ExactThenStructural`
677  (default). Devuelven `Match::via` con la estrategia que ganó.
678- Override `pin_to`: el consumer pide un productor por label; si la
679  pista no resuelve, cae en type-search.
680- Tiebreak por `Card.priority` desc, luego `label` asc (estable y
681  determinista).
682- API: `register`, `unregister`, `find_producer_for`, `all_matches`,
683  `cards`, `sessions`, `len`, `is_empty`.
684- 11 tests (matching, pin_to, priority, no-self-loops, all-matches).
685
686### `814390f` feat(core): brahman-handshake — protocolo runtime
687- Crate nuevo `crates/core/brahman-handshake` con server y client
688  Rust↔Rust sobre Unix socket.
689- Frames length-prefixed (4 bytes LE) + cuerpo postcard.
690- Mensajes: `Hello`, `HelloAck`, `Ping`, `Pong`, `Farewell`, `Error`.
691- `MAX_FRAME_BYTES = 4 MiB` para evitar reservas absurdas.
692- Tradeoff: drop `extensions`/`extra` de Card por incompat
693  postcard ↔ `serde_json::Value`. Forward-compat queda en
694  `schema_version` + `protocol_version` negotiation.
695- 4 tests integ + 1 unit en codec.
696
697### `ed0e973` refactor(arje): migra ente-card a re-export de brahman-card
698- `ente-card/src/lib.rs` reescrito como crate-shim de re-export
699  (327 LOC → 25 LOC).
700- `EntityCard` ≡ `brahman_card::Card` por type alias.
701- `ente-card/Cargo.toml`: deps reducidas a `brahman-card`.
702- `Card` impl `Default` (Ulid::nil(), label vacío) para que
703  `..Default::default()` funcione en struct-literals.
704- 4 sitios en `ente-zero/src/seed.rs` actualizados con
705  `..Default::default()` para los campos aditivos.
706- Los 21 consumidores arje compilan sin tocar fuente.
707
708### `0feba74` feat(core): brahman-card — Tarjeta canónica híbrida
709- Crate nuevo `crates/core/brahman-card`.
710- Hereda de arje: `id: Ulid`, `lineage`, `Capability` tipado,
711  `Payload::{Wasm, Native, Virtual, Legacy}`, `SomaSpec`
712  (namespaces, cgroups, rlimits, cpu_affinity), `Supervision`
713  (Restart con backoff, OneShot, Delegate), `genesis` recursivo.
714- Aditivo brahman: `Permissions` enumerados (`NetworkingPolicy`,
715  `FsPolicy`, `IpcPolicy`), `Lifecycle` ortogonal a Supervision,
716  `Priority` de scheduling, `Flows` con `TypeRef` discriminado
717  (Primitive | Wit), `pin_to` opcional.
718- `TrustLevel` derivado de `Permissions` (no declarado).
719- `ResolvedCard { card, wit: Option<WitInterface>, trust }`.
720- Soporta JSON (canónico) + TOML (auto-detectado por extensión).
721- 8 tests incluido `arje_seed_format_compatible` que valida que
722  el JSON de arje sigue parseando con defaults para los aditivos.
723
724### `4d50bfc` chore: absorbe nakui (ERP matemático) en modules/nakui
725- `~/nakui` → `crates/modules/nakui/{core,modules}`.
726- `core/`: el crate `nakui-core` con 4 bins (nakui, demo,
727  inventory_demo, sales_demo) y tests.
728- `modules/{inventory,sales,treasury}/`: data declarativa
729  (`nsmc.json`, `schema.k`, `morphisms/`) que el crate consume.
730  No son crates Cargo.
731- Deps directas (no `workspace = true`): thiserror v1, surrealdb,
732  rhai, petgraph. No conflicto con el resto del workspace.
733
734### `53dbdf0` chore: monorepo inicial con arje + minga + yahweh absorbidos
735- 45 crates absorbidos en 4 ejes:
736  - `crates/core/`: 24 crates de arje (Init systemd-compatible:
737    `ente-card`, `ente-zero`, `ente-kernel`, `ente-bus`, `ente-cas`,
738    `ente-soma`, `ente-wasm`, `ente-snapshot`, `ente-brain`,
739    `ente-echo`, `ente-policy-provider`, + 12 `*-compat`).
740  - `crates/modules/semantic_dht/`: 5 crates de minga (`minga-core`
741    con AST/CAS/MST, `minga-p2p` con libp2p Kad, `minga-store`,
742    `minga-vfs`, `minga-cli`).
743  - `crates/modules/ui_engine/`: 11 crates de yahweh (libs/{core,
744    theme, bus, providers}, widgets/{tree, splitter, tabs, tiled,
745    container_core, text_input}).
746  - `crates/apps/`: 5 crates de yahweh (file_explorer,
747    database_explorer, text_viewer, image_viewer, yahweh-shell).
748- `shared_wit/protocol.wit` con handshake/lifecycle inicial.
749- `Cargo.toml` unificado: thiserror bumped a 2 (transparente para
750  arje), tokio "full", paths intra-workspace de yahweh redirigidos.
751- `cargo check --workspace`: 0 errores (sólo dead-code warnings
752  preexistentes en ente-zero).

CHANGELOG.md

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-08

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.

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):

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):

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:

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:

Pendientes para D-3 / futuro:

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:

Cambios:

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:

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:

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 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 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:

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:

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:

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:

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):

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

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

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

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

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

7b589b8 chore: agrega CHANGELOG.md retroactivo

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

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

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

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

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

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

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

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

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

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

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