Watch
0
0
Fork
You've already forked hyperhive
0

test the archetype module import in the meta flake render; update archetypes doc

This commit is contained in:
atlas 2026-10-25 16:37:23 +01:00 • committed by mara
commit 88053f437c
3 changed files with 80 additions and 20 deletions

View file

@ -7,8 +7,8 @@ resource caps and inference fields from it, and you can still change each
field before you create the agent. field before you create the agent.
An agent created from an archetype has no `agent-configs/<name>` repo of its An agent created from an archetype has no `agent-configs/<name>` repo of its
own. Its hive builds it from the archetype's module, so every agent of that own. The swarm's `internal/meta` flake builds it from the archetype's module,
archetype runs the same configuration. To change what it runs, change the so every agent of that archetype runs the same configuration. To change what it runs, change the
archetype. Picking "none" creates a custom agent, with its own config repo. archetype. Picking "none" creates a custom agent, with its own config repo.
## The built-in archetypes ## The built-in archetypes
@ -41,11 +41,13 @@ hyperhiveArchetypes.reviewer = {
- Every archetype needs a `description`. `permissions`, `limits` and - Every archetype needs a `description`. `permissions`, `limits` and
`inference` are optional and take the shapes `POST /api/agents` takes. The swarm ignores `inference` are optional and take the shapes `POST /api/agents` takes. The swarm ignores
other keys in `meta`. other keys in `meta`.
- `module` is a NixOS module for the agent's container. A hive imports it - `module` is a NixOS module for the agent's container. The meta flake
into each agent created from the archetype. imports it into each agent created from the archetype.
Today the swarm reads one flake: the hyperhive source the Today the swarm reads one flake: the hyperhive source the
[builder](builder.md)'s host runs, so only the built-ins appear. [builder](builder.md)'s host runs, so only the built-ins appear. The meta
flake has one archetype source too, its own `hyperhive` input, and refuses
an archetype from any other source name.
## How the list stays current ## How the list stays current
@ -110,23 +112,31 @@ Deploy `swarm-nats-auth` before the hives: every provision reads the
agent's key, and a hive whose queue connection predates the grant gets a agent's key, and a hive whose queue connection predates the grant gets a
refused read, so its `Provision` nodes fail until it reconnects. refused read, so its `Provision` nodes fail until it reconnects.
The hive reads the agent's key when it provisions the agent and on every swarm-controller renders `internal/meta`'s `flake.nix` with the
rebuild, and keeps the last value read in `hive-meta-flake` crate, and reads each agent's archetype from the
`/var/lib/hyperhive/swarm-agent-archetypes.json`, so the meta flake renders `agent-archetype` bucket to do so. For an archetype agent the render:
without the queue. When the read fails, a rebuild keeps the value
stored there. For an archetype agent the hive:
- seeds no `proposed` or `applied` repo, and gives `internal/meta` no - adds no `agent-<name>` input and reads no config repo, and lists the agent
`agent-<name>` input. `mkAgent` imports as `mkAgent { …; archetype = { source = "hyperhive"; name = "<name>"; }; }`;
`hyperhive.hyperhiveArchetypes.<name>.module` in place of the agent's own - makes `mkAgent` import `hyperhive.hyperhiveArchetypes.<name>.module` in
flake, and sets `HIVE_ARCHETYPE` in the harness environment; place of the agent's own flake, and set `HIVE_ARCHETYPE` in the harness
environment.
The hive builds from `internal/meta` alone. It reads the agent's key when it
provisions the agent and on every rebuild, and keeps the last value read in
`/var/lib/hyperhive/swarm-agent-archetypes.json`. It takes the head of
`internal/meta` only when that `flake.nix` lists the agent at the archetype
it has now. When the read fails, a rebuild keeps the stored value. For an
archetype agent the hive also:
- seeds no `proposed` or `applied` repo;
- ignores the `rev` of a swarm deploy request; - ignores the `rev` of a swarm deploy request;
- mounts an empty `/agents/<name>/config/`. - mounts an empty `/agents/<name>/config/`.
The agent's system prompt says it has no config repo and names its The agent's system prompt says it has no config repo and names its
archetype. archetype.
The hive takes the module from its own `hyperhive` input. When that input The module comes from the `hyperhive` input that `internal/meta` pins, not
predates the archetype, the agent's build fails with from the hive's own. When that pin predates the archetype, the agent's build
`this hive's hyperhive input has no archetype <name>`. Update the hive's fails with `this hive's hyperhive input has no archetype <name>; update that
`hyperhive` input to fix it. input`. Update the `hyperhive` pin in `internal/meta` to fix it.

View file

@ -194,8 +194,9 @@
nixosConfigurations = nixosConfigurations =
let let
# These two configs are what hive-c0re extends per agent # These two configs are what hive-c0re extends per agent
# (meta.rs's `mkAgent` does `<base>.extendModules { … }`, so # (the `mkAgent` in hive-meta-flake's render does
# the built base and every container stay on one version). # `<base>.extendModules { … }`, so the built base and every
# container stay on one version).
# Nothing deployment-specific may be set here: it would be # Nothing deployment-specific may be set here: it would be
# inherited by every container on the hive and collide with # inherited by every container on the hive and collide with
# the per-agent values hive-c0re renders. The agent modules # the per-agent values hive-c0re renders. The agent modules

View file

@ -617,6 +617,55 @@ mod tests {
assert!(!builds_agent_at(&flake, "ghost", Some(&reviewer()), None)); assert!(!builds_agent_at(&flake, "ghost", Some(&reviewer()), None));
} }
/// The text of `mkAgent`'s `let` binding `name`: from `name =` to the
/// next binding at the same indent (`;` ends it at line end).
fn binding<'a>(flake: &'a str, name: &str) -> &'a str {
let from = flake
.find(&format!("\n {name} =\n"))
.or_else(|| flake.find(&format!("\n {name} = ")))
.unwrap_or_else(|| panic!("no `{name}` binding in mkAgent"));
let rest = &flake[from + 1..];
let end = rest
.find("\n service =")
.unwrap_or_else(|| panic!("`{name}` is the last binding"));
&rest[..end]
}
#[test]
fn an_archetype_agent_s_module_is_the_archetype_source_s_module() {
let flake = rendered();
let module = binding(&flake, "agentModule");
assert!(
module.contains("archetypeSources.${archetype.source}"),
"the archetype branch picks its source by name: {module}"
);
assert!(
module.contains(".hyperhiveArchetypes.${archetype.name}.module"),
"the archetype branch imports the archetype's module: {module}"
);
// The source it picks from must offer the hyperhive input.
assert!(
binding(&flake, "archetypeSources").contains("inherit hyperhive;"),
"the `hyperhive` archetype source"
);
// The module reaches the agent's system.
let modules = &flake[flake.find("modules = [").expect("a module list")..];
assert!(
modules
.trim_start_matches("modules = [")
.trim_start()
.starts_with("agentModule"),
"agentModule heads the extendModules list"
);
// The entry names the source and archetype the import reads.
assert!(
flake.contains("rev = mkAgent { name = \"rev\"; port = ")
&& flake
.contains(" archetype = { source = \"hyperhive\"; name = \"reviewer\"; }; };"),
"the archetype agent's entry"
);
}
#[test] #[test]
fn an_agent_whose_name_ends_another_s_is_not_that_agent() { fn an_agent_whose_name_ends_another_s_is_not_that_agent() {
let flake = rendered(); let flake = rendered();