Watch
0
0
Fork
You've already forked hyperhive
0

the meta flake takes docs from the pinned hyperhive input

This commit is contained in:
atlas 2026-10-18 20:06:08 +02:00
commit daf38e4c55
16 changed files with 29 additions and 197 deletions

View file

@ -117,10 +117,7 @@ covers spreading a swarm across hosts.
{ {
inputs = { inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05"; nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05";
hyperhive = { hyperhive.url = "git+https://forge.darkest.space/hyperhive/hyperhive";
url = "git+https://forge.darkest.space/hyperhive/hyperhive";
inputs.nixpkgs.follows = "nixpkgs"; # see "Overriding nixpkgs"
};
}; };
outputs = { nixpkgs, hyperhive, ... }: { outputs = { nixpkgs, hyperhive, ... }: {
@ -160,14 +157,6 @@ identity, creating your SSO account. Walk through
**[`docs/getting-started/setup.md`](docs/getting-started/setup.md)** **[`docs/getting-started/setup.md`](docs/getting-started/setup.md)**
once, top to bottom. once, top to bottom.
### Overriding nixpkgs
hyperhive pins its own `nixpkgs` so it builds standalone in CI. The
`follows` line above builds it against your host's `nixpkgs` instead: one
evaluation, no version drift. It works as long as your channel is close to
the `nixos-26.05` hyperhive develops against — drop it if a much older or
newer channel breaks in ways hyperhive's CI doesn't catch.
## Operating it ## Operating it
- **swarm UI** — the day-to-day surface, on the swarm apex behind SSO - **swarm UI** — the day-to-day surface, on the swarm apex behind SSO

View file

@ -30,10 +30,6 @@ Where it runs and how it's enabled: [`services.md`](services.md#builder).
- `hyperhive` and `nixpkgs`: the sources the builder's own host was - `hyperhive` and `nixpkgs`: the sources the builder's own host was
deployed with (`c0re.hyperhiveFlake`, `c0re.nixpkgsFlake`). A hive's meta deployed with (`c0re.hyperhiveFlake`, `c0re.nixpkgsFlake`). A hive's meta
flake pins `hyperhive` by forge URL and rev instead. 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,
since the `hyperhive` source has no `docs/`.
- `agent`: `git+https://<forge>/agent-configs/<agent>?ref=refs/pull/<pr>/head&rev=<head>`. - `agent`: `git+https://<forge>/agent-configs/<agent>?ref=refs/pull/<pr>/head&rev=<head>`.
A build has one hour. A build has one hour.
4. The reply is the system closure's store path, or the last lines `nix` 4. The reply is the system closure's store path, or the last lines `nix`

View file

@ -20,9 +20,8 @@ markdown is readable during every turn.
Off by default for every agent, ruth included. An agent opts in by adding the Off by default for every agent, ruth included. An agent opts in by adding the
line above to its `agent.nix`. line above to its `agent.nix`.
The `docs/` source is a narrow flake input (`hyperhive-docs`) tracked The docs come from the pinned `hyperhive` input (the `reference-docs` package
separately from the main `hyperhive` flake so editing docs re-locks only built from its `docs/` tree), so they match the hyperhive rev the host runs.
that input — not every agent's container gets rebuilt on a doc-only change.
## Agent icon ## Agent icon

View file

@ -14,7 +14,7 @@
}; };
# Thin entry point — the real logic lives under nix/: # Thin entry point — the real logic lives under nix/:
# nix/sources.nix filtered source views (meta-flake + docs inputs) # nix/sources.nix filtered source views (the builder's hyperhive input)
# nix/rust.nix shared crane wiring (cleanSrc, cargoArtifacts) # nix/rust.nix shared crane wiring (cleanSrc, cargoArtifacts)
# nix/packages/ every package output # nix/packages/ every package output
# nix/checks.nix flake checks # nix/checks.nix flake checks
@ -130,9 +130,6 @@
xdgIcons = lib.mkDefault self.packages.${pkgs.stdenv.hostPlatform.system}.xdg-icons; xdgIcons = lib.mkDefault self.packages.${pkgs.stdenv.hostPlatform.system}.xdg-icons;
hyperhiveFlake = lib.mkDefault "${sources.hyperhiveFlakeSource}"; hyperhiveFlake = lib.mkDefault "${sources.hyperhiveFlakeSource}";
hyperhiveRev = lib.mkDefault (self.rev or null); 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}";
# The agent-base toplevel every container builds from — wired into # The agent-base toplevel every container builds from — wired into
# `system.extraDependencies` when # `system.extraDependencies` when
# `services.hyperhive.c0re.preBuildAgentTemplates` is on so # `services.hyperhive.c0re.preBuildAgentTemplates` is on so

View file

@ -40,11 +40,6 @@ pub struct Coordinator {
/// identify the running revision (`current_flake_rev`); the meta flake /// identify the running revision (`current_flake_rev`); the meta flake
/// pins `hyperhive` by forge URL and [`Self::hyperhive_rev`] instead. /// pins `hyperhive` by forge URL and [`Self::hyperhive_rev`] instead.
pub hyperhive_flake: String, 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,
/// Git rev of the hyperhive checkout this host runs. Pins the meta /// Git rev of the hyperhive checkout this host runs. Pins the meta
/// flake's `hyperhive` input; `None` makes the meta render fail. /// flake's `hyperhive` input; `None` makes the meta render fail.
pub hyperhive_rev: Option<String>, pub hyperhive_rev: Option<String>,
@ -194,11 +189,6 @@ pub struct Coordinator {
#[serde(default)] #[serde(default)]
pub struct HiveEnv { pub struct HiveEnv {
pub hyperhive_flake: String, pub hyperhive_flake: String,
/// Store-path URL of the narrow `docs/` source, wired into the meta
/// flake as `inputs.hyperhive-docs.url` and threaded to each agent as
/// `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,
/// Git rev of the hyperhive checkout this host runs; see [`Coordinator::hyperhive_rev`]. /// Git rev of the hyperhive checkout this host runs; see [`Coordinator::hyperhive_rev`].
pub hyperhive_rev: Option<String>, pub hyperhive_rev: Option<String>,
/// Store path of the `claude-code` agents run, or `None` for "each /// Store path of the `claude-code` agents run, or `None` for "each
@ -224,7 +214,6 @@ impl Default for HiveEnv {
fn default() -> Self { fn default() -> Self {
Self { Self {
hyperhive_flake: "/etc/hyperhive".to_string(), hyperhive_flake: "/etc/hyperhive".to_string(),
hyperhive_docs_flake: String::new(),
hyperhive_rev: None, hyperhive_rev: None,
claude_code_path: None, claude_code_path: None,
dashboard_port: 7000, dashboard_port: 7000,
@ -401,7 +390,6 @@ impl Coordinator {
) -> Result<Self> { ) -> Result<Self> {
let HiveEnv { let HiveEnv {
hyperhive_flake, hyperhive_flake,
hyperhive_docs_flake,
hyperhive_rev, hyperhive_rev,
claude_code_path, claude_code_path,
dashboard_port, dashboard_port,
@ -440,7 +428,6 @@ impl Coordinator {
broker: Arc::new(broker), broker: Arc::new(broker),
build_logs, build_logs,
hyperhive_flake, hyperhive_flake,
hyperhive_docs_flake,
hyperhive_rev, hyperhive_rev,
claude_code_path, claude_code_path,
dashboard_port, dashboard_port,
@ -471,7 +458,6 @@ impl Coordinator {
pub fn hive_env(&self) -> HiveEnv { pub fn hive_env(&self) -> HiveEnv {
HiveEnv { HiveEnv {
hyperhive_flake: self.hyperhive_flake.clone(), hyperhive_flake: self.hyperhive_flake.clone(),
hyperhive_docs_flake: self.hyperhive_docs_flake.clone(),
hyperhive_rev: self.hyperhive_rev.clone(), hyperhive_rev: self.hyperhive_rev.clone(),
claude_code_path: self.claude_code_path.clone(), claude_code_path: self.claude_code_path.clone(),
dashboard_port: self.dashboard_port, dashboard_port: self.dashboard_port,

View file

@ -358,7 +358,6 @@ fn render_hive_flake(hive: &HiveEnv, agents: &[AgentSpec]) -> Result<String> {
fn render_hive_flake_at(hive: &HiveEnv, hyperhive_url: &str, agents: &[AgentSpec]) -> String { fn render_hive_flake_at(hive: &HiveEnv, hyperhive_url: &str, agents: &[AgentSpec]) -> String {
render_flake( render_flake(
hyperhive_url, hyperhive_url,
&hive.hyperhive_docs_flake,
hive.claude_code_path.as_deref(), hive.claude_code_path.as_deref(),
hive.dashboard_port, hive.dashboard_port,
agents, agents,
@ -367,14 +366,12 @@ fn render_hive_flake_at(hive: &HiveEnv, hyperhive_url: &str, agents: &[AgentSpec
fn render_flake( fn render_flake(
hyperhive_url: &str, hyperhive_url: &str,
docs_flake: &str,
claude_code_path: Option<&str>, claude_code_path: Option<&str>,
dashboard_port: u16, dashboard_port: u16,
agents: &[AgentSpec], agents: &[AgentSpec],
) -> String { ) -> String {
render_flake_with_lookup( render_flake_with_lookup(
hyperhive_url, hyperhive_url,
docs_flake,
claude_code_path, claude_code_path,
dashboard_port, dashboard_port,
agents, agents,
@ -756,7 +753,6 @@ fn agent_canonical_inputs(name: &str) -> Vec<&'static str> {
)] )]
fn render_flake_with_lookup<F>( fn render_flake_with_lookup<F>(
hyperhive_url: &str, hyperhive_url: &str,
docs_flake: &str,
claude_code_path: Option<&str>, claude_code_path: Option<&str>,
dashboard_port: u16, dashboard_port: u16,
agents: &[AgentSpec], agents: &[AgentSpec],
@ -774,17 +770,6 @@ where
// `agent-<n>.inputs.nixpkgs.follows = "nixpkgs"` resolves to this node. // `agent-<n>.inputs.nixpkgs.follows = "nixpkgs"` resolves to this node.
let _ = writeln!(out, " hyperhive.url = \"{hyperhive_url}\";"); let _ = writeln!(out, " hyperhive.url = \"{hyperhive_url}\";");
out.push_str(" nixpkgs.follows = \"hyperhive/nixpkgs\";\n"); out.push_str(" nixpkgs.follows = \"hyperhive/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`.
// Empty = hive-c0re not built with the option wired up (legacy);
// agents then keep the harness default (`services.hyperhive.agent.packages.reference-docs`).
if !docs_flake.is_empty() {
// `flake = false`: the docs/ tree is a plain source (no flake.nix),
// so nix must treat it as raw source, not evaluate it as a flake.
let _ = writeln!(out, " hyperhive-docs.url = \"{docs_flake}\";");
out.push_str(" hyperhive-docs.flake = false;\n");
}
// Each agent's config input is its LOCAL applied mirror // Each agent's config input is its LOCAL applied mirror
// (`git+file://<applied>`), so the meta flake resolves entirely from // (`git+file://<applied>`), so the meta flake resolves entirely from
// on-disk state and boot never depends on the forge being reachable. // on-disk state and boot never depends on the forge being reachable.
@ -845,14 +830,6 @@ where
{ {
"#, "#,
); );
// Point the in-container docs dir (`$HIVE_DOCS_DIR`) at the narrow
// `hyperhive-docs` input instead of the harness default
// (`services.hyperhive.agent.packages.reference-docs`, built from the now-docs-stripped source).
// `inputs."hyperhive-docs"` is reachable via the outputs `@inputs`
// capture. Emitted only when the input exists (docs_flake non-empty).
if !docs_flake.is_empty() {
out.push_str(" services.hyperhive.agent.docs.source = inputs.\"hyperhive-docs\".outPath;\n");
}
// The `claude-code` agents run, as a bare store path rather than a // The `claude-code` agents run, as a bare store path rather than a
// flake input: containers share the host's `/nix/store`, so the // flake input: containers share the host's `/nix/store`, so the
// binary is already there with its closure and has nothing to // binary is already there with its closure and has nothing to
@ -1305,7 +1282,6 @@ mod tests {
fn render_flake_pins_hyperhive_by_url_and_follows_its_nixpkgs() { fn render_flake_pins_hyperhive_by_url_and_follows_its_nixpkgs() {
let out = render_flake( let out = render_flake(
"git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd", "git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
"path:/nix/store/bbbb-hyperhive-docs-source",
None, None,
8000, 8000,
&[sample_spec("alice", 9001)], &[sample_spec("alice", 9001)],
@ -1324,38 +1300,11 @@ mod tests {
!out.contains("nixpkgs.url") && !out.contains("hyperhive.inputs.nixpkgs"), !out.contains("nixpkgs.url") && !out.contains("hyperhive.inputs.nixpkgs"),
"no separate nixpkgs input:\n{out}" "no separate nixpkgs input:\n{out}"
); );
// the narrow docs source is its own non-flake input, and each // docs come from the pinned hyperhive input: no separate docs input,
// agent's docs dir resolves from it rather than services.hyperhive.agent.packages.reference-docs. // no docs.source override.
assert!( assert!(
out.contains("hyperhive-docs.url = \"path:/nix/store/bbbb-hyperhive-docs-source\""), !out.contains("hyperhive-docs") && !out.contains("docs.source"),
"expected hyperhive-docs input url:\n{out}" "no docs input or override:\n{out}"
);
assert!(
out.contains("hyperhive-docs.flake = false;"),
"docs source is not a flake, must be flake = false:\n{out}"
);
assert!(
out.contains(
"services.hyperhive.agent.docs.source = inputs.\"hyperhive-docs\".outPath;"
),
"expected per-agent docs source wired to the input:\n{out}"
);
}
#[test]
fn render_flake_omits_docs_input_when_docs_flake_empty() {
// 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(
"git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
"",
None,
8000,
&[sample_spec("alice", 9001)],
);
assert!(
!out.contains("hyperhive-docs"),
"no docs input/source when docs_flake is empty:\n{out}"
); );
} }
@ -1363,7 +1312,6 @@ mod tests {
fn render_flake_names_each_agent_container_after_the_agent() { fn render_flake_names_each_agent_container_after_the_agent() {
let out = render_flake( let out = render_flake(
"git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd", "git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
"",
None, None,
8000, 8000,
&[sample_spec("alice", 9001)], &[sample_spec("alice", 9001)],
@ -1400,7 +1348,6 @@ mod tests {
// the closure the binary actually needs. // the closure the binary actually needs.
let out = render_flake( let out = render_flake(
"git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd", "git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
"path:/nix/store/bbbb-hyperhive-docs-source",
Some("/nix/store/cccc-claude-code-2.1.220"), Some("/nix/store/cccc-claude-code-2.1.220"),
8000, 8000,
&[sample_spec("alice", 9001)], &[sample_spec("alice", 9001)],
@ -1425,7 +1372,6 @@ mod tests {
// unpinned case self-contained). // unpinned case self-contained).
let out = render_flake( let out = render_flake(
"git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd", "git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
"path:/nix/store/bbbb-hyperhive-docs-source",
None, None,
8000, 8000,
&[sample_spec("alice", 9001)], &[sample_spec("alice", 9001)],
@ -1448,7 +1394,6 @@ mod tests {
}; };
let out = render_flake_with_lookup( let out = render_flake_with_lookup(
"git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd", "git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
"path:/nix/store/bbbb-hyperhive-docs-source",
None, None,
8000, 8000,
&[ &[
@ -1478,7 +1423,6 @@ mod tests {
fn render_flake_builds_every_agent_from_agent_base() { fn render_flake_builds_every_agent_from_agent_base() {
let out = render_flake( let out = render_flake(
"git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd", "git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
"path:/nix/store/bbbb-hyperhive-docs-source",
None, None,
8000, 8000,
&[sample_spec("alice", 9001), sample_spec("ruth", 9002)], &[sample_spec("alice", 9001), sample_spec("ruth", 9002)],
@ -1504,7 +1448,6 @@ 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(
"git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd", "git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
"path:/nix/store/bbbb-hyperhive-docs-source",
None, None,
8000, 8000,
&[sample_spec("alice", 9001)], &[sample_spec("alice", 9001)],
@ -1533,7 +1476,6 @@ mod tests {
}; };
let out = render_flake_with_lookup( let out = render_flake_with_lookup(
"git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd", "git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
"path:/nix/store/bbbb-hyperhive-docs-source",
None, None,
8000, 8000,
&[sample_spec("alice", 9001), android], &[sample_spec("alice", 9001), android],
@ -1585,7 +1527,6 @@ mod tests {
} }
let out = render_flake( let out = render_flake(
"git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd", "git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
"path:/nix/store/bbbb-hyperhive-docs-source",
None, None,
8000, 8000,
&[sample_spec("alice", 9001)], &[sample_spec("alice", 9001)],
@ -1627,7 +1568,6 @@ mod tests {
} }
render_flake( render_flake(
"git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd", "git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
"path:/nix/store/bbbb-hyperhive-docs-source",
None, None,
8000, 8000,
&[sample_spec("alice", 9001)], &[sample_spec("alice", 9001)],
@ -1823,7 +1763,6 @@ mod tests {
} }
let out = render_flake( let out = render_flake(
"git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd", "git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
"path:/nix/store/bbbb-hyperhive-docs-source",
None, None,
8000, 8000,
&[sample_spec("alice", 9001)], &[sample_spec("alice", 9001)],
@ -1856,7 +1795,6 @@ mod tests {
// review surface, but they are not the flake's build input. // review surface, but they are not the flake's build input.
let out = render_flake( let out = render_flake(
"git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd", "git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
"path:/nix/store/bbbb-hyperhive-docs-source",
None, None,
8000, 8000,
&[sample_spec("alice", 9001)], &[sample_spec("alice", 9001)],
@ -1897,7 +1835,6 @@ mod tests {
let render = || { let render = || {
render_flake( render_flake(
"git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd", "git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
"path:/nix/store/bbbb-hyperhive-docs-source",
None, None,
8000, 8000,
&[sample_spec("alice", 9001)], &[sample_spec("alice", 9001)],
@ -1986,7 +1923,6 @@ mod tests {
let render = || { let render = || {
render_flake( render_flake(
"git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd", "git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
"path:/nix/store/bbbb-hyperhive-docs-source",
None, None,
8000, 8000,
&[sample_spec("alice", 9001)], &[sample_spec("alice", 9001)],
@ -2057,7 +1993,6 @@ mod tests {
let render = || { let render = || {
render_flake( render_flake(
"git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd", "git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
"path:/nix/store/bbbb-hyperhive-docs-source",
None, None,
8000, 8000,
&[sample_spec("alice", 9001)], &[sample_spec("alice", 9001)],

View file

@ -9,7 +9,7 @@
{ {
options.services.hyperhive.agent.docs.enable = lib.mkEnableOption '' options.services.hyperhive.agent.docs.enable = lib.mkEnableOption ''
make the hyperhive reference docs (the repo `docs/` tree, shipped make the hyperhive reference docs (the repo `docs/` tree, shipped
read-only as the standalone `hyperhive-docs` derivation) available read-only as the `reference-docs` package) available
in-container. When enabled the harness exposes the docs dir to claude in-container. When enabled the harness exposes the docs dir to claude
via `claude --add-dir`, so the markdown is readable at via `claude --add-dir`, so the markdown is readable at
`$HIVE_DOCS_DIR/`, and appends a single pointer sentence to the agent's `$HIVE_DOCS_DIR/`, and appends a single pointer sentence to the agent's
@ -26,11 +26,7 @@
Store path of the reference-docs tree exposed at `$HIVE_DOCS_DIR` Store path of the reference-docs tree exposed at `$HIVE_DOCS_DIR`
when `services.hyperhive.agent.docs.enable` is set. Defaults to the flake's when `services.hyperhive.agent.docs.enable` is set. Defaults to the flake's
`reference-docs` package (the `nix/packages/reference-docs.nix` `reference-docs` package (the `nix/packages/reference-docs.nix`
build) so a standalone container build from a full checkout build), which the meta flake's pinned `hyperhive` input provides.
works unchanged. The generated meta flake overrides this with the
narrow `hyperhive-docs` flake input so a doc edit only
re-locks that input instead of rebuilding the container from a
re-hashed `hyperhive` source.
''; '';
}; };
@ -40,9 +36,7 @@
# `--add-dir` so the docs are readable, and appends a single # `--add-dir` so the docs are readable, and appends a single
# pointer sentence to the system prompt # pointer sentence to the system prompt
# (hive-agent::prompt::render) telling the agent the docs exist. # (hive-agent::prompt::render) telling the agent the docs exist.
# Source is `services.hyperhive.agent.docs.source` (the narrow `hyperhive-docs` # Source is `services.hyperhive.agent.docs.source` (default: the `reference-docs` package). See hive-agent::turn.
# meta-flake input, or `pkgs.hyperhive-docs` for standalone
# builds). See hive-agent::turn.
HIVE_DOCS_DIR = "${config.services.hyperhive.agent.docs.source}"; HIVE_DOCS_DIR = "${config.services.hyperhive.agent.docs.source}";
}; };
}; };

View file

@ -1,8 +1,6 @@
# The swarm builder's build check: one agent config on hyperhive's agent base, # The swarm builder's build check: one agent config on hyperhive's agent base,
# composed the way a hive's meta flake composes it, minus the values a hive # composed the way a hive's meta flake composes it, minus the values a hive
# adds (its CA, claude-code path and ports). The docs source is set the way the # adds (its CA, claude-code path and ports).
# meta flake sets it: `hyperhive`'s source has no `docs/`, so the default
# `docs.source` fails to evaluate.
# #
# Every input is replaced at build time with `--override-input` # Every input is replaced at build time with `--override-input`
# (swarm-builder/src/nix_build.rs), so the URLs below are never fetched and # (swarm-builder/src/nix_build.rs), so the URLs below are never fetched and
@ -14,25 +12,19 @@
nixpkgs.url = "flake:nixpkgs"; nixpkgs.url = "flake:nixpkgs";
hyperhive.url = "flake:hyperhive"; hyperhive.url = "flake:hyperhive";
hyperhive.inputs.nixpkgs.follows = "nixpkgs"; hyperhive.inputs.nixpkgs.follows = "nixpkgs";
hyperhive-docs.url = "flake:hyperhive-docs";
hyperhive-docs.flake = false;
agent.url = "flake:agent"; agent.url = "flake:agent";
}; };
outputs = outputs =
{ {
hyperhive, hyperhive,
hyperhive-docs,
agent, agent,
... ...
}: }:
{ {
packages.x86_64-linux.check = packages.x86_64-linux.check =
(hyperhive.nixosConfigurations.agent-base.extendModules { (hyperhive.nixosConfigurations.agent-base.extendModules {
modules = [ modules = [ agent.nixosModules.default ];
agent.nixosModules.default
{ services.hyperhive.agent.docs.source = hyperhive-docs.outPath; }
];
}).config.system.build.toplevel; }).config.system.build.toplevel;
}; };
} }

View file

@ -96,7 +96,6 @@ let
# c0re binary itself changes. # c0re binary itself changes.
serveConfigJson = builtins.toJSON { serveConfigJson = builtins.toJSON {
hyperhive_flake = cfg.hyperhiveFlake; hyperhive_flake = cfg.hyperhiveFlake;
hyperhive_docs_flake = cfg.hyperhiveDocs;
hyperhive_rev = cfg.hyperhiveRev; hyperhive_rev = cfg.hyperhiveRev;
# The `claude-code` every agent runs, or null for "each agent keeps # The `claude-code` every agent runs, or null for "each agent keeps
# the one out of its own nixpkgs". `builtins.toJSON` serialises a # the one out of its own nixpkgs". `builtins.toJSON` serialises a

View file

@ -16,6 +16,10 @@
(lib.mkRemovedOptionModule [ "services" "hyperhive" "c0re" "managerToplevel" ] '' (lib.mkRemovedOptionModule [ "services" "hyperhive" "c0re" "managerToplevel" ] ''
Every agent, ruth included, builds from `agentBaseToplevel`. Remove this definition. Every agent, ruth included, builds from `agentBaseToplevel`. Remove this definition.
'') '')
(lib.mkRemovedOptionModule [ "services" "hyperhive" "c0re" "hyperhiveDocs" ] ''
Agents take their reference docs from the pinned `hyperhive` input, which
carries `docs/`. Remove this definition.
'')
]; ];
options.services.hyperhive.c0re = { options.services.hyperhive.c0re = {
@ -90,18 +94,6 @@
hive-c0re refuses to render the meta flake. hive-c0re refuses to render the meta flake.
''; '';
}; };
hyperhiveDocs = lib.mkOption {
type = lib.types.str;
defaultText = lib.literalMD "the docs/ tree's own store path";
description = ''
URL of the narrow `docs/` source (no fragment). Inlined into the
generated meta `flake.nix` at `inputs.hyperhive-docs.url` and
threaded to each agent as `services.hyperhive.agent.docs.source`, from which the
harness resolves `$HIVE_DOCS_DIR`. Its own store path — separate
from `hyperhiveFlake` — so a doc edit only re-locks this input
instead of rebuilding every agent container.
'';
};
agentBaseToplevel = lib.mkOption { agentBaseToplevel = lib.mkOption {
type = lib.types.package; type = lib.types.package;
defaultText = lib.literalExpression "hyperhive.packages.x86_64-linux.agent-base-toplevel"; defaultText = lib.literalExpression "hyperhive.packages.x86_64-linux.agent-base-toplevel";

View file

@ -89,11 +89,9 @@ let
# The `nix` the host daemon runs, so client and daemon speak one protocol. # The `nix` the host daemon runs, so client and daemon speak one protocol.
nix = config.nix.package; nix = config.nix.package;
# Strings, not paths: all three are already store paths, and the flake # Strings, not paths: both are already store paths.
# references the daemon passes must be the ones a hive's meta flake names.
hyperhiveFlake = hyperhiveCfg.c0re.hyperhiveFlake; hyperhiveFlake = hyperhiveCfg.c0re.hyperhiveFlake;
nixpkgsFlake = hyperhiveCfg.c0re.nixpkgsFlake; nixpkgsFlake = hyperhiveCfg.c0re.nixpkgsFlake;
docsFlake = hyperhiveCfg.c0re.hyperhiveDocs;
privateNetwork = true; privateNetwork = true;
in in
@ -254,7 +252,6 @@ in
SWARM_BUILDER_CHECK_FLAKE = "${../builder/check-flake}"; SWARM_BUILDER_CHECK_FLAKE = "${../builder/check-flake}";
SWARM_BUILDER_HYPERHIVE_FLAKE = hyperhiveFlake; SWARM_BUILDER_HYPERHIVE_FLAKE = hyperhiveFlake;
SWARM_BUILDER_NIXPKGS_FLAKE = nixpkgsFlake; SWARM_BUILDER_NIXPKGS_FLAKE = nixpkgsFlake;
SWARM_BUILDER_HYPERHIVE_DOCS_FLAKE = docsFlake;
SWARM_BUILDER_FORGE_URL = "https://${forgeCfg.domain}"; SWARM_BUILDER_FORGE_URL = "https://${forgeCfg.domain}";
SWARM_BUILDER_CONFIG_ORG = "agent-configs"; SWARM_BUILDER_CONFIG_ORG = "agent-configs";
} }

View file

@ -51,8 +51,7 @@ let
# The check flake on the inputs the builder's unit names, called as # The check flake on the inputs the builder's unit names, called as
# `nix build` calls it. `hyperhive` is entered through the filtered source's # `nix build` calls it. `hyperhive` is entered through the filtered source's
# own `flake.nix`, so its agent base sees the tree without `docs/` that a # own `flake.nix`.
# real build gets; `self`'s own agent base would find `docs/` here.
builderCheck = builderCheck =
agentModule: agentModule:
let let
@ -69,7 +68,6 @@ let
in in
((import "${env.SWARM_BUILDER_CHECK_FLAKE}/flake.nix").outputs { ((import "${env.SWARM_BUILDER_CHECK_FLAKE}/flake.nix").outputs {
inherit hyperhive; inherit hyperhive;
hyperhive-docs.outPath = env.SWARM_BUILDER_HYPERHIVE_DOCS_FLAKE;
agent.nixosModules.default = agentModule; agent.nixosModules.default = agentModule;
}).packages.x86_64-linux.check; }).packages.x86_64-linux.check;
@ -136,11 +134,10 @@ let
} }
{ {
# The check builds on the sources this host was deployed with, so a green check means # The check builds on the sources this host was deployed with, so a green check means
# the same hyperhive, docs and nixpkgs base. # the same hyperhive and nixpkgs base.
name = "the builder builds on the hyperhive, docs and nixpkgs this host's hives use"; name = "the builder builds on the hyperhive and nixpkgs this host was deployed with";
ok = ok =
env.SWARM_BUILDER_HYPERHIVE_FLAKE == withBuilder.services.hyperhive.c0re.hyperhiveFlake env.SWARM_BUILDER_HYPERHIVE_FLAKE == withBuilder.services.hyperhive.c0re.hyperhiveFlake
&& env.SWARM_BUILDER_HYPERHIVE_DOCS_FLAKE == withBuilder.services.hyperhive.c0re.hyperhiveDocs
&& env.SWARM_BUILDER_NIXPKGS_FLAKE == withBuilder.services.hyperhive.c0re.nixpkgsFlake && env.SWARM_BUILDER_NIXPKGS_FLAKE == withBuilder.services.hyperhive.c0re.nixpkgsFlake
&& lib.hasSuffix "-check-flake" env.SWARM_BUILDER_CHECK_FLAKE && lib.hasSuffix "-check-flake" env.SWARM_BUILDER_CHECK_FLAKE
&& env.SWARM_BUILDER_CONFIG_ORG == "agent-configs" && env.SWARM_BUILDER_CONFIG_ORG == "agent-configs"

View file

@ -4,23 +4,10 @@
# (crane's cargo-source filter — see ./rust.nix). # (crane's cargo-source filter — see ./rust.nix).
{ lib }: { lib }:
{ {
# Filtered view of this repo handed to hive-c0re as the `hyperhive` # Filtered view of this repo the swarm builder builds on as its `hyperhive`
# meta-flake input. Drops files no nix or cargo derivation reads — # input. Drops files no nix or cargo derivation reads — shell helper
# shell helper scripts and root-level markdown — so editing them # scripts and root-level markdown. `docs/` is kept: the agent base's
# does NOT change the store path and therefore does NOT force a # `reference-docs` package builds from it.
# rebuild of every agent container.
#
# Dropped: scripts/, docs/, root-level *.md (README/AGENTS/TODO/…).
# Kept (build needs them): .nix, .rs, Cargo.*, branding/,
# frontend/, prompts/, flake.lock. docs/ is deliberately dropped:
# it is shipped to agent containers as its OWN narrow meta-flake
# input (`hyperhiveDocsSource`, below), threaded through hive-c0re
# → the meta flake → `services.hyperhive.agent.docs.source`. Keeping docs/ out of
# THIS source means a doc edit only re-hashes the docs input (a
# cheap re-link), not the whole flake source (which would rebuild
# every agent container). The `reference-docs` build from `../docs`
# stays only for standalone `nix build .#reference-docs` from a
# full checkout — the meta path never evaluates it.
hyperhiveFlakeSource = lib.cleanSourceWith { hyperhiveFlakeSource = lib.cleanSourceWith {
name = "hyperhive-flake-source"; name = "hyperhive-flake-source";
src = ../.; src = ../.;
@ -31,19 +18,6 @@
rel = lib.removePrefix (toString ../. + "/") (toString path); rel = lib.removePrefix (toString ../. + "/") (toString path);
in in
!(lib.hasPrefix "scripts/" rel || rel == "scripts") !(lib.hasPrefix "scripts/" rel || rel == "scripts")
&& !(lib.hasPrefix "docs/" rel || rel == "docs")
&& !(type == "regular" && !lib.hasInfix "/" rel && lib.hasSuffix ".md" rel); && !(type == "regular" && !lib.hasInfix "/" rel && lib.hasSuffix ".md" rel);
}; };
# The repo `docs/` tree as a standalone narrow source. Its store
# path moves ONLY on doc edits, decoupled from `hyperhiveFlakeSource`.
# hive-c0re threads this to the meta flake as the `hyperhive-docs`
# input (same pattern as the `hyperhive` input); the harness resolves
# `$HIVE_DOCS_DIR` from it via `services.hyperhive.agent.docs.source`. Evaluated
# here on the host where docs/ exists — it cannot be derived from
# inside the docs-stripped `hyperhiveFlakeSource`.
hyperhiveDocsSource = lib.cleanSourceWith {
name = "hyperhive-docs-source";
src = ../docs;
};
} }

View file

@ -12,9 +12,8 @@ the answer into a `swarm-builder` commit status on the PR's head.
## What a build is ## What a build is
- `nix build` of `nix/builder/check-flake`'s `check` output, with its four - `nix build` of `nix/builder/check-flake`'s `check` output, with its three
inputs overridden: `hyperhive` and `nixpkgs` to this host's own sources, inputs overridden: `hyperhive` and `nixpkgs` to this host's own sources,
`hyperhive-docs` to the `docs/` source its hives use (`c0re.hyperhiveDocs`),
and `agent` to `git+https://<forge>/agent-configs/<agent>?ref=refs/pull/<pr>/head&rev=<rev>`. and `agent` to `git+https://<forge>/agent-configs/<agent>?ref=refs/pull/<pr>/head&rev=<rev>`.
- The evaluation runs here, pure; the builds run in the host daemon's sandbox - The evaluation runs here, pure; the builds run in the host daemon's sandbox
as an untrusted client, so a config can neither set daemon options nor read as an untrusted client, so a config can neither set daemon options nor read

View file

@ -68,7 +68,6 @@ async fn main() -> Result<()> {
check_flake: var("CHECK_FLAKE")?, check_flake: var("CHECK_FLAKE")?,
hyperhive: var("HYPERHIVE_FLAKE")?, hyperhive: var("HYPERHIVE_FLAKE")?,
nixpkgs: var("NIXPKGS_FLAKE")?, nixpkgs: var("NIXPKGS_FLAKE")?,
hyperhive_docs: var("HYPERHIVE_DOCS_FLAKE")?,
forge_url: forge_url()?, forge_url: forge_url()?,
config_org: var("CONFIG_ORG")?, config_org: var("CONFIG_ORG")?,
deadline: BUILD_DEADLINE, deadline: BUILD_DEADLINE,

View file

@ -1,5 +1,5 @@
//! One build: `nix build` of the check flake with its four inputs pointed at //! One build: `nix build` of the check flake with its three inputs pointed at
//! this host's hyperhive, hyperhive docs and nixpkgs and at the agent's config //! this host's hyperhive and nixpkgs and at the agent's config
//! commit. //! commit.
//! //!
//! The config repo is private, so `git` fetches it with the builder's forge //! The config repo is private, so `git` fetches it with the builder's forge
@ -45,8 +45,6 @@ pub struct Builder {
pub hyperhive: String, pub hyperhive: String,
/// The nixpkgs source this host was deployed from. /// The nixpkgs source this host was deployed from.
pub nixpkgs: String, pub nixpkgs: String,
/// The hyperhive `docs/` source this host was deployed from.
pub hyperhive_docs: String,
/// `https://<forge>`, with no trailing slash and no path. /// `https://<forge>`, with no trailing slash and no path.
pub forge_url: String, pub forge_url: String,
/// The org the config repos are in. /// The org the config repos are in.
@ -102,9 +100,6 @@ impl Builder {
"nixpkgs".to_owned(), "nixpkgs".to_owned(),
self.nixpkgs.clone(), self.nixpkgs.clone(),
"--override-input".to_owned(), "--override-input".to_owned(),
"hyperhive-docs".to_owned(),
self.hyperhive_docs.clone(),
"--override-input".to_owned(),
"agent".to_owned(), "agent".to_owned(),
self.agent_url(job), self.agent_url(job),
]) ])
@ -308,7 +303,6 @@ mod tests {
check_flake: "/nix/store/c-check".to_owned(), check_flake: "/nix/store/c-check".to_owned(),
hyperhive: "/nix/store/h-source".to_owned(), hyperhive: "/nix/store/h-source".to_owned(),
nixpkgs: "/nix/store/n-source".to_owned(), nixpkgs: "/nix/store/n-source".to_owned(),
hyperhive_docs: "/nix/store/d-docs-source".to_owned(),
forge_url: "https://forge.example".to_owned(), forge_url: "https://forge.example".to_owned(),
config_org: "agent-configs".to_owned(), config_org: "agent-configs".to_owned(),
nix: PathBuf::from("nix"), nix: PathBuf::from("nix"),
@ -333,14 +327,7 @@ mod tests {
.filter(|w| w[0] == "--override-input") .filter(|w| w[0] == "--override-input")
.map(|w| w[1].as_str()) .map(|w| w[1].as_str())
.collect(); .collect();
assert_eq!( assert_eq!(overridden, ["hyperhive", "nixpkgs", "agent"]);
overridden,
["hyperhive", "nixpkgs", "hyperhive-docs", "agent"]
);
assert!(
args.windows(2)
.any(|w| w[0] == "hyperhive-docs" && w[1] == "/nix/store/d-docs-source")
);
assert!(args.iter().any(|a| a == "/nix/store/c-check#check")); assert!(args.iter().any(|a| a == "/nix/store/c-check#check"));
assert!(args.iter().any(|a| a == "--no-write-lock-file")); assert!(args.iter().any(|a| a == "--no-write-lock-file"));
} }