From c071752d1c10fec48fdbbec2f49bbd8e1fd96144 Mon Sep 17 00:00:00 2001 From: Sam Kaplan Date: Mon, 3 Aug 2026 21:06:24 -0700 Subject: [PATCH] fix(desktop): connect managed agents to the configured relay, not the normalized loopback identity Managed agents came up "online" but discovered 0 channels and sat idle whenever the workspace relay was addressed as `localhost` (the desktop default and the seeded dev community). Root cause: the relay is multi-tenant, keyed by the literal request Host, so `localhost:3000` and `127.0.0.1:3000` are distinct communities. `buzz_core::relay::normalize_relay_url` intentionally folds all loopback spellings to `127.0.0.1` for the runtime *identity* key -- its own doc says "connection code may retain the configured URL; this canonical form is for identity, receipts, status and deduplication." But the spawn path reused that normalized URL as the child's actual BUZZ_RELAY_URL, so agents connected to the empty `127.0.0.1:3000` tenant while the human and their channels lived on `localhost:3000`; subscribe resolved 0 channels and the agent never woke. Fix: connect via the configured relay URL at every spawn site, keeping the normalized ManagedAgentRuntimeKey solely as the dedup identity (receipts, pid files, logs): - spawn_agent_child: use the raw `relay_url` param for BUZZ_RELAY_URL - start_managed_agent_process / start_pair: pass the configured `relay_url`, not `key.relay_url` - reconcile start loop: pass the raw requested community URL Honors normalize_relay_url's documented contract and restores channel discovery for loopback-addressed local communities. Signed-off-by: Sam Kaplan --- desktop/src-tauri/src/managed_agents/restore.rs | 7 ++++++- desktop/src-tauri/src/managed_agents/runtime.rs | 17 +++++++++++++---- .../src/managed_agents/runtime_commands.rs | 10 ++++++++-- 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/desktop/src-tauri/src/managed_agents/restore.rs b/desktop/src-tauri/src/managed_agents/restore.rs index 1910620159..0fdcf72a99 100644 --- a/desktop/src-tauri/src/managed_agents/restore.rs +++ b/desktop/src-tauri/src/managed_agents/restore.rs @@ -330,10 +330,15 @@ pub async fn restore_managed_agents_on_launch( // mid-turn session is not resumed by an // eager child — and silently reintroduces // N idle brains on every launch. + // Connect via the configured relay + // (`relay_url`), not the normalized + // key — see spawn_agent_child: the + // loopback fold to 127.0.0.1 is + // identity-only. spawn_agent_child( app, record, - &key.relay_url, + &relay_url, true, owner_hex_ref, ) diff --git a/desktop/src-tauri/src/managed_agents/runtime.rs b/desktop/src-tauri/src/managed_agents/runtime.rs index 3173126b90..7b18761dd4 100644 --- a/desktop/src-tauri/src/managed_agents/runtime.rs +++ b/desktop/src-tauri/src/managed_agents/runtime.rs @@ -544,9 +544,16 @@ pub fn spawn_agent_child( .map(|p| p.display().to_string()) .unwrap_or_else(|| effective_command.clone()); - // The caller supplies the explicit canonical pair relay. This is the only - // relay this child may connect to, regardless of the record/workspace default. - let effective_relay_url = runtime_key.relay_url.clone(); + // Connect the child to the CONFIGURED relay URL (the raw `relay_url` the + // caller passed), NOT the normalized `runtime_key.relay_url`. Buzz's relay + // is multi-tenant and keys each community by the literal request Host, so + // folding loopback spellings to 127.0.0.1 — correct for the dedup IDENTITY + // key, and still used for receipts/pids/logs via `runtime_key` — would + // connect the agent to a DIFFERENT, empty tenant than the one the human + // joined on `localhost`, leaving it "online" but discovering 0 channels. + // `normalize_relay_url`'s own contract says connection code must retain the + // configured URL; this honors that. + let effective_relay_url = relay_url.to_string(); // Augment PATH for DMG launches so child processes can find: // - bundled CLI via ~/.local/bin symlink @@ -997,7 +1004,9 @@ pub fn start_managed_agent_process( // Scalar PIDs are migration-only and never establish pair liveness. record.runtime_pid = None; - let mut process = spawn_agent_child(app, record, &key.relay_url, false, owner_hex)?; + // Connect via the configured relay (`relay_url`), not the normalized key — + // see spawn_agent_child: the loopback fold to 127.0.0.1 is identity-only. + let mut process = spawn_agent_child(app, record, &relay_url, false, owner_hex)?; let now = now_iso(); let receipt = super::ManagedAgentRuntimeReceipt { key: key.clone(), diff --git a/desktop/src-tauri/src/managed_agents/runtime_commands.rs b/desktop/src-tauri/src/managed_agents/runtime_commands.rs index c0e55184b1..06bb209663 100644 --- a/desktop/src-tauri/src/managed_agents/runtime_commands.rs +++ b/desktop/src-tauri/src/managed_agents/runtime_commands.rs @@ -283,7 +283,9 @@ fn start_pair( .lock() .ok() .map(|keys| keys.public_key().to_hex()); - let mut process = spawn_agent_child(&app, record, &key.relay_url, lazy, owner.as_deref())?; + // Connect via the configured relay (`relay_url`), not the normalized key — + // see spawn_agent_child: the loopback fold to 127.0.0.1 is identity-only. + let mut process = spawn_agent_child(&app, record, &relay_url, lazy, owner.as_deref())?; let now = crate::util::now_iso(); let receipt = ManagedAgentRuntimeReceipt { key: key.clone(), @@ -504,7 +506,11 @@ pub async fn reconcile_managed_agent_runtimes( Ok((record, key, requested)) => { match start_pair( record.pubkey.clone(), - key.relay_url.clone(), + // Connect via the raw requested community URL, not the + // normalized pair key — see spawn_agent_child: the + // loopback fold to 127.0.0.1 is identity-only and would + // land the agent in a different, empty tenant. + requested.clone(), true, Some(&record.updated_at), app.clone(),