diff --git a/docs/gotchas.md b/docs/gotchas.md index b5fdb56d..71ed4694 100644 --- a/docs/gotchas.md +++ b/docs/gotchas.md @@ -76,14 +76,13 @@ socket without needing a clean reinstall. ## `claude-code` is unfree -The flake pins it to **nixpkgs-unstable** via -`overlays.claude-unstable` (stable lags too far). The overlay sets -`config.allowUnfreePredicate` on its unstable import to whitelist -`claude-code` specifically — scoped, only this one package. -`harness-base.nix` does the same at the container level because -each per-agent `nixosConfiguration` evaluates its own nixpkgs -instance and the operator's host-level `allowUnfree` does **not** -propagate in. Operators don't need to set anything on their side. +`claude-code` comes from the flake's main `nixpkgs` (nixos-26.05). +It's unfree, so `harness-base.nix` sets `config.allowUnfreePredicate` +at the container level to whitelist `claude-code` specifically — +scoped, only this one package. This is needed because each per-agent +`nixosConfiguration` evaluates its own nixpkgs instance and the +operator's host-level `allowUnfree` does **not** propagate in. +Operators don't need to set anything on their side. ## Claude credentials are per-agent diff --git a/flake.lock b/flake.lock index 1ce362be..eed4737a 100644 --- a/flake.lock +++ b/flake.lock @@ -17,11 +17,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1779971959, - "narHash": "sha256-R5nauXyqyfRUFiZycFFZdkF7wl6eaUpPLst35+2nJQY=", + "lastModified": 1782847225, + "narHash": "sha256-JC9PjqKYG9ve5U8aDOLQipp3+KLANBHUvGdLZlxzdKI=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "ec942ba042dad5ef097e2ef3a3effc034241f011", + "rev": "95ca1e203c0750115fd4a6f17d5a245dfe6b1edd", "type": "github" }, "original": { @@ -31,27 +31,10 @@ "type": "github" } }, - "nixpkgs-unstable": { - "locked": { - "lastModified": 1778869304, - "narHash": "sha256-30sZNZoA1cqF5JNO9fVX+wgiQYjB7HJqqJ4ztCDeBZE=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "d233902339c02a9c334e7e593de68855ad26c4cb", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixpkgs-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, "root": { "inputs": { "crane": "crane", "nixpkgs": "nixpkgs", - "nixpkgs-unstable": "nixpkgs-unstable", "treefmt-nix": "treefmt-nix" } }, diff --git a/flake.nix b/flake.nix index 85b4f623..4c4b3979 100644 --- a/flake.nix +++ b/flake.nix @@ -3,7 +3,6 @@ inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05"; - nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; # Crane (replaces the former naersk-based build). Stateless — no nixpkgs input to # follow; `crane.mkLib pkgs` returns the lib at whatever pkgs we # pass it (we use the project's pinned nixpkgs). @@ -18,7 +17,6 @@ inputs@{ self, nixpkgs, - nixpkgs-unstable, crane, treefmt-nix, }: @@ -267,24 +265,6 @@ # in-container agent docs dir from it. hyperhive-docs = self.packages.${prev.stdenv.hostPlatform.system}.reference-docs; }; - claude-unstable = - final: prev: - let - # The overlay imports its own nixpkgs-unstable instance to - # pin claude-code there. That instance has its own config - # (independent from the user's prev.config), so we have to - # set allowUnfreePredicate inline to whitelist claude-code - # specifically — otherwise the unstable import itself - # refuses to evaluate. This is scoped: only claude-code - # bypasses unfree, nothing else. - unstable = import nixpkgs-unstable { - inherit (prev.stdenv.hostPlatform) system; - config.allowUnfreePredicate = pkg: builtins.elem (prev.lib.getName pkg) [ "claude-code" ]; - }; - in - { - inherit (unstable) claude-code; - }; }; nixosModules = { @@ -295,18 +275,11 @@ # overlay (which would also pollute their host pkgs with our # build), we thread the package straight from this flake's # `packages..default` via a `hyperhivePackage` argument. - # The `claude-unstable` overlay only matters inside our container - # builds (already applied internally in `nixosConfigurations`). hive-c0re = import ./nix/modules/hive-c0re.nix { hyperhivePackage = system: self.packages.${system}.default; hyperhiveFrontend = system: self.packages.${system}.frontend; hyperhiveAssets = system: self.packages.${system}.assets; hyperhiveFlake = "${hyperhiveFlakeSource}"; - # Store path of the nixpkgs-unstable input this flake was evaluated - # with — the channel that carries claude-code. Passed as the default - # for `services.hyperhive.c0re.nixpkgsUnstableFlake` so operators can - # override it without touching this file. - hyperhiveNixpkgsUnstable = "path:${nixpkgs-unstable}"; # Per-container toplevels — wired into `system.extraDependencies` # when `services.hyperhive.c0re.preBuildAgentTemplates` is on so the # host system closure pre-fetches the heavy build inputs. @@ -340,7 +313,6 @@ { nixpkgs.overlays = [ self.overlays.default - self.overlays.claude-unstable ]; } ]; diff --git a/hive-c0re/src/coordinator.rs b/hive-c0re/src/coordinator.rs index 6fd96eec..93068813 100644 --- a/hive-c0re/src/coordinator.rs +++ b/hive-c0re/src/coordinator.rs @@ -67,11 +67,6 @@ pub struct Coordinator { /// "nixpkgs"` is set in the host flake). Empty string = legacy /// `follows = "hyperhive/nixpkgs"` behaviour. pub nixpkgs_flake: String, - /// Store-path URL for `nixpkgs-unstable` to wire as a top-level meta - /// flake input. Hyperhive's `inputs.nixpkgs-unstable` then follows it. - /// Set via `--nixpkgs-unstable-flake` from `hive-c0re.nix`. Empty string - /// falls back to the legacy `follows = "hyperhive/nixpkgs-unstable"`. - pub nixpkgs_unstable_flake: String, /// TCP port the host's hive-c0re dashboard listens on. Inlined into /// each per-agent flake so the agent's web UI can build the right /// rebuild-button URL pointing back at the dashboard. @@ -203,7 +198,6 @@ pub struct Coordinator { pub struct HiveEnv { pub hyperhive_flake: String, pub nixpkgs_flake: String, - pub nixpkgs_unstable_flake: String, pub dashboard_port: u16, pub operator_pronouns: String, pub context_window_tokens: std::collections::HashMap, @@ -218,7 +212,6 @@ impl Default for HiveEnv { Self { hyperhive_flake: "/etc/hyperhive".to_string(), nixpkgs_flake: String::new(), - nixpkgs_unstable_flake: String::new(), dashboard_port: 7000, operator_pronouns: "she/her".to_string(), context_window_tokens: std::collections::HashMap::from([ @@ -433,7 +426,6 @@ impl Coordinator { let HiveEnv { hyperhive_flake, nixpkgs_flake, - nixpkgs_unstable_flake, dashboard_port, operator_pronouns, context_window_tokens, @@ -476,7 +468,6 @@ impl Coordinator { audit_log, hyperhive_flake, nixpkgs_flake, - nixpkgs_unstable_flake, dashboard_port, operator_pronouns, context_window_tokens, @@ -506,7 +497,6 @@ impl Coordinator { HiveEnv { hyperhive_flake: self.hyperhive_flake.clone(), nixpkgs_flake: self.nixpkgs_flake.clone(), - nixpkgs_unstable_flake: self.nixpkgs_unstable_flake.clone(), dashboard_port: self.dashboard_port, operator_pronouns: self.operator_pronouns.clone(), context_window_tokens: self.context_window_tokens.clone(), diff --git a/hive-c0re/src/flake_check.rs b/hive-c0re/src/flake_check.rs index 87a2db67..b8d16179 100644 --- a/hive-c0re/src/flake_check.rs +++ b/hive-c0re/src/flake_check.rs @@ -309,10 +309,6 @@ mod tests { "locked": {"rev": "aaa"}, "original": {"owner": "NixOS", "repo": "nixpkgs", "ref": "nixos-26.05", "type": "github"} }, - "nixpkgs-unstable": { - "locked": {"rev": "bbb"}, - "original": {"owner": "NixOS", "repo": "nixpkgs", "ref": "nixpkgs-unstable", "type": "github"} - }, "root": {"inputs": {"nixpkgs": "nixpkgs"}} }, "root": "root", diff --git a/hive-c0re/src/main.rs b/hive-c0re/src/main.rs index 6cee9a28..5e5ac9e2 100644 --- a/hive-c0re/src/main.rs +++ b/hive-c0re/src/main.rs @@ -54,11 +54,6 @@ enum Cmd { /// `follows = "hyperhive/nixpkgs"` fallback. #[arg(long)] nixpkgs_flake: Option, - /// Override: store-path URL of the nixpkgs-unstable to wire into - /// the meta flake as `inputs.nixpkgs-unstable.url`. Empty = legacy - /// `follows = "hyperhive/nixpkgs-unstable"` fallback. - #[arg(long)] - nixpkgs_unstable_flake: Option, /// Override: dashboard HTTP port. #[arg(long)] dashboard_port: Option, @@ -155,7 +150,6 @@ async fn main() -> Result<()> { db, hyperhive_flake, nixpkgs_flake, - nixpkgs_unstable_flake, dashboard_port, operator_pronouns, context_window_tokens, @@ -181,9 +175,6 @@ async fn main() -> Result<()> { if let Some(v) = nixpkgs_flake { sc.env.nixpkgs_flake = v; } - if let Some(v) = nixpkgs_unstable_flake { - sc.env.nixpkgs_unstable_flake = v; - } if let Some(v) = dashboard_port { sc.env.dashboard_port = v; } diff --git a/hive-c0re/src/meta.rs b/hive-c0re/src/meta.rs index 22753764..8f6b055d 100644 --- a/hive-c0re/src/meta.rs +++ b/hive-c0re/src/meta.rs @@ -57,7 +57,6 @@ pub async fn sync_agents(hive: &HiveEnv, agents: &[AgentSpec]) -> Result<()> { let new_flake = render_flake( &hive.hyperhive_flake, &hive.nixpkgs_flake, - &hive.nixpkgs_unstable_flake, hive.dashboard_port, &hive.operator_pronouns, &hive.context_window_tokens, @@ -563,7 +562,6 @@ pub async fn bulk_commit_topology( fn render_flake( hyperhive_flake: &str, nixpkgs_flake: &str, - nixpkgs_unstable_flake: &str, dashboard_port: u16, operator_pronouns: &str, context_window_tokens: &std::collections::HashMap, @@ -572,7 +570,6 @@ fn render_flake( render_flake_with_lookup( hyperhive_flake, nixpkgs_flake, - nixpkgs_unstable_flake, dashboard_port, operator_pronouns, context_window_tokens, @@ -586,7 +583,7 @@ fn render_flake( /// `follows = ""` line emitted in meta — collapsing the /// otherwise-separate-but-identical `nixpkgs_N` nodes into a single /// meta-level reference. -const CANONICAL_INPUTS: &[&str] = &["nixpkgs", "nixpkgs-unstable"]; +const CANONICAL_INPUTS: &[&str] = &["nixpkgs"]; /// Env vars hive-c0re forwards from its own systemd unit env into every /// sub-agent's harness service env. Each entry is `(env_var_name, @@ -841,7 +838,6 @@ fn agent_canonical_inputs(name: &str) -> Vec<&'static str> { fn render_flake_with_lookup( hyperhive_flake: &str, nixpkgs_flake: &str, - nixpkgs_unstable_flake: &str, dashboard_port: u16, operator_pronouns: &str, context_window_tokens: &std::collections::HashMap, @@ -854,37 +850,30 @@ where use std::fmt::Write as _; let mut out = String::new(); out.push_str("{\n description = \"hyperhive deployed agents\";\n inputs = {\n"); - // `nixpkgs` + `nixpkgs-unstable` are top-level meta inputs with explicit - // store-path URLs. `hyperhive` then follows them via - // `hyperhive.inputs.*.follows`. This cascades through to every agent - // because `agent-.inputs.nixpkgs.follows = "nixpkgs"` resolves to - // the same top-level node. + // `nixpkgs` is a top-level meta input with an explicit store-path URL. + // `hyperhive` then follows it via `hyperhive.inputs.nixpkgs.follows`. + // This cascades through to every agent because + // `agent-.inputs.nixpkgs.follows = "nixpkgs"` resolves to the same + // top-level node. // - // Why explicit `path:` URLs instead of + // Why an explicit `path:` URL instead of // `nixpkgs.follows = "hyperhive/nixpkgs"`: // meta points to hyperhive's *store path* as its flake input, so nix // reads hyperhive's own pinned lock when evaluating that input — the // host-level `follows` the operator set never propagates. Injecting the - // evaluated `pkgs.path` / nixpkgs-unstable path directly at nix-module - // evaluation time is the only reliable way to honour the host's channel - // choice. + // evaluated `pkgs.path` directly at nix-module evaluation time is the + // only reliable way to honour the host's channel choice. // - // Fallback (both flake args empty): legacy `follows` wiring — used when + // Fallback (flake arg empty): legacy `follows` wiring — used when // hive-c0re is not built with this option wired up. if nixpkgs_flake.is_empty() { // Legacy path: meta defers to hyperhive's own lock. let _ = writeln!(out, " hyperhive.url = \"{hyperhive_flake}\";"); out.push_str(" nixpkgs.follows = \"hyperhive/nixpkgs\";\n"); - out.push_str(" nixpkgs-unstable.follows = \"hyperhive/nixpkgs-unstable\";\n"); } else { let _ = writeln!(out, " nixpkgs.url = \"{nixpkgs_flake}\";"); - let _ = writeln!( - out, - " nixpkgs-unstable.url = \"{nixpkgs_unstable_flake}\";" - ); let _ = writeln!(out, " hyperhive.url = \"{hyperhive_flake}\";"); out.push_str(" hyperhive.inputs.nixpkgs.follows = \"nixpkgs\";\n"); - out.push_str(" hyperhive.inputs.nixpkgs-unstable.follows = \"nixpkgs-unstable\";\n"); } for spec in agents { let _ = writeln!( @@ -1276,30 +1265,21 @@ mod tests { let out = render_flake( "github:example/hyperhive", "path:/nix/store/aaaa-nixpkgs-source", - "path:/nix/store/bbbb-nixpkgs-unstable-source", 8000, "she/her", &std::collections::HashMap::new(), &[sample_spec("alice", false, 9001)], ); - // Both nixpkgs + nixpkgs-unstable are top-level inputs with - // explicit URLs; hyperhive follows them. + // nixpkgs is a top-level input with an explicit URL; hyperhive + // follows it. assert!( out.contains("nixpkgs.url = \"path:/nix/store/aaaa-nixpkgs-source\""), "expected explicit nixpkgs.url:\n{out}" ); - assert!( - out.contains("nixpkgs-unstable.url = \"path:/nix/store/bbbb-nixpkgs-unstable-source\""), - "expected explicit nixpkgs-unstable.url:\n{out}" - ); assert!( out.contains("hyperhive.inputs.nixpkgs.follows = \"nixpkgs\""), "expected hyperhive.inputs.nixpkgs.follows:\n{out}" ); - assert!( - out.contains("hyperhive.inputs.nixpkgs-unstable.follows = \"nixpkgs-unstable\""), - "expected hyperhive.inputs.nixpkgs-unstable.follows:\n{out}" - ); assert!( !out.contains("nixpkgs.follows = \"hyperhive"), "old-style follows must not appear when flake args are set:\n{out}" @@ -1313,7 +1293,6 @@ mod tests { let out = render_flake( "github:example/hyperhive", "", - "", 8000, "she/her", &std::collections::HashMap::new(), @@ -1323,10 +1302,6 @@ mod tests { out.contains("nixpkgs.follows = \"hyperhive/nixpkgs\""), "expected fallback follows:\n{out}" ); - assert!( - out.contains("nixpkgs-unstable.follows = \"hyperhive/nixpkgs-unstable\""), - "expected fallback unstable follows:\n{out}" - ); assert!( !out.contains("nixpkgs.url ="), "no explicit url should be emitted in fallback mode:\n{out}" @@ -1335,19 +1310,17 @@ mod tests { #[test] fn render_flake_emits_follows_for_agents_declaring_nixpkgs() { - // Stub lookup: pretend `bitburner` declares `nixpkgs` at its - // root, while `argus` has no canonical inputs at all. + // Stub lookup: pretend `bitburner` + `dmatrix` declare `nixpkgs` + // at their root, while `argus` has no canonical inputs at all. let lookup = |name: &str| -> Vec<&'static str> { match name { - "bitburner" => vec!["nixpkgs"], - "dmatrix" => vec!["nixpkgs", "nixpkgs-unstable"], + "bitburner" | "dmatrix" => vec!["nixpkgs"], _ => vec![], } }; let out = render_flake_with_lookup( "github:example/hyperhive", "path:/nix/store/aaaa-nixpkgs-source", - "path:/nix/store/bbbb-nixpkgs-unstable-source", 8000, "she/her", &std::collections::HashMap::new(), @@ -1363,11 +1336,8 @@ mod tests { out.contains("agent-bitburner.inputs.nixpkgs.follows = \"nixpkgs\""), "missing bitburner nixpkgs follows:\n{out}" ); - // dmatrix declares both → both follows emitted. + // dmatrix declares nixpkgs → follows emitted. assert!(out.contains("agent-dmatrix.inputs.nixpkgs.follows = \"nixpkgs\"")); - assert!( - out.contains("agent-dmatrix.inputs.nixpkgs-unstable.follows = \"nixpkgs-unstable\"") - ); // argus declares neither → no follows emitted for it. Asserting // ABSENCE is the important bit: emitting a follows on a // non-existent input errors at `nix flake lock` time. @@ -1382,7 +1352,6 @@ mod tests { let out = render_flake_with_lookup( "github:example/hyperhive", "path:/nix/store/aaaa-nixpkgs-source", - "path:/nix/store/bbbb-nixpkgs-unstable-source", 8000, "she/her", &std::collections::HashMap::new(), @@ -1416,7 +1385,6 @@ mod tests { let out = render_flake( "github:example/hyperhive", "path:/nix/store/aaaa-nixpkgs-source", - "path:/nix/store/bbbb-nixpkgs-unstable-source", 8000, "she/her", &std::collections::HashMap::new(), @@ -1464,7 +1432,6 @@ mod tests { render_flake( "github:example/hyperhive", "path:/nix/store/aaaa-nixpkgs-source", - "path:/nix/store/bbbb-nixpkgs-unstable-source", 8000, "she/her", &std::collections::HashMap::new(), @@ -1546,7 +1513,6 @@ mod tests { render_flake( "github:example/hyperhive", "path:/nix/store/aaaa-nixpkgs-source", - "path:/nix/store/bbbb-nixpkgs-unstable-source", 8000, "she/her", &std::collections::HashMap::new(), diff --git a/nix/modules/hive-c0re.nix b/nix/modules/hive-c0re.nix index 368a3a04..04edb769 100644 --- a/nix/modules/hive-c0re.nix +++ b/nix/modules/hive-c0re.nix @@ -3,7 +3,6 @@ hyperhiveFrontend, hyperhiveAssets, hyperhiveFlake, - hyperhiveNixpkgsUnstable, agentBaseToplevel, managerToplevel, }: @@ -41,7 +40,6 @@ let builtins.toJSON { hyperhive_flake = cfg.hyperhiveFlake; nixpkgs_flake = cfg.nixpkgsFlake; - nixpkgs_unstable_flake = cfg.nixpkgsUnstableFlake; dashboard_port = cfg.dashboardPort; operator_pronouns = cfg.operatorPronouns; context_window_tokens = cfg.contextWindowTokens; @@ -569,22 +567,6 @@ in of the host's channel. ''; }; - nixpkgsUnstableFlake = lib.mkOption { - type = lib.types.str; - default = hyperhiveNixpkgsUnstable; - defaultText = lib.literalMD "hyperhive's own `nixpkgs-unstable` store path"; - description = '' - Store-path URL for the `nixpkgs-unstable` input in the generated - meta flake. The meta flake declares this as a top-level input and - wires `inputs.hyperhive.inputs.nixpkgs-unstable.follows = - "nixpkgs-unstable"` so agents use this exact unstable nixpkgs. - - Defaults to the store path of the `nixpkgs-unstable` input - hyperhive's own `flake.nix` was evaluated with (the channel that - carries `claude-code`). Override when you want to track a newer - unstable snapshot or a custom `claude-code` package. - ''; - }; dashboardPort = lib.mkOption { type = lib.types.port; default = 7000;