From 35a7ff03b7695bbcae737cbb7e47c9a8942b3c0c Mon Sep 17 00:00:00 2001 From: damocles Date: Sun, 31 May 2026 15:36:04 +0200 Subject: [PATCH] hive-c0re: bind-mount /run/hive-agent// per sub-agent (#784 phase 2 step 2b) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit builds on step 2a (#809). lifecycle::set_nspawn_flags now adds a --bind={socket_dir}:{socket_dir} flag per sub-agent so the harness's HIVE_WEB_SOCKET bind (PR #800) lives in a dir both the agent container and the host can see. design (matches #809's a1a601d explanation): - bind the SUBDIR, not the socket file. file bind-mounts drop on unlink; the harness's bind_unix unlinks any stale socket before binding, so a file bind would land the new socket in the agent's private namespace, invisible to the gateway. dir bind keeps both sides on the same dir inode. - per-agent dir (one /run/hive-agent// per agent, not a shared /run/hive-agent/ mount). The agent's container only sees its own subdir — never siblings' (mara on #800). - manager skipped — the manager's UI serves at / via the c0re dashboard upstream, not via /agent//, so it never needs the per-agent socket dir. mkdir source defensively before bind: nspawn refuses to start when the bind source is missing, and /run/hive-agent/ doesn't exist on fresh hosts. remaining work in this phase: - step 3 (atlas): gateway proxy_pass http://unix:/run/hive-agent//web.sock:/ - per-agent: flip HIVE_WEB_SOCKET in agent.nix to opt in (separate PRs) - step 4 (later): drop TCP fallback once everyone's flipped --- hive-c0re/src/lifecycle.rs | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/hive-c0re/src/lifecycle.rs b/hive-c0re/src/lifecycle.rs index 73032c4e..9303a79d 100644 --- a/hive-c0re/src/lifecycle.rs +++ b/hive-c0re/src/lifecycle.rs @@ -1141,6 +1141,40 @@ fn set_nspawn_flags( // empty RO dir instead of a container that won't boot. std::fs::create_dir_all(&config_dir).with_context(|| format!("create {config_dir}"))?; let _ = write!(binds, " --bind-ro={config_dir}:/agents/{agent_name}/config"); + + // Per-agent socket subdir (#784 phase 2 step 2b). Bind-mounts + // `/run/hive-agent//` into the container at the same + // path so the harness's `HIVE_WEB_SOCKET` bind has a stable + // location both sides can see. Sub-agents only — the + // manager's UI is served at `/` via the c0re dashboard + // upstream, not via `/agent//`, so it never needs the + // per-agent socket dir. + // + // Bind-mounting the SUBDIR (not the socket file) is mandatory: + // the harness's `bind_unix` helper unlinks any stale socket + // before calling `bind(2)`, and a file bind-mount drops its + // host-side anchor on unlink — the rebind would land in the + // container's private namespace, invisible to the gateway. + // Dir bind keeps the same dir inode visible on both sides, so + // the new `web.sock` shows up on the host the moment the + // harness binds it. + // + // Per-agent dir (rather than a shared `/run/hive-agent/` + // mount) means the agent's container only sees its own + // subdir — never siblings' (mara on #800: "agents can only + // access their own sockets"). + // + // mkdir source defensively: nspawn refuses to start when the + // bind source is missing, and on a fresh host `/run/hive-agent/` + // doesn't exist yet. + let socket_dir = crate::agent_sockets::agent_dir_for(agent_name); + std::fs::create_dir_all(&socket_dir) + .with_context(|| format!("create {}", socket_dir.display()))?; + let _ = write!( + binds, + " --bind={socket_dir}:{socket_dir}", + socket_dir = socket_dir.display(), + ); } let bind_flag = format!("EXTRA_NSPAWN_FLAGS=\"{binds}\""); let mut lines: Vec = original