diff --git a/docs/getting-started/setup.md b/docs/getting-started/setup.md index 1258cc68..8d09aa3f 100644 --- a/docs/getting-started/setup.md +++ b/docs/getting-started/setup.md @@ -89,9 +89,6 @@ swarmctl forge make-admin mara If the forge already has a local account with your username, the first SSO login asks for its forge password once, to link the two. -Agents learn your pronouns from the **Pronouns** field of your forge profile -settings. Leave it empty and agents refer to you by name only. - ## 4 · Ruth hive-c0re creates no agent at startup. You create ruth, the manager agent, diff --git a/docs/turn-loop/claude-invocation.md b/docs/turn-loop/claude-invocation.md index 28dc5548..992b6314 100644 --- a/docs/turn-loop/claude-invocation.md +++ b/docs/turn-loop/claude-invocation.md @@ -255,21 +255,13 @@ needs to `chown` a bind mount), once at startup: `hive-agent/prompts/system.md` by `hive-agent`'s `prompt::render`: HTML-comment markers (`...`, same for `role:manager`) gate the role-specific blocks; both flavors - share everything else. Five placeholders are then + share everything else. Four placeholders are then substituted: `{label}` (short agent name), `{qualified_label}` (hive-qualified `name@domain` form), `{hive_identity}` (for example `` on hive `pr1ma` ``; empty when - `HYPERHIVE_HIVE_NAME` is unset), `{swarm_identity}` (same - shape for the swarm), and `{operators}`: a paragraph listing the - swarm's operators with the pronouns from each one's forge profile, - for example `Operators: mara (she/her), sol.`. The harness reads - them with one direct get on the one key of the `operator-profiles` - KV bucket, which `swarm-controller` writes. The harness waits up to five seconds for - them. When the bucket is empty or doesn't answer in time, - `{operators}` is empty and the prompt names no operator. Either way - the template tells agents to say "the operator" in third person. A - pronoun change reaches an agent at its next harness start, since - the prompt renders once per start. + `HYPERHIVE_HIVE_NAME` is unset), and `{swarm_identity}` (same + shape for the swarm). The template names no pronouns for the + operator, and tells agents to call them the operator. When you enable `services.hyperhive.agent.docs.enable`, `HIVE_DOCS_DIR` is present in the environment and `render()` appends a one-sentence pointer telling the agent where the docs live. diff --git a/hive-agent/Cargo.toml b/hive-agent/Cargo.toml index ffd7e439..b31612c3 100644 --- a/hive-agent/Cargo.toml +++ b/hive-agent/Cargo.toml @@ -38,16 +38,14 @@ rusqlite.workspace = true schemars.workspace = true serde.workspace = true serde_json.workspace = true -# `agent-schedules`, `agent-inference` and `operator-profiles`: the schedule -# ticker and the inference poller each read their own key, and the prompt -# renderer the operator list, with one direct get. -# `kv`/`notices` name buckets and streams this harness opens neither end of, -# and the terminal publisher is a plain core-subject publish. +# `agent-schedules` and `agent-inference`: the schedule ticker and the +# inference poller each read their own key with one direct get. `kv`/`notices` +# name buckets and streams this harness opens neither end of, and the terminal +# publisher is a plain core-subject publish. swarm-queue-client = { workspace = true, features = [ "agent-schedules", "agent-inference", "agent-permissions", - "operator-profiles", ] } # This agent's own queue secret, read from the store under its own certificate. swarm-secret-client.workspace = true diff --git a/hive-agent/prompts/system.md b/hive-agent/prompts/system.md index 18bba8de..1fce9027 100644 --- a/hive-agent/prompts/system.md +++ b/hive-agent/prompts/system.md @@ -1,4 +1,4 @@ -You are hyperhive agent `{label}` (qualified: `{qualified_label}`){hive_identity}{swarm_identity} in a multi-agent system. The operator is the human at the dashboard; say "the operator" when you mention the operator in third person (e.g. when relaying to a peer or the manager). When you're talking to or about a peer on a different hive, use the qualified form (`name@hive`) so the operator + peers can disambiguate; within your own hive the short form is fine.{operators} +You are hyperhive agent `{label}` (qualified: `{qualified_label}`){hive_identity}{swarm_identity} in a multi-agent system. The operator is the human at the dashboard; say "the operator" when you mention the operator in third person (e.g. when relaying to a peer or the manager). When you're talking to or about a peer on a different hive, use the qualified form (`name@hive`) so the operator + peers can disambiguate; within your own hive the short form is fine. Tools (hyperhive surface). Full signature + behavior for each comes from the tool's own MCP description (you already received it via the MCP tool schema) — this is just the map of what exists and which ones are gated, so you know where to look: diff --git a/hive-agent/src/prompt.rs b/hive-agent/src/prompt.rs index 5dccb8ea..7ca58e82 100644 --- a/hive-agent/src/prompt.rs +++ b/hive-agent/src/prompt.rs @@ -5,17 +5,11 @@ //! `docs/turn-loop/claude-invocation.md::On-boot files` (`claude-system-prompt.md`). use std::path::{Path, PathBuf}; -use std::time::Duration; use anyhow::{Context, Result}; -use swarm_queue_client::operator_profiles::{self, OperatorProfile}; - -/// How long [`write_system_prompt`] waits for the operator list before -/// rendering without it. -const OPERATORS_TIMEOUT: Duration = Duration::from_secs(5); /// Assemble the system prompt for a given label + optional hive / -/// swarm display names + the swarm's operators. Pure function — no I/O. Splits out from +/// swarm display names. Pure function — no I/O. Splits out from /// [`write_system_prompt`] so the marker logic + substitution is unit-testable /// in isolation. The caller supplies the template body so tests can pass an /// inline fixture and production reads it once at harness startup via @@ -30,7 +24,6 @@ pub fn render( hive_name: Option<&str>, swarm_name: Option<&str>, docs_dir: Option<&str>, - operators: &[OperatorProfile], ) -> String { let body = filter_role_blocks(template, "agent"); let qualified = crate::identity::qualify(label); @@ -44,8 +37,7 @@ pub fn render( .replace("{label}", label) .replace("{qualified_label}", &qualified) .replace("{hive_identity}", &hive_identity) - .replace("{swarm_identity}", &swarm_identity) - .replace("{operators}", &operators_line(operators)); + .replace("{swarm_identity}", &swarm_identity); // When the reference docs are mounted in-container (`services.hyperhive.agent.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 @@ -61,63 +53,6 @@ pub fn render( } } -/// The `{operators}` substitution: a paragraph naming each operator, with their -/// pronouns where set, or nothing when there are none. -fn operators_line(operators: &[OperatorProfile]) -> String { - if operators.is_empty() { - return String::new(); - } - let names: Vec = operators - .iter() - .map(|o| match &o.pronouns { - Some(pronouns) => format!("{} ({pronouns})", o.name), - None => o.name.clone(), - }) - .collect(); - format!("\n\nOperators: {}.", names.join(", ")) -} - -/// The swarm's operators, or none when the operator-profile bucket cannot be -/// read within [`OPERATORS_TIMEOUT`]: the prompt then reads as it does with no -/// operator list. -async fn operators() -> Vec { - within_timeout(OPERATORS_TIMEOUT, read_operators()).await -} - -/// One read of the operator list over this agent's queue connection. -async fn read_operators() --> std::result::Result, Box> { - let client = crate::swarm_queue::client().await; - swarm_queue_client::ensure_connected(&client)?; - match operator_profiles::read(&client).await? { - operator_profiles::Read::Current(operators) => Ok(operators), - operator_profiles::Read::NoBucket => { - Err("the operator-profiles bucket does not answer".into()) - } - } -} - -/// `read`'s profiles, or none when it fails or outlasts `timeout`. -async fn within_timeout( - timeout: Duration, - read: impl Future, E>>, -) -> Vec { - match tokio::time::timeout(timeout, read).await { - Ok(Ok(operators)) => operators, - Ok(Err(e)) => { - tracing::warn!(error = %e, "prompt: operator profiles unreadable; rendering without them"); - Vec::new() - } - Err(_) => { - tracing::warn!( - timeout_s = timeout.as_secs(), - "prompt: no operator profiles within the timeout; rendering without them" - ); - Vec::new() - } - } -} - /// Walk `template` line-by-line, stripping `` / /// `` marker lines and suppressing the lines inside a block /// unless `X == target`. A close marker ends the current block. Production @@ -202,7 +137,6 @@ pub async fn write_system_prompt(_socket: &Path, label: &str) -> Result hive_name.as_deref(), swarm_name.as_deref(), docs_dir.as_deref(), - &operators().await, ); let path = parent.join("claude-system-prompt.md"); tokio::fs::write(&path, body).await?; @@ -300,7 +234,7 @@ shared closer #[test] fn render_substitutes_label_and_names_no_operator_pronouns() { // Real template's first agent line. - let rendered = render(&PRODUCTION_TEMPLATE, "alice", None, None, None, &[]); + let rendered = render(&PRODUCTION_TEMPLATE, "alice", None, None, None); assert!(rendered.contains("hyperhive agent `alice`")); assert!(rendered.contains("say \"the operator\"")); assert!(!rendered.contains("pronoun")); @@ -311,7 +245,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", None, None, None, &[]); + let rendered = render(&PRODUCTION_TEMPLATE, "alice", None, None, None); assert!(!rendered.contains("