From 89665b94dedbe6456cd60aba11a627a7bf1bfae8 Mon Sep 17 00:00:00 2001 From: damocles Date: Tue, 2 Jun 2026 17:02:01 +0200 Subject: [PATCH] =?UTF-8?q?feat(#1014):=20rename=20manager=20agent=20root?= =?UTF-8?q?=E2=86=92ruth=20across=20all=20crates=20+=20frontend?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- flake.nix | 8 ++-- frontend/packages/dashboard/src/flow.js | 4 +- frontend/packages/dashboard/src/tabs.js | 20 ++------- hive-ag3nt/src/mcp.rs | 11 ++--- hive-ag3nt/src/prompt.rs | 6 +-- hive-c0re/src/agent_server.rs | 7 ---- hive-c0re/src/auto_update.rs | 17 ++++---- hive-c0re/src/container_view.rs | 54 +++++++------------------ hive-c0re/src/dashboard.rs | 16 ++------ hive-c0re/src/forge.rs | 8 +--- hive-c0re/src/manager_server.rs | 40 ++++++------------ hive-c0re/src/matrix.rs | 8 +--- hive-c0re/src/meta.rs | 2 +- hive-c0re/src/reminder_scheduler.rs | 10 ++--- hive-priv/src/main.rs | 13 ++---- hive-sh4re/src/lib.rs | 1 - hive-sh4re/src/priv_proto.rs | 6 +-- nix/templates/harness-base.nix | 2 +- nix/templates/manager.nix | 4 +- 19 files changed, 71 insertions(+), 166 deletions(-) diff --git a/flake.nix b/flake.nix index d82e9169..1c7d8032 100644 --- a/flake.nix +++ b/flake.nix @@ -173,7 +173,7 @@ # they're plain derivations, but `nix build` from a non-x86 # host would only succeed via a remote x86 builder. agent-base-toplevel = self.nixosConfigurations.agent-base.config.system.build.toplevel; - root-toplevel = self.nixosConfigurations.root.config.system.build.toplevel; + ruth-toplevel = self.nixosConfigurations.ruth.config.system.build.toplevel; # Auto-generated nix options reference for hyperhive (#616). # `docs` bundles host + agent pages into one tree; the split @@ -223,7 +223,7 @@ nixosModules = { agent-base = ./nix/templates/agent-base.nix; - root = ./nix/templates/manager.nix; + ruth = ./nix/templates/manager.nix; # The hive-c0re module wants `pkgs.hyperhive` for its default # `services.hyperhive.c0re.package`. To avoid making operators apply an # overlay (which would also pollute their host pkgs with our @@ -249,7 +249,7 @@ # extra deps gated so aarch64 hosts don't accidentally pull # them in via cross-build. agentBaseToplevel = self.packages.x86_64-linux.agent-base-toplevel; - managerToplevel = self.packages.x86_64-linux.root-toplevel; + managerToplevel = self.packages.x86_64-linux.ruth-toplevel; }; hive-ci = ./nix/modules/hive-ci.nix; hive-forge = ./nix/modules/hive-forge.nix; @@ -282,7 +282,7 @@ in { agent-base = mkContainer self.nixosModules.agent-base; - root = mkContainer self.nixosModules.root; + ruth = mkContainer self.nixosModules.ruth; }; devShells = forAllSystems ( diff --git a/frontend/packages/dashboard/src/flow.js b/frontend/packages/dashboard/src/flow.js index b52ab2fa..1d8a23f1 100644 --- a/frontend/packages/dashboard/src/flow.js +++ b/frontend/packages/dashboard/src/flow.js @@ -264,10 +264,8 @@ import { function knownAgents() { // Read live from the flow-local containers cache so newly-spawned // agents become addressable without a manual reload. - // Broker uses the literal recipient `manager` for the manager's - // inbox, not the container name `hm1nd`. const names = Array.from(flowContainers.values()) - .map((c) => (c.is_manager ? 'manager' : c.name)); + .map((c) => c.name); // `*` fans out to every registered agent (server-side // broadcast_send). names.unshift('*'); diff --git a/frontend/packages/dashboard/src/tabs.js b/frontend/packages/dashboard/src/tabs.js index 44f2f7d2..855c8dca 100644 --- a/frontend/packages/dashboard/src/tabs.js +++ b/frontend/packages/dashboard/src/tabs.js @@ -429,7 +429,7 @@ window.marked = marked; menuItem('↻ R3BU1LD', { action: '/rebuild/', confirm: `rebuild ${c.name}? hot-reloads the container.` }), ); - if (!c.is_manager) { + { dropdown.append( menuSep(), menuItem('DESTR0Y', { @@ -707,8 +707,6 @@ window.marked = marked; const head = el('div', { class: 'head' }); head.append( el('a', { class: 'name', href: url, target: '_blank', rel: 'noopener' }, c.name), - el('span', { class: c.is_manager ? 'role role-m1nd' : 'role role-ag3nt' }, - c.is_manager ? 'm1nd' : 'ag3nt'), ); // Icon-only nav strip — populated async from `/api/agent/{name}/links`, // a same-origin proxy that forwards the agent backend's own link list @@ -886,11 +884,9 @@ window.marked = marked; actions.replaceChildren(); const allRunning = selected.every((c) => c.running); const allStopped = selected.every((c) => !c.running); - const noManagers = selected.every((c) => !c.is_manager); const stoppedNames = selected.filter((c) => !c.running).map((c) => c.name); const runningNames = selected.filter((c) => c.running).map((c) => c.name); - const managerNames = selected.filter((c) => c.is_manager).map((c) => c.name); function why(label, blockers) { if (!blockers.length) return null; @@ -916,21 +912,14 @@ window.marked = marked; action: '/rebuild/', confirm: (names) => `rebuild ${names.length} agent${names.length === 1 ? '' : 's'} (${names.join(', ')})? hot-reloads each container.`, }); - // DESTR0Y / PURG3: sub-agents only (manager has its own - // `refusing to destroy` guard at the host layer). When the - // selection includes the manager, both buttons go disabled with a - // clear reason rather than letting the operator submit and eat a - // 500. - addBulkButton(actions, 'btn-destroy', 'DESTR0Y', noManagers, selected, { + addBulkButton(actions, 'btn-destroy', 'DESTR0Y', true, selected, { action: '/destroy/', confirm: (names) => `destroy ${names.length} agent${names.length === 1 ? '' : 's'} (${names.join(', ')})? containers are removed; state + creds kept.`, - disabledTitle: why('DESTR0Y', managerNames.map((n) => `\`${n}\` is the manager`)), }); - addBulkButton(actions, 'btn-destroy', 'PURG3', noManagers, selected, { + addBulkButton(actions, 'btn-destroy', 'PURG3', true, selected, { action: '/destroy/', body: { purge: 'on' }, confirm: (names) => `PURGE ${names.length} agent${names.length === 1 ? '' : 's'} (${names.join(', ')})? containers, config history, claude creds, and notes are all WIPED. no undo.`, - disabledTitle: why('PURG3', managerNames.map((n) => `\`${n}\` is the manager`)), }); // Move agent(s) in the topology tree — selecting an option in the @@ -2588,8 +2577,7 @@ window.marked = marked; // ready to append to the form. function buildTargetChips({ idPrefix, fieldName, checked, extraNames = [] }) { // Derive the manager's actual name from live state rather than - // hardcoding it — the manager's container name may differ from the - // logical label the broker uses (e.g. "root" after the rename). + // hardcoding it — surface it first so it always appears before agents. const managerContainer = Array.from(containersState.values()).find((c) => c.is_manager); const managerName = managerContainer?.name; const candidates = ['operator']; diff --git a/hive-ag3nt/src/mcp.rs b/hive-ag3nt/src/mcp.rs index 61c07458..c0b0e4cc 100644 --- a/hive-ag3nt/src/mcp.rs +++ b/hive-ag3nt/src/mcp.rs @@ -55,7 +55,6 @@ pub enum SocketReply { ReminderRollup(hive_sh4re::ReminderStats), AgentMeta { name: String, - role: String, running: bool, hyperhive_rev: Option, status_text: Option, @@ -84,7 +83,6 @@ impl From for SocketReply { hive_sh4re::Response::Schedules { schedules } => Self::Schedules(schedules), hive_sh4re::Response::AgentMeta { name, - role, running, hyperhive_rev, status_text, @@ -93,7 +91,6 @@ impl From for SocketReply { swarm_name, } => Self::AgentMeta { name, - role, running, hyperhive_rev, status_text, @@ -269,7 +266,6 @@ pub fn format_agent_meta(resp: Result) -> String { match resp { Ok(SocketReply::AgentMeta { name, - role, running, hyperhive_rev, status_text, @@ -279,8 +275,7 @@ pub fn format_agent_meta(resp: Result) -> String { }) => { let rev = hyperhive_rev.as_deref().unwrap_or(""); let run = if running { "yes" } else { "no" }; - let mut out = - format!("name: {name}\nrole: {role}\nhyperhive_rev: {rev}\nrunning: {run}"); + let mut out = format!("name: {name}\nhyperhive_rev: {rev}\nrunning: {run}"); // Surface hive + swarm display names only when set, so // single-hive deployments don't see noisy `` lines. if let Some(hn) = hive_name.as_deref() { @@ -713,7 +708,7 @@ impl AgentServer { #[tool( description = "Fetch identity + status metadata for an agent. Returns canonical \ - `name`, `role` (`agent` / `manager`), the current `hyperhive_rev` hive-c0re is \ + `name`, the current `hyperhive_rev` hive-c0re is \ running against, and the target's self-reported `status` text (set via \ `set_status`) plus how long ago it was set. Pass `name` to query a peer (e.g. \ check whether iris is idle before pinging them); omit `name` to get your own \ @@ -1782,7 +1777,7 @@ impl ManagerServer { #[tool( description = "Fetch identity + status metadata for an agent. Returns canonical \ - `name`, `role` (`agent` / `manager`), the current `hyperhive_rev` hive-c0re is \ + `name`, the current `hyperhive_rev` hive-c0re is \ running against, and the target's self-reported `status` text (set via \ `set_status`) plus how long ago it was set. Pass `name` to query a sub-agent or \ peer manager; omit `name` for the manager's own identity stamp — useful for \ diff --git a/hive-ag3nt/src/prompt.rs b/hive-ag3nt/src/prompt.rs index b88569a7..e4e88d30 100644 --- a/hive-ag3nt/src/prompt.rs +++ b/hive-ag3nt/src/prompt.rs @@ -318,7 +318,7 @@ shared closer let rendered = render( &PRODUCTION_TEMPLATE, Flavor::Manager, - "root", + "ruth", "she/her", None, None, @@ -346,7 +346,7 @@ shared closer let manager = render( &PRODUCTION_TEMPLATE, Flavor::Manager, - "root", + "ruth", "she/her", None, None, @@ -390,7 +390,7 @@ You are hyperhive agent `{label}` (qualified: `{qualified_label}`){hive_identity let rendered = render( IDENTITY_FIXTURE, Flavor::Manager, - "root", + "ruth", "she/her", None, Some("constellat1on"), diff --git a/hive-c0re/src/agent_server.rs b/hive-c0re/src/agent_server.rs index 92b7f6cf..b9964468 100644 --- a/hive-c0re/src/agent_server.rs +++ b/hive-c0re/src/agent_server.rs @@ -253,16 +253,9 @@ pub(crate) async fn dispatch_shared( let target = name.as_deref().unwrap_or(agent); let (status_text, status_set_at, running) = crate::container_view::read_agent_status_live(target).await; - let role = if target == hive_sh4re::MANAGER_AGENT { - "manager" - } else { - "agent" - } - .to_owned(); let (hive_name, swarm_name) = crate::container_view::hive_swarm_names(); hive_sh4re::Response::AgentMeta { name: target.to_owned(), - role, running, hyperhive_rev: crate::auto_update::current_flake_rev(&coord.hyperhive_flake), status_text, diff --git a/hive-c0re/src/auto_update.rs b/hive-c0re/src/auto_update.rs index 7c8bfdac..87341af7 100644 --- a/hive-c0re/src/auto_update.rs +++ b/hive-c0re/src/auto_update.rs @@ -156,13 +156,16 @@ pub async fn rebuild_agent( } /// Auto-create the manager container on startup if it isn't already there. -/// hive-c0re manages `root` end-to-end: operators no -/// longer declare `containers.root` in their host NixOS config. Bypasses +/// hive-c0re manages `ruth` end-to-end: operators no +/// longer declare `containers.h-ruth` in their host NixOS config. Bypasses /// the approval queue — manager is required infrastructure. Idempotent. pub async fn ensure_manager(coord: &Arc) -> Result<()> { let existing = lifecycle::list().await.unwrap_or_default(); let current_rev = current_flake_rev(&coord.hyperhive_flake); - if existing.iter().any(|c| c == MANAGER_NAME) { + if existing + .iter() + .any(|c| c.strip_prefix(AGENT_PREFIX) == Some(MANAGER_NAME)) + { // Container exists already. If it predates the unified lifecycle // (no applied flake on disk) we must rebuild — otherwise it's // running whatever the host-declarative config was at create @@ -285,13 +288,7 @@ pub async fn run(coord: Arc) -> Result<()> { // the topology file sort last (stable, alphabetical within tier). let mut logical_names: Vec = containers .iter() - .filter_map(|c| { - if c == MANAGER_NAME { - Some(MANAGER_NAME.to_owned()) - } else { - c.strip_prefix(AGENT_PREFIX).map(str::to_owned) - } - }) + .filter_map(|c| c.strip_prefix(AGENT_PREFIX).map(str::to_owned)) .collect(); let topo = crate::topology::read(); topology_sort(&mut logical_names, &topo); diff --git a/hive-c0re/src/container_view.rs b/hive-c0re/src/container_view.rs index 378d05ca..94dd00a2 100644 --- a/hive-c0re/src/container_view.rs +++ b/hive-c0re/src/container_view.rs @@ -12,7 +12,7 @@ use rusqlite::Connection; use serde::{Deserialize, Serialize}; use crate::coordinator::Coordinator; -use crate::lifecycle::{self, AGENT_PREFIX, MANAGER_CONTAINER, MANAGER_NAME}; +use crate::lifecycle::{self, AGENT_PREFIX, MANAGER_NAME}; /// An agent-declared extra navigation link surfaced on the dashboard card. /// Written by the `hive-dashboard-links` NixOS oneshot into @@ -30,8 +30,11 @@ pub struct DashboardLink { pub struct ContainerView { /// Logical agent name (no `h-` prefix). Used in action URLs. pub name: String, - /// Container name as nixos-container sees it (`h-foo`, `root`). + /// Container name as nixos-container sees it (`h-foo`). pub container: String, + /// True when this is the manager agent. Informational — not used + /// to gate any server-side actions. Computed from `name == + /// MANAGER_NAME` so it doesn't add new state. pub is_manager: bool, pub port: u16, pub running: bool, @@ -108,11 +111,7 @@ pub async fn build_all(coord: &Coordinator) -> Vec { let topology = crate::topology::read(); let mut out = Vec::new(); for c in &raw { - let (logical, is_manager) = if c == MANAGER_CONTAINER { - (MANAGER_NAME.to_owned(), true) - } else if let Some(n) = c.strip_prefix(AGENT_PREFIX) { - (n.to_owned(), false) - } else { + let Some(logical) = c.strip_prefix(AGENT_PREFIX).map(str::to_owned) else { continue; }; let deployed_full = locked @@ -120,19 +119,9 @@ pub async fn build_all(coord: &Coordinator) -> Vec { .map(std::string::String::as_str); let needs_update = crate::auto_update::agent_config_pending(&logical, deployed_full); let deployed_sha = deployed_full.map(|s| s[..s.len().min(12)].to_owned()); - // Recipient name the broker uses for this agent — sub-agents - // are addressed by logical name, the manager by the - // MANAGER_AGENT constant. Mirrors the rest of the broker - // surface so the count matches what `mcp__hyperhive__remind` - // queued. - let reminder_recipient = if is_manager { - hive_sh4re::MANAGER_AGENT - } else { - logical.as_str() - }; let pending_reminders = coord .broker - .count_pending_reminders_for(reminder_recipient) + .count_pending_reminders_for(logical.as_str()) .unwrap_or(0); let extra_links = read_dashboard_links(&logical); let parent = topology.get(&logical).cloned().flatten(); @@ -155,12 +144,9 @@ pub async fn build_all(coord: &Coordinator) -> Vec { ) = if running { // needs_login fires when EITHER the claude session dir is // missing (boot-time / fresh container) OR the harness wrote - // the auth-failed sentinel because a turn hit 401. The - // manager has its own session lifecycle and never - // participates in needs_login. - let needs_login = !is_manager - && (!claude_has_session(&Coordinator::agent_claude_dir(&logical)) - || auth_failed_sentinel(&logical)); + // the auth-failed sentinel because a turn hit 401. + let needs_login = !claude_has_session(&Coordinator::agent_claude_dir(&logical)) + || auth_failed_sentinel(&logical); let last_turn = read_last_turn(&logical); let ctx_tokens = last_turn.as_ref().map(|(toks, _)| *toks); let context_window_tokens = last_turn @@ -180,11 +166,11 @@ pub async fn build_all(coord: &Coordinator) -> Vec { (false, None, None, false, None, None) }; out.push(ContainerView { + is_manager: logical == MANAGER_NAME, port: lifecycle::agent_web_port(&logical), running, container: c.clone(), name: logical, - is_manager, needs_update, needs_login, deployed_sha, @@ -298,22 +284,10 @@ fn read_status(name: &str) -> (Option, Option) { /// when the container isn't running so callers don't have to know /// about the sentinel rules — they just hand back what we give them. /// -/// Returned tuple is `(status_text, status_set_at, running)`. The -/// `name` argument is the broker-side recipient — `MANAGER_AGENT` for -/// the manager, the logical agent name otherwise — so callers can -/// reuse the same string they used to look the agent up. +/// Returned tuple is `(status_text, status_set_at, running)`. +/// `name` is the logical agent name (same as the broker recipient). pub async fn read_agent_status_live(name: &str) -> (Option, Option, bool) { - // The lifecycle helper wants the on-disk name (`root` for the - // manager, the bare logical name for sub-agents) and internally - // adds the `h-` prefix. Map the broker-side `MANAGER_AGENT` - // sentinel back to the lifecycle name here so callers don't have - // to bother. - let lifecycle_name = if name == hive_sh4re::MANAGER_AGENT { - lifecycle::MANAGER_NAME - } else { - name - }; - if !lifecycle::is_running(lifecycle_name).await { + if !lifecycle::is_running(name).await { return (None, None, false); } let (text, set_at) = read_agent_status(name); diff --git a/hive-c0re/src/dashboard.rs b/hive-c0re/src/dashboard.rs index 69af5662..84e59668 100644 --- a/hive-c0re/src/dashboard.rs +++ b/hive-c0re/src/dashboard.rs @@ -1178,11 +1178,7 @@ async fn get_journal( // Validate the container name against the list of managed // containers so we don't shell out with arbitrary input. let container = strip_container_prefix(&name); - let prefixed = if container == lifecycle::MANAGER_NAME { - container.clone() - } else { - format!("{}{container}", lifecycle::AGENT_PREFIX) - }; + let prefixed = format!("{}{container}", lifecycle::AGENT_PREFIX); let live = lifecycle::list().await.unwrap_or_default(); if !live.iter().any(|c| c == &prefixed) { return error_response(&format!("journal: no managed container {prefixed:?}")); @@ -2285,9 +2281,6 @@ async fn post_purge_tombstone( if let Some(reason) = validate_agent_name(&name) { return (StatusCode::BAD_REQUEST, format!("bad agent name: {reason}")).into_response(); } - if name == lifecycle::MANAGER_NAME { - return error_response("refusing to purge the manager's state"); - } // Sanity: refuse to purge if a live container still exists with this // name. The dashboard already filters tombstones to non-live names, // but the operator could send a stale POST. @@ -2755,11 +2748,8 @@ async fn post_start(State(state): State, AxumPath(name): AxumPath) -> Response { let containers = lifecycle::list().await.unwrap_or_default(); for container in containers { - let logical = if container == lifecycle::MANAGER_NAME { - lifecycle::MANAGER_NAME.to_owned() - } else if let Some(n) = container.strip_prefix(lifecycle::AGENT_PREFIX) { - n.to_owned() - } else { + let Some(logical) = container.strip_prefix(lifecycle::AGENT_PREFIX).map(str::to_owned) + else { continue; }; state.coord.rebuild_queue.enqueue( diff --git a/hive-c0re/src/forge.rs b/hive-c0re/src/forge.rs index 509f521c..06083cb3 100644 --- a/hive-c0re/src/forge.rs +++ b/hive-c0re/src/forge.rs @@ -822,13 +822,9 @@ pub async fn ensure_all() { return; }; for c in containers { - let name = if c == crate::lifecycle::MANAGER_NAME { - c - } else if let Some(n) = c.strip_prefix(crate::lifecycle::AGENT_PREFIX) { - n.to_owned() - } else { + let Some(name) = c.strip_prefix(crate::lifecycle::AGENT_PREFIX) else { continue; }; - sync_agent(&name, core_token.as_deref()).await; + sync_agent(name, core_token.as_deref()).await; } } diff --git a/hive-c0re/src/manager_server.rs b/hive-c0re/src/manager_server.rs index 4ac9695d..246c3106 100644 --- a/hive-c0re/src/manager_server.rs +++ b/hive-c0re/src/manager_server.rs @@ -118,11 +118,6 @@ async fn dispatch(req: &ManagerRequest, coord: &Arc) -> ManagerResp } ManagerRequest::Kill { name } => { tracing::info!(%name, "manager: kill"); - if name == crate::lifecycle::MANAGER_NAME { - return ManagerResponse::Err { - message: "refusing to kill the manager".into(), - }; - } let result: Result<()> = async { lifecycle::kill(name).await?; coord.unregister_agent(name); @@ -143,11 +138,6 @@ async fn dispatch(req: &ManagerRequest, coord: &Arc) -> ManagerResp } ManagerRequest::Start { name } => { tracing::info!(%name, "manager: start"); - if name == crate::lifecycle::MANAGER_NAME { - return ManagerResponse::Err { - message: "refusing to start the manager from itself".into(), - }; - } match lifecycle::start(name).await { Ok(()) => { coord.kick_agent(name, "container started"); @@ -160,11 +150,6 @@ async fn dispatch(req: &ManagerRequest, coord: &Arc) -> ManagerResp } ManagerRequest::Restart { name } => { tracing::info!(%name, "manager: enqueue restart"); - if name == crate::lifecycle::MANAGER_NAME { - return ManagerResponse::Err { - message: "refusing to restart the manager from itself".into(), - }; - } coord.rebuild_queue.enqueue( crate::rebuild_queue::QueueKind::Restart, name.to_owned(), @@ -267,16 +252,9 @@ async fn dispatch(req: &ManagerRequest, coord: &Arc) -> ManagerResp } ManagerRequest::GetLogs { agent, lines } => { let n = lines.unwrap_or(50); - // `journalctl -M` wants the *machine* name, not the - // logical agent name: `gui` → `h-gui`. `container_name` - // does that and passes the manager name through unprefixed. - // The explicit check here keeps parity with the MANAGER_AGENT - // constant so the two never diverge. - let machine = if agent == MANAGER_AGENT { - crate::lifecycle::MANAGER_NAME.to_owned() - } else { - crate::lifecycle::container_name(agent) - }; + // `journalctl -M` wants the container name (`h-`), + // not the logical agent name. `container_name` adds the prefix. + let machine = crate::lifecycle::container_name(agent); tracing::info!(%agent, %machine, %n, "manager: get_logs"); match tokio::process::Command::new("journalctl") .args([ @@ -322,7 +300,15 @@ async fn dispatch(req: &ManagerRequest, coord: &Arc) -> ManagerResp } ManagerRequest::GetLooseEnds { agent } => { let result = match agent.as_deref() { - Some("*") => crate::loose_ends::hive_wide(coord), + Some("*") => { + // Hive-wide query requires query_agent_state capability. + if !crate::capabilities::has_cap(MANAGER_AGENT, hive_sh4re::Capability::QueryAgentState) { + return ManagerResponse::Err { + message: "query_agent_state capability required for hive-wide loose ends".into(), + }; + } + crate::loose_ends::hive_wide(coord) + } Some(name) => crate::loose_ends::for_agent(coord, name), None => crate::loose_ends::for_agent(coord, MANAGER_AGENT), }; @@ -726,7 +712,7 @@ fn handle_edit_schedule( } /// Permission check for `CancelSchedule` on the manager surface. -/// `requester` (always `root` here) can cancel its own schedules. +/// `requester` (always `ruth` here) can cancel its own schedules. /// Sub-agent ownership is delegated to topology — see /// `crate::topology::is_descendant_of`. Also reused by /// `handle_fire_schedule_now` — fire-auth follows the same shape. diff --git a/hive-c0re/src/matrix.rs b/hive-c0re/src/matrix.rs index 1d4b2336..c6047be0 100644 --- a/hive-c0re/src/matrix.rs +++ b/hive-c0re/src/matrix.rs @@ -431,14 +431,10 @@ pub async fn ensure_all() { return; }; for c in containers { - let name = if c == crate::lifecycle::MANAGER_NAME { - c - } else if let Some(n) = c.strip_prefix(crate::lifecycle::AGENT_PREFIX) { - n.to_owned() - } else { + let Some(name) = c.strip_prefix(crate::lifecycle::AGENT_PREFIX) else { continue; }; - sync_agent(&client, &name, ®ister_token).await; + sync_agent(&client, name, ®ister_token).await; } } diff --git a/hive-c0re/src/meta.rs b/hive-c0re/src/meta.rs index 758df508..cf7bda75 100644 --- a/hive-c0re/src/meta.rs +++ b/hive-c0re/src/meta.rs @@ -500,7 +500,7 @@ where out.push_str( r#" let base = if isManager - then hyperhive.nixosConfigurations.root + then hyperhive.nixosConfigurations.ruth else hyperhive.nixosConfigurations.agent-base; input = inputs."agent-${name}"; service = "hive-ag3nt"; diff --git a/hive-c0re/src/reminder_scheduler.rs b/hive-c0re/src/reminder_scheduler.rs index a626d3d3..58a02d25 100644 --- a/hive-c0re/src/reminder_scheduler.rs +++ b/hive-c0re/src/reminder_scheduler.rs @@ -248,14 +248,14 @@ mod tests { #[test] fn manager_uses_container_name_prefix() { - // Manager's container view of its state is at `/agents/root/state/`. - assert_eq!(container_state_prefix("root"), "/agents/root/state/"); - let p = resolve_host_path("root", "/agents/root/state/reminders/x.md").unwrap(); + // Manager's container view of its state is at `/agents/ruth/state/`. + assert_eq!(container_state_prefix("ruth"), "/agents/ruth/state/"); + let p = resolve_host_path("ruth", "/agents/ruth/state/reminders/x.md").unwrap(); assert_eq!( p, - PathBuf::from("/var/lib/hyperhive/agents/root/state/reminders/x.md") + PathBuf::from("/var/lib/hyperhive/agents/ruth/state/reminders/x.md") ); - assert!(resolve_host_path("root", "/state/x.md").is_err()); + assert!(resolve_host_path("ruth", "/state/x.md").is_err()); } #[test] diff --git a/hive-priv/src/main.rs b/hive-priv/src/main.rs index da557ec6..deb2b4a2 100644 --- a/hive-priv/src/main.rs +++ b/hive-priv/src/main.rs @@ -314,13 +314,9 @@ async fn container_run(args: &[&str]) -> Result<(String, String)> { } /// Return the system container name for a logical agent name. -/// Manager (`MANAGER_NAME`) passes through; sub-agents get `h-` prefix. +/// All agents (including the manager) use the `h-` prefix. fn container_system_name(name: &str) -> String { - if name == MANAGER_NAME { - name.to_owned() - } else { - format!("{AGENT_PREFIX}{name}") - } + format!("{AGENT_PREFIX}{name}") } /// Path of the per-agent unix-socket dir on the host. @@ -351,11 +347,8 @@ fn validate_container_name(name: &str) -> Result<()> { } /// Validate a system-level container name (already has `h-` prefix for -/// sub-agents, or is the manager name / sibling service name). +/// all agents including the manager, or is a sibling service name). fn validate_container_system_name(name: &str) -> Result<()> { - if name == MANAGER_NAME { - return Ok(()); - } if SIBLING_CONTAINERS.contains(&name) { return Ok(()); } diff --git a/hive-sh4re/src/lib.rs b/hive-sh4re/src/lib.rs index 4fd307b1..66dd820d 100644 --- a/hive-sh4re/src/lib.rs +++ b/hive-sh4re/src/lib.rs @@ -571,7 +571,6 @@ pub enum Response { /// live in `docs/conventions.md::Agent metadata`. AgentMeta { name: String, - role: String, #[serde(default = "default_true")] running: bool, #[serde(default, skip_serializing_if = "Option::is_none")] diff --git a/hive-sh4re/src/priv_proto.rs b/hive-sh4re/src/priv_proto.rs index 2bc9b87e..82e99783 100644 --- a/hive-sh4re/src/priv_proto.rs +++ b/hive-sh4re/src/priv_proto.rs @@ -8,9 +8,9 @@ use serde::{Deserialize, Serialize}; /// Default socket path for the privileged helper. pub const PRIV_SOCK: &str = "/run/hive/priv.sock"; -/// Manager container name. Used by `hive-priv` to skip the `h-` prefix -/// and by `hive-c0re` for identity checks. -pub const MANAGER_NAME: &str = "root"; +/// Manager logical agent name. The manager's system container name is +/// `h-ruth` (same `h-` prefix convention as every other agent). +pub const MANAGER_NAME: &str = "ruth"; /// Sub-agent container prefix. System container name = `h-`. pub const AGENT_PREFIX: &str = "h-"; diff --git a/nix/templates/harness-base.nix b/nix/templates/harness-base.nix index d59d11f8..b7fafaa5 100644 --- a/nix/templates/harness-base.nix +++ b/nix/templates/harness-base.nix @@ -1307,7 +1307,7 @@ in } // lib.optionalAttrs isManager { # Standalone-eval fallback; meta.rs overrides at deploy time. - HIVE_LABEL = "root"; + HIVE_LABEL = "ruth"; }; serviceConfig = { ExecStart = "${pkgs.hyperhive}/bin/${binary} serve"; diff --git a/nix/templates/manager.nix b/nix/templates/manager.nix index 20edbd10..64fc4bb9 100644 --- a/nix/templates/manager.nix +++ b/nix/templates/manager.nix @@ -7,8 +7,8 @@ # `skipNotifyReasons = [ "subscribed" "participating" ]`) live in # `harness-base.nix` under `lib.mkIf (config.hyperhive.role == # "manager")`. This file is the bare entry-point referenced from - # `flake.nix` (`nixosConfigurations.root`) and the meta-flake's - # `applied/root/flake.nix`. HIVE_PORT / HIVE_LABEL are injected by + # `flake.nix` (`nixosConfigurations.ruth`) and the meta-flake's + # `applied/ruth/flake.nix`. HIVE_PORT / HIVE_LABEL are injected by # the meta-flake at deploy time and have manager-only standalone-eval # fallbacks in `harness-base.nix`. hyperhive.role = "manager";