Watch
0
0
Fork
You've already forked hyperhive
0

the rendered meta flake pins hyperhive by forge URL and follows its nixpkgs

This commit is contained in:
atlas 2026-10-18 19:02:09 +02:00
commit bb13909d8e
8 changed files with 135 additions and 153 deletions

View file

@ -28,8 +28,8 @@ Where it runs and how it's enabled: [`services.md`](services.md#builder).
3. The builder runs `nix build` of `nix/builder/check-flake`'s `check` output
with every input overridden:
- `hyperhive` and `nixpkgs`: the sources the builder's own host was
deployed with, the same two a hive on that host renders into its meta
flake (`c0re.hyperhiveFlake`, `c0re.nixpkgsFlake`).
deployed with (`c0re.hyperhiveFlake`, `c0re.nixpkgsFlake`). A hive's meta
flake pins `hyperhive` by forge URL and rev instead.
- `hyperhive-docs`: the `docs/` source a hive on that host renders into its
meta flake (`c0re.hyperhiveDocs`). The check sets
`services.hyperhive.agent.docs.source` from it, as the meta flake does,

View file

@ -129,6 +129,7 @@
assets = lib.mkDefault self.packages.${pkgs.stdenv.hostPlatform.system}.assets;
xdgIcons = lib.mkDefault self.packages.${pkgs.stdenv.hostPlatform.system}.xdg-icons;
hyperhiveFlake = lib.mkDefault "${sources.hyperhiveFlakeSource}";
hyperhiveRev = lib.mkDefault (self.rev or null);
# Narrow docs/ source, threaded as its own meta-flake input
# so doc edits don't re-hash the whole flake source.
hyperhiveDocs = lib.mkDefault "${sources.hyperhiveDocsSource}";

View file

@ -36,22 +36,18 @@ pub struct Coordinator {
/// `get_full` for the per-card chip + side-panel viewer. See
/// `build_logs.rs` for retention.
pub build_logs: Arc<crate::build_logs::BuildLogs>,
/// URL of the hyperhive flake (no fragment). Inlined into per-agent
/// `flake.nix` files as `inputs.hyperhive.url`.
/// Store path of the hyperhive flake source (no fragment). Used to
/// identify the running revision (`current_flake_rev`); the meta flake
/// pins `hyperhive` by forge URL and [`Self::hyperhive_rev`] instead.
pub hyperhive_flake: String,
/// URL of the narrow `docs/` source (no fragment). Inlined into the
/// meta `flake.nix` as `inputs.hyperhive-docs.url` and threaded to
/// each agent as `services.hyperhive.agent.docs.source`. Its own store path
/// so doc edits don't re-hash `hyperhive_flake`.
pub hyperhive_docs_flake: String,
/// Store-path URL of the nixpkgs to wire into the meta flake as
/// `inputs.nixpkgs.url`. Populated by `--nixpkgs-flake` (set by the
/// NixOS module to `"path:${pkgs.path}"` so the meta flake always
/// tracks the same nixpkgs the host evaluated with — which is the
/// host's nixpkgs when `inputs.hyperhive.inputs.nixpkgs.follows =
/// "nixpkgs"` is set in the host flake). Empty string = legacy
/// `follows = "hyperhive/nixpkgs"` behaviour.
pub nixpkgs_flake: String,
/// Git rev of the hyperhive checkout this host runs. Pins the meta
/// flake's `hyperhive` input; `None` makes the meta render fail.
pub hyperhive_rev: Option<String>,
/// Store path of the `claude-code` build every agent runs, written
/// into each per-agent flake as `services.hyperhive.agent.claudeCodePath`. Set by
/// the NixOS module option
@ -203,7 +199,8 @@ pub struct HiveEnv {
/// `services.hyperhive.agent.docs.source`. Separate from `hyperhive_flake`
/// so a doc edit only re-locks this input, not the whole source.
pub hyperhive_docs_flake: String,
pub nixpkgs_flake: String,
/// Git rev of the hyperhive checkout this host runs; see [`Coordinator::hyperhive_rev`].
pub hyperhive_rev: Option<String>,
/// Store path of the `claude-code` agents run, or `None` for "each
/// agent keeps the one out of its own nixpkgs". Travels into the
/// container as `services.hyperhive.agent.claudeCodePath` — a plain string, kept
@ -228,7 +225,7 @@ impl Default for HiveEnv {
Self {
hyperhive_flake: "/etc/hyperhive".to_string(),
hyperhive_docs_flake: String::new(),
nixpkgs_flake: String::new(),
hyperhive_rev: None,
claude_code_path: None,
dashboard_port: 7000,
agent_cpu_quota: "200%".to_string(),
@ -405,7 +402,7 @@ impl Coordinator {
let HiveEnv {
hyperhive_flake,
hyperhive_docs_flake,
nixpkgs_flake,
hyperhive_rev,
claude_code_path,
dashboard_port,
agent_cpu_quota,
@ -444,7 +441,7 @@ impl Coordinator {
build_logs,
hyperhive_flake,
hyperhive_docs_flake,
nixpkgs_flake,
hyperhive_rev,
claude_code_path,
dashboard_port,
agent_cpu_quota,
@ -475,7 +472,7 @@ impl Coordinator {
HiveEnv {
hyperhive_flake: self.hyperhive_flake.clone(),
hyperhive_docs_flake: self.hyperhive_docs_flake.clone(),
nixpkgs_flake: self.nixpkgs_flake.clone(),
hyperhive_rev: self.hyperhive_rev.clone(),
claude_code_path: self.claude_code_path.clone(),
dashboard_port: self.dashboard_port,
agent_cpu_quota: self.agent_cpu_quota.clone(),

View file

@ -78,15 +78,10 @@ enum Cmd {
/// Path to the sqlite message store.
#[arg(long, default_value = crate::paths::BROKER_DB)]
db: PathBuf,
/// Override: URL of the hyperhive flake. Inlined into each
/// per-agent `flake.nix` as the `hyperhive` input.
/// Override: store path of the hyperhive flake source, used to
/// identify the running revision.
#[arg(long)]
hyperhive_flake: Option<String>,
/// Override: store-path URL of the nixpkgs to wire into the meta
/// flake as `inputs.nixpkgs.url`. Empty = legacy
/// `follows = "hyperhive/nixpkgs"` fallback.
#[arg(long)]
nixpkgs_flake: Option<String>,
/// Override: dashboard HTTP port.
#[arg(long)]
dashboard_port: Option<u16>,
@ -137,7 +132,6 @@ async fn main() -> Result<()> {
config,
db,
hyperhive_flake,
nixpkgs_flake,
dashboard_port,
agent_cpu_quota,
agent_memory_max,
@ -161,9 +155,6 @@ async fn main() -> Result<()> {
if let Some(v) = hyperhive_flake {
sc.env.hyperhive_flake = v;
}
if let Some(v) = nixpkgs_flake {
sc.env.nixpkgs_flake = v;
}
if let Some(v) = dashboard_port {
sc.env.dashboard_port = v;
}

View file

@ -74,7 +74,7 @@ pub async fn sync_agents(hive: &HiveEnv, agents: &[AgentSpec]) -> Result<()> {
let dir = crate::paths::meta_root();
std::fs::create_dir_all(&dir).with_context(|| format!("create {}", dir.display()))?;
let new_flake = render_hive_flake(hive, agents);
let new_flake = render_hive_flake(hive, agents)?;
let flake_path = dir.join("flake.nix");
let on_disk = std::fs::read_to_string(&flake_path).unwrap_or_default();
let initial = !dir.join(".git").exists();
@ -327,13 +327,38 @@ pub async fn remove_permission_files() -> Result<()> {
Ok(())
}
/// Pinned forge URL of the hyperhive flake: `git+<forge>/hyperhive/hyperhive.git?rev=<rev>`.
/// Errors when the host has no rev, rather than rendering an unpinned URL.
fn hyperhive_input_url(hive: &HiveEnv) -> Result<String> {
pinned_git_url(
&crate::forge::forge_git_url("hyperhive/hyperhive"),
hive.hyperhive_rev.as_deref(),
)
}
/// The pure half of [`hyperhive_input_url`], testable without `HIVE_FORGE_URL`.
fn pinned_git_url(forge_git_url: &str, rev: Option<&str>) -> Result<String> {
let rev = rev.filter(|r| !r.is_empty()).context(
"hyperhive rev unknown: the host was built from a dirty tree \
(`self.rev` unset) and `services.hyperhive.c0re.hyperhiveRev` is not set",
)?;
Ok(format!("git+{forge_git_url}?rev={rev}"))
}
/// [`render_flake`] with its inputs taken from `hive`, as [`sync_agents`]
/// renders it.
fn render_hive_flake(hive: &HiveEnv, agents: &[AgentSpec]) -> String {
fn render_hive_flake(hive: &HiveEnv, agents: &[AgentSpec]) -> Result<String> {
Ok(render_hive_flake_at(
hive,
&hyperhive_input_url(hive)?,
agents,
))
}
fn render_hive_flake_at(hive: &HiveEnv, hyperhive_url: &str, agents: &[AgentSpec]) -> String {
render_flake(
&hive.hyperhive_flake,
hyperhive_url,
&hive.hyperhive_docs_flake,
&hive.nixpkgs_flake,
hive.claude_code_path.as_deref(),
hive.dashboard_port,
agents,
@ -341,17 +366,15 @@ fn render_hive_flake(hive: &HiveEnv, agents: &[AgentSpec]) -> String {
}
fn render_flake(
hyperhive_flake: &str,
hyperhive_url: &str,
docs_flake: &str,
nixpkgs_flake: &str,
claude_code_path: Option<&str>,
dashboard_port: u16,
agents: &[AgentSpec],
) -> String {
render_flake_with_lookup(
hyperhive_flake,
hyperhive_url,
docs_flake,
nixpkgs_flake,
claude_code_path,
dashboard_port,
agents,
@ -732,9 +755,8 @@ fn agent_canonical_inputs(name: &str) -> Vec<&'static str> {
contiguous fmt block, splitting it would just hide the shape"
)]
fn render_flake_with_lookup<F>(
hyperhive_flake: &str,
hyperhive_url: &str,
docs_flake: &str,
nixpkgs_flake: &str,
claude_code_path: Option<&str>,
dashboard_port: u16,
agents: &[AgentSpec],
@ -746,31 +768,12 @@ where
use std::fmt::Write as _;
let mut out = String::new();
out.push_str("{\n description = \"hyperhive deployed agents\";\n inputs = {\n");
// `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-<n>.inputs.nixpkgs.follows = "nixpkgs"` resolves to the same
// top-level node.
//
// 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` directly at nix-module evaluation time is the
// only reliable way to honour the host's channel choice.
//
// 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}\";");
// `hyperhive` is pinned by forge URL and rev, so the flake resolves on
// any machine that can reach the forge. `nixpkgs` follows hyperhive's
// own pin, which cascades to every agent because
// `agent-<n>.inputs.nixpkgs.follows = "nixpkgs"` resolves to this node.
let _ = writeln!(out, " hyperhive.url = \"{hyperhive_url}\";");
out.push_str(" nixpkgs.follows = \"hyperhive/nixpkgs\";\n");
} else {
let _ = writeln!(out, " nixpkgs.url = \"{nixpkgs_flake}\";");
let _ = writeln!(out, " hyperhive.url = \"{hyperhive_flake}\";");
out.push_str(" hyperhive.inputs.nixpkgs.follows = \"nixpkgs\";\n");
}
// Narrow `docs/` source as its own input so a doc edit only
// re-locks THIS input instead of re-hashing the whole `hyperhive`
// source. Threaded to each agent below as `services.hyperhive.agent.docs.source`.
@ -1283,28 +1286,43 @@ mod tests {
}
#[test]
fn render_flake_uses_explicit_nixpkgs_url_when_provided() {
fn pinned_git_url_carries_the_rev_and_refuses_without_one() {
assert_eq!(
pinned_git_url(
"https://forge.example/hyperhive/hyperhive.git",
Some("abc123")
)
.unwrap(),
"git+https://forge.example/hyperhive/hyperhive.git?rev=abc123"
);
for missing in [None, Some("")] {
let err = pinned_git_url("https://forge.example/x.git", missing).unwrap_err();
assert!(err.to_string().contains("rev unknown"), "{err}");
}
}
#[test]
fn render_flake_pins_hyperhive_by_url_and_follows_its_nixpkgs() {
let out = render_flake(
"github:example/hyperhive",
"git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
"path:/nix/store/bbbb-hyperhive-docs-source",
"path:/nix/store/aaaa-nixpkgs-source",
None,
8000,
&[sample_spec("alice", 9001)],
);
// 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}"
out.contains(
"hyperhive.url = \"git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd\";"
),
"expected hyperhive pinned by forge URL:\n{out}"
);
assert!(
out.contains("hyperhive.inputs.nixpkgs.follows = \"nixpkgs\""),
"expected hyperhive.inputs.nixpkgs.follows:\n{out}"
out.contains("nixpkgs.follows = \"hyperhive/nixpkgs\";"),
"expected nixpkgs to follow hyperhive:\n{out}"
);
assert!(
!out.contains("nixpkgs.follows = \"hyperhive"),
"old-style follows must not appear when flake args are set:\n{out}"
!out.contains("nixpkgs.url") && !out.contains("hyperhive.inputs.nixpkgs"),
"no separate nixpkgs input:\n{out}"
);
// the narrow docs source is its own non-flake input, and each
// agent's docs dir resolves from it rather than services.hyperhive.agent.packages.reference-docs.
@ -1329,9 +1347,8 @@ mod tests {
// Legacy / not-wired-up: empty docs_flake emits no docs input and
// leaves each agent on the harness default (services.hyperhive.agent.packages.reference-docs).
let out = render_flake(
"github:example/hyperhive",
"git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
"",
"path:/nix/store/aaaa-nixpkgs-source",
None,
8000,
&[sample_spec("alice", 9001)],
@ -1345,9 +1362,8 @@ mod tests {
#[test]
fn render_flake_names_each_agent_container_after_the_agent() {
let out = render_flake(
"github:example/hyperhive",
"git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
"",
"path:/nix/store/aaaa-nixpkgs-source",
None,
8000,
&[sample_spec("alice", 9001)],
@ -1383,9 +1399,8 @@ mod tests {
// input would re-copy it as a reference-less `-source` and strip
// the closure the binary actually needs.
let out = render_flake(
"github:example/hyperhive",
"git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
"path:/nix/store/bbbb-hyperhive-docs-source",
"path:/nix/store/aaaa-nixpkgs-source",
Some("/nix/store/cccc-claude-code-2.1.220"),
8000,
&[sample_spec("alice", 9001)],
@ -1409,9 +1424,8 @@ mod tests {
// in `environment.systemPackages`, which is what makes the
// unpinned case self-contained).
let out = render_flake(
"github:example/hyperhive",
"git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
"path:/nix/store/bbbb-hyperhive-docs-source",
"path:/nix/store/aaaa-nixpkgs-source",
None,
8000,
&[sample_spec("alice", 9001)],
@ -1422,28 +1436,6 @@ mod tests {
);
}
#[test]
fn render_flake_falls_back_to_follows_when_nixpkgs_flake_empty() {
// Empty nixpkgs_flake → legacy follows behaviour (backward compat
// for any code path that can't inject pkgs.path).
let out = render_flake(
"github:example/hyperhive",
"path:/nix/store/bbbb-hyperhive-docs-source",
"",
None,
8000,
&[sample_spec("alice", 9001)],
);
assert!(
out.contains("nixpkgs.follows = \"hyperhive/nixpkgs\""),
"expected fallback follows:\n{out}"
);
assert!(
!out.contains("nixpkgs.url ="),
"no explicit url should be emitted in fallback mode:\n{out}"
);
}
#[test]
fn render_flake_emits_follows_for_agents_declaring_nixpkgs() {
// Stub lookup: pretend `bitburner` + `dmatrix` declare `nixpkgs`
@ -1455,9 +1447,8 @@ mod tests {
}
};
let out = render_flake_with_lookup(
"github:example/hyperhive",
"git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
"path:/nix/store/bbbb-hyperhive-docs-source",
"path:/nix/store/aaaa-nixpkgs-source",
None,
8000,
&[
@ -1486,9 +1477,8 @@ mod tests {
#[test]
fn render_flake_builds_every_agent_from_agent_base() {
let out = render_flake(
"github:example/hyperhive",
"git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
"path:/nix/store/bbbb-hyperhive-docs-source",
"path:/nix/store/aaaa-nixpkgs-source",
None,
8000,
&[sample_spec("alice", 9001), sample_spec("ruth", 9002)],
@ -1513,9 +1503,8 @@ mod tests {
#[test]
fn render_flake_skips_canonical_follows_when_lookup_returns_empty() {
let out = render_flake_with_lookup(
"github:example/hyperhive",
"git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
"path:/nix/store/bbbb-hyperhive-docs-source",
"path:/nix/store/aaaa-nixpkgs-source",
None,
8000,
&[sample_spec("alice", 9001)],
@ -1543,9 +1532,8 @@ mod tests {
..sample_spec("droid", 9002)
};
let out = render_flake_with_lookup(
"github:example/hyperhive",
"git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
"path:/nix/store/bbbb-hyperhive-docs-source",
"path:/nix/store/aaaa-nixpkgs-source",
None,
8000,
&[sample_spec("alice", 9001), android],
@ -1596,9 +1584,8 @@ mod tests {
std::env::set_var("HIVE_FORGE_URL", "http://forge.example.test");
}
let out = render_flake(
"github:example/hyperhive",
"git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
"path:/nix/store/bbbb-hyperhive-docs-source",
"path:/nix/store/aaaa-nixpkgs-source",
None,
8000,
&[sample_spec("alice", 9001)],
@ -1639,9 +1626,8 @@ mod tests {
std::env::set_var(VARS[1], domain);
}
render_flake(
"github:example/hyperhive",
"git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
"path:/nix/store/bbbb-hyperhive-docs-source",
"path:/nix/store/aaaa-nixpkgs-source",
None,
8000,
&[sample_spec("alice", 9001)],
@ -1836,9 +1822,8 @@ mod tests {
std::env::set_var("HIVE_MATRIX_URL", "http://matrix.example.test");
}
let out = render_flake(
"github:example/hyperhive",
"git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
"path:/nix/store/bbbb-hyperhive-docs-source",
"path:/nix/store/aaaa-nixpkgs-source",
None,
8000,
&[sample_spec("alice", 9001)],
@ -1870,9 +1855,8 @@ mod tests {
// forge being reachable. The forge `agent-configs/<n>` repos stay the
// review surface, but they are not the flake's build input.
let out = render_flake(
"github:example/hyperhive",
"git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
"path:/nix/store/bbbb-hyperhive-docs-source",
"path:/nix/store/aaaa-nixpkgs-source",
None,
8000,
&[sample_spec("alice", 9001)],
@ -1886,8 +1870,8 @@ mod tests {
"expected the agent input to point at the local applied mirror ({want}):\n{out}"
);
assert!(
!out.contains("git+http"),
"no forge git+http URL must remain in the rendered flake:\n{out}"
!out.contains("agent-alice.url = \"git+http"),
"no forge git+http URL must remain for the agent input:\n{out}"
);
}
@ -1912,9 +1896,8 @@ mod tests {
let render = || {
render_flake(
"github:example/hyperhive",
"git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
"path:/nix/store/bbbb-hyperhive-docs-source",
"path:/nix/store/aaaa-nixpkgs-source",
None,
8000,
&[sample_spec("alice", 9001)],
@ -2002,9 +1985,8 @@ mod tests {
// SAFETY: serialised by the guard above; restored before returning.
let render = || {
render_flake(
"github:example/hyperhive",
"git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
"path:/nix/store/bbbb-hyperhive-docs-source",
"path:/nix/store/aaaa-nixpkgs-source",
None,
8000,
&[sample_spec("alice", 9001)],
@ -2074,9 +2056,8 @@ mod tests {
// SAFETY: serialised by the guard above; restored before returning.
let render = || {
render_flake(
"github:example/hyperhive",
"git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
"path:/nix/store/bbbb-hyperhive-docs-source",
"path:/nix/store/aaaa-nixpkgs-source",
None,
8000,
&[sample_spec("alice", 9001)],
@ -2112,8 +2093,16 @@ mod tests {
..HiveEnv::default()
};
let agents = [sample_spec("alice", 9001)];
let small = render_hive_flake(&hive("4G"), &agents);
let large = render_hive_flake(&hive("16G"), &agents);
let small = render_hive_flake_at(
&hive("4G"),
"git+https://forge.example/h.git?rev=abc",
&agents,
);
let large = render_hive_flake_at(
&hive("16G"),
"git+https://forge.example/h.git?rev=abc",
&agents,
);
assert_eq!(
small, large,
"the rendered flake must not depend on the memory cap"

View file

@ -97,7 +97,7 @@ let
serveConfigJson = builtins.toJSON {
hyperhive_flake = cfg.hyperhiveFlake;
hyperhive_docs_flake = cfg.hyperhiveDocs;
nixpkgs_flake = cfg.nixpkgsFlake;
hyperhive_rev = cfg.hyperhiveRev;
# The `claude-code` every agent runs, or null for "each agent keeps
# the one out of its own nixpkgs". `builtins.toJSON` serialises a
# derivation as its out path (and null as null), so the package goes

View file

@ -70,12 +70,24 @@
type = lib.types.str;
defaultText = lib.literalMD "the hyperhive flake's own filtered source store path";
description = ''
URL of the hyperhive flake (no fragment). Inlined into each
per-agent `flake.nix` at `inputs.hyperhive.url`. The per-agent
flake then pulls `hyperhive.nixosConfigurations.agent-base` to
build the container. Wired by `nixosModules.default` to this
flake's own filtered source — only override if you want agents
tracking a different ref.
Store path of the hyperhive flake source (no fragment). hive-c0re
reads it to tell which revision is running, and the swarm builder
builds on it. The meta flake does not use it: that pins
`hyperhive` by forge URL and `hyperhiveRev`. Wired by
`nixosModules.default` to this flake's own filtered source.
'';
};
hyperhiveRev = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
defaultText = lib.literalMD "the hyperhive flake's own `self.rev`";
description = ''
Git rev of the hyperhive checkout this host runs. hive-c0re pins
the generated meta flake's `hyperhive` input to
`git+<forge>/hyperhive/hyperhive.git?rev=<rev>`, and `nixpkgs`
follows that input. Wired by `nixosModules.default` from
`self.rev`, which a dirty tree does not have; with `null`,
hive-c0re refuses to render the meta flake.
'';
};
hyperhiveDocs = lib.mkOption {
@ -105,20 +117,12 @@
default = "path:${pkgs.path}";
defaultText = lib.literalMD "`\"path:\${pkgs.path}\"`";
description = ''
Store-path URL for the `nixpkgs` input in the generated meta
flake. The meta flake declares this as a top-level input and
wires `inputs.hyperhive.inputs.nixpkgs.follows = "nixpkgs"` so
every agent container evaluates with this exact nixpkgs.
Store-path URL of the nixpkgs the swarm builder's check flake
builds on. The generated meta flake does not use it: its `nixpkgs`
follows `hyperhive/nixpkgs`.
Defaults to `"path:''${pkgs.path}"` — the store path of the
nixpkgs the host NixOS module was evaluated with. When the
operator sets `inputs.hyperhive.inputs.nixpkgs.follows =
"nixpkgs"` in their host flake, `pkgs.path` resolves to the
host's own nixpkgs, so agents transparently track the same
channel as the host.
Override to pin agents to a specific nixpkgs version regardless
of the host's channel.
Defaults to `"path:''${pkgs.path}"`, the nixpkgs the host NixOS
module was evaluated with.
'';
};
preBuildAgentTemplates = lib.mkOption {
@ -340,7 +344,7 @@
description = ''
The `claude-code` build every agent on this hive runs, or `null` (the
default) to leave each agent on the `claude-code` from its own
nixpkgs — i.e. whatever `c0re.nixpkgsFlake` resolves to.
nixpkgs — i.e. the nixpkgs pinned by hyperhive's own `flake.lock`.
This is the one binary the whole hive is built around, and it
moves fast enough that a release channel routinely trails

View file

@ -135,8 +135,8 @@ let
&& lib.elem "swarm-bao-pki.service" c.after;
}
{
# The check builds on the sources a hive on this host would render into
# its meta flake, so a green check means the same base.
# The check builds on the sources this host was deployed with, so a green check means
# the same hyperhive, docs and nixpkgs base.
name = "the builder builds on the hyperhive, docs and nixpkgs this host's hives use";
ok =
env.SWARM_BUILDER_HYPERHIVE_FLAKE == withBuilder.services.hyperhive.c0re.hyperhiveFlake