remove Role::Manager + ManagerSurface + Flavor::Manager — there is only one role: agent
This commit is contained in:
parent
186ee430b5
commit
f56b272a23
8 changed files with 100 additions and 489 deletions
|
|
@ -1,38 +1,31 @@
|
|||
//! System-prompt renderer. Single `prompts/system.md` with
|
||||
//! HTML-comment markers gating role-specific blocks; this module
|
||||
//! assembles the final prompt for a given flavor. Marker grammar +
|
||||
//! placeholder substitution rules in
|
||||
//! assembles the final prompt (always "agent" role — there is only one
|
||||
//! role). Marker grammar + placeholder substitution rules in
|
||||
//! `docs/turn-loop.md::On-boot files` (`claude-system-prompt.md`).
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
|
||||
use crate::mcp::Flavor;
|
||||
|
||||
/// Assemble the system prompt for a given flavor + label + pronouns +
|
||||
/// optional hive / 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 [`hive_sh4re::assets::prompt_template`]
|
||||
/// Assemble the system prompt for a given label + pronouns + optional hive /
|
||||
/// 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
|
||||
/// [`hive_sh4re::assets::prompt_template`]
|
||||
/// (`$HIVE_ASSETS_DIR/prompts/system.md`). Substitution placeholders +
|
||||
/// marker grammar documented in
|
||||
/// `docs/turn-loop.md::On-boot files` (`claude-system-prompt.md`).
|
||||
#[must_use]
|
||||
pub fn render(
|
||||
template: &str,
|
||||
flavor: Flavor,
|
||||
label: &str,
|
||||
operator_pronouns: &str,
|
||||
hive_name: Option<&str>,
|
||||
swarm_name: Option<&str>,
|
||||
) -> String {
|
||||
let target = match flavor {
|
||||
Flavor::Agent => "agent",
|
||||
Flavor::Manager => "manager",
|
||||
};
|
||||
let body = filter_role_blocks(template, target);
|
||||
let body = filter_role_blocks(template, "agent");
|
||||
let qualified = crate::identity::qualify(label);
|
||||
let hive_identity = hive_name
|
||||
.filter(|n| !n.is_empty())
|
||||
|
|
@ -113,7 +106,7 @@ fn parse_close_marker(line: &str) -> Option<&str> {
|
|||
/// # Errors
|
||||
///
|
||||
/// Returns an error if the system prompt file cannot be written.
|
||||
pub async fn write_system_prompt(_socket: &Path, label: &str, flavor: Flavor) -> Result<PathBuf> {
|
||||
pub async fn write_system_prompt(_socket: &Path, label: &str) -> Result<PathBuf> {
|
||||
let parent = crate::paths::config_dir();
|
||||
tokio::fs::create_dir_all(&parent).await.ok();
|
||||
let pronouns = std::env::var("HIVE_OPERATOR_PRONOUNS").unwrap_or_else(|_| "she/her".to_owned());
|
||||
|
|
@ -133,7 +126,6 @@ pub async fn write_system_prompt(_socket: &Path, label: &str, flavor: Flavor) ->
|
|||
let swarm_name = crate::identity::swarm_name();
|
||||
let body = render(
|
||||
&template,
|
||||
flavor,
|
||||
label,
|
||||
&pronouns,
|
||||
hive_name.as_deref(),
|
||||
|
|
@ -279,7 +271,6 @@ shared closer
|
|||
// harness already relied on.
|
||||
let rendered = render(
|
||||
&PRODUCTION_TEMPLATE,
|
||||
Flavor::Agent,
|
||||
"alice",
|
||||
"they/them",
|
||||
None,
|
||||
|
|
@ -292,66 +283,32 @@ shared closer
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn render_agent_excludes_manager_only_tools() {
|
||||
// Spot-check: the manager-only tool block (request_init_config,
|
||||
// kill, schedule_*) MUST NOT appear in the agent's rendered
|
||||
// prompt. Drift between flavor and tool surface bites every
|
||||
// time it happens.
|
||||
fn render_no_role_markers_in_output() {
|
||||
// No raw role markers should survive into the rendered prompt.
|
||||
let rendered = render(
|
||||
&PRODUCTION_TEMPLATE,
|
||||
Flavor::Agent,
|
||||
"alice",
|
||||
"she/her",
|
||||
None,
|
||||
None,
|
||||
);
|
||||
assert!(!rendered.contains("request_init_config"));
|
||||
assert!(!rendered.contains("request_apply_commit"));
|
||||
assert!(!rendered.contains("get_logs"));
|
||||
// Sanity: shared tools DO appear.
|
||||
assert!(!rendered.contains("<!-- role:"));
|
||||
assert!(!rendered.contains("<!-- /role:"));
|
||||
// Shared tools appear.
|
||||
assert!(rendered.contains("mcp__hyperhive__recv"));
|
||||
assert!(rendered.contains("mcp__hyperhive__ask"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_manager_includes_manager_only_tools() {
|
||||
fn render_uses_agent_opener() {
|
||||
let rendered = render(
|
||||
&PRODUCTION_TEMPLATE,
|
||||
Flavor::Manager,
|
||||
"ruth",
|
||||
"she/her",
|
||||
None,
|
||||
None,
|
||||
);
|
||||
assert!(rendered.contains("request_init_config"));
|
||||
assert!(rendered.contains("request_apply_commit"));
|
||||
assert!(rendered.contains("get_logs"));
|
||||
assert!(rendered.contains("request_schedule_prompt"));
|
||||
assert!(rendered.contains("cancel_schedule"));
|
||||
// Sub-agent-only sections must NOT appear in manager prompt.
|
||||
assert!(!rendered.contains("request_next_turn"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_uses_correct_role_opener() {
|
||||
let agent = render(
|
||||
&PRODUCTION_TEMPLATE,
|
||||
Flavor::Agent,
|
||||
"alice",
|
||||
"she/her",
|
||||
None,
|
||||
None,
|
||||
);
|
||||
assert!(agent.starts_with("You are hyperhive agent"));
|
||||
let manager = render(
|
||||
&PRODUCTION_TEMPLATE,
|
||||
Flavor::Manager,
|
||||
"ruth",
|
||||
"she/her",
|
||||
None,
|
||||
None,
|
||||
);
|
||||
assert!(manager.starts_with("You are the hyperhive manager"));
|
||||
assert!(rendered.starts_with("You are hyperhive agent"));
|
||||
}
|
||||
|
||||
// Inline fixture for the {hive_identity} / {swarm_identity}
|
||||
|
|
@ -371,7 +328,6 @@ You are hyperhive agent `{label}` (qualified: `{qualified_label}`){hive_identity
|
|||
fn render_substitutes_hive_identity_when_set() {
|
||||
let rendered = render(
|
||||
IDENTITY_FIXTURE,
|
||||
Flavor::Agent,
|
||||
"alice",
|
||||
"she/her",
|
||||
Some("pr1ma"),
|
||||
|
|
@ -389,7 +345,6 @@ You are hyperhive agent `{label}` (qualified: `{qualified_label}`){hive_identity
|
|||
fn render_substitutes_swarm_identity_when_set() {
|
||||
let rendered = render(
|
||||
IDENTITY_FIXTURE,
|
||||
Flavor::Manager,
|
||||
"ruth",
|
||||
"she/her",
|
||||
None,
|
||||
|
|
@ -403,7 +358,6 @@ You are hyperhive agent `{label}` (qualified: `{qualified_label}`){hive_identity
|
|||
fn render_substitutes_both_when_both_set() {
|
||||
let rendered = render(
|
||||
IDENTITY_FIXTURE,
|
||||
Flavor::Agent,
|
||||
"iris",
|
||||
"she/her",
|
||||
Some("pr1ma"),
|
||||
|
|
@ -418,14 +372,7 @@ You are hyperhive agent `{label}` (qualified: `{qualified_label}`){hive_identity
|
|||
fn render_omits_identity_when_unset() {
|
||||
// None / None must round-trip the non-identity opener verbatim
|
||||
// — single-hive deployments see zero diff.
|
||||
let rendered = render(
|
||||
IDENTITY_FIXTURE,
|
||||
Flavor::Agent,
|
||||
"alice",
|
||||
"she/her",
|
||||
None,
|
||||
None,
|
||||
);
|
||||
let rendered = render(IDENTITY_FIXTURE, "alice", "she/her", None, None);
|
||||
assert!(!rendered.contains("on hive"));
|
||||
assert!(!rendered.contains("in swarm"));
|
||||
assert!(!rendered.contains("{hive_identity}"));
|
||||
|
|
@ -438,14 +385,7 @@ You are hyperhive agent `{label}` (qualified: `{qualified_label}`){hive_identity
|
|||
// through `identity::hive_name()` as None (the accessor
|
||||
// filters empty), but `render` should still no-op on a
|
||||
// direct `Some("")` from a test fixture or a future caller.
|
||||
let rendered = render(
|
||||
IDENTITY_FIXTURE,
|
||||
Flavor::Agent,
|
||||
"alice",
|
||||
"she/her",
|
||||
Some(""),
|
||||
Some(""),
|
||||
);
|
||||
let rendered = render(IDENTITY_FIXTURE, "alice", "she/her", Some(""), Some(""));
|
||||
assert!(!rendered.contains("on hive"));
|
||||
assert!(!rendered.contains("in swarm"));
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue