use std::path::{Path, PathBuf};
use crate::core::instruction_pipeline::SECTION_SEPARATOR;
use trusty_agents_common::harness_doc::HarnessDoc;
use crate::core::content_source::{self, AgentContentError};
use crate::core::framework_content::FrameworkContent;
pub(crate) const SM_INSTRUCTIONS: &str = "sm_instructions/SM_INSTRUCTIONS.md";
pub(crate) const SM_WORKFLOW: &str = "sm_instructions/SM_WORKFLOW.md";
pub(crate) const SM_TOOLS: &str = "sm_instructions/SM_TOOLS.md";
pub(crate) const BASE_SM: &str = "sm_instructions/BASE_SM.md";
pub const SM_OVERRIDE_SUBDIR: &str = "sm";
pub const FILE_SM_INSTRUCTIONS: &str = "SM_INSTRUCTIONS.md";
pub const FILE_SM_WORKFLOW: &str = "SM_WORKFLOW.md";
pub const FILE_SM_TOOLS: &str = "SM_TOOLS.md";
pub const FILE_SM_HARNESS: &str = "SM_HARNESS.md";
pub fn assemble_sm_prompt(content: &FrameworkContent, harness: &HarnessDoc) -> String {
let harness = harness.harness_understanding();
let harness_trimmed = harness.trim();
vec![
content.required(SM_INSTRUCTIONS).trim(),
harness_trimmed,
content.required(SM_WORKFLOW).trim(),
content.required(SM_TOOLS).trim(),
content.required(BASE_SM).trim(),
]
.into_iter()
.filter(|s| !s.is_empty())
.collect::<Vec<_>>()
.join(SECTION_SEPARATOR)
}
pub fn sm_override_dir() -> Option<PathBuf> {
dirs::home_dir().map(|home| home.join(".trusty-mpm").join(SM_OVERRIDE_SUBDIR))
}
fn read_override(dir: &Path, name: &str) -> Option<String> {
let path = dir.join(name);
match std::fs::read_to_string(&path) {
Ok(text) => {
let trimmed = text.trim();
if trimmed.is_empty() {
tracing::warn!(
path = %path.display(),
"SM instruction override file is empty; using bundled default"
);
None
} else {
tracing::info!(path = %path.display(), "applying SM instruction override");
Some(trimmed.to_string())
}
}
Err(err) if err.kind() == std::io::ErrorKind::NotFound => None,
Err(err) => {
tracing::warn!(
path = %path.display(),
%err,
"SM instruction override file unreadable; using bundled default"
);
None
}
}
}
pub fn resolve_sm_prompt(dir: &Path) -> Result<String, AgentContentError> {
let content = content_source::framework_content_local(None)?;
resolve_sm_prompt_with(dir, &content, content_source::harness_doc)
}
pub fn resolve_sm_prompt_with(
dir: &Path,
content: &FrameworkContent,
harness: impl FnOnce() -> Result<HarnessDoc, AgentContentError>,
) -> Result<String, AgentContentError> {
let instructions = read_override(dir, FILE_SM_INSTRUCTIONS)
.unwrap_or_else(|| content.required(SM_INSTRUCTIONS).trim().to_string());
let harness = match read_override(dir, FILE_SM_HARNESS) {
Some(text) => text,
None => harness()?.harness_understanding().trim().to_string(),
};
let workflow = read_override(dir, FILE_SM_WORKFLOW)
.unwrap_or_else(|| content.required(SM_WORKFLOW).trim().to_string());
let tools = read_override(dir, FILE_SM_TOOLS)
.unwrap_or_else(|| content.required(SM_TOOLS).trim().to_string());
Ok(vec![
instructions,
harness,
workflow,
tools,
content.required(BASE_SM).trim().to_string(),
]
.into_iter()
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect::<Vec<_>>()
.join(SECTION_SEPARATOR))
}
pub fn resolve_sm_prompt_default() -> Result<String, AgentContentError> {
match sm_override_dir() {
Some(dir) => resolve_sm_prompt(&dir),
None => Ok(assemble_sm_prompt(
&content_source::framework_content_local(None)?,
&content_source::harness_doc()?,
)),
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::TempDir;
fn harness() -> HarnessDoc {
crate::core::content_source::test_support::repo_harness_doc()
}
fn content() -> FrameworkContent {
crate::core::content_source::test_support::repo_content()
}
fn resolve(dir: &Path) -> String {
resolve_sm_prompt_with(dir, &content(), || Ok(harness())).expect("prompt")
}
fn write_override(dir: &Path, name: &str, content: &str) {
fs::create_dir_all(dir).expect("create override dir");
fs::write(dir.join(name), content).expect("write override");
}
#[test]
fn assemble_sm_prompt_contains_all_sections() {
let prompt = assemble_sm_prompt(&content(), &harness());
assert!(prompt.contains("# Session Manager (SM) -- trusty-mpm"));
assert!(prompt.contains("| SP1 |"));
assert!(prompt.contains("| SP7 |"));
assert!(prompt.contains("You MAY do directly (Allowlist)"));
assert!(prompt.contains('✻'), "harness ✻ glyph must be present");
let marker = trusty_agents_common::events::EVENT_LINE_PREFIX.trim_end();
assert!(
prompt.contains(marker),
"harness relay marker `{marker}` must be present"
);
assert!(prompt.contains("# SM Workflow -- the delegation loop"));
assert!(prompt.contains("1. **INTAKE.**"));
assert!(prompt.contains("6. **REPORT & PERSIST.**"));
assert!(prompt.contains("Verification gate (BLOCKING)"));
assert!(prompt.contains("# SM Tools -- the verbs you may call"));
assert!(prompt.contains("# BASE_SM Framework Floor"));
assert!(prompt.contains(SECTION_SEPARATOR));
}
#[test]
fn assemble_sm_prompt_base_floor_is_last() {
let prompt = assemble_sm_prompt(&content(), &harness());
let base = prompt.find("# BASE_SM Framework Floor").expect("base_sm");
let tools = prompt
.find("# SM Tools -- the verbs you may call")
.expect("tools");
let workflow = prompt
.find("# SM Workflow -- the delegation loop")
.expect("workflow");
assert!(workflow < tools, "workflow precedes tools");
assert!(base > tools, "BASE_SM floor must be appended last");
let harness_pos = prompt.find('✻').expect("harness ✻ glyph");
assert!(
harness_pos < base,
"harness section must precede BASE_SM floor"
);
}
#[test]
fn assemble_sm_prompt_contains_identity_protocol() {
let prompt = assemble_sm_prompt(&content(), &harness());
assert!(prompt.contains("## Identity & Self-Awareness Protocol (Non-Overridable)"));
assert!(prompt.contains("pip3 show"));
assert!(prompt.contains("which claude-mpm"));
}
#[test]
fn identity_protocol_survives_every_override_branch() {
let tmp = TempDir::new().unwrap();
write_override(
tmp.path(),
FILE_SM_INSTRUCTIONS,
"# Custom Identity\n\nCUSTOM_BODY\n",
);
write_override(tmp.path(), FILE_SM_WORKFLOW, "# Custom Loop\n\nBODY\n");
write_override(tmp.path(), FILE_SM_TOOLS, "# Custom Verbs\n\nBODY\n");
let prompt = resolve(tmp.path());
assert!(
prompt.contains("## Identity & Self-Awareness Protocol (Non-Overridable)"),
"identity protocol must survive every override branch"
);
assert!(prompt.contains("<!-- trusty-mpm-instructions-loaded: v1 -->"));
}
#[test]
fn base_sm_carries_the_load_marker_as_first_line_of_its_section() {
const MARKER: &str = "<!-- trusty-mpm-instructions-loaded: v1 -->";
const HEADING: &str = "## Identity & Self-Awareness Protocol (Non-Overridable)";
let content = content();
let base_sm = content.required(BASE_SM);
let marker_pos = base_sm.find(MARKER).expect("marker present in BASE_SM");
let heading_pos = base_sm.find(HEADING).expect("heading present in BASE_SM");
let between = &base_sm[marker_pos + MARKER.len()..heading_pos];
assert_eq!(
between.trim(),
"",
"marker must immediately precede the heading with no other content between"
);
}
#[test]
fn assemble_sm_prompt_contains_harness_section() {
let prompt = assemble_sm_prompt(&content(), &harness());
assert!(
prompt.contains('✻'),
"assembled SM prompt must contain ✻ (harness understanding glyph marker)"
);
let marker = trusty_agents_common::events::EVENT_LINE_PREFIX.trim_end();
assert!(
prompt.contains(marker),
"assembled SM prompt must contain `{marker}` (tcode event marker)"
);
}
#[test]
fn harness_override_replaces() {
let tmp = TempDir::new().unwrap();
write_override(
tmp.path(),
FILE_SM_HARNESS,
"# Custom Harness Doc\n\nHARNESS_OVERRIDE_SENTINEL\n",
);
let prompt = resolve(tmp.path());
assert!(prompt.contains("HARNESS_OVERRIDE_SENTINEL"));
assert!(prompt.contains("# Session Manager (SM) -- trusty-mpm"));
assert!(prompt.contains("# SM Workflow -- the delegation loop"));
assert!(prompt.contains("# SM Tools -- the verbs you may call"));
assert!(prompt.contains("# BASE_SM Framework Floor"));
}
#[test]
fn no_overrides_uses_bundled() {
let tmp = TempDir::new().unwrap();
let prompt = resolve(tmp.path());
assert!(prompt.contains("# Session Manager (SM) -- trusty-mpm"));
assert!(prompt.contains("# SM Workflow -- the delegation loop"));
assert!(prompt.contains("# SM Tools -- the verbs you may call"));
assert!(prompt.contains("# BASE_SM Framework Floor"));
let base = prompt.find("# BASE_SM Framework Floor").expect("base");
let tools = prompt
.find("# SM Tools -- the verbs you may call")
.expect("tools");
assert!(base > tools, "BASE_SM floor must be last");
}
#[test]
fn sm_instructions_override_replaces() {
let tmp = TempDir::new().unwrap();
write_override(
tmp.path(),
FILE_SM_INSTRUCTIONS,
"# Custom Identity\n\nDELEGATE_EVERYTHING_TO_ALICE\n",
);
let prompt = resolve(tmp.path());
assert!(prompt.contains("DELEGATE_EVERYTHING_TO_ALICE"));
assert!(
!prompt.contains("# Session Manager (SM) -- trusty-mpm"),
"bundled SM_INSTRUCTIONS heading must be replaced"
);
assert!(prompt.contains("# SM Workflow -- the delegation loop"));
assert!(prompt.contains("# SM Tools -- the verbs you may call"));
assert!(prompt.contains("# BASE_SM Framework Floor"));
let body = prompt.find("DELEGATE_EVERYTHING_TO_ALICE").expect("body");
let base = prompt.find("# BASE_SM Framework Floor").expect("base");
assert!(body < base, "BASE_SM floor follows the override body");
}
#[test]
fn workflow_override_replaces() {
let tmp = TempDir::new().unwrap();
write_override(
tmp.path(),
FILE_SM_WORKFLOW,
"# Custom Loop\n\nTWO_PHASE_ONLY\n",
);
let prompt = resolve(tmp.path());
assert!(prompt.contains("TWO_PHASE_ONLY"));
assert!(
!prompt.contains("# SM Workflow -- the delegation loop"),
"bundled workflow heading must be replaced"
);
assert!(prompt.contains("# Session Manager (SM) -- trusty-mpm"));
assert!(prompt.contains("# SM Tools -- the verbs you may call"));
assert!(prompt.contains("# BASE_SM Framework Floor"));
}
#[test]
fn tools_override_replaces() {
let tmp = TempDir::new().unwrap();
write_override(
tmp.path(),
FILE_SM_TOOLS,
"# Custom Verbs\n\nONLY_LAUNCH_AND_STOP\n",
);
let prompt = resolve(tmp.path());
assert!(prompt.contains("ONLY_LAUNCH_AND_STOP"));
assert!(
!prompt.contains("# SM Tools -- the verbs you may call"),
"bundled tools heading must be replaced"
);
assert!(prompt.contains("# Session Manager (SM) -- trusty-mpm"));
assert!(prompt.contains("# SM Workflow -- the delegation loop"));
assert!(prompt.contains("# BASE_SM Framework Floor"));
}
#[test]
fn base_sm_floor_is_never_overridable() {
let tmp = TempDir::new().unwrap();
write_override(
tmp.path(),
"BASE_SM.md",
"# Fake Floor\n\nNO_PROHIBITIONS_ANYMORE\n",
);
write_override(
tmp.path(),
FILE_SM_INSTRUCTIONS,
"# Custom Identity\n\nCUSTOM_IDENTITY_BODY\n",
);
let prompt = resolve(tmp.path());
assert!(prompt.contains("CUSTOM_IDENTITY_BODY"));
assert!(
prompt.contains("# BASE_SM Framework Floor"),
"bundled BASE_SM floor must always be appended"
);
assert!(prompt.contains("Trusty Tool Priority (Non-Overridable)"));
assert!(
!prompt.contains("NO_PROHIBITIONS_ANYMORE"),
"override-dir BASE_SM.md must be ignored"
);
assert!(!prompt.contains("# Fake Floor"));
let body = prompt.find("CUSTOM_IDENTITY_BODY").expect("body");
let base = prompt.find("# BASE_SM Framework Floor").expect("base");
assert!(body < base, "bundled BASE_SM floor must come last");
}
#[test]
fn missing_override_dir_uses_bundled() {
let tmp = TempDir::new().unwrap();
let missing = tmp.path().join("does-not-exist");
assert!(!missing.exists());
let prompt = resolve(&missing);
assert!(prompt.contains("# Session Manager (SM) -- trusty-mpm"));
assert!(prompt.contains("# BASE_SM Framework Floor"));
}
#[test]
fn empty_override_falls_back() {
let tmp = TempDir::new().unwrap();
write_override(tmp.path(), FILE_SM_WORKFLOW, " \n\t\n");
let prompt = resolve(tmp.path());
assert!(prompt.contains("# SM Workflow -- the delegation loop"));
assert!(prompt.contains("# BASE_SM Framework Floor"));
}
#[test]
fn unreadable_override_falls_back() {
let tmp = TempDir::new().unwrap();
fs::create_dir(tmp.path().join(FILE_SM_WORKFLOW)).unwrap();
let prompt = resolve(tmp.path());
assert!(prompt.contains("# SM Workflow -- the delegation loop"));
assert!(prompt.contains("# BASE_SM Framework Floor"));
}
#[test]
fn separators_are_consistent() {
let tmp = TempDir::new().unwrap();
let prompt = resolve(tmp.path());
assert!(prompt.contains(SECTION_SEPARATOR));
}
#[test]
fn resolve_with_no_overrides_matches_assembled_sections() {
let tmp = TempDir::new().unwrap();
let resolved = resolve(tmp.path());
let assembled = assemble_sm_prompt(&content(), &harness());
assert_eq!(
resolved, assembled,
"no-override resolve must be byte-identical to assemble"
);
let section_count = assembled.split(SECTION_SEPARATOR).count();
assert_eq!(
section_count, 5,
"exactly five top-level sections expected (got {section_count}); \
the harness block must not embed the top-level separator"
);
}
#[test]
fn sm_override_dir_under_home() {
match sm_override_dir() {
Some(dir) => {
assert!(dir.ends_with("sm"), "override dir must end with `sm`");
assert!(
dir.to_string_lossy().contains(".trusty-mpm"),
"override dir must be anchored under `.trusty-mpm`"
);
}
None => {
eprintln!(
"sm_override_dir_under_home: no home resolved; \
graceful-None fallback exercised"
);
}
}
}
#[test]
fn missing_content_fails_the_prompt() {
use crate::core::content_source::DevOverride;
use trusty_agents_common::agent_content::resolve_content_in;
let cache = TempDir::new().unwrap();
let overrides = TempDir::new().unwrap();
let err = resolve_sm_prompt_with(overrides.path(), &content(), || {
HarnessDoc::load(&resolve_content_in(cache.path(), DevOverride::Off)?)
})
.expect_err("no content, no override");
assert!(err.to_string().contains("tm content install"), "{err}");
write_override(overrides.path(), FILE_SM_HARNESS, "# operator harness");
let prompt = resolve_sm_prompt_with(overrides.path(), &content(), || {
panic!("an SM_HARNESS.md override must not read content")
})
.expect("override supplies the harness");
assert!(prompt.contains("# operator harness"));
}
}