feat(#2693): let the operator pin the claude-code every agent runs
Agents run whatever `claude-code` the meta flake's `nixpkgs` resolves to, and that is normally a release channel. This one package moves fast enough that stable trails unstable by weeks — 26.05 is on 2.1.187 while unstable carries 2.1.220 — and an agent cannot fix it for itself: it only ever sees the single nixpkgs hive-c0re injects, so an `agent.nix` has no other tree to reach for. New host option `services.hyperhive.c0re.claudeCodePackage` takes the package directly and rides the existing `hyperhiveDocs` threading path — serveConfigJson -> HiveEnv -> render_flake — to reach each agent as `hyperhive.claudeCodePath`. Null (the default) is today's behaviour. What travels is the store *path*, as a plain string literal, not a flake input: containers share the host's `/nix/store`, so the build is already reachable inside them with its whole closure and has nothing to travel. An input would be worse than useless — a `path:/nix/store/<pkg>` input is re-copied as a reference-less `-source`, which strips exactly the closure the binary needs. The catch is that a path written into a generated flake is text, so nothing in the container's closure keeps the binary alive. The host does that instead, and gets it for free: the package is interpolated into `/etc/hyperhive/serve.json`, `builtins.toJSON` preserves string context, so the /etc entry references it and the system closure gc-roots it for as long as that generation is the one the agents were rendered from. An assertion pins that property, because losing the context is invisible at eval and at deploy — it would surface only as every agent failing to spawn `claude` whenever the next gc ran. Container side wraps the path in a symlink farm rather than putting it on PATH directly: `systemd.services.<name>.path` and `environment.systemPackages` both coerce a store-path *string* through `lib.toDerivation`, i.e. `builtins.storePath`, which pure evaluation rejects. Interpolating the path into a builder is just text and evaluates anywhere. `claude-code` drops out of systemPackages when a pin is set, so there is exactly one claude in the container. Refs #2693
This commit is contained in:
parent
ca7146e4f0
commit
b08176f089
7 changed files with 281 additions and 1 deletions
|
|
@ -128,6 +128,45 @@
|
|||
of the host's channel.
|
||||
'';
|
||||
};
|
||||
claudeCodePackage = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.package;
|
||||
default = null;
|
||||
example = lib.literalExpression "inputs.nixpkgs-unstable.legacyPackages.x86_64-linux.claude-code";
|
||||
description = ''
|
||||
The `claude-code` build every agent runs, or `null` (the
|
||||
default) to leave each agent on the `claude-code` from its own
|
||||
nixpkgs — i.e. whatever `nixpkgsFlake` resolves to.
|
||||
|
||||
This is the one binary the whole hive is built around, and it
|
||||
moves fast enough that a release channel routinely trails
|
||||
unstable by weeks on it. An agent cannot fix that for itself:
|
||||
agents evaluate against the single nixpkgs hive-c0re injects,
|
||||
so an `agent.nix` has no other tree to reach for. Set this from
|
||||
a second nixpkgs in the host flake and every agent follows,
|
||||
without moving the nixpkgs the rest of the container is built
|
||||
from.
|
||||
|
||||
What travels into the container is the **store path**, not the
|
||||
derivation: agents share the host's `/nix/store`, so the binary
|
||||
and its full closure are already reachable there — nothing
|
||||
needs rebuilding or copying. hive-c0re writes the path into
|
||||
each agent's generated flake as a plain string literal (a bare
|
||||
path fed to `lib.types.package` would run `builtins.storePath`,
|
||||
which is illegal under pure evaluation) and the agent module
|
||||
puts its `bin/` on the harness's PATH.
|
||||
|
||||
The flip side of a plain string is that nothing in the agent's
|
||||
own closure refers to it, so the container cannot keep it
|
||||
alive. The **host** does that instead: this package is
|
||||
interpolated into `/etc/hyperhive/serve.json`, which puts it in
|
||||
the host's system closure — so it is gc-rooted by the running
|
||||
generation for exactly as long as that generation is the one
|
||||
the agents were rendered from. The cost is that
|
||||
`nix-collect-garbage` cannot reclaim an old `claude-code` until
|
||||
every agent has been rebuilt past it and the old generations
|
||||
are gone.
|
||||
'';
|
||||
};
|
||||
dashboardPort = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 7000;
|
||||
|
|
|
|||
Loading…
Reference in a new issue