feat(yahweh-shell): primer módulo brahman vivo
yahweh-shell se presenta al Init brahman como módulo Widget mediante un
sidecar en thread aparte. La GUI GPUI levanta normalmente; el sidecar
mantiene la sesión brahman en paralelo, desacoplado.
Cambios:
- crates/apps/yahweh-shell/Cargo.toml: deps brahman-card, brahman-handshake,
ulid.
- crates/apps/yahweh-shell/src/brahman_client.rs: thread con tokio
current_thread runtime que arma una Card, llama Client::connect, y
loop de pings cada 30s. Si el Init no está disponible, loggea y
termina — yahweh sigue funcionando standalone.
- crates/apps/yahweh-shell/src/main.rs: brahman_client::spawn() antes
de Application::new(). El spawn no bloquea.
Card declarada por yahweh:
- label: "brahman.ui_engine"
- lifecycle: Widget
- payload: Virtual (yahweh no se inicia desde el Init, se presenta)
- supervision: Delegate
- permissions: filesystem read-write (persiste layout.json), IPC wit-v1
- flow.input: render-data (json)
- flow.output: user-intent (json)
Validación end-to-end:
$ ente-zero &
$ probe → session=...8G, init_attached=true
$ yahweh → [brahman] attached: session=...Y7
Ambos clientes (probe + yahweh sidecar) se registran en el broker del
Init en sesiones distintas. yahweh es el primer módulo "real" — no un
tester — que vive como nodo del fractal mientras corre.
Tests: 27/27 verdes. cargo check --workspace: 0 errores.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
595f68e2526c8dae4e8bc2a810f671bcdcc9f018 · by Sergio · 2026-05-08T15:08:03Z (5 months ago) · parent df9d10cc5 · browse the tree at this commit
Semantic delta · 7
crates/apps/yahweh-shell/src/brahman_client.rs
crates/apps/yahweh-shell/src/main.rs
diff --git a/Cargo.lock b/Cargo.lock
index 2c2e0b1..ae516db 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -11439,11 +11439,14 @@ dependencies = [
name = "yahweh-shell"
version = "0.1.0"
dependencies = [
+ "brahman-card",
+ "brahman-handshake",
"gpui",
"notify",
"serde",
"serde_json",
"tokio",
+ "ulid",
"yahweh-bus",
"yahweh-core",
"yahweh-database-explorer",
diff --git a/crates/apps/yahweh-shell/Cargo.toml b/crates/apps/yahweh-shell/Cargo.toml
index 3d66cae..f42a371 100644
--- a/crates/apps/yahweh-shell/Cargo.toml
+++ b/crates/apps/yahweh-shell/Cargo.toml
@@ -26,6 +26,11 @@ serde = { workspace = true }
serde_json = { workspace = true }
notify = { workspace = true }
+# Brahman protocol — sidecar thread que se presenta al Init.
+brahman-card = { path = "../../core/brahman-card" }
+brahman-handshake = { path = "../../core/brahman-handshake" }
+ulid = { workspace = true }
+
[[bin]]
name = "yahweh"
path = "src/main.rs"
diff --git a/crates/apps/yahweh-shell/src/brahman_client.rs b/crates/apps/yahweh-shell/src/brahman_client.rs
new file mode 100644
index 0000000..cc87c90
--- /dev/null
+++ b/crates/apps/yahweh-shell/src/brahman_client.rs
@@ -0,0 +1,121 @@
+//! Sidecar brahman: yahweh se presenta al Init como módulo `Widget`.
+//!
+//! Vive en un thread aparte con tokio runtime current_thread, desacoplado
+//! de GPUI. Si el Init no está disponible, loggea y termina — yahweh
+//! sigue funcionando standalone. Si conecta, mantiene la sesión viva
+//! con pings periódicos hasta que la GUI termine o el server caiga.
+//!
+//! Card declarada:
+//! - label: `brahman.ui_engine`
+//! - lifecycle: `Widget`
+//! - flow.input: `render-data` (json)
+//! - flow.output: `user-intent` (json)
+//! - permissions: filesystem read-write (yahweh persiste `layout.json`),
+//! IPC `wit-v1`.
+
+use std::collections::BTreeSet;
+use std::time::Duration;
+
+use brahman_card::{
+ Card, Flow, Flows, FsPolicy, IpcPolicy, Lifecycle, Payload, Permissions, Priority, Supervision,
+ TypeRef, CARD_SCHEMA_VERSION,
+};
+use brahman_handshake::{client::Client, transport};
+use ulid::Ulid;
+
+/// Período entre pings al Init.
+const PING_INTERVAL: Duration = Duration::from_secs(30);
+
+/// Spawn del sidecar brahman. No-op si el thread no se puede crear.
+/// Devuelve inmediatamente; la conexión se establece en background.
+pub fn spawn() {
+ let result = std::thread::Builder::new()
+ .name("brahman-client".into())
+ .spawn(run_thread);
+ if let Err(e) = result {
+ eprintln!("[brahman] no se pudo spawnear el sidecar: {e}");
+ }
+}
+
+fn run_thread() {
+ let rt = match tokio::runtime::Builder::new_current_thread()
+ .enable_io()
+ .enable_time()
+ .build()
+ {
+ Ok(rt) => rt,
+ Err(e) => {
+ eprintln!("[brahman] tokio runtime falló: {e}");
+ return;
+ }
+ };
+ rt.block_on(run_client());
+}
+
+async fn run_client() {
+ let path = transport::default_socket_path();
+ let card = build_card();
+
+ let mut client = match Client::connect(&path, card).await {
+ Ok(c) => {
+ eprintln!(
+ "[brahman] attached: session={} init_attached={} server={}",
+ c.session(),
+ c.server_info().init_attached,
+ c.server_info().server_version
+ );
+ c
+ }
+ Err(e) => {
+ eprintln!("[brahman] no conectado a {} ({e})", path.display());
+ return;
+ }
+ };
+
+ loop {
+ tokio::time::sleep(PING_INTERVAL).await;
+ if let Err(e) = client.ping().await {
+ eprintln!("[brahman] ping falló: {e}");
+ return;
+ }
+ }
+}
+
+fn build_card() -> Card {
+ Card {
+ schema_version: CARD_SCHEMA_VERSION,
+ id: Ulid::new(),
+ lineage: None,
+ label: "brahman.ui_engine".into(),
+ provides: BTreeSet::new(),
+ requires: BTreeSet::new(),
+ payload: Payload::Virtual,
+ supervision: Supervision::Delegate,
+ lifecycle: Lifecycle::Widget,
+ priority: Priority::Normal,
+ permissions: Permissions {
+ filesystem: FsPolicy::ReadWrite,
+ ipc: IpcPolicy {
+ allow: vec!["wit-v1".into()],
+ },
+ ..Default::default()
+ },
+ flow: Flows {
+ input: vec![Flow {
+ name: "render-data".into(),
+ ty: TypeRef::Primitive {
+ name: "json".into(),
+ },
+ pin_to: None,
+ }],
+ output: vec![Flow {
+ name: "user-intent".into(),
+ ty: TypeRef::Primitive {
+ name: "json".into(),
+ },
+ pin_to: None,
+ }],
+ },
+ ..Default::default()
+ }
+}
diff --git a/crates/apps/yahweh-shell/src/main.rs b/crates/apps/yahweh-shell/src/main.rs
index 9a31784..8afbea6 100644
--- a/crates/apps/yahweh-shell/src/main.rs
+++ b/crates/apps/yahweh-shell/src/main.rs
@@ -6,6 +6,7 @@
//! eventos tipados de los explorers (FileExplorer, DatabaseExplorer)
//! traducidos a AppEvent.
+mod brahman_client;
mod hot_reload;
mod layout_host;
mod layout_model;
@@ -26,6 +27,10 @@ use crate::persister::Persister;
const LAYOUT_PATH: &str = "layout.json";
fn main() {
+ // Sidecar brahman: yahweh se presenta al Init antes de levantar GPUI.
+ // No bloquea: si el Init no está, el thread loggea y termina.
+ brahman_client::spawn();
+
Application::new().run(|cx: &mut App| {
Theme::install_default(cx);