refactor(#2012): docs in own derivation, ship via claude --add-dir (no ~/.claude clobber)
This commit is contained in:
parent
e6bc621f59
commit
362d392993
7 changed files with 123 additions and 67 deletions
11
flake.nix
11
flake.nix
|
|
@ -212,6 +212,13 @@
|
||||||
# NOT of `packages.default`, so the binary derivation stays
|
# NOT of `packages.default`, so the binary derivation stays
|
||||||
# cached when a prompt edit ripples through.
|
# cached when a prompt edit ripples through.
|
||||||
assets = pkgs.callPackage ./nix/assets.nix { };
|
assets = pkgs.callPackage ./nix/assets.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,
|
||||||
|
# neither of which needs the branding/prompt assets. See
|
||||||
|
# nix/reference-docs.nix. (`docs` above is the auto-generated
|
||||||
|
# nix-options reference, a different artifact.)
|
||||||
|
reference-docs = pkgs.callPackage ./nix/reference-docs.nix { };
|
||||||
# Pre-built per-container system closures. Exposed as packages
|
# Pre-built per-container system closures. Exposed as packages
|
||||||
# so operators can `nix build .#agent-base-toplevel` (or wire
|
# so operators can `nix build .#agent-base-toplevel` (or wire
|
||||||
# them into their host system closure via the
|
# them into their host system closure via the
|
||||||
|
|
@ -255,6 +262,10 @@
|
||||||
# so the harness module can wire $HIVE_ASSETS_DIR straight
|
# so the harness module can wire $HIVE_ASSETS_DIR straight
|
||||||
# to `${pkgs.hyperhive-assets}/share/hyperhive`.
|
# to `${pkgs.hyperhive-assets}/share/hyperhive`.
|
||||||
hyperhive-assets = self.packages.${prev.stdenv.hostPlatform.system}.assets;
|
hyperhive-assets = self.packages.${prev.stdenv.hostPlatform.system}.assets;
|
||||||
|
# Standalone docs/ tree (see nix/reference-docs.nix). Exposed
|
||||||
|
# via the overlay so the harness module can build the
|
||||||
|
# in-container agent docs dir from it.
|
||||||
|
hyperhive-docs = self.packages.${prev.stdenv.hostPlatform.system}.reference-docs;
|
||||||
};
|
};
|
||||||
claude-unstable =
|
claude-unstable =
|
||||||
final: prev:
|
final: prev:
|
||||||
|
|
|
||||||
|
|
@ -694,6 +694,19 @@ async fn run_claude(prompt: &str, files: &TurnFiles, bus: &Bus) -> Result<(bool,
|
||||||
.arg(mcp::builtin_tools_arg())
|
.arg(mcp::builtin_tools_arg())
|
||||||
.arg("--allowedTools")
|
.arg("--allowedTools")
|
||||||
.arg(mcp::allowed_tools_arg());
|
.arg(mcp::allowed_tools_arg());
|
||||||
|
// hyperhive.docs.enable wires HIVE_DOCS_DIR to the in-container
|
||||||
|
// reference-docs dir (the docs/ tree plus a generic `CLAUDE.md`
|
||||||
|
// pointer at its root). Expose it to claude as an additional
|
||||||
|
// directory so the docs are readable, and enable additive CLAUDE.md
|
||||||
|
// loading so claude picks up `<dir>/CLAUDE.md` ALONGSIDE the agent's
|
||||||
|
// own memory files — never clobbering ~/.claude/CLAUDE.md or the
|
||||||
|
// project one. Unset (docs disabled) → neither flag is passed.
|
||||||
|
if let Some(docs_dir) = std::env::var_os("HIVE_DOCS_DIR")
|
||||||
|
&& !docs_dir.is_empty()
|
||||||
|
{
|
||||||
|
cmd.arg("--add-dir").arg(&docs_dir);
|
||||||
|
cmd.env("CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD", "1");
|
||||||
|
}
|
||||||
let mut child = cmd
|
let mut child = cmd
|
||||||
.stdin(Stdio::piped())
|
.stdin(Stdio::piped())
|
||||||
.stdout(Stdio::piped())
|
.stdout(Stdio::piped())
|
||||||
|
|
|
||||||
9
nix/agent-claude.md
Normal file
9
nix/agent-claude.md
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
# Hyperhive reference docs
|
||||||
|
|
||||||
|
The hyperhive subsystem docs ship read-only inside this container at
|
||||||
|
`$HIVE_DOCS_DIR/docs/` (resolve the env var, e.g.
|
||||||
|
`ls "$HIVE_DOCS_DIR/docs"`).
|
||||||
|
|
||||||
|
On a fresh deploy, read `$HIVE_DOCS_DIR/docs/setup.md` first for the
|
||||||
|
first-run hive bootstrap commands (forge / gateway / matrix
|
||||||
|
provisioning, spawning the first sub-agents).
|
||||||
|
|
@ -12,28 +12,25 @@
|
||||||
# Output layout:
|
# Output layout:
|
||||||
# $out/share/hyperhive/branding/{hyperhive,agent-configs}.{svg,png}
|
# $out/share/hyperhive/branding/{hyperhive,agent-configs}.{svg,png}
|
||||||
# $out/share/hyperhive/prompts/{system.md, claude-settings.json}
|
# $out/share/hyperhive/prompts/{system.md, claude-settings.json}
|
||||||
# $out/share/hyperhive/docs/ — the repo docs/ tree
|
#
|
||||||
|
# The repo docs/ tree is a SEPARATE derivation (nix/reference-docs.nix)
|
||||||
|
# so agents can consume the docs without the branding+prompt assets and
|
||||||
|
# the website repo can reuse it — see that file.
|
||||||
|
|
||||||
stdenv.mkDerivation {
|
stdenv.mkDerivation {
|
||||||
pname = "hyperhive-assets";
|
pname = "hyperhive-assets";
|
||||||
version = "0.1.0";
|
version = "0.1.0";
|
||||||
# Narrow `srcs` (branding/ + hive-ag3nt/prompts/ + docs/) is what
|
# Narrow `srcs` (branding/ + hive-ag3nt/prompts/) is what decouples
|
||||||
# decouples this derivation's input hash from the rest of the tree.
|
# this derivation's input hash from the rest of the tree.
|
||||||
# Including docs/ surfaces the reference docs in-container at
|
|
||||||
# `$HIVE_ASSETS_DIR/docs/` (no host mount, pulled from the nix store);
|
|
||||||
# a docs/ edit re-hashes this asset, the accepted tradeoff for shipping
|
|
||||||
# the docs declaratively.
|
|
||||||
srcs = [
|
srcs = [
|
||||||
../branding
|
../branding
|
||||||
../hive-ag3nt/prompts
|
../hive-ag3nt/prompts
|
||||||
../docs
|
|
||||||
];
|
];
|
||||||
unpackPhase = ''
|
unpackPhase = ''
|
||||||
runHook preUnpack
|
runHook preUnpack
|
||||||
cp -r ${../branding} branding
|
cp -r ${../branding} branding
|
||||||
cp -r ${../hive-ag3nt/prompts} prompts
|
cp -r ${../hive-ag3nt/prompts} prompts
|
||||||
cp -r ${../docs} docs
|
chmod -R u+w branding prompts
|
||||||
chmod -R u+w branding prompts docs
|
|
||||||
runHook postUnpack
|
runHook postUnpack
|
||||||
'';
|
'';
|
||||||
|
|
||||||
|
|
@ -54,7 +51,6 @@ stdenv.mkDerivation {
|
||||||
mkdir -p $out/share/hyperhive
|
mkdir -p $out/share/hyperhive
|
||||||
cp -r branding $out/share/hyperhive/branding
|
cp -r branding $out/share/hyperhive/branding
|
||||||
cp -r prompts $out/share/hyperhive/prompts
|
cp -r prompts $out/share/hyperhive/prompts
|
||||||
cp -r docs $out/share/hyperhive/docs
|
|
||||||
runHook postInstall
|
runHook postInstall
|
||||||
'';
|
'';
|
||||||
|
|
||||||
|
|
@ -62,7 +58,7 @@ stdenv.mkDerivation {
|
||||||
dontFixup = true;
|
dontFixup = true;
|
||||||
|
|
||||||
meta = {
|
meta = {
|
||||||
description = "hyperhive static assets (branding + claude prompts + docs)";
|
description = "hyperhive static assets (branding + claude prompts)";
|
||||||
homepage = "https://forge.darkest.space/hyperhive/hyperhive";
|
homepage = "https://forge.darkest.space/hyperhive/hyperhive";
|
||||||
license = lib.licenses.mit;
|
license = lib.licenses.mit;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
43
nix/reference-docs.nix
Normal file
43
nix/reference-docs.nix
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
{
|
||||||
|
stdenv,
|
||||||
|
lib,
|
||||||
|
}:
|
||||||
|
|
||||||
|
# The repo `docs/` markdown tree, shipped as a standalone derivation so
|
||||||
|
# agents can read the reference docs in-container (added as a claude
|
||||||
|
# additional directory by the harness) WITHOUT pulling the branding +
|
||||||
|
# prompt assets they don't need, and so the `hyperhive/website` repo can
|
||||||
|
# reuse the exact same tree as a flake input.
|
||||||
|
#
|
||||||
|
# Pure data: the docs are copied verbatim (never transformed), so there
|
||||||
|
# is no writable/build step — `$out` is the docs tree as-is.
|
||||||
|
#
|
||||||
|
# Output layout:
|
||||||
|
# $out/ — the repo `docs/` tree verbatim (e.g. `$out/setup.md`)
|
||||||
|
|
||||||
|
stdenv.mkDerivation {
|
||||||
|
pname = "hyperhive-docs";
|
||||||
|
version = "0.1.0";
|
||||||
|
# Narrow src (just docs/) keeps this derivation's input hash decoupled
|
||||||
|
# from the rest of the tree — a doc edit only re-hashes this.
|
||||||
|
src = ../docs;
|
||||||
|
|
||||||
|
# No build: pure markdown, nothing to compile or render.
|
||||||
|
dontBuild = true;
|
||||||
|
dontConfigure = true;
|
||||||
|
|
||||||
|
installPhase = ''
|
||||||
|
runHook preInstall
|
||||||
|
mkdir -p $out
|
||||||
|
cp -r ./* $out/
|
||||||
|
runHook postInstall
|
||||||
|
'';
|
||||||
|
|
||||||
|
dontFixup = true;
|
||||||
|
|
||||||
|
meta = {
|
||||||
|
description = "hyperhive reference docs (the repo docs/ tree)";
|
||||||
|
homepage = "https://forge.darkest.space/hyperhive/hyperhive";
|
||||||
|
license = lib.licenses.mit;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -70,23 +70,22 @@ let
|
||||||
iconPng = pkgs.runCommand "hive-agent-icon.png" { nativeBuildInputs = [ pkgs.librsvg ]; } ''
|
iconPng = pkgs.runCommand "hive-agent-icon.png" { nativeBuildInputs = [ pkgs.librsvg ]; } ''
|
||||||
rsvg-convert -f png -w 512 -h 512 ${config.hyperhive.icon} -o $out
|
rsvg-convert -f png -w 512 -h 512 ${config.hyperhive.icon} -o $out
|
||||||
'';
|
'';
|
||||||
# Generic, agent-agnostic `~/.claude/CLAUDE.md` installed when
|
# hyperhive.docs.enable: the in-container directory claude is pointed
|
||||||
# `hyperhive.docs.enable` is on (root/manager default-on; see
|
# at for the hyperhive reference docs (root/manager default-on; see
|
||||||
# `manager.nix`). claude auto-loads this user-global memory file every
|
# `manager.nix`). Combines the standalone docs tree
|
||||||
# session, so it's the discovery hook for the docs asset — no
|
# (`pkgs.hyperhive-docs`) under `docs/` with a generic, agent-agnostic
|
||||||
# per-agent prompt injection. It references the STABLE `$HIVE_ASSETS_DIR`
|
# `CLAUDE.md` pointer (`nix/agent-claude.md`) at its root. The harness
|
||||||
# env var (not the hashed nix-store path) which the agent resolves at
|
# adds this dir via `--add-dir` and sets
|
||||||
# read time.
|
# `CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD=1`, so claude loads the
|
||||||
docsClaudeMd = pkgs.writeText "hive-docs-claude.md" ''
|
# sibling `CLAUDE.md` ADDITIVELY (never replacing the agent's own
|
||||||
# Hyperhive reference docs
|
# `~/.claude/CLAUDE.md` or the project one) and gets read access to
|
||||||
|
# `docs/`. Built as a runCommand (like `iconPng` above) so the tree is
|
||||||
The hyperhive subsystem docs ship read-only inside this container at
|
# GC-rooted by the system closure — no persistent symlink into the
|
||||||
`$HIVE_ASSETS_DIR/docs/` (resolve the env var, e.g.
|
# store to dangle after a content change + GC.
|
||||||
`ls "$HIVE_ASSETS_DIR/docs"`).
|
agentDocs = pkgs.runCommand "hive-agent-docs" { } ''
|
||||||
|
mkdir -p $out
|
||||||
On a fresh deploy, read `$HIVE_ASSETS_DIR/docs/setup.md` first for the
|
cp ${../agent-claude.md} $out/CLAUDE.md
|
||||||
first-run hive bootstrap commands (forge / gateway / matrix
|
cp -r ${pkgs.hyperhive-docs} $out/docs
|
||||||
provisioning, spawning the first sub-agents).
|
|
||||||
'';
|
'';
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
|
|
@ -229,16 +228,16 @@ in
|
||||||
};
|
};
|
||||||
|
|
||||||
options.hyperhive.docs.enable = lib.mkEnableOption ''
|
options.hyperhive.docs.enable = lib.mkEnableOption ''
|
||||||
install a generic `~/.claude/CLAUDE.md` pointing this agent at the
|
point this agent at the hyperhive reference docs (the repo `docs/`
|
||||||
hyperhive reference docs shipped read-only at `$HIVE_ASSETS_DIR/docs/`
|
tree, shipped read-only as the standalone `hyperhive-docs`
|
||||||
(the repo `docs/` tree, packaged into the assets derivation). claude
|
derivation). When enabled the harness adds the in-container docs dir
|
||||||
auto-loads the file every session, so the docs are discoverable
|
via `claude --add-dir` and turns on additive CLAUDE.md loading
|
||||||
without any per-agent prompt injection. The docs themselves are
|
(`CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD=1`), so claude loads a
|
||||||
always present at `$HIVE_ASSETS_DIR/docs/`; this option only controls
|
generic pointer `CLAUDE.md` from that dir ALONGSIDE — never replacing
|
||||||
the auto-loaded pointer. When enabled the managed symlink clobbers any
|
— the agent's own `~/.claude/CLAUDE.md` and project memory. The docs
|
||||||
existing `~/.claude/CLAUDE.md`. Default-on for the root/manager agent
|
are readable at `$HIVE_DOCS_DIR/docs/`. Default-on for the
|
||||||
(see `manager.nix`), off elsewhere; any agent can flip it from its
|
root/manager agent (see `manager.nix`), off elsewhere; any agent can
|
||||||
`agent.nix`.
|
flip it from its `agent.nix`.
|
||||||
'';
|
'';
|
||||||
|
|
||||||
options.hyperhive.allowedBashPatterns = lib.mkOption {
|
options.hyperhive.allowedBashPatterns = lib.mkOption {
|
||||||
|
|
@ -1042,21 +1041,6 @@ in
|
||||||
[ -d "$configDir" ] || continue
|
[ -d "$configDir" ] || continue
|
||||||
chown -hR "$userName:$userName" "$configDir" 2>/dev/null || true
|
chown -hR "$userName:$userName" "$configDir" 2>/dev/null || true
|
||||||
done
|
done
|
||||||
${lib.optionalString config.hyperhive.docs.enable ''
|
|
||||||
# hyperhive.docs.enable: point this agent at the hyperhive docs via
|
|
||||||
# a managed `~/.claude/CLAUDE.md` (claude auto-loads it every
|
|
||||||
# session). The link lives in the bind-mounted (persistent) home, so
|
|
||||||
# it must NOT target a bare `/nix/store` path — that path is only
|
|
||||||
# GC-rooted by the current generation, and a content change (new
|
|
||||||
# hash) plus `nix-collect-garbage` would leave the persistent link
|
|
||||||
# dangling. Instead it targets the STABLE `/etc/hyperhive/claude/
|
|
||||||
# CLAUDE.md` path, which the `environment.etc` entry below
|
|
||||||
# regenerates declaratively every rebuild (and which holds the
|
|
||||||
# docs store-path reference that keeps the content GC-rooted). The
|
|
||||||
# chown -h below sets the link's ownership.
|
|
||||||
mkdir -p "$homeDir/.claude"
|
|
||||||
ln -sfn /etc/hyperhive/claude/CLAUDE.md "$homeDir/.claude/CLAUDE.md"
|
|
||||||
''}
|
|
||||||
if [ -d "$homeDir/.claude" ]; then
|
if [ -d "$homeDir/.claude" ]; then
|
||||||
chown -hR "$userName:$userName" "$homeDir/.claude" 2>/dev/null || true
|
chown -hR "$userName:$userName" "$homeDir/.claude" 2>/dev/null || true
|
||||||
# 0755 so hive-core (a different unix user) can list the dir and
|
# 0755 so hive-core (a different unix user) can list the dir and
|
||||||
|
|
@ -1097,16 +1081,6 @@ in
|
||||||
|
|
||||||
environment.etc."hyperhive/extra-mcp.json".text = builtins.toJSON config.hyperhive.extraMcpServers;
|
environment.etc."hyperhive/extra-mcp.json".text = builtins.toJSON config.hyperhive.extraMcpServers;
|
||||||
|
|
||||||
# hyperhive.docs.enable: the managed `~/.claude/CLAUDE.md` content,
|
|
||||||
# placed declaratively in /etc so it's regenerated every rebuild and
|
|
||||||
# GC-rooted by the system closure. The activation script above symlinks
|
|
||||||
# the agent's (bind-mounted, persistent) `~/.claude/CLAUDE.md` at this
|
|
||||||
# stable path rather than at the bare `${docsClaudeMd}` store path, so
|
|
||||||
# the persistent link can never dangle after a content change + GC.
|
|
||||||
environment.etc."hyperhive/claude/CLAUDE.md" = lib.mkIf config.hyperhive.docs.enable {
|
|
||||||
source = docsClaudeMd;
|
|
||||||
};
|
|
||||||
|
|
||||||
# Operator-set per-agent icon (hyperhive.icon). When configured, the
|
# Operator-set per-agent icon (hyperhive.icon). When configured, the
|
||||||
# SVG lands at /etc/hyperhive/icon.svg; the harness serves it at
|
# SVG lands at /etc/hyperhive/icon.svg; the harness serves it at
|
||||||
# GET /icon, falling back to the bundled hyperhive logo when absent.
|
# GET /icon, falling back to the bundled hyperhive logo when absent.
|
||||||
|
|
@ -1295,6 +1269,15 @@ in
|
||||||
# (compact-on-overflow) still fires when the session is truly full.
|
# (compact-on-overflow) still fires when the session is truly full.
|
||||||
HIVE_COMPACT_WATERMARK_TOKENS = "0";
|
HIVE_COMPACT_WATERMARK_TOKENS = "0";
|
||||||
}
|
}
|
||||||
|
// lib.optionalAttrs config.hyperhive.docs.enable {
|
||||||
|
# hyperhive.docs.enable: the in-container reference-docs dir — the
|
||||||
|
# docs/ tree plus the agent CLAUDE.md pointer (see `agentDocs`
|
||||||
|
# above). The harness reads this and passes it to claude as
|
||||||
|
# `--add-dir` with additive CLAUDE.md loading enabled, so the docs
|
||||||
|
# are discoverable without touching the agent's own memory files.
|
||||||
|
# See hive-ag3nt::turn.
|
||||||
|
HIVE_DOCS_DIR = "${agentDocs}";
|
||||||
|
}
|
||||||
// lib.optionalAttrs (config.hyperhive._bashEnvFragments != "") {
|
// lib.optionalAttrs (config.hyperhive._bashEnvFragments != "") {
|
||||||
# Non-interactive bash invocations (claude's `Bash` tool runs
|
# Non-interactive bash invocations (claude's `Bash` tool runs
|
||||||
# `bash -c`) source $BASH_ENV at startup — drops every active
|
# `bash -c`) source $BASH_ENV at startup — drops every active
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,9 @@
|
||||||
imports = [ ./harness-base.nix ];
|
imports = [ ./harness-base.nix ];
|
||||||
|
|
||||||
# The root/manager bootstraps a fresh hive, so it gets the hyperhive
|
# The root/manager bootstraps a fresh hive, so it gets the hyperhive
|
||||||
# reference docs surfaced by default (the `~/.claude/CLAUDE.md` pointer
|
# reference docs surfaced by default (the additive `CLAUDE.md` pointer
|
||||||
# → `$HIVE_ASSETS_DIR/docs/setup.md`). `mkDefault` so a manager's own
|
# → `$HIVE_DOCS_DIR/docs/setup.md`, added via `claude --add-dir`).
|
||||||
# `agent.nix` can still turn it off. Other agents default off.
|
# `mkDefault` so a manager's own `agent.nix` can still turn it off.
|
||||||
|
# Other agents default off.
|
||||||
hyperhive.docs.enable = lib.mkDefault true;
|
hyperhive.docs.enable = lib.mkDefault true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue