diff --git a/claude-plugins/.claude-plugin/marketplace.json b/claude-plugins/.claude-plugin/marketplace.json deleted file mode 100644 index 1ada195c..00000000 --- a/claude-plugins/.claude-plugin/marketplace.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "$schema": "https://anthropic.com/claude-code/marketplace.schema.json", - "name": "hyperhive", - "description": "hyperhive-authored Claude Code plugins, shipped hive-wide via the default hyperhive.claudeMarketplaces/claudePlugins nix options (nix/agent-modules/claude-settings.nix). Built as a nix store path and added with `claude plugin marketplace add ` - no forge repo or git remote needed. Add a new plugins// subtree + one entry below to ship another.", - "owner": { - "name": "hyperhive" - }, - "plugins": [ - { - "name": "base", - "description": "hyperhive's base plugin - skills every agent needs, regardless of role. Currently: state-hygiene (read-before-write discipline and dated-archive convention for durable agent notes/state files). Add a new skill here (not a new plugin) if it applies to all agents; give it its own specialized plugin if it doesn't.", - "source": "./plugins/base", - "category": "productivity" - } - ] -} diff --git a/claude-plugins/plugins/base/.claude-plugin/plugin.json b/claude-plugins/plugins/base/.claude-plugin/plugin.json deleted file mode 100644 index bb267b49..00000000 --- a/claude-plugins/plugins/base/.claude-plugin/plugin.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "base", - "description": "hyperhive's base plugin - skills every agent needs, regardless of role.", - "author": { - "name": "hyperhive" - } -} diff --git a/claude-plugins/plugins/base/skills/state-hygiene/SKILL.md b/claude-plugins/plugins/base/skills/state-hygiene/SKILL.md deleted file mode 100644 index 276f1f49..00000000 --- a/claude-plugins/plugins/base/skills/state-hygiene/SKILL.md +++ /dev/null @@ -1,49 +0,0 @@ ---- -name: state-hygiene -description: Enforces safe handling of an agent's own durable state/notes files (e.g. state/notes.md, state/TODO.md, or anything else under state/ meant to survive a container restart) - read the file before archiving, overwriting, or pruning it, split resolved work into small dated archive files instead of growing one giant live file, and never shell-pipe-append (>>) into a notes file. Use this whenever you are about to write to a durable notes/state file, especially right before a context-compaction checkpoint, when told to "flush state" or "write down anything relevant", or when a live notes file has grown large and needs an archive pass. ---- - -# State Hygiene - -Durable notes/state files (your `CLAUDE.md`, `state/notes.md`, `state/TODO.md`, -and anything else under `state/`) are your memory across container resets and -context compactions. They only work if you actually read them before you -change them - writing blind is how history gets silently destroyed or -duplicated. - -## Before writing to any durable notes file - -1. **Read it first.** Never overwrite or append to a notes/state file based - on memory or assumption of its current contents - the file may have moved - on since your last read (another turn, another agent touched shared - space, a prior session ended mid-edit). Read (or re-read the relevant - section) immediately before editing. -2. **Never pipe-append.** Don't do `echo "..." >> state/notes.md` (or - equivalent) from a shell command. That's a blind write with no chance to - see - and reconcile with - what's already there. Use your file-edit tool - (read, then edit/write) instead. -3. **Prefer a targeted edit over a full rewrite** for anything long-lived: an - edit is less likely to accidentally clobber unrelated content than - regenerating the whole file from your current context. - -## Keeping a notes file lean - -- Split into a **live file** (only what's currently active/gated/relevant - - this is what you re-read every turn, keep it short) and an **archive - subdirectory** (e.g. `state/notes-archive/`) for resolved/closed work, - moved out of the hot path, never deleted. Don't drop archived files - loose at the top level of `state/` alongside the live files - a - dedicated subdir keeps `ls state/` showing only what's live. -- Archive in **dated, per-topic chunks** - `YYYY-MM-DD-.md` - not one - ever-growing archive blob. `ls` on the archive subdir should read like a - timeline. -- When you finish a piece of work, collapse its entry in the live file to a - one-line "done" summary and move the full detail into a new dated - archive file. -- A live file well past ~500 lines (or into four figures) is overdue for an - archive pass - do it at a natural checkpoint (finishing a task, a - context-compaction prompt, or whenever you notice it). - -If your environment ships its own hive-wide/repo-wide notes convention doc, -treat that as the canonical source and this skill as a reminder to follow -it - don't duplicate its rationale into your own notes either. diff --git a/docs/gotchas.md b/docs/gotchas.md index 6cbc7f9c..119bc38e 100644 --- a/docs/gotchas.md +++ b/docs/gotchas.md @@ -84,47 +84,6 @@ scoped, only this one package. This is needed because each per-agent operator's host-level `allowUnfree` does **not** propagate in. Operators don't need to set anything on their side. -That same isolation is why an agent can't pick a claude out of a -*different* nixpkgs by itself: a container only ever sees the one -nixpkgs the meta flake injects, so an `agent.nix` naming the host's -`nixpkgs-unstable` has nothing to name. A release channel can trail -unstable by weeks on this package, which is what -`services.hyperhive.c0re.claudeCodePackage` is for — set it host-side -and every agent runs that build. - -What crosses is the **store path**, not the derivation. Containers -share the host's `/nix/store`, so the binary is already reachable -inside them with its whole closure; hive-c0re writes the path into each -agent's flake as a string literal and the agent module symlinks -`bin/claude` onto PATH. Two things rule out the obvious alternatives: a -`path:/nix/store/` flake input is re-copied into the store as a -reference-less `-source` (so the runtime closure never arrives), and -`lib.types.package` fed a bare path runs `builtins.storePath`, which -pure evaluation rejects. `hyperhive.docs.source` gets away with being -an input only because a docs tree has no runtime dependencies. - -The `storePath` trap is worth spelling out, because it is not confined -to options the operator writes: **any** option of type `package` fed a -store-path *string* coerces through `lib.toDerivation`, i.e. -`builtins.storePath`. `environment.systemPackages` and -`systemd.services..path` both do it (the latter takes plain -strings like `/run/wrappers` happily, but anything under -`builtins.storeDir` is treated as a package). So a path handed to the -container as text has to be wrapped in a real derivation — a symlink -farm built from the interpolated string — before it can go anywhere a -package is expected. - -The catch is that a path written into a generated flake is text, not a -reference — the container's closure does not keep the binary alive. -The **host** does: the package is interpolated into -`/etc/hyperhive/serve.json`, so it lands in the host's system closure -and is gc-rooted by the running generation. `builtins.toJSON` preserves -string context, which is the load-bearing detail; discard the context -anywhere on that path and `nix-collect-garbage` will eventually take -the hive's `claude` out from under it. The price of the root is that an -old `claude-code` can't be reclaimed until every agent has rebuilt past -it and the old generations are gone. - ## Claude credentials are per-agent `/var/lib/hyperhive/agents//claude/` bind-mounts to diff --git a/docs/turn-loop/claude-invocation.md b/docs/turn-loop/claude-invocation.md index b9d2f26c..9d93619e 100644 --- a/docs/turn-loop/claude-invocation.md +++ b/docs/turn-loop/claude-invocation.md @@ -21,17 +21,6 @@ auto-reset / retry decisions in `drive_turn`. The lib returns everything it parsed from a turn (usage, cost, context window, resolved model) as `Telemetry`, which the policy layer applies to the bus. -**Which `claude` binary.** The bare name `claude`, resolved off the -harness unit's PATH. By default that's the `claude-code` in the agent's -own nixpkgs (the meta flake's `nixpkgs` input) via -`environment.systemPackages`. Since that's usually a release channel and -this package moves fast, the operator can pin one hive-wide with -`services.hyperhive.c0re.claudeCodePackage`: its store path is written -into each agent's flake, and `claude` on PATH becomes a symlink to it -instead of the container's own `claude-code` — so there's only ever one -`claude` in the container. Agents pick up a new build on their -next rebuild, not live. See docs/gotchas.md::`claude-code` is unfree. - Hive-enforced settings ship at `/etc/claude-code/managed-settings.json` (claude-code's canonical managed-settings path — precedence #1, read-only, un-overridable), wired in `nix/agent-modules/claude-settings.nix` diff --git a/flake.nix b/flake.nix index 5c127078..4146a87a 100644 --- a/flake.nix +++ b/flake.nix @@ -94,7 +94,6 @@ assets frontend reference-docs - claude-plugins ; }; }; diff --git a/hive-c0re/src/coordinator.rs b/hive-c0re/src/coordinator.rs index 7029cbf3..c08927df 100644 --- a/hive-c0re/src/coordinator.rs +++ b/hive-c0re/src/coordinator.rs @@ -62,17 +62,6 @@ pub struct Coordinator { /// "nixpkgs"` is set in the host flake). Empty string = legacy /// `follows = "hyperhive/nixpkgs"` behaviour. pub nixpkgs_flake: String, - /// Store path of the `claude-code` build every agent runs, written - /// into each per-agent flake as `hyperhive.claudeCodePath`. Set by - /// the NixOS module option `services.hyperhive.c0re.claudeCodePackage` - /// (which resolves the package and hands us its path). `None` = every - /// agent keeps the `claude-code` from its own nixpkgs. - /// - /// A path rather than a flake input because containers share the - /// host's `/nix/store`: the binary is already reachable inside them, - /// closure and all. The host module is what keeps it from being - /// garbage-collected — see that option. - pub claude_code_path: Option, /// 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 /// rebuild-button URL pointing back at the dashboard. @@ -224,11 +213,6 @@ pub struct HiveEnv { /// so a doc edit only re-locks this input, not the whole source. pub hyperhive_docs_flake: String, pub nixpkgs_flake: 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 `hyperhive.claudeCodePath` — a plain string, kept - /// alive host-side by the module that resolved it. - pub claude_code_path: Option, pub dashboard_port: u16, pub operator_pronouns: String, pub context_window_tokens: std::collections::HashMap, @@ -251,7 +235,6 @@ impl Default for HiveEnv { hyperhive_flake: "/etc/hyperhive".to_string(), hyperhive_docs_flake: String::new(), nixpkgs_flake: String::new(), - claude_code_path: None, dashboard_port: 7000, operator_pronouns: "she/her".to_string(), context_window_tokens: std::collections::HashMap::from([ @@ -491,7 +474,6 @@ impl Coordinator { hyperhive_flake, hyperhive_docs_flake, nixpkgs_flake, - claude_code_path, dashboard_port, operator_pronouns, context_window_tokens, @@ -538,7 +520,6 @@ impl Coordinator { hyperhive_flake, hyperhive_docs_flake, nixpkgs_flake, - claude_code_path, dashboard_port, operator_pronouns, context_window_tokens, @@ -572,7 +553,6 @@ impl Coordinator { hyperhive_flake: self.hyperhive_flake.clone(), hyperhive_docs_flake: self.hyperhive_docs_flake.clone(), nixpkgs_flake: self.nixpkgs_flake.clone(), - claude_code_path: self.claude_code_path.clone(), dashboard_port: self.dashboard_port, operator_pronouns: self.operator_pronouns.clone(), context_window_tokens: self.context_window_tokens.clone(), diff --git a/hive-c0re/src/meta.rs b/hive-c0re/src/meta.rs index 167d0908..bc1fb6f4 100644 --- a/hive-c0re/src/meta.rs +++ b/hive-c0re/src/meta.rs @@ -92,7 +92,6 @@ pub async fn sync_agents(hive: &HiveEnv, agents: &[AgentSpec]) -> Result<()> { &hive.hyperhive_flake, &hive.hyperhive_docs_flake, &hive.nixpkgs_flake, - hive.claude_code_path.as_deref(), hive.dashboard_port, &hive.operator_pronouns, &hive.context_window_tokens, @@ -651,7 +650,6 @@ fn render_flake( hyperhive_flake: &str, docs_flake: &str, nixpkgs_flake: &str, - claude_code_path: Option<&str>, dashboard_port: u16, operator_pronouns: &str, context_window_tokens: &std::collections::HashMap, @@ -662,7 +660,6 @@ fn render_flake( hyperhive_flake, docs_flake, nixpkgs_flake, - claude_code_path, dashboard_port, operator_pronouns, context_window_tokens, @@ -940,7 +937,6 @@ fn render_flake_with_lookup( hyperhive_flake: &str, docs_flake: &str, nixpkgs_flake: &str, - claude_code_path: Option<&str>, dashboard_port: u16, operator_pronouns: &str, context_window_tokens: &std::collections::HashMap, @@ -1055,18 +1051,6 @@ where if !docs_flake.is_empty() { out.push_str(" hyperhive.docs.source = inputs.\"hyperhive-docs\".outPath;\n"); } - // The `claude-code` agents run, as a bare store path rather than a - // flake input: containers share the host's `/nix/store`, so the - // binary is already there with its closure and has nothing to - // travel. A string literal is also the only shape that evaluates — - // `lib.types.package` on a bare path runs `builtins.storePath`, - // which pure eval rejects. The agent module puts its `bin/` on the - // harness PATH; the host module holds the gc root, since a path - // spelled out here is text and references nothing. - // `None` = no override; agents keep their own nixpkgs' `claude-code`. - if let Some(path) = claude_code_path { - let _ = writeln!(out, " hyperhive.claudeCodePath = \"{path}\";"); - } // CA trust: embed every hive-trusted CA so each agent validates them at // build time. The list is the hive's own self-signed CA (when active) // plus every peer-hive root CA (`swarm.peers..caCert`) — a peer CA is @@ -1577,7 +1561,6 @@ mod tests { "github:example/hyperhive", "path:/nix/store/bbbb-hyperhive-docs-source", "path:/nix/store/aaaa-nixpkgs-source", - None, 8000, "she/her", &std::collections::HashMap::new(), @@ -1622,7 +1605,6 @@ mod tests { "github:example/hyperhive", "", "path:/nix/store/aaaa-nixpkgs-source", - None, 8000, "she/her", &std::collections::HashMap::new(), @@ -1635,57 +1617,6 @@ mod tests { ); } - #[test] - fn render_flake_pins_claude_path_without_adding_an_input() { - // The host-pinned claude travels as a bare store path assigned to - // an option — deliberately NOT as a flake input. Containers share - // the host store, so the build is already reachable; making it an - // 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", - "path:/nix/store/bbbb-hyperhive-docs-source", - "path:/nix/store/aaaa-nixpkgs-source", - Some("/nix/store/cccc-claude-code-2.1.220"), - 8000, - "she/her", - &std::collections::HashMap::new(), - "4G", - &[sample_spec("alice", false, 9001)], - ); - assert!( - out.contains("hyperhive.claudeCodePath = \"/nix/store/cccc-claude-code-2.1.220\";"), - "claude path assigned as a plain string literal:\n{out}" - ); - assert!( - !out.contains("claude-code-2.1.220\".url"), - "the pinned claude must not become a flake input:\n{out}" - ); - } - - #[test] - fn render_flake_omits_claude_path_when_unset() { - // `None` = no host-level pin: the option is left undefined so the - // agent module keeps its own nixpkgs' `claude-code` (and keeps it - // in `environment.systemPackages`, which is what makes the - // unpinned case self-contained). - let out = render_flake( - "github:example/hyperhive", - "path:/nix/store/bbbb-hyperhive-docs-source", - "path:/nix/store/aaaa-nixpkgs-source", - None, - 8000, - "she/her", - &std::collections::HashMap::new(), - "4G", - &[sample_spec("alice", false, 9001)], - ); - assert!( - !out.contains("claudeCodePath"), - "no claude assignment when unpinned:\n{out}" - ); - } - #[test] fn render_flake_falls_back_to_follows_when_nixpkgs_flake_empty() { // Empty nixpkgs_flake → legacy follows behaviour (backward compat @@ -1694,7 +1625,6 @@ mod tests { "github:example/hyperhive", "path:/nix/store/bbbb-hyperhive-docs-source", "", - None, 8000, "she/her", &std::collections::HashMap::new(), @@ -1725,7 +1655,6 @@ mod tests { "github:example/hyperhive", "path:/nix/store/bbbb-hyperhive-docs-source", "path:/nix/store/aaaa-nixpkgs-source", - None, 8000, "she/her", &std::collections::HashMap::new(), @@ -1759,7 +1688,6 @@ mod tests { "github:example/hyperhive", "path:/nix/store/bbbb-hyperhive-docs-source", "path:/nix/store/aaaa-nixpkgs-source", - None, 8000, "she/her", &std::collections::HashMap::new(), @@ -1795,7 +1723,6 @@ mod tests { "github:example/hyperhive", "path:/nix/store/bbbb-hyperhive-docs-source", "path:/nix/store/aaaa-nixpkgs-source", - None, 8000, "she/her", &std::collections::HashMap::new(), @@ -1834,7 +1761,6 @@ mod tests { "github:example/hyperhive", "path:/nix/store/bbbb-hyperhive-docs-source", "path:/nix/store/aaaa-nixpkgs-source", - None, 8000, "she/her", &std::collections::HashMap::new(), @@ -1877,7 +1803,6 @@ mod tests { "github:example/hyperhive", "path:/nix/store/bbbb-hyperhive-docs-source", "path:/nix/store/aaaa-nixpkgs-source", - None, 8000, "she/her", &std::collections::HashMap::new(), @@ -1961,7 +1886,6 @@ mod tests { "github:example/hyperhive", "path:/nix/store/bbbb-hyperhive-docs-source", "path:/nix/store/aaaa-nixpkgs-source", - None, 8000, "she/her", &std::collections::HashMap::new(), @@ -2047,7 +1971,6 @@ mod tests { "github:example/hyperhive", "path:/nix/store/bbbb-hyperhive-docs-source", "path:/nix/store/aaaa-nixpkgs-source", - None, 8000, "she/her", &std::collections::HashMap::new(), @@ -2090,7 +2013,6 @@ mod tests { "github:example/hyperhive", "path:/nix/store/bbbb-hyperhive-docs-source", "path:/nix/store/aaaa-nixpkgs-source", - None, 8000, "she/her", &std::collections::HashMap::new(), diff --git a/nix/agent-modules/claude-settings.nix b/nix/agent-modules/claude-settings.nix index f946839d..fe5c1943 100644 --- a/nix/agent-modules/claude-settings.nix +++ b/nix/agent-modules/claude-settings.nix @@ -228,14 +228,7 @@ in options.hyperhive.claudeMarketplaces = lib.mkOption { type = lib.types.listOf lib.types.str; - default = [ - "anthropics/claude-plugins-official" - "${config.hyperhive.packages.claude-plugins}" - ]; - defaultText = lib.literalMD '' - `[ "anthropics/claude-plugins-official" "''${hyperhive.packages.claude-plugins}" ]` - (the flake's own local-path plugin marketplace) - ''; + default = [ "anthropics/claude-plugins-official" ]; example = [ "anthropics/claude-plugins-official" "anthropics/claude-plugins-community" @@ -249,24 +242,14 @@ in reference a marketplace (e.g. `foo@claude-plugins-official`). Rendered to `/etc/hyperhive/claude-marketplaces.json`. - Defaults to Anthropic's official marketplace plus hyperhive's - own `claude-plugins` nix package (see - `nix/packages/claude-plugins.nix`) — a local-path marketplace - built as a plain nix store path, registered under the name - `hyperhive` (from its `marketplace.json`, not the store path - itself, so plugin specs stay stable across rebuilds). No forge - repo or git remote needed to ship a hive-authored skill; agents - get both marketplaces out of the box without any per-agent.nix - wiring. + Defaults to Anthropic's official marketplace; agents get it + out of the box without any per-agent.nix wiring. ''; }; options.hyperhive.claudePlugins = lib.mkOption { type = lib.types.listOf lib.types.str; - default = [ - "skill-creator@claude-plugins-official" - "base@hyperhive" - ]; + default = [ "skill-creator@claude-plugins-official" ]; example = [ "formatter@my-marketplace" "thinking-tools@anthropics" @@ -281,22 +264,15 @@ in `/etc/hyperhive/claude-plugins.json`; the harness reads it via `plugins::install_configured`. - Defaults to Anthropic's `skill-creator` (teaches an agent to - write, refine, and evaluate its own skills) plus hyperhive's own - `base` plugin — one plugin bundling every skill that applies to - *all* agents regardless of role (currently just `state-hygiene`, - read-before-write + dated-archive discipline for durable - notes/state files; more all-agent skills land as additional - skills inside this same plugin, not new plugins — a skill that - only some agents need gets its own specialized plugin instead). - Agents get both out of the box, matching the default - marketplaces above. + Defaults to Anthropic's `skill-creator`, which teaches an agent + to write, refine, and evaluate its own skills — agents get it + out of the box, matching the default marketplace above. Note that a per-agent definition REPLACES this default rather than extending it (ordinary NixOS list-option semantics, same as `claudeMarketplaces`). An agent that wants extra plugins AND the - defaults should list both `skill-creator@claude-plugins-official` - and `base@hyperhive` alongside them. + default should list `skill-creator@claude-plugins-official` + alongside them. ''; }; diff --git a/nix/agent-modules/default.nix b/nix/agent-modules/default.nix index 5b282fb0..810ab3b5 100644 --- a/nix/agent-modules/default.nix +++ b/nix/agent-modules/default.nix @@ -61,33 +61,6 @@ ''; }; - options.hyperhive.claudeCodePath = lib.mkOption { - type = lib.types.nullOr lib.types.str; - default = null; - example = "/nix/store/…-claude-code-2.1.220"; - description = '' - Store path of the `claude-code` this agent runs, or `null` (the - default) to use the `claude-code` from the container's own - nixpkgs. - - Set by the generated meta flake when the operator sets - `services.hyperhive.c0re.claudeCodePackage` host-side, so a hive - can run a claude built from a *different* nixpkgs than the one - its agents evaluate against — a release channel can trail - unstable by weeks on this one package. It arrives as a path and - not a package because agents share the host's `/nix/store`, so - the build is already reachable here with its whole closure and - has nothing to travel. - - When set, `claude` on PATH is a symlink to this path's - `bin/claude` and the container's own `claude-code` is dropped, so - there is only ever one claude in the container. Note that neither - the symlink nor anything else in the container's closure *refers* - to the target — keeping it alive is the host's job, see - `services.hyperhive.c0re.claudeCodePackage`. - ''; - }; - config = { assertions = [ # Guard the inputs-routed-as-output pattern: the agent flake.nix is @@ -210,31 +183,8 @@ environment.systemPackages = [ config.hyperhive.packages.hive-metric ] - ++ [ - ( - if config.hyperhive.claudeCodePath == null then - pkgs.claude-code - else - # Host-pinned claude: a symlink farm around a path the - # container was handed as text. It has to be a derivation — - # `environment.systemPackages` coerces a store-path *string* - # with `toDerivation`, i.e. `builtins.storePath`, which pure - # evaluation rejects (`systemd.services.*.path` does the same, - # which is why the harness gets this via PATH like everything - # else rather than a unit-level entry). Interpolating the path - # into the builder is just text, so it evaluates anywhere. - # - # The symlink registers no store reference — the target isn't - # among this derivation's inputs, so nothing here keeps the - # binary alive. That is deliberate and it is the host's job: - # see `services.hyperhive.c0re.claudeCodePackage`. - pkgs.runCommandLocal "claude-code-pinned" { } '' - mkdir -p "$out/bin" - ln -s ${config.hyperhive.claudeCodePath}/bin/claude "$out/bin/claude" - '' - ) - ] ++ (with pkgs; [ + claude-code bashInteractive coreutils-full # procps for pkill — used by the web UI's /api/cancel to SIGINT the diff --git a/nix/agent-modules/packages.nix b/nix/agent-modules/packages.nix index ca465fee..af3052b8 100644 --- a/nix/agent-modules/packages.nix +++ b/nix/agent-modules/packages.nix @@ -13,8 +13,8 @@ per-binary daemon/CLI packages (`hive-agent`, `hive-agent-mcp`, `hive-bash-daemon`, `hive-forge`, `hive-forge-notify`, `hive-matrix-daemon`, - `hive-metric`, `hive-screen-mcp`) plus the `assets`, `frontend`, - `reference-docs` and `claude-plugins` trees. Wired by the flake's agent-base/ruth + `hive-metric`, `hive-screen-mcp`) plus the `assets`, `frontend` and + `reference-docs` trees. Wired by the flake's agent-base/ruth nixosModules to `hyperhive.packages..*`; override an individual key per-agent to swap in a patched binary. ''; diff --git a/nix/host-modules/hive-c0re/default.nix b/nix/host-modules/hive-c0re/default.nix index 28937561..5372ed7a 100644 --- a/nix/host-modules/hive-c0re/default.nix +++ b/nix/host-modules/hive-c0re/default.nix @@ -76,20 +76,6 @@ let hyperhive_flake = cfg.hyperhiveFlake; hyperhive_docs_flake = cfg.hyperhiveDocs; nixpkgs_flake = cfg.nixpkgsFlake; - # 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 - # in whole rather than interpolated — that is also the ONLY thing - # gc-rooting it: the resulting string carries store context, so this - # /etc entry genuinely references the package and the host's system - # closure holds it alive. Nothing container-side can — meta.rs writes - # the path into each agent's generated flake as a plain string - # literal, and text is not a reference. Hence the assertion below: do - # NOT discard this context, and do not route the path through - # anything that drops it. The failure mode is a garbage-collected - # `claude` and a hive that can't take a turn, weeks after the commit - # that caused it. - claude_code_path = cfg.claudeCodePackage; dashboard_port = cfg.dashboardPort; operator_pronouns = cfg.operatorPronouns; context_window_tokens = cfg.contextWindowTokens; @@ -108,28 +94,6 @@ in ]; config = lib.mkIf cfg.enable { - assertions = [ - { - # The pinned claude reaches agents as a bare path, so this - # string's store context is the whole reason the binary survives - # a `nix-collect-garbage`. Losing it is invisible at eval and at - # deploy — it only shows up as every agent failing to spawn - # `claude`, at whatever unrelated moment the gc runs. Cheap - # enough to just check. - assertion = - cfg.claudeCodePackage == null || builtins.hasContext (builtins.toJSON cfg.claudeCodePackage); - message = '' - services.hyperhive.c0re.claudeCodePackage lost its store - context on the way into /etc/hyperhive/serve.json, so the - package is no longer gc-rooted by the system closure and - `nix-collect-garbage` may delete the claude every agent runs. - Something on that path discarded the context (e.g. - builtins.unsafeDiscardStringContext, toString, or reading the - path back out of a plain file) — undo it. - ''; - } - ]; - environment.systemPackages = [ cfg.package pkgs.git diff --git a/nix/host-modules/hive-c0re/options.nix b/nix/host-modules/hive-c0re/options.nix index bd10cecd..eee0237c 100644 --- a/nix/host-modules/hive-c0re/options.nix +++ b/nix/host-modules/hive-c0re/options.nix @@ -128,45 +128,6 @@ of the host's channel. ''; }; - claudeCodePackage = lib.mkOption { - type = lib.types.nullOr lib.types.package; - default = null; - example = lib.literalExpression "inputs.nixpkgs-unstable.legacyPackages.x86_64-linux.claude-code"; - description = '' - The `claude-code` build every agent runs, or `null` (the - default) to leave each agent on the `claude-code` from its own - nixpkgs — i.e. whatever `nixpkgsFlake` resolves to. - - This is the one binary the whole hive is built around, and it - moves fast enough that a release channel routinely trails - unstable by weeks on it. An agent cannot fix that for itself: - agents evaluate against the single nixpkgs hive-c0re injects, - so an `agent.nix` has no other tree to reach for. Set this from - a second nixpkgs in the host flake and every agent follows, - without moving the nixpkgs the rest of the container is built - from. - - What travels into the container is the **store path**, not the - derivation: agents share the host's `/nix/store`, so the binary - and its full closure are already reachable there — nothing - needs rebuilding or copying. hive-c0re writes the path into - each agent's generated flake as a plain string literal (a bare - path fed to `lib.types.package` would run `builtins.storePath`, - which is illegal under pure evaluation) and the agent module - puts its `bin/` on the harness's PATH. - - The flip side of a plain string is that nothing in the agent's - own closure refers to it, so the container cannot keep it - alive. The **host** does that instead: this package is - interpolated into `/etc/hyperhive/serve.json`, which puts it in - the host's system closure — so it is gc-rooted by the running - generation for exactly as long as that generation is the one - the agents were rendered from. The cost is that - `nix-collect-garbage` cannot reclaim an old `claude-code` until - every agent has been rebuilt past it and the old generations - are gone. - ''; - }; dashboardPort = lib.mkOption { type = lib.types.port; default = 7000; diff --git a/nix/packages/claude-plugins.nix b/nix/packages/claude-plugins.nix deleted file mode 100644 index b83896a0..00000000 --- a/nix/packages/claude-plugins.nix +++ /dev/null @@ -1,48 +0,0 @@ -{ - stdenv, -}: - -# hyperhive-authored Claude Code plugin marketplace (source tree at -# ../../claude-plugins), shipped as a standalone derivation and spliced -# into the *default* `hyperhive.claudeMarketplaces`/`claudePlugins` lists -# (nix/agent-modules/claude-settings.nix) as a local marketplace source. -# `claude plugin marketplace add` accepts a plain filesystem path (verified: -# `claude plugin marketplace add --help` → "Add a marketplace from a URL, -# path, or GitHub repo"), and registers it under the marketplace.json's own -# `name` field ("hyperhive") rather than the path — so -# `@hyperhive` is a stable plugin spec regardless of the store -# path's hash. The built store path IS the marketplace: no forge repo, no -# git remote, no PAT/PR ceremony needed to ship a skill hive-wide. -# -# Every hive-authored skill lives under its own `plugins//` subtree -# in the source tree; add one there plus a matching entry in -# `.claude-plugin/marketplace.json`'s `plugins` list to ship another. -# -# Pure data: nothing to build, `$out` is the tree as-is (mirrors -# ./reference-docs.nix). `cp -r . "$out"` (not `cp -r ./* $out`) because -# the marketplace/plugin manifests live under `.claude-plugin/` — a -# hidden directory a shell glob (`*`) would silently skip. -stdenv.mkDerivation { - pname = "hyperhive-claude-plugins"; - version = "0.1.0"; - # Narrow src keeps this derivation's input hash decoupled from the rest - # of the tree — a skill edit only re-hashes this. - src = ../../claude-plugins; - - dontBuild = true; - dontConfigure = true; - - installPhase = '' - runHook preInstall - mkdir -p $out - cp -r . "$out"/ - runHook postInstall - ''; - - dontFixup = true; - - meta = { - description = "hyperhive-authored Claude Code plugin marketplace (skills shipped hive-wide)"; - homepage = "https://forge.darkest.space/hyperhive/hyperhive"; - }; -} diff --git a/nix/packages/default.nix b/nix/packages/default.nix index b3759bb9..8a8746fe 100644 --- a/nix/packages/default.nix +++ b/nix/packages/default.nix @@ -139,11 +139,6 @@ in # so the binary derivation stays cached when a prompt edit ripples # through. assets = pkgs.callPackage ./assets.nix { }; - # hyperhive-authored Claude Code plugin marketplace (source tree at - # ../../claude-plugins) — a local-path marketplace spliced into the - # default `hyperhive.claudeMarketplaces`/`claudePlugins` lists. See - # ./claude-plugins.nix for the "why a store path, not a repo" rationale. - claude-plugins = pkgs.callPackage ./claude-plugins.nix { }; # The repo docs/ markdown tree as a standalone derivation — # agents read it in-container (added as a claude additional # directory) and the website repo reuses it as a flake input,