From 1ce5996c493e390c6c93da1fe0ee398590c9bf80 Mon Sep 17 00:00:00 2001 From: atlas Date: Wed, 4 Nov 2026 20:52:49 +0100 Subject: [PATCH] agent: pull in and allow claude-code only for the claude runtime --- docs/getting-started/nixos.md | 5 +++-- nix/agent-modules/default.nix | 13 +++++++++---- nix/module-eval/agent-runtime.nix | 18 ++++++++++++++++++ 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/docs/getting-started/nixos.md b/docs/getting-started/nixos.md index 48685247..a116ff7b 100644 --- a/docs/getting-started/nixos.md +++ b/docs/getting-started/nixos.md @@ -39,8 +39,9 @@ nixosConfigurations.my-host = nixpkgs.lib.nixosSystem { }; ``` -`claude-code` is unfree. Hyperhive scopes the allowance to itself, so you -don't set `allowUnfree`. +`claude-code` is unfree. Hyperhive scopes the allowance to itself, and only +for agents that run claude (`services.hyperhive.agent.runtime = "claude"`, the +default), so you don't set `allowUnfree`. ## 3 · Set the required options diff --git a/nix/agent-modules/default.nix b/nix/agent-modules/default.nix index c31a40a4..668fbf7a 100644 --- a/nix/agent-modules/default.nix +++ b/nix/agent-modules/default.nix @@ -41,6 +41,10 @@ let queue option. Remove the setting. ''; + # Only a claude-runtime agent runs the binary. ACP subagents run on the + # parent agent's runtime, so they need no claude either. + runsClaude = config.services.hyperhive.agent.runtime == "claude"; + # `claude` on PATH. A hive that pins a claude-code # (`services.hyperhive.deploy.hive-controller.claudeCodePackage`) hands its # store path over at container start as `HYPERHIVE_CLAUDE_CODE_PATH`, and @@ -237,7 +241,10 @@ in # evaluates its own `nixpkgs` instance, so the operator's host-level # `nixpkgs.config.allowUnfreePredicate` does not propagate into here — # we have to allow it inside the container's config as well. - nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (pkgs.lib.getName pkg) [ "claude-code" ]; + # Only an agent that runs claude needs it: `runtime = "acp"` never + # references the package, so it is neither built nor allowed there. + nixpkgs.config.allowUnfreePredicate = + pkg: runsClaude && builtins.elem (pkgs.lib.getName pkg) [ "claude-code" ]; # Core tooling every agent gets. Per-bin split packages (see # nix/packages/default.nix + ./packages.nix) rather than the full @@ -256,9 +263,7 @@ in environment.systemPackages = [ config.services.hyperhive.agent.packages.hive-metric ] - ++ [ - claude - ] + ++ lib.optional runsClaude claude ++ (with pkgs; [ bashInteractive coreutils-full diff --git a/nix/module-eval/agent-runtime.nix b/nix/module-eval/agent-runtime.nix index 308f4b4f..f43f8a94 100644 --- a/nix/module-eval/agent-runtime.nix +++ b/nix/module-eval/agent-runtime.nix @@ -83,6 +83,9 @@ let }; }; + hasClaude = machine: lib.any (p: (p.name or "") == "claude") machine.environment.systemPackages; + allowsClaudeCode = machine: machine.nixpkgs.config.allowUnfreePredicate { pname = "claude-code"; }; + subagent = machine: machine.systemd.services.hive-subagent-daemon; harness = machine: machine.systemd.services.hive-agent; runtimeVars = [ @@ -234,6 +237,21 @@ let && !(lib.hasInfix "rm -f \"$earlier\"" s) && !(lib.hasInfix "mv -f \"$staged\" \"$earlier\"" s); } + { + name = "an ACP agent has no claude on PATH and no unfree allowance for claude-code"; + ok = !(hasClaude acp) && !(allowsClaudeCode acp) && !(allowsClaudeCode opencode); + } + { + # Presence control for the case above: the same lookups find both on a + # claude agent, and the allowance is for claude-code alone. + name = "a claude agent has claude on PATH and an unfree allowance for claude-code only"; + ok = + hasClaude claude + && hasClaude claudeApiKey + && allowsClaudeCode claude + && allowsClaudeCode claudeApiKey + && !(claude.nixpkgs.config.allowUnfreePredicate { pname = "other-unfree"; }); + } ]; in runGroup "agent-runtime" cases