use std::fmt;
use crate::config::AgentMode;
use crate::context::estimate_tokens;
use crate::guardrails::{self, GuardrailProfile};
use crate::personality::{soul_identity_text, PersonalityBand, PersonalityProfile};
use crate::resources::{AgentsMd, Skill, SoulDoc};
use crate::roles::Role;
use crate::tools::ToolRegistry;
#[derive(Debug, Clone)]
pub struct Fact {
pub text: String,
pub verified_ago: String,
}
#[derive(Debug, Clone)]
pub struct Attempt {
pub number: u32,
pub outcome: String,
pub summary: String,
}
#[derive(Debug, Clone)]
pub struct Dependency {
pub name: String,
pub status: String,
pub detail: String,
}
#[derive(Debug, Clone)]
pub struct TaskContext {
pub title: String,
pub description: String,
pub acceptance: Option<String>,
pub verify: Option<String>,
pub notes: Option<String>,
pub attempts: Vec<Attempt>,
pub dependencies: Vec<Dependency>,
pub decisions: Vec<String>,
pub context_paths: Vec<String>,
pub constraints: Vec<String>,
}
#[derive(Debug)]
pub struct AssembledPrompt {
pub text: String,
pub estimated_tokens: u32,
}
impl fmt::Display for AssembledPrompt {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.text)
}
}
pub struct AssembleParams<'a> {
pub tools: &'a ToolRegistry,
pub agents_md: &'a [AgentsMd],
pub skills: &'a [Skill],
pub facts: &'a [Fact],
pub project_memory_status: Option<&'a str>,
pub personality: Option<&'a PersonalityProfile>,
pub soul: Option<&'a SoulDoc>,
pub task: Option<&'a TaskContext>,
pub role: Option<&'a Role>,
pub mode: &'a AgentMode,
pub memory: Option<&'a str>,
pub user_profile: Option<&'a str>,
pub cwd: Option<&'a std::path::Path>,
pub learning_enabled: bool,
pub guardrail_profile: Option<GuardrailProfile>,
}
pub fn assemble(params: &AssembleParams<'_>) -> AssembledPrompt {
assemble_inner(params)
}
fn assemble_inner(p: &AssembleParams<'_>) -> AssembledPrompt {
let mut parts = Vec::new();
parts.push(identity_layer(
p.tools,
p.role,
p.mode,
p.learning_enabled,
p.personality,
p.soul,
));
parts.push(execution_policy_layer());
parts.push(environment_layer(p.cwd));
if !p.agents_md.is_empty() {
parts.push(agents_md_layer(p.agents_md));
}
if !p.skills.is_empty() {
parts.push(skills_layer(p.skills, p.mode));
}
if !p.facts.is_empty() {
parts.push(facts_layer(p.facts));
}
if let Some(status) = p.project_memory_status {
if !status.is_empty() {
parts.push(project_memory_status_layer(status));
}
}
if let Some(profile) = p.guardrail_profile {
parts.push(guardrails::guardrails_layer(profile));
}
if let Some(task) = p.task {
parts.push(task_layer(task));
parts.push(headless_execution_layer(task));
}
if let Some(mem) = p.memory {
if !mem.is_empty() {
parts.push(mem.to_string());
}
}
if let Some(user) = p.user_profile {
if !user.is_empty() {
parts.push(user.to_string());
}
}
let text = parts.join("\n\n");
let estimated_tokens = estimate_tokens(&text);
AssembledPrompt {
text,
estimated_tokens,
}
}
fn identity_layer(
tools: &ToolRegistry,
role: Option<&Role>,
mode: &AgentMode,
learning_enabled: bool,
personality: Option<&PersonalityProfile>,
soul: Option<&SoulDoc>,
) -> String {
let mut s = String::new();
if let Some(soul) = soul {
s.push_str(&soul_identity_text(&soul.content));
} else if let Some(personality) = personality {
s.push_str(&personality.identity.render_sentence());
} else {
s.push_str("You are imp, a coding agent.");
}
s.push_str("\n\nAvailable tools:\n");
let defs = match role {
Some(r) if r.readonly => tools.readonly_definitions(),
_ => tools.definitions_for_mode(mode),
};
for def in &defs {
s.push_str(&format!("- {}: {}\n", def.name, def.description));
}
if let Some(soul) = soul {
s.push_str("\n\nSoul:\n");
s.push_str(&soul.content);
s.push('\n');
} else if let Some(personality) = personality {
let working_style = working_style_lines(&personality.sliders);
if !working_style.is_empty() {
s.push_str("\nWorking style:\n");
for line in working_style {
s.push_str("- ");
s.push_str(line);
s.push('\n');
}
}
}
s.push_str("\nTool usage guide:\n");
s.push_str("- Use `shell` for search, file discovery, directory listing, builds, tests, scripts, package managers, and other shell-native tasks. Prefer `scan` for structural code understanding before broad text search when symbol relationships or code blocks matter.\n");
if defs.iter().any(|def| def.name == "git") {
s.push_str("- Use `git` for local repo/worktree operations; use `shell` for uncovered git commands.\n");
}
if defs.iter().any(|def| def.name == "mana") {
s.push_str("- Prefer the native `mana` tool over `shell` for mana operations when an equivalent action exists; use `shell` only for mana-adjacent shell work with no native mana action.\n");
}
s.push_str("- Use `read` to inspect a specific file with stable line-oriented output.\n");
s.push_str("- Use `scan` for structural code understanding and for extracting code at file:line, file:start-end, or file#symbol. Prefer it over `shell`+search when you need symbols, call sites, or coherent code blocks.\n");
s.push_str("- Use `edit` and `write` for file changes.\n");
s.push_str("- Use specialized tools like `mana`, `ask`, `web`, `extend`, and `recall` when the task calls for them. Use `recall` when you need to search past conversations.\n");
s.push_str("\nMana doctrine:\n");
s.push_str("- Mana is imp's substrate for explicit work. Represent work in mana whenever structure, verification, retries, dependencies, or handoff would help. Any mana unit must be detailed enough for another agent to execute cold without guesswork, even if you end up doing the work yourself.\n");
s.push_str("- Treat each unit description as an execution prompt.\n");
s.push_str("- Include current state, concrete steps, file paths with intent, edge cases, and a targeted verify command.\n");
s.push_str("- Update units with new context after failures; do not retry unchanged.\n");
s.push_str("- Mana is the durable project context — session memory is ephemeral, personal memory is global preferences; project plans, architecture decisions, verified facts, and implementation structure belong in mana, not in session history or memory.md.\n");
s.push_str("- During planning and design conversations, proactively externalize durable structure as it appears. When the conversation settles on a goal, decomposition, architecture direction, dependency, blocker, or follow-up that should survive the turn, create or update mana during the conversation.\n");
s.push_str("- Map durable planning artifacts deliberately: use an epic, child job, note, or decision based on the shape of the information, and keep facts reserved for verifiable claims.\n");
s.push_str("- Do not ask permission merely to capture plan artifacts in mana. Do ask before making consequential scope, architecture, destructive, or execution commitments on the user's behalf.\n");
s.push_str("- If durable planning state changed this turn, make the between-turn mana update before the substantive reply. Summarize the delta only when it adds value beyond what the mana tool or UI already made visible.\n");
s.push_str("- A future wiki layer at `.mana/wiki/` will hold synthesized project knowledge; for now, use mana facts for verifiable claims and mana units/notes for everything else that should survive the session.\n");
if let Some(role) = role {
if let Some(ref instructions) = role.instructions {
s.push('\n');
s.push_str(instructions);
s.push('\n');
}
}
if let Some(instructions) = mode.instructions() {
s.push('\n');
s.push_str(instructions);
s.push('\n');
}
if learning_enabled {
s.push('\n');
s.push_str(crate::learning::LEARNING_INSTRUCTIONS);
s.push('\n');
}
s
}
fn execution_policy_layer() -> String {
let mut s = String::from("Execution discipline:\n");
s.push_str("- Re-evaluate the user's intent on every new message before acting. Distinguish analysis, implementation, planning, review, and orchestration.\n");
s.push_str("- Never claim repository facts that you have not inspected in this session. Ground important claims in opened files, tool output, or explicit user-provided evidence.\n");
s.push_str("- If the user references a specific file, symbol, command, or error, inspect it before explaining or proposing changes.\n");
s.push_str("- For analysis-only requests, stay read-only unless the user asks for changes.\n");
s.push_str("- For implementation, prefer small, reversible changes and verify them with the smallest relevant checks before declaring success.\n");
s.push_str("- Do not treat failed commands, failed checks, or missing evidence as success. If blocked, report the exact blocker and the next useful action.\n");
s.push_str("- Before changing code, make sure you understand the relevant local context: inspect the files you will modify, nearby patterns, and any task-specific constraints already present in the repo.\n");
s.push_str("- Match the completion mode to the request. For diagnosis, provide findings, root cause, and concrete next-step options without making changes. For implementation, make the change, verify it, and summarize the evidence.\n");
s.push_str("- Treat task verify commands, failing tests, compiler errors, and tool errors as first-class evidence. Either resolve them or explain precisely why they remain blocked.\n");
s.push_str("- Prefer the smallest check that proves the work: targeted tests, focused builds, existence checks, or narrow validation commands before broad project-wide verification.\n");
s.push_str("- When uncertainty is consequential, ask one concise question instead of guessing. When uncertainty is local and low-risk, proceed and state the assumption briefly.\n");
s.push_str("- If prior attempts failed, do not retry the same plan unchanged. Use the new evidence to adapt the approach first.\n");
s.push_str("- Keep user-facing updates concise and evidence-oriented: what you inspected, what you changed, what you verified, and what remains.\n");
s.push_str("- When working from a mana unit, treat the unit as an execution contract: use its scope, verify gate, dependencies, and embedded context before broadening the search.\n");
s.push_str("- Prefer mana-native inspection and progress recording over ad hoc shell workflows when mana already models the work state.\n");
s.push_str("- For worker-style execution, keep planning lightweight and execution direct; use `mana update` to record discoveries and blockers instead of carrying them only in transient chat.\n");
s.push_str("- If a mana unit is underspecified, identify the exact missing context and either inspect the relevant artifacts or report the gap precisely rather than inventing missing requirements.\n");
s.push_str("- Treat chat as discussion and mana as the persistent external work and idea graph. When a conversation produces durable structure — plans, architecture, decomposition, migrations, blockers, dependencies, or follow-up work — represent that structure in mana during the conversation, not after it.\n");
s.push_str("- When durable planning state changes, update mana before the substantive reply when that durable state should become the source of truth for the next turn. Avoid restating mana-visible deltas verbosely when the tool output or UI already shows them.\n");
s.push_str("- Do not use mana only when work is large; use it whenever externalizing the plan is likely to improve execution quality, correctness, scope clarity, reviewability, or future continuation.\n");
s.push_str("- Prefer native mana actions over shell or direct file mutation for durable planning/work structure. Use append-style mana updates to keep the graph current during conversation.\n");
s.push_str("- If work spans more than one project or clearly belongs at the ecosystem layer, target root mana explicitly rather than relying on the nearest project scope.\n");
s
}
fn working_style_lines(sliders: &crate::personality::PersonalitySliders) -> Vec<&'static str> {
vec![
autonomy_line(sliders.autonomy),
verbosity_line(sliders.verbosity),
caution_line(sliders.caution),
warmth_line(sliders.warmth),
planning_depth_line(sliders.planning_depth),
"If you find yourself repeating the same action without progress, step back and try a different approach or ask the user for guidance.",
]
}
pub(crate) fn autonomy_line(band: PersonalityBand) -> &'static str {
match band {
PersonalityBand::VeryLow => {
"Ask for confirmation before making consequential decisions or larger changes."
}
PersonalityBand::Low => {
"Prefer confirmation before acting when requirements or consequences are unclear."
}
PersonalityBand::Medium => {
"Act on clear next steps, but ask when requirements are ambiguous."
}
PersonalityBand::High => {
"Act independently by default and ask when blocked, uncertain, or facing a consequential decision. Keep working until the task is fully resolved before yielding."
}
PersonalityBand::VeryHigh => {
"Take initiative aggressively on clear work and only ask when blocked or genuinely uncertain. Keep working until the task is fully resolved before yielding."
}
}
}
pub(crate) fn verbosity_line(band: PersonalityBand) -> &'static str {
match band {
PersonalityBand::VeryLow => "Keep responses terse and strongly action-oriented.",
PersonalityBand::Low => "Keep responses brief and focused on progress.",
PersonalityBand::Medium => {
"Be concise by default, but explain important tradeoffs when useful."
}
PersonalityBand::High => {
"Explain reasoning and tradeoffs when they help the user follow the work."
}
PersonalityBand::VeryHigh => {
"Give fuller explanations of reasoning, tradeoffs, and next steps."
}
}
}
pub(crate) fn caution_line(band: PersonalityBand) -> &'static str {
match band {
PersonalityBand::VeryLow => {
"Move forward with reasonable assumptions when the path is clear."
}
PersonalityBand::Low => "Favor progress over caution when risks are limited and local.",
PersonalityBand::Medium => "Balance steady progress with avoiding avoidable risk.",
PersonalityBand::High => {
"Prefer small, reversible changes and verify assumptions before riskier actions."
}
PersonalityBand::VeryHigh => {
"Be highly conservative with risky changes: verify assumptions and avoid acting on weak evidence."
}
}
}
pub(crate) fn warmth_line(band: PersonalityBand) -> &'static str {
match band {
PersonalityBand::VeryLow => "Use a direct, neutral tone.",
PersonalityBand::Low => "Use a clear, matter-of-fact tone.",
PersonalityBand::Medium => "Use a clear and calm tone.",
PersonalityBand::High => "Use a warm, supportive tone without becoming verbose.",
PersonalityBand::VeryHigh => {
"Use a notably warm, encouraging tone while staying useful and grounded."
}
}
}
pub(crate) fn planning_depth_line(band: PersonalityBand) -> &'static str {
match band {
PersonalityBand::VeryLow => "Favor immediate execution on the most obvious next step.",
PersonalityBand::Low => "Plan lightly, then move quickly into execution.",
PersonalityBand::Medium => "Plan briefly, then execute.",
PersonalityBand::High => "Think through structure and likely consequences before acting.",
PersonalityBand::VeryHigh => {
"Be methodical: think through structure, dependencies, and consequences before acting."
}
}
}
fn environment_layer(cwd: Option<&std::path::Path>) -> String {
let home = std::env::var("HOME").unwrap_or_default();
let cwd_str = cwd.map(|p| p.display().to_string()).unwrap_or_else(|| {
std::env::current_dir()
.map(|p| p.display().to_string())
.unwrap_or_default()
});
let os = std::env::consts::OS;
let today = {
use std::time::{SystemTime, UNIX_EPOCH};
let secs = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
let days = secs / 86400;
let (y, m, d) = days_to_ymd(days);
format!("{y}-{m:02}-{d:02}")
};
format!("Environment: cwd={cwd_str}, os={os}, home={home}, date={today}")
}
fn days_to_ymd(mut days: u64) -> (u64, u64, u64) {
days += 719_468;
let era = days / 146_097;
let doe = days - era * 146_097;
let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146_096) / 365;
let y = yoe + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let d = doy - (153 * mp + 2) / 5 + 1;
let m = if mp < 10 { mp + 3 } else { mp - 9 };
let y = if m <= 2 { y + 1 } else { y };
(y, m, d)
}
fn agents_md_layer(agents: &[AgentsMd]) -> String {
let mut s = String::from("# Project Context\n\n");
for agent in agents {
s.push_str(&agent.content);
s.push('\n');
}
s
}
fn skills_layer(skills: &[Skill], mode: &AgentMode) -> String {
let mut s = String::from("Available skills (use read to load when relevant):\n");
if let Some(trigger) = mana_skill_trigger(skills, mode) {
s.push_str(&format!("- Trigger: {trigger}\n"));
}
for skill in skills {
s.push_str(&format!(
"- {}: {} [{}]\n",
skill.name,
skill.description,
skill.path.display()
));
}
s
}
fn mana_skill_trigger(skills: &[Skill], mode: &AgentMode) -> Option<&'static str> {
let has_mana = skills.iter().any(|skill| skill.name == "mana");
let has_mana_basics = skills.iter().any(|skill| skill.name == "mana-basics");
match mode {
AgentMode::Full | AgentMode::Orchestrator | AgentMode::Planner => {
if has_mana {
Some("Load `mana` before writing or restructuring mana units for non-trivial work.")
} else {
None
}
}
AgentMode::Worker => {
if has_mana_basics {
Some(
"Load `mana-basics` before using worker-safe mana actions beyond a quick status check.",
)
} else if has_mana {
Some(
"Load `mana` before using worker-safe mana actions beyond a quick status check.",
)
} else {
None
}
}
AgentMode::Auditor => {
if has_mana_basics {
Some(
"Load `mana-basics` before inspecting mana state across multiple units or runs.",
)
} else if has_mana {
Some("Load `mana` before inspecting mana state across multiple units or runs.")
} else {
None
}
}
AgentMode::Reviewer => None,
}
}
fn facts_layer(facts: &[Fact]) -> String {
let mut s = String::from("Project facts:\n");
for fact in facts {
s.push_str(&format!(
"- \"{}\" [verified {}]\n",
fact.text, fact.verified_ago
));
}
s
}
fn project_memory_status_layer(status: &str) -> String {
status.to_string()
}
fn task_layer(task: &TaskContext) -> String {
let mut s = String::from("## Task\n");
s.push_str(&format!("Title: {}\n", task.title));
s.push_str(&format!("Description: {}\n", task.description));
if let Some(ref notes) = task.notes {
if !notes.trim().is_empty() {
s.push_str("Notes:\n");
s.push_str(notes);
s.push('\n');
}
}
if let Some(ref acceptance) = task.acceptance {
s.push_str("Acceptance:\n");
s.push_str(acceptance);
s.push('\n');
}
if let Some(ref verify) = task.verify {
s.push_str(&format!("Verify: {}\n", verify));
s.push_str("Treat the verify command as the primary completion check for this task.\n");
}
if !task.context_paths.is_empty() {
s.push_str("\n## Referenced files\n");
s.push_str("Use these declared file/path hints before broadening the search.\n");
for path in &task.context_paths {
s.push_str(&format!("- {}\n", path));
}
}
if !task.constraints.is_empty() {
s.push_str("\n## Constraints\n");
for constraint in &task.constraints {
s.push_str(&format!("- {}\n", constraint));
}
}
if !task.attempts.is_empty() {
s.push_str("\n## Previous attempts\n");
s.push_str("Do not repeat a failed approach unchanged; use the attempt history to adjust your plan.\n");
for attempt in &task.attempts {
s.push_str(&format!(
"Attempt {} ({}): {}\n",
attempt.number, attempt.outcome, attempt.summary
));
}
}
if !task.dependencies.is_empty() {
s.push_str("\n## Dependencies\n");
s.push_str("Respect dependency state when sequencing work; unresolved dependencies are potential blockers.\n");
for dep in &task.dependencies {
s.push_str(&format!(
"- {} ({}): {}\n",
dep.name, dep.status, dep.detail
));
}
}
if !task.decisions.is_empty() {
s.push_str("\n## Unresolved decisions\n");
s.push_str("These decisions block fully autonomous execution; resolve them or surface them clearly instead of guessing.\n");
for decision in &task.decisions {
s.push_str(&format!("- {}\n", decision));
}
}
s
}
fn headless_execution_layer(task: &TaskContext) -> String {
let mut s = String::from("## Headless execution contract\n");
s.push_str("- You are executing an explicit mana unit, not exploring broadly.\n");
s.push_str("- Treat the unit title, description, notes, acceptance criteria, and verify gate as the source of truth for scope and success.\n");
s.push_str("- Execute the assigned outcome before expanding into adjacent cleanup, refactors, or unrelated improvements.\n");
s.push_str("- Use explicit file references and prefilled context first before searching more broadly.\n");
s.push_str(
"- If the unit includes prior failed attempts, do not retry the same plan unchanged.\n",
);
s.push_str("- If dependency state or prerequisite decisions are unresolved, treat that as a blocker rather than improvising around it.\n");
s.push_str("- Keep progress updates concise and useful. Record meaningful discoveries, blockers, and revised plans with `mana update`.\n");
if task.verify.is_some() {
s.push_str("- If the verify command fails, either fix the issue or report the exact blocker. Do not claim completion anyway.\n");
}
s.push_str("- In batch-verify flows, treat your goal as leaving the unit ready for verify rather than assuming verify already passed.\n");
s.push_str(
"- Respect parent/child structure: finish this unit's outcome, not the whole feature.\n",
);
s
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
use std::sync::Arc;
use crate::personality::{
PersonaFocus, PersonaRole, PersonalityBand, PersonalityIdentity, PersonalityProfile,
PersonalitySliders, VoiceWord, WorkStyleWord,
};
use crate::resources::SoulDoc;
use crate::tools::{Tool, ToolContext, ToolOutput};
use async_trait::async_trait;
struct FakeTool {
name: &'static str,
description: &'static str,
readonly: bool,
}
#[async_trait]
impl Tool for FakeTool {
fn name(&self) -> &str {
self.name
}
fn label(&self) -> &str {
self.name
}
fn description(&self) -> &str {
self.description
}
fn parameters(&self) -> serde_json::Value {
serde_json::json!({"type": "object"})
}
fn is_readonly(&self) -> bool {
self.readonly
}
async fn execute(
&self,
_: &str,
_: serde_json::Value,
_: ToolContext,
) -> crate::Result<ToolOutput> {
Ok(ToolOutput::text("ok"))
}
}
fn make_registry() -> ToolRegistry {
let mut reg = ToolRegistry::new();
reg.register(Arc::new(FakeTool {
name: "read",
description: "Read file contents",
readonly: true,
}));
reg.register(Arc::new(FakeTool {
name: "write",
description: "Write content to a file",
readonly: false,
}));
reg.register(Arc::new(FakeTool {
name: "edit",
description: "Edit a file by replacing exact text",
readonly: false,
}));
reg.register(Arc::new(FakeTool {
name: "bash",
description: "Run shell commands",
readonly: false,
}));
reg
}
fn make_skill(name: &str, desc: &str, path: &str) -> Skill {
Skill {
name: name.into(),
description: desc.into(),
path: PathBuf::from(path),
}
}
fn make_agents_md(content: &str) -> AgentsMd {
AgentsMd {
path: PathBuf::from("/project/AGENTS.md"),
content: content.into(),
}
}
fn make_readonly_role() -> Role {
use crate::roles::ToolSet;
Role {
name: "reviewer".into(),
model: None,
thinking_level: None,
tool_set: ToolSet::All,
readonly: true,
instructions: Some("Review code carefully. Do not modify files.".into()),
max_turns: Some(10),
}
}
fn make_worker_role() -> Role {
use crate::roles::ToolSet;
Role {
name: "worker".into(),
model: None,
thinking_level: None,
tool_set: ToolSet::All,
readonly: false,
instructions: None,
max_turns: None,
}
}
fn make_personality() -> PersonalityProfile {
PersonalityProfile {
identity: PersonalityIdentity {
name: "Nova".into(),
work_style: WorkStyleWord::Careful,
voice: VoiceWord::Direct,
focus: PersonaFocus::Research,
role: PersonaRole::Assistant,
},
sliders: PersonalitySliders {
autonomy: PersonalityBand::Low,
verbosity: PersonalityBand::Medium,
caution: PersonalityBand::VeryHigh,
warmth: PersonalityBand::High,
planning_depth: PersonalityBand::VeryLow,
},
}
}
fn test_assemble(
tools: &ToolRegistry,
agents_md: &[AgentsMd],
skills: &[Skill],
facts: &[Fact],
personality: Option<&PersonalityProfile>,
task: Option<&TaskContext>,
role: Option<&Role>,
) -> AssembledPrompt {
assemble(&AssembleParams {
tools,
agents_md,
skills,
facts,
project_memory_status: None,
personality,
soul: None,
task,
role,
mode: &AgentMode::Full,
memory: None,
user_profile: None,
cwd: None,
learning_enabled: false,
guardrail_profile: None,
})
}
#[test]
fn system_prompt_includes_execution_policy_layer() {
let reg = make_registry();
let result = test_assemble(®, &[], &[], &[], None, None, None);
assert!(result.text.contains("Execution discipline:"));
assert!(result
.text
.contains("Never claim repository facts that you have not inspected in this session."));
assert!(result.text.contains(
"For analysis-only requests, stay read-only unless the user asks for changes."
));
}
#[test]
fn system_prompt_includes_conversation_time_mana_planning_doctrine() {
let reg = make_registry();
let result = test_assemble(®, &[], &[], &[], None, None, None);
assert!(result.text.contains(
"During planning and design conversations, proactively externalize durable structure as it appears."
));
assert!(result.text.contains("epic, child job, note, or decision"));
assert!(result
.text
.contains("Do not ask permission merely to capture plan artifacts in mana."));
assert!(result.text.contains(
"If durable planning state changed this turn, make the between-turn mana update before the substantive reply"
));
assert!(result.text.contains(
"Summarize the delta only when it adds value beyond what the mana tool or UI already made visible."
));
assert_eq!(
result
.text
.matches("between-turn mana update before the substantive reply")
.count(),
1,
"between-turn mana update guidance should appear once"
);
assert!(!result
.text
.contains("include a concise mana delta summary in the response"));
}
#[test]
fn system_prompt_identity_includes_all_tools() {
let reg = make_registry();
let result = test_assemble(®, &[], &[], &[], None, None, None);
assert!(result.text.contains("You are imp, a coding agent."));
assert!(result.text.contains("- read: Read file contents"));
assert!(result.text.contains("- write: Write content to a file"));
assert!(result
.text
.contains("- edit: Edit a file by replacing exact text"));
assert!(result.text.contains("- bash: Run shell commands"));
}
#[test]
fn system_prompt_mana_guidance_prefers_native_tool_when_available() {
let mut reg = make_registry();
reg.register(Arc::new(FakeTool {
name: "mana",
description: "Manage mana work natively",
readonly: false,
}));
let result = test_assemble(®, &[], &[], &[], None, None, None);
assert!(result.text.contains(
"Prefer the native `mana` tool over `bash` for mana operations when an equivalent action exists"
));
}
#[test]
fn system_prompt_mana_guidance_omitted_without_mana_tool() {
let reg = make_registry();
let result = test_assemble(®, &[], &[], &[], None, None, None);
assert!(!result.text.contains(
"Prefer the native `mana` tool over `bash` for mana operations when an equivalent action exists"
));
}
#[test]
fn system_prompt_no_mana_guidance_or_delegation_in_prompt() {
let mut reg = make_registry();
reg.register(Arc::new(FakeTool {
name: "bash",
description: "Run shell commands",
readonly: false,
}));
reg.register(Arc::new(FakeTool {
name: "mana",
description: "Manage mana work",
readonly: false,
}));
let result = test_assemble(®, &[], &[], &[], None, None, None);
assert!(
!result.text.contains("Mana guidance:"),
"mana guidance block should not appear in system prompt"
);
assert!(
!result.text.contains("## Mana delegation"),
"delegation guidance should not appear in system prompt"
);
}
#[test]
fn system_prompt_identity_only_when_all_layers_empty() {
let reg = make_registry();
let result = test_assemble(®, &[], &[], &[], None, None, None);
assert!(result.text.contains("You are imp"));
assert!(!result.text.contains("# Project Context"));
assert!(!result.text.contains("Available skills"));
assert!(!result.text.contains("Project facts"));
assert!(!result.text.contains("## Task"));
}
#[test]
fn system_prompt_uses_personality_identity_sentence() {
let reg = make_registry();
let personality = make_personality();
let result = test_assemble(®, &[], &[], &[], Some(&personality), None, None);
assert!(result
.text
.contains("You are Nova, a careful, direct, research assistant."));
}
#[test]
fn system_prompt_renders_personality_working_style_block() {
let reg = make_registry();
let personality = make_personality();
let result = test_assemble(®, &[], &[], &[], Some(&personality), None, None);
assert!(result.text.contains("Working style:"));
assert!(result.text.contains(
"Prefer confirmation before acting when requirements or consequences are unclear."
));
assert!(result
.text
.contains("Be concise by default, but explain important tradeoffs when useful."));
assert!(result.text.contains(
"Be highly conservative with risky changes: verify assumptions and avoid acting on weak evidence."
));
assert!(result
.text
.contains("Use a warm, supportive tone without becoming verbose."));
assert!(result
.text
.contains("Favor immediate execution on the most obvious next step."));
}
#[test]
fn system_prompt_prefers_soul_over_personality_profile() {
let reg = make_registry();
let personality = make_personality();
let soul = SoulDoc {
path: PathBuf::from("/tmp/soul.md"),
content: "# Soul\n\nYou are Sol, a tuned and reflective collaborator.\n\n## Tunables\n\n- Autonomy: Act independently by default.\n".into(),
};
let result = assemble(&AssembleParams {
tools: ®,
agents_md: &[],
skills: &[],
facts: &[],
project_memory_status: None,
personality: Some(&personality),
soul: Some(&soul),
task: None,
role: None,
mode: &AgentMode::Full,
memory: None,
user_profile: None,
cwd: None,
learning_enabled: false,
guardrail_profile: None,
});
assert!(result
.text
.contains("You are Sol, a tuned and reflective collaborator."));
assert!(result.text.contains("Soul:"));
assert!(result.text.contains("## Tunables"));
assert!(!result.text.contains("Working style:"));
}
#[test]
fn system_prompt_without_soul_keeps_personality_working_style_block() {
let reg = make_registry();
let personality = make_personality();
let result = test_assemble(®, &[], &[], &[], Some(&personality), None, None);
assert!(result.text.contains("Working style:"));
}
#[test]
fn system_prompt_agents_md_included_verbatim() {
let reg = make_registry();
let agents = vec![make_agents_md("# Rules\n\nUse snake_case everywhere.")];
let result = test_assemble(®, &agents, &[], &[], None, None, None);
assert!(result.text.contains("# Project Context"));
assert!(result
.text
.contains("# Rules\n\nUse snake_case everywhere."));
}
#[test]
fn system_prompt_multiple_agents_md_concatenated() {
let reg = make_registry();
let agents = vec![
make_agents_md("Global rules here."),
make_agents_md("Project rules here."),
];
let result = test_assemble(®, &agents, &[], &[], None, None, None);
assert!(result.text.contains("Global rules here."));
assert!(result.text.contains("Project rules here."));
}
#[test]
fn system_prompt_empty_agents_md_skipped() {
let reg = make_registry();
let result = test_assemble(®, &[], &[], &[], None, None, None);
assert!(!result.text.contains("# Project Context"));
}
#[test]
fn system_prompt_skills_listed_with_paths() {
let reg = make_registry();
let skills = vec![
make_skill(
"rust",
"Conventions for Rust code",
"/home/.imp/skills/rust/SKILL.md",
),
make_skill(
"testing",
"Write and review tests",
"/home/.imp/skills/testing/SKILL.md",
),
];
let result = test_assemble(®, &[], &skills, &[], None, None, None);
assert!(result
.text
.contains("Available skills (use read to load when relevant):"));
assert!(result
.text
.contains("- rust: Conventions for Rust code [/home/.imp/skills/rust/SKILL.md]"));
assert!(result
.text
.contains("- testing: Write and review tests [/home/.imp/skills/testing/SKILL.md]"));
}
#[test]
fn system_prompt_includes_mode_aware_mana_skill_trigger() {
let reg = make_registry();
let skills = vec![make_skill(
"mana",
"Coordinate explicit work through mana",
"/home/.imp/skills/mana/SKILL.md",
)];
let result = assemble(&AssembleParams {
tools: ®,
agents_md: &[],
skills: &skills,
facts: &[],
project_memory_status: None,
personality: None,
soul: None,
task: None,
role: None,
mode: &AgentMode::Planner,
memory: None,
user_profile: None,
cwd: None,
learning_enabled: false,
guardrail_profile: None,
});
assert!(result.text.contains("- Trigger:"));
assert!(result.text.contains(
"Load `mana` before writing or restructuring mana units for non-trivial work."
));
}
#[test]
fn system_prompt_orchestrator_uses_same_mana_trigger() {
let reg = make_registry();
let skills = vec![make_skill(
"mana",
"Coordinate explicit work through mana",
"/home/.imp/skills/mana/SKILL.md",
)];
let result = assemble(&AssembleParams {
tools: ®,
agents_md: &[],
skills: &skills,
facts: &[],
project_memory_status: None,
personality: None,
soul: None,
task: None,
role: None,
mode: &AgentMode::Orchestrator,
memory: None,
user_profile: None,
cwd: None,
learning_enabled: false,
guardrail_profile: None,
});
assert!(result.text.contains(
"Load `mana` before writing or restructuring mana units for non-trivial work."
));
}
#[test]
fn system_prompt_worker_prefers_mana_basics_trigger() {
let reg = make_registry();
let skills = vec![
make_skill(
"mana",
"Coordinate multi-step work through mana",
"/home/.imp/skills/mana/SKILL.md",
),
make_skill(
"mana-basics",
"Use native mana actions safely and efficiently",
"/home/.imp/skills/mana-basics/SKILL.md",
),
];
let result = assemble(&AssembleParams {
tools: ®,
agents_md: &[],
skills: &skills,
facts: &[],
project_memory_status: None,
personality: None,
soul: None,
task: None,
role: None,
mode: &AgentMode::Worker,
memory: None,
user_profile: None,
cwd: None,
learning_enabled: false,
guardrail_profile: None,
});
assert!(result.text.contains(
"Load `mana-basics` before using worker-safe mana actions beyond a quick status check."
));
}
#[test]
fn system_prompt_omits_mana_trigger_without_mana_skill() {
let reg = make_registry();
let skills = vec![make_skill(
"rust",
"Conventions for Rust code",
"/home/.imp/skills/rust/SKILL.md",
)];
let result = assemble(&AssembleParams {
tools: ®,
agents_md: &[],
skills: &skills,
facts: &[],
project_memory_status: None,
personality: None,
soul: None,
task: None,
role: None,
mode: &AgentMode::Planner,
memory: None,
user_profile: None,
cwd: None,
learning_enabled: false,
guardrail_profile: None,
});
assert!(!result.text.contains("- Trigger:"));
}
#[test]
fn system_prompt_reviewer_mode_omits_mana_trigger() {
let reg = make_registry();
let skills = vec![make_skill(
"mana",
"Coordinate multi-step work through mana",
"/home/.imp/skills/mana/SKILL.md",
)];
let result = assemble(&AssembleParams {
tools: ®,
agents_md: &[],
skills: &skills,
facts: &[],
project_memory_status: None,
personality: None,
soul: None,
task: None,
role: None,
mode: &AgentMode::Reviewer,
memory: None,
user_profile: None,
cwd: None,
learning_enabled: false,
guardrail_profile: None,
});
assert!(!result.text.contains("- Trigger:"));
}
#[test]
fn system_prompt_empty_skills_skipped() {
let reg = make_registry();
let result = test_assemble(®, &[], &[], &[], None, None, None);
assert!(!result.text.contains("Available skills"));
}
#[test]
fn system_prompt_facts_included() {
let reg = make_registry();
let facts = vec![
Fact {
text: "Uses JWT for auth".into(),
verified_ago: "2h ago".into(),
},
Fact {
text: "Test suite requires Docker".into(),
verified_ago: "1d ago".into(),
},
];
let result = test_assemble(®, &[], &[], &facts, None, None, None);
assert!(result.text.contains("Project facts:"));
assert!(result
.text
.contains("\"Uses JWT for auth\" [verified 2h ago]"));
assert!(result
.text
.contains("\"Test suite requires Docker\" [verified 1d ago]"));
}
#[test]
fn system_prompt_empty_facts_skipped() {
let reg = make_registry();
let result = test_assemble(®, &[], &[], &[], None, None, None);
assert!(!result.text.contains("Project facts"));
}
#[test]
fn system_prompt_project_memory_status_included() {
let reg = make_registry();
let result = assemble(&AssembleParams {
tools: ®,
agents_md: &[],
skills: &[],
facts: &[],
project_memory_status: Some(
"Project memory status:\nWarnings:\n- STALE: \"Lockfile drift\"\n\nWorking on:\n- [12] Refresh auth flow",
),
personality: None,
soul: None,
task: None,
role: None,
mode: &AgentMode::Full,
memory: None,
user_profile: None,
cwd: None,
learning_enabled: false,
guardrail_profile: None,
});
assert!(result.text.contains("Project memory status:"));
assert!(result.text.contains("Warnings:"));
assert!(result.text.contains("Working on:"));
}
#[test]
fn system_prompt_project_memory_status_empty_string_is_skipped() {
let reg = make_registry();
let result = assemble(&AssembleParams {
tools: ®,
agents_md: &[],
skills: &[],
facts: &[],
project_memory_status: Some(""),
personality: None,
soul: None,
task: None,
role: None,
mode: &AgentMode::Full,
memory: None,
user_profile: None,
cwd: None,
learning_enabled: false,
guardrail_profile: None,
});
assert!(!result.text.contains("Project memory status:"));
}
#[test]
fn system_prompt_project_memory_status_included_separately_from_facts() {
let reg = make_registry();
let facts = vec![Fact {
text: "Uses JWT for auth".into(),
verified_ago: "2h ago".into(),
}];
let status =
"Project memory status:\nWarnings:\n- stale fact\n\nWorking on:\n- [7] Fix auth flow";
let result = assemble(&AssembleParams {
tools: ®,
agents_md: &[],
skills: &[],
facts: &facts,
project_memory_status: Some(status),
personality: None,
soul: None,
task: None,
role: None,
mode: &AgentMode::Full,
memory: None,
user_profile: None,
cwd: None,
learning_enabled: false,
guardrail_profile: None,
});
let facts_pos = result.text.find("Project facts:").unwrap();
let status_pos = result.text.find("Project memory status:").unwrap();
assert!(result
.text
.contains("\"Uses JWT for auth\" [verified 2h ago]"));
assert!(result.text.contains("Warnings:"));
assert!(result.text.contains("Working on:"));
assert!(facts_pos < status_pos);
}
#[test]
fn system_prompt_task_context_included() {
let reg = make_registry();
let task = TaskContext {
title: "Fix the failing auth test".into(),
description: "The JWT validation test panics on expired tokens".into(),
acceptance: None,
verify: Some("cargo test auth::jwt_test".into()),
notes: None,
attempts: vec![],
dependencies: vec![],
decisions: vec![],
context_paths: vec![],
constraints: vec![],
};
let result = test_assemble(®, &[], &[], &[], None, Some(&task), None);
assert!(result.text.contains("## Task"));
assert!(result.text.contains("Title: Fix the failing auth test"));
assert!(result
.text
.contains("Description: The JWT validation test panics"));
assert!(result.text.contains("Verify: cargo test auth::jwt_test"));
assert!(result
.text
.contains("Treat the verify command as the primary completion check for this task."));
}
#[test]
fn system_prompt_task_with_attempts() {
let reg = make_registry();
let task = TaskContext {
title: "Fix bug".into(),
description: "Something is broken".into(),
acceptance: None,
verify: None,
notes: None,
attempts: vec![
Attempt {
number: 1,
outcome: "failed".into(),
summary: "Tried X, got error Y".into(),
},
Attempt {
number: 2,
outcome: "failed".into(),
summary: "Tried Z, still broken".into(),
},
],
dependencies: vec![],
decisions: vec![],
context_paths: vec![],
constraints: vec![],
};
let result = test_assemble(®, &[], &[], &[], None, Some(&task), None);
assert!(result.text.contains("## Previous attempts"));
assert!(result.text.contains(
"Do not repeat a failed approach unchanged; use the attempt history to adjust your plan."
));
assert!(result
.text
.contains("Attempt 1 (failed): Tried X, got error Y"));
assert!(result
.text
.contains("Attempt 2 (failed): Tried Z, still broken"));
}
#[test]
fn system_prompt_task_with_dependencies() {
let reg = make_registry();
let task = TaskContext {
title: "Implement feature".into(),
description: "New feature".into(),
acceptance: None,
verify: None,
notes: None,
attempts: vec![],
dependencies: vec![Dependency {
name: "Schema types".into(),
status: "completed".into(),
detail: "defined in src/schema.rs".into(),
}],
decisions: vec![],
context_paths: vec![],
constraints: vec![],
};
let result = test_assemble(®, &[], &[], &[], None, Some(&task), None);
assert!(result.text.contains("## Dependencies"));
assert!(result.text.contains(
"Respect dependency state when sequencing work; unresolved dependencies are potential blockers."
));
assert!(result
.text
.contains("- Schema types (completed): defined in src/schema.rs"));
}
#[test]
fn system_prompt_task_with_notes_and_context_paths() {
let reg = make_registry();
let task = TaskContext {
title: "Fix auth".into(),
description: "Tighten token validation".into(),
acceptance: None,
verify: Some("cargo test auth".into()),
notes: Some("Prefer touching only auth paths unless necessary".into()),
attempts: vec![],
dependencies: vec![],
decisions: vec![],
context_paths: vec!["src/auth.rs".into(), "tests/auth.rs".into()],
constraints: vec![
"Scope changes to auth-related files unless broader edits are necessary".into(),
],
};
let result = test_assemble(®, &[], &[], &[], None, Some(&task), None);
assert!(result.text.contains("Notes:"));
assert!(result
.text
.contains("Prefer touching only auth paths unless necessary"));
assert!(result.text.contains("## Referenced files"));
assert!(result.text.contains("- src/auth.rs"));
assert!(result.text.contains("- tests/auth.rs"));
assert!(result.text.contains("## Constraints"));
assert!(result
.text
.contains("Scope changes to auth-related files unless broader edits are necessary"));
}
#[test]
fn system_prompt_no_task_skips_layer5() {
let reg = make_registry();
let result = test_assemble(®, &[], &[], &[], None, None, None);
assert!(!result.text.contains("## Task"));
}
#[test]
fn system_prompt_task_without_verify_omits_verify_line() {
let reg = make_registry();
let task = TaskContext {
title: "Do something".into(),
description: "Details here".into(),
acceptance: None,
verify: None,
notes: None,
attempts: vec![],
dependencies: vec![],
decisions: vec![],
context_paths: vec![],
constraints: vec![],
};
let result = test_assemble(®, &[], &[], &[], None, Some(&task), None);
assert!(result.text.contains("Title: Do something"));
assert!(!result.text.contains("Verify:"));
}
#[test]
fn system_prompt_readonly_role_filters_tools() {
let reg = make_registry();
let role = make_readonly_role();
let result = test_assemble(®, &[], &[], &[], None, None, Some(&role));
assert!(result.text.contains("- read:"));
assert!(!result.text.contains("- write:"));
assert!(!result.text.contains("- edit:"));
}
#[test]
fn system_prompt_role_instructions_appended() {
let reg = make_registry();
let role = make_readonly_role();
let result = test_assemble(®, &[], &[], &[], None, None, Some(&role));
assert!(result
.text
.contains("Review code carefully. Do not modify files."));
}
#[test]
fn system_prompt_worker_role_includes_all_tools() {
let reg = make_registry();
let role = make_worker_role();
let result = test_assemble(®, &[], &[], &[], None, None, Some(&role));
assert!(result.text.contains("- read:"));
assert!(result.text.contains("- write:"));
assert!(result.text.contains("- edit:"));
assert!(result.text.contains("- bash:"));
}
#[test]
fn system_prompt_no_role_instructions_when_none() {
let reg = make_registry();
let role = make_worker_role();
let result = test_assemble(®, &[], &[], &[], None, None, Some(&role));
let lines: Vec<&str> = result.text.lines().collect();
let after_tools = lines.iter().position(|l| l.starts_with("- bash:")).unwrap();
let remaining = &lines[after_tools + 1..];
let next_content = remaining.iter().find(|l| !l.is_empty());
assert!(next_content.is_none() || !next_content.unwrap().contains("Review"));
}
#[test]
fn system_prompt_tracks_estimated_tokens() {
let reg = make_registry();
let result = test_assemble(®, &[], &[], &[], None, None, None);
assert!(result.estimated_tokens > 0);
assert!(result.estimated_tokens >= 10);
}
#[test]
fn system_prompt_more_layers_means_more_tokens() {
let reg = make_registry();
let minimal = test_assemble(®, &[], &[], &[], None, None, None);
let agents = vec![make_agents_md(
"Lots of project context here with many words.",
)];
let skills = vec![make_skill(
"rust",
"Rust conventions",
"/skills/rust/SKILL.md",
)];
let facts = vec![Fact {
text: "Uses Postgres".into(),
verified_ago: "1h ago".into(),
}];
let full = test_assemble(®, &agents, &skills, &facts, None, None, None);
assert!(
full.estimated_tokens > minimal.estimated_tokens,
"full ({}) should have more tokens than minimal ({})",
full.estimated_tokens,
minimal.estimated_tokens
);
}
#[test]
fn system_prompt_all_layers_present() {
let reg = make_registry();
let agents = vec![make_agents_md("Be concise.")];
let skills = vec![make_skill(
"rust",
"Rust code conventions",
"/skills/rust/SKILL.md",
)];
let facts = vec![Fact {
text: "Uses SQLite".into(),
verified_ago: "30m ago".into(),
}];
let task = TaskContext {
title: "Add caching".into(),
description: "Add Redis caching layer".into(),
acceptance: None,
verify: Some("cargo test cache".into()),
notes: None,
attempts: vec![Attempt {
number: 1,
outcome: "failed".into(),
summary: "Wrong key format".into(),
}],
dependencies: vec![Dependency {
name: "Config".into(),
status: "done".into(),
detail: "src/config.rs".into(),
}],
decisions: vec![],
context_paths: vec![],
constraints: vec![],
};
let result = test_assemble(®, &agents, &skills, &facts, None, Some(&task), None);
let identity_pos = result.text.find("You are imp").unwrap();
let policy_pos = result.text.find("Execution discipline").unwrap();
let context_pos = result.text.find("# Project Context").unwrap();
let skills_pos = result.text.find("Available skills").unwrap();
let facts_pos = result.text.find("Project facts").unwrap();
let task_pos = result.text.find("## Task").unwrap();
assert!(identity_pos < policy_pos, "identity before policy");
assert!(policy_pos < context_pos, "policy before context");
assert!(context_pos < skills_pos, "context before skills");
assert!(skills_pos < facts_pos, "skills before facts");
assert!(facts_pos < task_pos, "facts before task");
}
#[test]
fn system_prompt_display_impl() {
let reg = make_registry();
let result = test_assemble(®, &[], &[], &[], None, None, None);
let displayed = format!("{result}");
assert_eq!(displayed, result.text);
}
#[test]
fn system_prompt_memory_included() {
let reg = make_registry();
let mem = "══════════════════\nMEMORY [50% — 100/200]\n══════════════════\nUser runs macOS";
let result = assemble(&AssembleParams {
tools: ®,
agents_md: &[],
skills: &[],
facts: &[],
project_memory_status: None,
personality: None,
soul: None,
task: None,
role: None,
mode: &AgentMode::Full,
memory: Some(mem),
user_profile: None,
cwd: None,
learning_enabled: false,
guardrail_profile: None,
});
assert!(result.text.contains("MEMORY"));
assert!(result.text.contains("User runs macOS"));
}
#[test]
fn system_prompt_user_profile_included() {
let reg = make_registry();
let user =
"══════════════════\nUSER PROFILE [30% — 42/140]\n══════════════════\nPrefers concise";
let result = assemble(&AssembleParams {
tools: ®,
agents_md: &[],
skills: &[],
facts: &[],
project_memory_status: None,
personality: None,
soul: None,
task: None,
role: None,
mode: &AgentMode::Full,
memory: None,
user_profile: Some(user),
cwd: None,
learning_enabled: false,
guardrail_profile: None,
});
assert!(result.text.contains("USER PROFILE"));
assert!(result.text.contains("Prefers concise"));
}
#[test]
fn system_prompt_empty_memory_skipped() {
let reg = make_registry();
let result = assemble(&AssembleParams {
tools: ®,
agents_md: &[],
skills: &[],
facts: &[],
project_memory_status: None,
personality: None,
soul: None,
task: None,
role: None,
mode: &AgentMode::Full,
memory: Some(""),
user_profile: Some(""),
cwd: None,
learning_enabled: false,
guardrail_profile: None,
});
assert!(!result.text.contains("MEMORY"));
assert!(!result.text.contains("USER PROFILE"));
}
#[test]
fn system_prompt_memory_after_all_other_layers() {
let reg = make_registry();
let agents = vec![make_agents_md("Project context.")];
let skills = vec![make_skill("rust", "Rust", "/skills/rust/SKILL.md")];
let facts = vec![Fact {
text: "Uses SQLite".into(),
verified_ago: "1h".into(),
}];
let task = TaskContext {
title: "Fix bug".into(),
description: "Broken".into(),
acceptance: None,
verify: None,
notes: None,
attempts: vec![],
dependencies: vec![],
decisions: vec![],
context_paths: vec![],
constraints: vec![],
};
let mem = "══════\nMEMORY [50%]\n══════\nSome fact";
let result = assemble(&AssembleParams {
tools: ®,
agents_md: &agents,
skills: &skills,
facts: &facts,
project_memory_status: None,
personality: None,
soul: None,
task: Some(&task),
role: None,
mode: &AgentMode::Full,
memory: Some(mem),
user_profile: None,
cwd: None,
learning_enabled: false,
guardrail_profile: None,
});
let identity_pos = result.text.find("You are imp").unwrap();
let context_pos = result.text.find("# Project Context").unwrap();
let facts_pos = result.text.find("Project facts").unwrap();
let task_pos = result.text.find("## Task").unwrap();
let memory_pos = result.text.find("MEMORY").unwrap();
assert!(identity_pos < context_pos);
assert!(context_pos < facts_pos);
assert!(facts_pos < task_pos);
assert!(task_pos < memory_pos, "memory should come after task");
}
}