diff --git a/flake.nix b/flake.nix index 0169702c..6d746ab6 100644 --- a/flake.nix +++ b/flake.nix @@ -139,7 +139,7 @@ inherit cargoArtifacts nativeBuildInputs; pname = "hyperhive-workspace"; version = "0.1.0"; - meta.description = "hyperhive workspace (hive-c0re, hive-ag3nt, hive-m1nd)"; + meta.description = "hyperhive workspace (hive-c0re, hive-ag3nt, hive-root)"; doCheck = false; }; # Bundled browser assets — see ./nix/frontend.nix. Output is @@ -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; - manager-toplevel = self.nixosConfigurations.manager.config.system.build.toplevel; + root-toplevel = self.nixosConfigurations.root.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; - manager = ./nix/templates/manager.nix; + root = ./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 @@ -244,7 +244,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.manager-toplevel; + managerToplevel = self.packages.x86_64-linux.root-toplevel; }; hive-ci = ./nix/modules/hive-ci.nix; hive-forge = ./nix/modules/hive-forge.nix; @@ -277,7 +277,7 @@ in { agent-base = mkContainer self.nixosModules.agent-base; - manager = mkContainer self.nixosModules.manager; + root = mkContainer self.nixosModules.root; }; devShells = forAllSystems ( diff --git a/hive-ag3nt/prompts/system.md b/hive-ag3nt/prompts/system.md index 355265aa..d60084bd 100644 --- a/hive-ag3nt/prompts/system.md +++ b/hive-ag3nt/prompts/system.md @@ -8,7 +8,7 @@ You are the hyperhive manager `{label}` (qualified: `{qualified_label}`){hive_id Tools (hyperhive surface): - `mcp__hyperhive__recv(wait_seconds?, max?)` — drain inbox messages (returns `(empty)` if nothing pending). Without `wait_seconds` (or with `0`) it returns immediately — a cheap "anything pending?" peek you can sprinkle between tool calls. To **wait** for work when you have nothing else useful to do this turn, call with a long wait (e.g. `wait_seconds: 180`, the max) — incoming messages wake you instantly, otherwise the call returns empty at the timeout. That's strictly better than a fixed `sleep` shell command: lower latency on new work, no busy-loop. `max` (default 1, cap 32) drains several queued messages in one call — the wake prompt tells you the pending count. -- `mcp__hyperhive__send(to, body, in_reply_to?)` — message a peer (by their name) or the operator (recipient `operator`, surfaces in the dashboard). Use `to: "*"` to broadcast to all agents (they receive a hint that it's a broadcast and may not need action). Use `to: ""` to address your structural parent without hardcoding their name — hive-c0re rewrites it at delivery time per `topology.json`, falling back to `operator` if you're a root agent. Use `to: ""` to fan-out to every direct child of yours per `topology.json` (no-op for leaf agents). Both sentinels let the operator reparent at runtime with zero change on your side. Optional `in_reply_to: ` threads this message under a prior one — the dashboard and per-agent inbox render it with a `↳ reply` link. Some agents have a per-agent allow-list (`hyperhive.allowedRecipients` in their `agent.nix`) — if so the tool refuses recipients outside the list with a clear error; route through the manager (`send(to: "manager", …)`) which is always reachable. +- `mcp__hyperhive__send(to, body, in_reply_to?)` — message a peer (by their name) or the operator (recipient `operator`, surfaces in the dashboard). Use `to: "*"` to broadcast to all agents (they receive a hint that it's a broadcast and may not need action). Use `to: ""` to address your structural parent without hardcoding their name — hive-c0re rewrites it at delivery time per `topology.json`, falling back to `operator` if you're a root agent. Use `to: ""` to fan-out to every direct child of yours per `topology.json` (no-op for leaf agents). Both sentinels let the operator reparent at runtime with zero change on your side. Optional `in_reply_to: ` threads this message under a prior one — the dashboard and per-agent inbox render it with a `↳ reply` link. Some agents have a per-agent allow-list (`hyperhive.allowedRecipients` in their `agent.nix`) — if so the tool refuses recipients outside the list with a clear error; route through the manager (`send(to: "root", …)`) which is always reachable. - (some agents only) **extra MCP tools** surfaced as `mcp____` — these are agent-specific (matrix client, scraper, db connector, etc.) declared in your `agent.nix` under `hyperhive.extraMcpServers`. Treat them as first-class tools alongside the hyperhive surface; the operator already auto-approved them at deploy time. @@ -43,7 +43,7 @@ Tools (hyperhive surface): - `mcp__hyperhive__request_next_turn()` — ask the harness to start another turn immediately after this one ends, even if the inbox is empty. Use for multi-turn tasks (long builds, sequential steps) where you want to continue without waiting for an external message. The next turn starts with `from: "self"` and `body: "continue"`. No-op if new inbox messages arrive before this turn ends (the harness already loops immediately on pending messages). No args. -Need new packages, env vars, or other NixOS config for yourself? You can't edit your own config directly — message the manager (recipient `manager`) describing what you need + why. The manager evaluates the request (it doesn't rubber-stamp), edits `/agents/{label}/config/agent.nix` on your behalf, commits, and submits an approval that the operator can accept on the dashboard; on approve hive-c0re rebuilds your container with the new config. +Need new packages, env vars, or other NixOS config for yourself? You can't edit your own config directly — message the manager (recipient `root`) describing what you need + why. The manager evaluates the request (it doesn't rubber-stamp), edits `/agents/{label}/config/agent.nix` on your behalf, commits, and submits an approval that the operator can accept on the dashboard; on approve hive-c0re rebuilds your container with the new config. Your config repo is mounted **read-only** at `/agents/{label}/config/` — `agent.nix` plus whatever extra files the manager has split the config into. Read it to see exactly what defines you (declared packages, env vars, MCP servers) before asking the manager for a change, so you can point at the precise file and line. You cannot write here; all changes flow through the manager. diff --git a/hive-c0re/src/auto_update.rs b/hive-c0re/src/auto_update.rs index 601d5417..9cd4022f 100644 --- a/hive-c0re/src/auto_update.rs +++ b/hive-c0re/src/auto_update.rs @@ -154,8 +154,8 @@ pub async fn rebuild_agent( } /// Auto-create the manager container on startup if it isn't already there. -/// hive-c0re manages hm1nd end-to-end (Phase 8 follow-up): operators no -/// longer declare `containers.hm1nd` in their host NixOS config. Bypasses +/// hive-c0re manages `root` end-to-end: operators no +/// longer declare `containers.root` 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(); diff --git a/hive-c0re/src/dashboard.rs b/hive-c0re/src/dashboard.rs index de780ea1..1dc0d30a 100644 --- a/hive-c0re/src/dashboard.rs +++ b/hive-c0re/src/dashboard.rs @@ -1157,9 +1157,8 @@ async fn get_journal( ]) .arg(lines.to_string()); if let Some(u) = q.unit.as_deref().filter(|s| !s.is_empty()) { - // accept hive-ag3nt[.service] / hive-m1nd[.service] — anything - // else we refuse, again to keep the shell-out tight. - let allowed = ["hive-ag3nt.service", "hive-m1nd.service"]; + // accept hive-ag3nt[.service] — anything else refused. + let allowed = ["hive-ag3nt.service"]; let unit = if u.ends_with(".service") { u.to_owned() } else { diff --git a/hive-c0re/src/lifecycle.rs b/hive-c0re/src/lifecycle.rs index 69178473..49821d2b 100644 --- a/hive-c0re/src/lifecycle.rs +++ b/hive-c0re/src/lifecycle.rs @@ -11,10 +11,10 @@ use tokio::process::Command; pub const AGENT_PREFIX: &str = "h-"; pub const MAX_AGENT_NAME: usize = 9; /// Container name of the manager. Lives in the same path scheme as sub-agents -/// (`/var/lib/hyperhive/agents/hm1nd/`, `/var/lib/hyperhive/applied/hm1nd/`), +/// (`/var/lib/hyperhive/agents/root/`, `/var/lib/hyperhive/applied/root/`), /// but its container has no `h-` prefix and extends a different -/// nixosConfiguration (`manager`, not `agent-base`). -pub const MANAGER_NAME: &str = "hm1nd"; +/// nixosConfiguration (`root`, not `agent-base`). +pub const MANAGER_NAME: &str = "root"; /// Mount point of the per-agent runtime directory inside the container. pub const CONTAINER_RUNTIME_MOUNT: &str = "/run/hive"; @@ -23,7 +23,7 @@ pub const CONTAINER_RUNTIME_MOUNT: &str = "/run/hive"; /// container. The harness service runs as a non-root unix user /// whose home is `/home//`, so the mount path varies per /// agent — `container_claude_mount(name)` returns -/// `/home//.claude` for sub-agents and `/home/hm1nd/.claude` +/// `/home//.claude` for sub-agents and `/home/root/.claude` /// for the manager. `claude` inside the container reads /// `$HOME/.claude` and the service environment sets `HOME` to the /// same path, so the OAuth session survives container restarts. @@ -1035,7 +1035,7 @@ async fn systemd_daemon_reload() -> Result<()> { /// `systemd-nspawn` command. /// Where in the container's filesystem the manager sees its agents tree. /// Matches the `/agents` path that pre-Phase-8 hosts declared via -/// `containers.hm1nd.bindMounts."/agents"`. +/// `containers.root.bindMounts."/agents"`. pub const CONTAINER_MANAGER_AGENTS_MOUNT: &str = "/agents"; /// Where the manager sees the applied trees of every agent, read-only. @@ -1252,7 +1252,7 @@ async fn run(args: &[&str]) -> Result<()> { // Convention: `nixos-container ...` — the // verb is `args[0]` (kind) and the container is `args[1]` - // (h- | hm1nd | hive-matrix | ...) for every long-running + // (h- | root | hive-matrix | ...) for every long-running // case we care about. Strip the `h-` prefix for sub-agents so the // build_logs row's `agent` column matches the agent's bare name // (`alice` rather than `h-alice`) — that's what the dashboard diff --git a/hive-c0re/src/manager_server.rs b/hive-c0re/src/manager_server.rs index d2f85f89..29513241 100644 --- a/hive-c0re/src/manager_server.rs +++ b/hive-c0re/src/manager_server.rs @@ -23,7 +23,7 @@ pub fn start(coord: Arc) -> Result<()> { } let listener = UnixListener::bind(&socket) .with_context(|| format!("bind manager socket {}", socket.display()))?; - // 0666 so the in-container hm1nd user (non-root) can connect; + // 0666 so the in-container root user (non-root) can connect; // the bind source dir is manager-only on host. See agent_server.rs. use std::os::unix::fs::PermissionsExt as _; std::fs::set_permissions(&socket, std::fs::Permissions::from_mode(0o666)) @@ -404,11 +404,9 @@ async fn dispatch(req: &ManagerRequest, coord: &Arc) -> ManagerResp 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 `hm1nd` through unprefixed — but - // it doesn't know the broker-logical manager name - // `"manager"` (it'd wrongly produce `h-manager`), so - // handle that alias explicitly. Either manager spelling - // resolves to the unprefixed `hm1nd` machine. + // 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 { diff --git a/hive-c0re/src/meta.rs b/hive-c0re/src/meta.rs index 43fe7bf3..6a8d137a 100644 --- a/hive-c0re/src/meta.rs +++ b/hive-c0re/src/meta.rs @@ -448,10 +448,10 @@ where out.push_str( r#" let base = if isManager - then hyperhive.nixosConfigurations.manager + then hyperhive.nixosConfigurations.root else hyperhive.nixosConfigurations.agent-base; input = inputs."agent-${name}"; - service = if isManager then "hive-m1nd" else "hive-ag3nt"; + service = "hive-ag3nt"; parentEnv = if parent == null then {} else { HIVE_PARENT = parent; }; toolGroupsEnv = if toolGroups == null then {} else { HIVE_TOOL_GROUPS = toolGroups; }; in @@ -461,7 +461,7 @@ where { # The harness service inside the container runs as a # non-root unix user named after the agent (`damocles`, - # `iris`, `hm1nd`, …). UID auto-assigned by NixOS; the + # `iris`, `root`, …). UID auto-assigned by NixOS; the # per-agent override here is what makes # `hyperhive.user.name` match the agent's identity # instead of the harness-base default of `"agent"`. diff --git a/hive-c0re/src/reminder_scheduler.rs b/hive-c0re/src/reminder_scheduler.rs index fd731dfd..392bbeae 100644 --- a/hive-c0re/src/reminder_scheduler.rs +++ b/hive-c0re/src/reminder_scheduler.rs @@ -249,21 +249,14 @@ mod tests { #[test] fn manager_uses_container_name_prefix() { - // Manager's container view of its state is at - // `/agents//state/` (= `/agents/hm1nd/state/`), - // same as every other agent. - assert_eq!(container_state_prefix("manager"), "/agents/hm1nd/state/"); - let p = resolve_host_path("manager", "/agents/hm1nd/state/reminders/x.md").unwrap(); - // NB: the host path still resolves under `agents/manager/` - // (Coordinator::agent_notes_dir takes the broker LOGICAL name). - // That's a pre-existing manager-logical-vs-container-name - // discrepancy tracked separately; out of scope here. + // 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(); assert_eq!( p, - PathBuf::from("/var/lib/hyperhive/agents/manager/state/reminders/x.md") + PathBuf::from("/var/lib/hyperhive/agents/root/state/reminders/x.md") ); - // And the legacy `/state/` prefix must NOT be accepted anymore. - assert!(resolve_host_path("manager", "/state/x.md").is_err()); + assert!(resolve_host_path("root", "/state/x.md").is_err()); } #[test] diff --git a/hive-sh4re/src/lib.rs b/hive-sh4re/src/lib.rs index 58c8dc8c..b0c9a531 100644 --- a/hive-sh4re/src/lib.rs +++ b/hive-sh4re/src/lib.rs @@ -578,7 +578,7 @@ fn default_true() -> bool { // ----------------------------------------------------------------------------- /// Logical name the broker uses for the manager. -pub const MANAGER_AGENT: &str = "manager"; +pub const MANAGER_AGENT: &str = "root"; /// Logical name the broker uses for the human operator. Messages with /// `to = OPERATOR_RECIPIENT` accumulate in sqlite and surface on the @@ -693,7 +693,7 @@ pub enum HelperEvent { /// (the dispatcher re-parses it on approve and inserts the schedule). #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct SchedulePromptPayload { - /// Names of recipient agents. Operator + `hm1nd` allowed. + /// Names of recipient agents. Operator + `root` allowed. pub targets: Vec, /// Message body delivered to each target's inbox at fire time. /// Same size budget as `Send.body` — soft cap at the broker level. diff --git a/nix/templates/harness-base.nix b/nix/templates/harness-base.nix index 53ffc9f1..9deb2c3e 100644 --- a/nix/templates/harness-base.nix +++ b/nix/templates/harness-base.nix @@ -110,8 +110,8 @@ in example = "manager"; description = '' Whether this container runs as a sub-agent (`"agent"`, the - default — invokes `hive-ag3nt serve`) or as the swarm's - manager (`"manager"` — invokes `hive-m1nd serve` and + default) or as the swarm's manager (`"manager"` — both invoke + `hive serve`; defaults the forge notification surface to mentions-only). meta.rs flips this to `"manager"` for the manager container @@ -690,7 +690,7 @@ in } ]; - # Per-agent unix user. Runs the hive-ag3nt / hive-m1nd harness + + # Per-agent unix user. Runs the hive harness + # co-process daemons under a non-root principal. UID auto-assigned by # NixOS. The container activation script (hive-agent-user-migrate) # chowns the bind-mounted state dir — including credential files @@ -1277,14 +1277,12 @@ in ]; }; - # Role-driven harness systemd unit: one binary, two unit names - # for log/ExecStartPre stability. Unit shape (PATH wrapper-dir - # trick, env vars, RuntimeDirectory, User=, standalone-eval - # fallbacks): docs/agent-hierarchy.md::Harness systemd unit - # shape (per-role). PATH /bin auto-append behaviour: - # docs/gotchas.md::systemd.services.*.path appends /bin to - # every entry. - systemd.services.${if config.hyperhive.role == "manager" then "hive-m1nd" else "hive-ag3nt"} = + # Harness systemd unit. Unit shape (PATH wrapper-dir trick, env vars, + # RuntimeDirectory, User=, standalone-eval fallbacks): + # docs/agent-hierarchy.md::Harness systemd unit shape. PATH /bin + # auto-append behaviour: docs/gotchas.md::systemd.services.*.path + # appends /bin to every entry. + systemd.services.hive-ag3nt = let isManager = config.hyperhive.role == "manager"; binary = "hive"; @@ -1319,9 +1317,9 @@ in } // lib.optionalAttrs isManager { # Standalone-eval fallbacks; meta.rs overrides at deploy time. - # HIVE_PORT = FNV-1a("hm1nd") % 900 + 8100. - HIVE_PORT = "8875"; - HIVE_LABEL = "hm1nd"; + # HIVE_PORT = FNV-1a("root") % 900 + 8100. + HIVE_PORT = "8273"; + HIVE_LABEL = "root"; # Manager always uses a unix socket so the gateway can route # /agent// to it the same way it routes sub-agents. HIVE_WEB_SOCKET = "/run/hive-agent/${userName}/web.sock"; diff --git a/nix/templates/manager.nix b/nix/templates/manager.nix index 1c857e3b..20edbd10 100644 --- a/nix/templates/manager.nix +++ b/nix/templates/manager.nix @@ -2,13 +2,13 @@ { imports = [ ./harness-base.nix ]; - # Manager role: the role-driven `systemd.services.hive-m1nd` plus + # Manager role: the `systemd.services.hive-ag3nt` unit plus # the manager-only forge defaults (`keepSubscriptions = false`, # `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.manager`) and the meta-flake's - # `applied/hm1nd/flake.nix`. HIVE_PORT / HIVE_LABEL are injected by + # `flake.nix` (`nixosConfigurations.root`) and the meta-flake's + # `applied/root/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";