From a26bbb15fc20f8498e153186aeca2db5410c8d95 Mon Sep 17 00:00:00 2001 From: damocles Date: Sat, 4 Jul 2026 11:40:29 +0200 Subject: [PATCH] feat(#2089): docs pointer via system-prompt line, drop CLAUDE.md mechanism --- hive-ag3nt/prompts/docs-pointer.md | 19 ------- hive-ag3nt/src/prompt.rs | 82 +++++++++++++++++++++++++++--- nix/templates/harness-base.nix | 47 +++++------------ 3 files changed, 86 insertions(+), 62 deletions(-) delete mode 100644 hive-ag3nt/prompts/docs-pointer.md diff --git a/hive-ag3nt/prompts/docs-pointer.md b/hive-ag3nt/prompts/docs-pointer.md deleted file mode 100644 index 21cd380e..00000000 --- a/hive-ag3nt/prompts/docs-pointer.md +++ /dev/null @@ -1,19 +0,0 @@ -# hyperhive reference docs - -The hyperhive reference docs are mounted read-only in this directory -(the harness exposes the path as `$HIVE_DOCS_DIR`). They are the -canonical reference for how this swarm actually works - the turn loop, -the tool surface, persistence, conventions, runbooks, and the -approval/CI/forge/matrix workflows. - -- On a fresh deploy, or any time you are unsure how a hyperhive - mechanism behaves, read `setup.md` first, then the topic file for the - area you are touching (e.g. `turn-loop.md`, `persistence.md`, - `tools/`, `conventions.md`, `gotchas.md`). -- Prefer these docs over guessing: they describe the live system, not a - generic one. - -This pointer is additive. It does not replace your own memory or project -instructions - your `~/.claude/CLAUDE.md` and any harness/project memory -still apply in full. Nothing here overrides what you already know about -yourself; it only tells you where the shared reference material lives. diff --git a/hive-ag3nt/src/prompt.rs b/hive-ag3nt/src/prompt.rs index a86d9054..aa584747 100644 --- a/hive-ag3nt/src/prompt.rs +++ b/hive-ag3nt/src/prompt.rs @@ -24,6 +24,7 @@ pub fn render( operator_pronouns: &str, hive_name: Option<&str>, swarm_name: Option<&str>, + docs_dir: Option<&str>, ) -> String { let body = filter_role_blocks(template, "agent"); let qualified = crate::identity::qualify(label); @@ -33,11 +34,25 @@ pub fn render( let swarm_identity = swarm_name .filter(|n| !n.is_empty()) .map_or(String::new(), |n| format!(" in swarm `{n}`")); - body.replace("{label}", label) + let rendered = body + .replace("{label}", label) .replace("{qualified_label}", &qualified) .replace("{operator_pronouns}", operator_pronouns) .replace("{hive_identity}", &hive_identity) - .replace("{swarm_identity}", &swarm_identity) + .replace("{swarm_identity}", &swarm_identity); + // When the reference docs are mounted in-container (`hyperhive.docs.enable` + // wires `HIVE_DOCS_DIR` + `claude --add-dir`), append a single pointer + // sentence so the agent knows they exist. Additive: it doesn't replace the + // agent's own memory/project instructions. Absent env → no change. + match docs_dir.filter(|d| !d.is_empty()) { + Some(dir) => format!( + "{rendered}\n\nThe hyperhive reference docs (the repo `docs/` tree \ + describing this live system) are mounted read-only at `{dir}` — read \ + them (start at the index, then the topic file for the area you're \ + touching) rather than guessing.\n" + ), + None => rendered, + } } /// Walk `template` line-by-line. Inside a `` block, @@ -124,12 +139,18 @@ pub async fn write_system_prompt(_socket: &Path, label: &str) -> Result // verbatim (single-hive deployments see no diff). let hive_name = crate::identity::hive_name(); let swarm_name = crate::identity::swarm_name(); + // `hyperhive.docs.enable` sets HIVE_DOCS_DIR (and the harness passes it to + // claude via `--add-dir`); when present, render() appends a pointer line. + let docs_dir = std::env::var("HIVE_DOCS_DIR") + .ok() + .filter(|d| !d.is_empty()); let body = render( &template, label, &pronouns, hive_name.as_deref(), swarm_name.as_deref(), + docs_dir.as_deref(), ); let path = parent.join("claude-system-prompt.md"); tokio::fs::write(&path, body).await?; @@ -269,7 +290,7 @@ shared closer // Real template's first agent line — keeps the renderer // honest about the {label} / {operator_pronouns} pair the // harness already relied on. - let rendered = render(&PRODUCTION_TEMPLATE, "alice", "they/them", None, None); + let rendered = render(&PRODUCTION_TEMPLATE, "alice", "they/them", None, None, None); assert!(rendered.contains("hyperhive agent `alice`")); assert!(rendered.contains("**they/them** pronouns")); assert!(!rendered.contains("{label}")); @@ -279,7 +300,7 @@ shared closer #[test] fn render_no_role_markers_in_output() { // No raw role markers should survive into the rendered prompt. - let rendered = render(&PRODUCTION_TEMPLATE, "alice", "she/her", None, None); + let rendered = render(&PRODUCTION_TEMPLATE, "alice", "she/her", None, None, None); assert!(!rendered.contains("