Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
db50da570a | ||
|
|
fe5a41288d |
10 changed files with 194 additions and 38 deletions
|
|
@ -236,6 +236,11 @@
|
||||||
hyperhiveFrontend = system: self.packages.${system}.frontend;
|
hyperhiveFrontend = system: self.packages.${system}.frontend;
|
||||||
hyperhiveAssets = system: self.packages.${system}.assets;
|
hyperhiveAssets = system: self.packages.${system}.assets;
|
||||||
hyperhiveFlake = "${self}";
|
hyperhiveFlake = "${self}";
|
||||||
|
# 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`
|
# Per-container toplevels — wired into `system.extraDependencies`
|
||||||
# when `services.hyperhive.c0re.preBuildAgentTemplates` is on so the
|
# when `services.hyperhive.c0re.preBuildAgentTemplates` is on so the
|
||||||
# host system closure pre-fetches the heavy build inputs (#97).
|
# host system closure pre-fetches the heavy build inputs (#97).
|
||||||
|
|
|
||||||
|
|
@ -224,6 +224,8 @@ pub async fn run_approval_spawn(
|
||||||
let result = lifecycle::spawn(
|
let result = lifecycle::spawn(
|
||||||
&approval.agent,
|
&approval.agent,
|
||||||
&coord.hyperhive_flake,
|
&coord.hyperhive_flake,
|
||||||
|
&coord.nixpkgs_flake,
|
||||||
|
&coord.nixpkgs_unstable_flake,
|
||||||
&agent_dir,
|
&agent_dir,
|
||||||
&proposed_dir,
|
&proposed_dir,
|
||||||
&applied_dir,
|
&applied_dir,
|
||||||
|
|
@ -543,6 +545,8 @@ async fn run_apply_commit(
|
||||||
};
|
};
|
||||||
if let Err(e) = crate::meta::sync_agents(
|
if let Err(e) = crate::meta::sync_agents(
|
||||||
&coord.hyperhive_flake,
|
&coord.hyperhive_flake,
|
||||||
|
&coord.nixpkgs_flake,
|
||||||
|
&coord.nixpkgs_unstable_flake,
|
||||||
coord.dashboard_port,
|
coord.dashboard_port,
|
||||||
&coord.operator_pronouns,
|
&coord.operator_pronouns,
|
||||||
&coord.context_window_tokens,
|
&coord.context_window_tokens,
|
||||||
|
|
@ -713,6 +717,8 @@ async fn sync_meta_after_lifecycle(coord: &Coordinator) -> Result<()> {
|
||||||
let agents = lifecycle::agents_for_meta_listing().await?;
|
let agents = lifecycle::agents_for_meta_listing().await?;
|
||||||
crate::meta::sync_agents(
|
crate::meta::sync_agents(
|
||||||
&coord.hyperhive_flake,
|
&coord.hyperhive_flake,
|
||||||
|
&coord.nixpkgs_flake,
|
||||||
|
&coord.nixpkgs_unstable_flake,
|
||||||
coord.dashboard_port,
|
coord.dashboard_port,
|
||||||
&coord.operator_pronouns,
|
&coord.operator_pronouns,
|
||||||
&coord.context_window_tokens,
|
&coord.context_window_tokens,
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,8 @@ pub async fn rebuild_agent(
|
||||||
let result = lifecycle::rebuild(
|
let result = lifecycle::rebuild(
|
||||||
name,
|
name,
|
||||||
&coord.hyperhive_flake,
|
&coord.hyperhive_flake,
|
||||||
|
&coord.nixpkgs_flake,
|
||||||
|
&coord.nixpkgs_unstable_flake,
|
||||||
&agent_dir,
|
&agent_dir,
|
||||||
&applied_dir,
|
&applied_dir,
|
||||||
&claude_dir,
|
&claude_dir,
|
||||||
|
|
@ -190,6 +192,8 @@ pub async fn ensure_manager(coord: &Arc<Coordinator>) -> Result<()> {
|
||||||
lifecycle::spawn(
|
lifecycle::spawn(
|
||||||
MANAGER_NAME,
|
MANAGER_NAME,
|
||||||
&coord.hyperhive_flake,
|
&coord.hyperhive_flake,
|
||||||
|
&coord.nixpkgs_flake,
|
||||||
|
&coord.nixpkgs_unstable_flake,
|
||||||
&runtime,
|
&runtime,
|
||||||
&proposed,
|
&proposed,
|
||||||
&applied,
|
&applied,
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,19 @@ pub struct Coordinator {
|
||||||
/// URL of the hyperhive flake (no fragment). Inlined into per-agent
|
/// URL of the hyperhive flake (no fragment). Inlined into per-agent
|
||||||
/// `flake.nix` files as `inputs.hyperhive.url`.
|
/// `flake.nix` files as `inputs.hyperhive.url`.
|
||||||
pub hyperhive_flake: String,
|
pub hyperhive_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,
|
||||||
|
/// 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
|
/// 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
|
/// each per-agent flake so the agent's web UI can build the right
|
||||||
/// rebuild-button URL pointing back at the dashboard.
|
/// rebuild-button URL pointing back at the dashboard.
|
||||||
|
|
@ -211,6 +224,8 @@ impl Coordinator {
|
||||||
pub fn open(
|
pub fn open(
|
||||||
db_path: &Path,
|
db_path: &Path,
|
||||||
hyperhive_flake: String,
|
hyperhive_flake: String,
|
||||||
|
nixpkgs_flake: String,
|
||||||
|
nixpkgs_unstable_flake: String,
|
||||||
dashboard_port: u16,
|
dashboard_port: u16,
|
||||||
operator_pronouns: String,
|
operator_pronouns: String,
|
||||||
context_window_tokens: std::collections::HashMap<String, u64>,
|
context_window_tokens: std::collections::HashMap<String, u64>,
|
||||||
|
|
@ -242,6 +257,8 @@ impl Coordinator {
|
||||||
scheduled_prompts: Arc::new(scheduled_prompts),
|
scheduled_prompts: Arc::new(scheduled_prompts),
|
||||||
build_logs,
|
build_logs,
|
||||||
hyperhive_flake,
|
hyperhive_flake,
|
||||||
|
nixpkgs_flake,
|
||||||
|
nixpkgs_unstable_flake,
|
||||||
dashboard_port,
|
dashboard_port,
|
||||||
operator_pronouns,
|
operator_pronouns,
|
||||||
context_window_tokens,
|
context_window_tokens,
|
||||||
|
|
|
||||||
|
|
@ -185,6 +185,8 @@ async fn port_collision(self_name: &str) -> Option<String> {
|
||||||
pub async fn spawn(
|
pub async fn spawn(
|
||||||
name: &str,
|
name: &str,
|
||||||
hyperhive_flake: &str,
|
hyperhive_flake: &str,
|
||||||
|
nixpkgs_flake: &str,
|
||||||
|
nixpkgs_unstable_flake: &str,
|
||||||
agent_dir: &Path,
|
agent_dir: &Path,
|
||||||
proposed_dir: &Path,
|
proposed_dir: &Path,
|
||||||
applied_dir: &Path,
|
applied_dir: &Path,
|
||||||
|
|
@ -211,6 +213,8 @@ pub async fn spawn(
|
||||||
let agents = agents_after_spawn(name).await?;
|
let agents = agents_after_spawn(name).await?;
|
||||||
crate::meta::sync_agents(
|
crate::meta::sync_agents(
|
||||||
hyperhive_flake,
|
hyperhive_flake,
|
||||||
|
nixpkgs_flake,
|
||||||
|
nixpkgs_unstable_flake,
|
||||||
dashboard_port,
|
dashboard_port,
|
||||||
operator_pronouns,
|
operator_pronouns,
|
||||||
context_window_tokens,
|
context_window_tokens,
|
||||||
|
|
@ -349,6 +353,8 @@ pub async fn destroy(name: &str) -> Result<()> {
|
||||||
pub async fn rebuild(
|
pub async fn rebuild(
|
||||||
name: &str,
|
name: &str,
|
||||||
hyperhive_flake: &str,
|
hyperhive_flake: &str,
|
||||||
|
nixpkgs_flake: &str,
|
||||||
|
nixpkgs_unstable_flake: &str,
|
||||||
agent_dir: &Path,
|
agent_dir: &Path,
|
||||||
applied_dir: &Path,
|
applied_dir: &Path,
|
||||||
claude_dir: &Path,
|
claude_dir: &Path,
|
||||||
|
|
@ -366,6 +372,8 @@ pub async fn rebuild(
|
||||||
let agents = agents_for_meta(None).await?;
|
let agents = agents_for_meta(None).await?;
|
||||||
crate::meta::sync_agents(
|
crate::meta::sync_agents(
|
||||||
hyperhive_flake,
|
hyperhive_flake,
|
||||||
|
nixpkgs_flake,
|
||||||
|
nixpkgs_unstable_flake,
|
||||||
dashboard_port,
|
dashboard_port,
|
||||||
operator_pronouns,
|
operator_pronouns,
|
||||||
context_window_tokens,
|
context_window_tokens,
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,23 @@ enum Cmd {
|
||||||
/// `flake.nix` as the `hyperhive` input.
|
/// `flake.nix` as the `hyperhive` input.
|
||||||
#[arg(long, default_value = "/etc/hyperhive")]
|
#[arg(long, default_value = "/etc/hyperhive")]
|
||||||
hyperhive_flake: String,
|
hyperhive_flake: String,
|
||||||
|
/// Store-path URL of the nixpkgs to wire into the meta flake as
|
||||||
|
/// `inputs.nixpkgs.url`. Set by the NixOS module to
|
||||||
|
/// `"path:${pkgs.path}"` so the meta flake tracks exactly the
|
||||||
|
/// nixpkgs the host was evaluated with (the host's own nixpkgs
|
||||||
|
/// when `inputs.hyperhive.inputs.nixpkgs.follows = "nixpkgs"` is
|
||||||
|
/// set, otherwise hyperhive's pin). Empty = legacy
|
||||||
|
/// `follows = "hyperhive/nixpkgs"` fallback.
|
||||||
|
#[arg(long, default_value = "")]
|
||||||
|
nixpkgs_flake: String,
|
||||||
|
/// Store-path URL of the nixpkgs-unstable to wire into the meta
|
||||||
|
/// flake as `inputs.nixpkgs-unstable.url`. Hyperhive's
|
||||||
|
/// `inputs.nixpkgs-unstable` then follows this top-level input.
|
||||||
|
/// Set by the NixOS module; defaults to the hyperhive flake's own
|
||||||
|
/// nixpkgs-unstable store path. Empty = legacy
|
||||||
|
/// `follows = "hyperhive/nixpkgs-unstable"` fallback.
|
||||||
|
#[arg(long, default_value = "")]
|
||||||
|
nixpkgs_unstable_flake: String,
|
||||||
/// Path to the sqlite message store.
|
/// Path to the sqlite message store.
|
||||||
#[arg(long, default_value = "/var/lib/hyperhive/broker.sqlite")]
|
#[arg(long, default_value = "/var/lib/hyperhive/broker.sqlite")]
|
||||||
db: PathBuf,
|
db: PathBuf,
|
||||||
|
|
@ -118,6 +135,8 @@ async fn main() -> Result<()> {
|
||||||
match cli.cmd {
|
match cli.cmd {
|
||||||
Cmd::Serve {
|
Cmd::Serve {
|
||||||
hyperhive_flake,
|
hyperhive_flake,
|
||||||
|
nixpkgs_flake,
|
||||||
|
nixpkgs_unstable_flake,
|
||||||
db,
|
db,
|
||||||
dashboard_port,
|
dashboard_port,
|
||||||
operator_pronouns,
|
operator_pronouns,
|
||||||
|
|
@ -125,6 +144,8 @@ async fn main() -> Result<()> {
|
||||||
} => {
|
} => {
|
||||||
cmd_serve(
|
cmd_serve(
|
||||||
hyperhive_flake,
|
hyperhive_flake,
|
||||||
|
nixpkgs_flake,
|
||||||
|
nixpkgs_unstable_flake,
|
||||||
db,
|
db,
|
||||||
dashboard_port,
|
dashboard_port,
|
||||||
operator_pronouns,
|
operator_pronouns,
|
||||||
|
|
@ -172,6 +193,8 @@ async fn main() -> Result<()> {
|
||||||
/// dashboard), then serve the admin socket until a signal arrives.
|
/// dashboard), then serve the admin socket until a signal arrives.
|
||||||
async fn cmd_serve(
|
async fn cmd_serve(
|
||||||
hyperhive_flake: String,
|
hyperhive_flake: String,
|
||||||
|
nixpkgs_flake: String,
|
||||||
|
nixpkgs_unstable_flake: String,
|
||||||
db: std::path::PathBuf,
|
db: std::path::PathBuf,
|
||||||
dashboard_port: u16,
|
dashboard_port: u16,
|
||||||
operator_pronouns: String,
|
operator_pronouns: String,
|
||||||
|
|
@ -183,6 +206,8 @@ async fn cmd_serve(
|
||||||
let coord = Arc::new(Coordinator::open(
|
let coord = Arc::new(Coordinator::open(
|
||||||
&db,
|
&db,
|
||||||
hyperhive_flake,
|
hyperhive_flake,
|
||||||
|
nixpkgs_flake,
|
||||||
|
nixpkgs_unstable_flake,
|
||||||
dashboard_port,
|
dashboard_port,
|
||||||
operator_pronouns,
|
operator_pronouns,
|
||||||
cwt,
|
cwt,
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,8 @@ pub fn meta_dir() -> PathBuf {
|
||||||
#[allow(dead_code, clippy::implicit_hasher)] // first caller lands in a later commit
|
#[allow(dead_code, clippy::implicit_hasher)] // first caller lands in a later commit
|
||||||
pub async fn sync_agents(
|
pub async fn sync_agents(
|
||||||
hyperhive_flake: &str,
|
hyperhive_flake: &str,
|
||||||
|
nixpkgs_flake: &str,
|
||||||
|
nixpkgs_unstable_flake: &str,
|
||||||
dashboard_port: u16,
|
dashboard_port: u16,
|
||||||
operator_pronouns: &str,
|
operator_pronouns: &str,
|
||||||
context_window_tokens: &std::collections::HashMap<String, u64>,
|
context_window_tokens: &std::collections::HashMap<String, u64>,
|
||||||
|
|
@ -63,6 +65,8 @@ pub async fn sync_agents(
|
||||||
|
|
||||||
let new_flake = render_flake(
|
let new_flake = render_flake(
|
||||||
hyperhive_flake,
|
hyperhive_flake,
|
||||||
|
nixpkgs_flake,
|
||||||
|
nixpkgs_unstable_flake,
|
||||||
dashboard_port,
|
dashboard_port,
|
||||||
operator_pronouns,
|
operator_pronouns,
|
||||||
context_window_tokens,
|
context_window_tokens,
|
||||||
|
|
@ -300,6 +304,8 @@ pub async fn lock_update_hyperhive() -> Result<()> {
|
||||||
|
|
||||||
fn render_flake(
|
fn render_flake(
|
||||||
hyperhive_flake: &str,
|
hyperhive_flake: &str,
|
||||||
|
nixpkgs_flake: &str,
|
||||||
|
nixpkgs_unstable_flake: &str,
|
||||||
dashboard_port: u16,
|
dashboard_port: u16,
|
||||||
operator_pronouns: &str,
|
operator_pronouns: &str,
|
||||||
context_window_tokens: &std::collections::HashMap<String, u64>,
|
context_window_tokens: &std::collections::HashMap<String, u64>,
|
||||||
|
|
@ -307,6 +313,8 @@ fn render_flake(
|
||||||
) -> String {
|
) -> String {
|
||||||
render_flake_with_lookup(
|
render_flake_with_lookup(
|
||||||
hyperhive_flake,
|
hyperhive_flake,
|
||||||
|
nixpkgs_flake,
|
||||||
|
nixpkgs_unstable_flake,
|
||||||
dashboard_port,
|
dashboard_port,
|
||||||
operator_pronouns,
|
operator_pronouns,
|
||||||
context_window_tokens,
|
context_window_tokens,
|
||||||
|
|
@ -392,6 +400,8 @@ fn agent_canonical_inputs(name: &str) -> Vec<&'static str> {
|
||||||
)]
|
)]
|
||||||
fn render_flake_with_lookup<F>(
|
fn render_flake_with_lookup<F>(
|
||||||
hyperhive_flake: &str,
|
hyperhive_flake: &str,
|
||||||
|
nixpkgs_flake: &str,
|
||||||
|
nixpkgs_unstable_flake: &str,
|
||||||
dashboard_port: u16,
|
dashboard_port: u16,
|
||||||
operator_pronouns: &str,
|
operator_pronouns: &str,
|
||||||
context_window_tokens: &std::collections::HashMap<String, u64>,
|
context_window_tokens: &std::collections::HashMap<String, u64>,
|
||||||
|
|
@ -404,28 +414,35 @@ where
|
||||||
use std::fmt::Write as _;
|
use std::fmt::Write as _;
|
||||||
let mut out = String::new();
|
let mut out = String::new();
|
||||||
out.push_str("{\n description = \"hyperhive deployed agents\";\n inputs = {\n");
|
out.push_str("{\n description = \"hyperhive deployed agents\";\n inputs = {\n");
|
||||||
// hyperhive's own flake.nix is the single channel-pin authority.
|
// `nixpkgs` + `nixpkgs-unstable` are top-level meta inputs with explicit
|
||||||
// meta declares `nixpkgs` + `nixpkgs-unstable` as aliases for
|
// store-path URLs. `hyperhive` then follows them via
|
||||||
// hyperhive's sub-inputs via `follows`, so every agent-level
|
// `hyperhive.inputs.*.follows`. This cascades through to every agent
|
||||||
// `inputs.<X>.inputs.nixpkgs.follows = "nixpkgs"` directive
|
// because `agent-<n>.inputs.nixpkgs.follows = "nixpkgs"` resolves to
|
||||||
// resolves transitively to hyperhive's pin. One channel decision
|
// the same top-level node.
|
||||||
// in the whole tree, no second source to drift.
|
|
||||||
//
|
//
|
||||||
// All nixpkgs follow the one hyperhive was deployed with —
|
// Why explicit `path:` URLs instead of
|
||||||
// anything else would drift.
|
// `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.
|
||||||
//
|
//
|
||||||
// Operators who want to slide the whole swarm onto a different
|
// Fallback (both flake args empty): legacy `follows` wiring — used when
|
||||||
// channel do it at the host level via
|
// hive-c0re is not built with this option wired up.
|
||||||
// `inputs.hyperhive.inputs.nixpkgs.follows = "nixpkgs"`, which
|
if nixpkgs_flake.is_empty() {
|
||||||
// makes hyperhive's nixpkgs = the host's nixpkgs and cascades
|
// Legacy path: meta defers to hyperhive's own lock.
|
||||||
// through to every agent.
|
let _ = writeln!(out, " hyperhive.url = \"{hyperhive_flake}\";");
|
||||||
//
|
out.push_str(" nixpkgs.follows = \"hyperhive/nixpkgs\";\n");
|
||||||
// `nixpkgs` is still a single canonical name in the meta tree,
|
out.push_str(" nixpkgs-unstable.follows = \"hyperhive/nixpkgs-unstable\";\n");
|
||||||
// it just resolves through hyperhive instead of being its own
|
} else {
|
||||||
// root input.
|
let _ = writeln!(out, " nixpkgs.url = \"{nixpkgs_flake}\";");
|
||||||
let _ = writeln!(out, " hyperhive.url = \"{hyperhive_flake}\";");
|
let _ = writeln!(out, " nixpkgs-unstable.url = \"{nixpkgs_unstable_flake}\";");
|
||||||
out.push_str(" nixpkgs.follows = \"hyperhive/nixpkgs\";\n");
|
let _ = writeln!(out, " hyperhive.url = \"{hyperhive_flake}\";");
|
||||||
out.push_str(" nixpkgs-unstable.follows = \"hyperhive/nixpkgs-unstable\";\n");
|
out.push_str(" hyperhive.inputs.nixpkgs.follows = \"nixpkgs\";\n");
|
||||||
|
out.push_str(" hyperhive.inputs.nixpkgs-unstable.follows = \"nixpkgs-unstable\";\n");
|
||||||
|
}
|
||||||
for spec in agents {
|
for spec in agents {
|
||||||
let _ = writeln!(
|
let _ = writeln!(
|
||||||
out,
|
out,
|
||||||
|
|
@ -687,40 +704,66 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn render_flake_aliases_nixpkgs_to_hyperhive() {
|
fn render_flake_uses_explicit_nixpkgs_url_when_provided() {
|
||||||
let out = render_flake(
|
let out = render_flake(
|
||||||
"github:example/hyperhive",
|
"github:example/hyperhive",
|
||||||
|
"path:/nix/store/aaaa-nixpkgs-source",
|
||||||
|
"path:/nix/store/bbbb-nixpkgs-unstable-source",
|
||||||
8000,
|
8000,
|
||||||
"she/her",
|
"she/her",
|
||||||
&std::collections::HashMap::new(),
|
&std::collections::HashMap::new(),
|
||||||
&[sample_spec("alice", false, 9001)],
|
&[sample_spec("alice", false, 9001)],
|
||||||
);
|
);
|
||||||
// Meta's `nixpkgs` + `nixpkgs-unstable` are aliases for
|
// Both nixpkgs + nixpkgs-unstable are top-level inputs with
|
||||||
// hyperhive's sub-inputs. Single channel-pin authority:
|
// explicit URLs; hyperhive follows them.
|
||||||
// hyperhive's own flake.nix. All nixpkgs follow the one
|
|
||||||
// hyperhive was deployed with.
|
|
||||||
assert!(
|
assert!(
|
||||||
out.contains("nixpkgs.follows = \"hyperhive/nixpkgs\""),
|
out.contains("nixpkgs.url = \"path:/nix/store/aaaa-nixpkgs-source\""),
|
||||||
"missing nixpkgs follows alias:\n{out}"
|
"expected explicit nixpkgs.url:\n{out}"
|
||||||
);
|
);
|
||||||
assert!(
|
assert!(
|
||||||
out.contains("nixpkgs-unstable.follows = \"hyperhive/nixpkgs-unstable\""),
|
out.contains("nixpkgs-unstable.url = \"path:/nix/store/bbbb-nixpkgs-unstable-source\""),
|
||||||
"missing nixpkgs-unstable follows alias:\n{out}"
|
"expected explicit nixpkgs-unstable.url:\n{out}"
|
||||||
);
|
);
|
||||||
// And conversely: no literal channel ref baked into meta. If
|
|
||||||
// this fails, someone reintroduced a hardcoded ref — would
|
|
||||||
// drift away from hyperhive's pin.
|
|
||||||
assert!(
|
assert!(
|
||||||
!out.contains("nixpkgs.url ="),
|
out.contains("hyperhive.inputs.nixpkgs.follows = \"nixpkgs\""),
|
||||||
"no literal `nixpkgs.url` should be emitted (hyperhive owns the pin):\n{out}"
|
"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}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// `render_flake_collapses_hyperhive_nixpkgs_via_follows` dropped:
|
#[test]
|
||||||
// with meta's `nixpkgs.follows = "hyperhive/nixpkgs"`, there's no
|
fn render_flake_falls_back_to_follows_when_nixpkgs_flake_empty() {
|
||||||
// separate meta-level nixpkgs to collapse hyperhive's into. The
|
// Empty nixpkgs_flake → legacy follows behaviour (backward compat
|
||||||
// redirect goes the other way now (the alias test above covers
|
// for any code path that can't inject pkgs.path).
|
||||||
// the new invariant).
|
let out = render_flake(
|
||||||
|
"github:example/hyperhive",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
8000,
|
||||||
|
"she/her",
|
||||||
|
&std::collections::HashMap::new(),
|
||||||
|
&[sample_spec("alice", false, 9001)],
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
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}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn render_flake_emits_follows_for_agents_declaring_nixpkgs() {
|
fn render_flake_emits_follows_for_agents_declaring_nixpkgs() {
|
||||||
|
|
@ -735,6 +778,8 @@ mod tests {
|
||||||
};
|
};
|
||||||
let out = render_flake_with_lookup(
|
let out = render_flake_with_lookup(
|
||||||
"github:example/hyperhive",
|
"github:example/hyperhive",
|
||||||
|
"path:/nix/store/aaaa-nixpkgs-source",
|
||||||
|
"path:/nix/store/bbbb-nixpkgs-unstable-source",
|
||||||
8000,
|
8000,
|
||||||
"she/her",
|
"she/her",
|
||||||
&std::collections::HashMap::new(),
|
&std::collections::HashMap::new(),
|
||||||
|
|
@ -768,6 +813,8 @@ mod tests {
|
||||||
fn render_flake_skips_canonical_follows_when_lookup_returns_empty() {
|
fn render_flake_skips_canonical_follows_when_lookup_returns_empty() {
|
||||||
let out = render_flake_with_lookup(
|
let out = render_flake_with_lookup(
|
||||||
"github:example/hyperhive",
|
"github:example/hyperhive",
|
||||||
|
"path:/nix/store/aaaa-nixpkgs-source",
|
||||||
|
"path:/nix/store/bbbb-nixpkgs-unstable-source",
|
||||||
8000,
|
8000,
|
||||||
"she/her",
|
"she/her",
|
||||||
&std::collections::HashMap::new(),
|
&std::collections::HashMap::new(),
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,8 @@ pub async fn run(coord: &Arc<Coordinator>) -> Result<()> {
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
if let Err(e) = meta::sync_agents(
|
if let Err(e) = meta::sync_agents(
|
||||||
&coord.hyperhive_flake,
|
&coord.hyperhive_flake,
|
||||||
|
&coord.nixpkgs_flake,
|
||||||
|
&coord.nixpkgs_unstable_flake,
|
||||||
coord.dashboard_port,
|
coord.dashboard_port,
|
||||||
&coord.operator_pronouns,
|
&coord.operator_pronouns,
|
||||||
&coord.context_window_tokens,
|
&coord.context_window_tokens,
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,8 @@ async fn dispatch(req: &HostRequest, coord: Arc<Coordinator>) -> HostResponse {
|
||||||
match lifecycle::spawn(
|
match lifecycle::spawn(
|
||||||
name,
|
name,
|
||||||
&coord.hyperhive_flake,
|
&coord.hyperhive_flake,
|
||||||
|
&coord.nixpkgs_flake,
|
||||||
|
&coord.nixpkgs_unstable_flake,
|
||||||
&agent_dir,
|
&agent_dir,
|
||||||
&proposed_dir,
|
&proposed_dir,
|
||||||
&applied_dir,
|
&applied_dir,
|
||||||
|
|
@ -151,6 +153,8 @@ async fn dispatch(req: &HostRequest, coord: Arc<Coordinator>) -> HostResponse {
|
||||||
let result = lifecycle::rebuild(
|
let result = lifecycle::rebuild(
|
||||||
name,
|
name,
|
||||||
&coord.hyperhive_flake,
|
&coord.hyperhive_flake,
|
||||||
|
&coord.nixpkgs_flake,
|
||||||
|
&coord.nixpkgs_unstable_flake,
|
||||||
&agent_dir,
|
&agent_dir,
|
||||||
&applied_dir,
|
&applied_dir,
|
||||||
&claude_dir,
|
&claude_dir,
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
hyperhiveFrontend,
|
hyperhiveFrontend,
|
||||||
hyperhiveAssets,
|
hyperhiveAssets,
|
||||||
hyperhiveFlake,
|
hyperhiveFlake,
|
||||||
|
hyperhiveNixpkgsUnstable,
|
||||||
agentBaseToplevel,
|
agentBaseToplevel,
|
||||||
managerToplevel,
|
managerToplevel,
|
||||||
}:
|
}:
|
||||||
|
|
@ -187,6 +188,43 @@ in
|
||||||
only override if you want agents tracking a different ref.
|
only override if you want agents tracking a different ref.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
nixpkgsFlake = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
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 {
|
dashboardPort = lib.mkOption {
|
||||||
type = lib.types.port;
|
type = lib.types.port;
|
||||||
default = 7000;
|
default = 7000;
|
||||||
|
|
@ -373,7 +411,7 @@ in
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
ExecStart = "${cfg.package}/bin/hive-c0re --socket /run/hyperhive/host.sock serve --hyperhive-flake ${cfg.hyperhiveFlake} --dashboard-port ${toString cfg.dashboardPort} --operator-pronouns ${lib.escapeShellArg cfg.operatorPronouns} --context-window-tokens ${lib.escapeShellArg (builtins.toJSON cfg.contextWindowTokens)}";
|
ExecStart = "${cfg.package}/bin/hive-c0re --socket /run/hyperhive/host.sock serve --hyperhive-flake ${cfg.hyperhiveFlake} --nixpkgs-flake ${cfg.nixpkgsFlake} --nixpkgs-unstable-flake ${cfg.nixpkgsUnstableFlake} --dashboard-port ${toString cfg.dashboardPort} --operator-pronouns ${lib.escapeShellArg cfg.operatorPronouns} --context-window-tokens ${lib.escapeShellArg (builtins.toJSON cfg.contextWindowTokens)}";
|
||||||
Restart = "on-failure";
|
Restart = "on-failure";
|
||||||
RestartSec = 2;
|
RestartSec = 2;
|
||||||
RuntimeDirectory = "hyperhive";
|
RuntimeDirectory = "hyperhive";
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue