1pub const SYSTEM_DEFAULT: &str = include_str!("../prompts/system_default.md");
31
32pub const CONTINUATION: &str = include_str!("../prompts/continuation.md");
35
36pub const SUBAGENT_EXPLORE: &str = include_str!("../prompts/subagent_explore.md");
42
43pub const SUBAGENT_PLAN: &str = include_str!("../prompts/subagent_plan.md");
45
46pub const SUBAGENT_TITLE: &str = include_str!("../prompts/subagent_title.md");
48
49pub const SUBAGENT_SUMMARY: &str = include_str!("../prompts/subagent_summary.md");
51
52pub const CONTEXT_COMPACT: &str = include_str!("../prompts/context_compact.md");
58
59pub const CONTEXT_SUMMARY_PREFIX: &str = include_str!("../prompts/context_summary_prefix.md");
61
62#[allow(dead_code)]
68pub const TITLE_GENERATE: &str = include_str!("../prompts/title_generate.md");
69
70pub const LLM_PLAN_SYSTEM: &str = include_str!("../prompts/llm_plan_system.md");
76
77pub const LLM_GOAL_EXTRACT_SYSTEM: &str = include_str!("../prompts/llm_goal_extract_system.md");
79
80pub const LLM_GOAL_CHECK_SYSTEM: &str = include_str!("../prompts/llm_goal_check_system.md");
82
83pub const PLAN_EXECUTE_GOAL: &str = include_str!("../prompts/plan_execute_goal.md");
89
90pub const PLAN_EXECUTE_STEP: &str = include_str!("../prompts/plan_execute_step.md");
92
93pub const PLAN_FALLBACK_STEP: &str = include_str!("../prompts/plan_fallback_step.md");
95
96pub const PLAN_PARALLEL_RESULTS: &str = include_str!("../prompts/plan_parallel_results.md");
98
99pub const TEAM_LEAD: &str = include_str!("../prompts/team_lead.md");
101
102pub const TEAM_REVIEWER: &str = include_str!("../prompts/team_reviewer.md");
104
105pub const SKILLS_CATALOG_HEADER: &str = include_str!("../prompts/skills_catalog_header.md");
107
108pub const BTW_SYSTEM: &str = include_str!("../prompts/btw_system.md");
117
118#[derive(Debug, Clone, Default)]
138pub struct SystemPromptSlots {
139 pub role: Option<String>,
144
145 pub guidelines: Option<String>,
149
150 pub response_style: Option<String>,
154
155 pub extra: Option<String>,
160}
161
162const DEFAULT_ROLE_LINE: &str = include_str!("../prompts/system_default_role_line.md");
164
165const DEFAULT_RESPONSE_FORMAT: &str = include_str!("../prompts/system_default_response_format.md");
167
168impl SystemPromptSlots {
169 pub fn build(&self) -> String {
174 let mut parts: Vec<String> = Vec::new();
175
176 let system_default = SYSTEM_DEFAULT.replace('\r', "");
179 let default_role_line = DEFAULT_ROLE_LINE.replace('\r', "");
180 let default_response_format = DEFAULT_RESPONSE_FORMAT.replace('\r', "");
181
182 let core = if let Some(ref role) = self.role {
184 let custom_role = format!(
185 "{}. You operate in an agentic loop: you\nthink, use tools, observe results, and keep working until the task is fully complete.",
186 role.trim_end_matches('.')
187 );
188 system_default.replace(&default_role_line, &custom_role)
189 } else {
190 system_default
191 };
192
193 let core = if self.response_style.is_some() {
195 core.replace(&default_response_format, "")
196 .trim_end()
197 .to_string()
198 } else {
199 core.trim_end().to_string()
200 };
201
202 parts.push(core);
203
204 if let Some(ref style) = self.response_style {
206 parts.push(format!("## Response Format\n\n{}", style));
207 }
208
209 if let Some(ref guidelines) = self.guidelines {
211 parts.push(format!("## Guidelines\n\n{}", guidelines));
212 }
213
214 if let Some(ref extra) = self.extra {
216 parts.push(extra.clone());
217 }
218
219 parts.join("\n\n")
220 }
221
222 pub fn from_legacy(prompt: String) -> Self {
227 Self {
228 extra: Some(prompt),
229 ..Default::default()
230 }
231 }
232
233 pub fn is_empty(&self) -> bool {
235 self.role.is_none()
236 && self.guidelines.is_none()
237 && self.response_style.is_none()
238 && self.extra.is_none()
239 }
240}
241
242pub fn render(template: &str, vars: &[(&str, &str)]) -> String {
248 let mut result = template.to_string();
249 for (key, value) in vars {
250 result = result.replace(&format!("{{{}}}", key), value);
251 }
252 result
253}
254
255#[cfg(test)]
256mod tests {
257 use super::*;
258
259 #[test]
260 fn test_all_prompts_loaded() {
261 assert!(!SYSTEM_DEFAULT.is_empty());
263 assert!(!CONTINUATION.is_empty());
264 assert!(!SUBAGENT_EXPLORE.is_empty());
265 assert!(!SUBAGENT_PLAN.is_empty());
266 assert!(!SUBAGENT_TITLE.is_empty());
267 assert!(!SUBAGENT_SUMMARY.is_empty());
268 assert!(!CONTEXT_COMPACT.is_empty());
269 assert!(!TITLE_GENERATE.is_empty());
270 assert!(!LLM_PLAN_SYSTEM.is_empty());
271 assert!(!LLM_GOAL_EXTRACT_SYSTEM.is_empty());
272 assert!(!LLM_GOAL_CHECK_SYSTEM.is_empty());
273 assert!(!TEAM_LEAD.is_empty());
274 assert!(!TEAM_REVIEWER.is_empty());
275 assert!(!SKILLS_CATALOG_HEADER.is_empty());
276 assert!(!BTW_SYSTEM.is_empty());
277 assert!(!PLAN_EXECUTE_GOAL.is_empty());
278 assert!(!PLAN_EXECUTE_STEP.is_empty());
279 assert!(!PLAN_FALLBACK_STEP.is_empty());
280 assert!(!PLAN_PARALLEL_RESULTS.is_empty());
281 }
282
283 #[test]
284 fn test_render_template() {
285 let result = render(
286 PLAN_EXECUTE_GOAL,
287 &[("goal", "Build app"), ("steps", "1. Init")],
288 );
289 assert!(result.contains("Build app"));
290 assert!(!result.contains("{goal}"));
291 }
292
293 #[test]
294 fn test_render_multiple_placeholders() {
295 let template = "Goal: {goal}\nCriteria: {criteria}\nState: {current_state}";
296 let result = render(
297 template,
298 &[
299 ("goal", "Build a REST API"),
300 ("criteria", "- Endpoint works\n- Tests pass"),
301 ("current_state", "API is deployed"),
302 ],
303 );
304 assert!(result.contains("Build a REST API"));
305 assert!(result.contains("Endpoint works"));
306 assert!(result.contains("API is deployed"));
307 }
308
309 #[test]
310 fn test_subagent_prompts_contain_guidelines() {
311 assert!(SUBAGENT_EXPLORE.contains("Guidelines"));
312 assert!(SUBAGENT_EXPLORE.contains("read-only"));
313 assert!(SUBAGENT_PLAN.contains("Guidelines"));
314 assert!(SUBAGENT_PLAN.contains("read-only"));
315 }
316
317 #[test]
318 fn test_context_summary_prefix() {
319 assert!(CONTEXT_SUMMARY_PREFIX.contains("Context Summary"));
320 }
321
322 #[test]
325 fn test_slots_default_builds_system_default() {
326 let slots = SystemPromptSlots::default();
327 let built = slots.build();
328 assert!(built.contains("Core Behaviour"));
329 assert!(built.contains("Tool Usage Strategy"));
330 assert!(built.contains("Completion Criteria"));
331 assert!(built.contains("Response Format"));
332 assert!(built.contains("A3S Code"));
333 }
334
335 #[test]
336 fn test_slots_custom_role_replaces_default() {
337 let slots = SystemPromptSlots {
338 role: Some("You are a senior Python developer".to_string()),
339 ..Default::default()
340 };
341 let built = slots.build();
342 assert!(built.contains("You are a senior Python developer"));
343 assert!(!built.contains("You are A3S Code"));
344 assert!(built.contains("Core Behaviour"));
346 assert!(built.contains("Tool Usage Strategy"));
347 }
348
349 #[test]
350 fn test_slots_custom_guidelines_appended() {
351 let slots = SystemPromptSlots {
352 guidelines: Some("Always use type hints. Follow PEP 8.".to_string()),
353 ..Default::default()
354 };
355 let built = slots.build();
356 assert!(built.contains("## Guidelines"));
357 assert!(built.contains("Always use type hints"));
358 assert!(built.contains("Core Behaviour"));
359 }
360
361 #[test]
362 fn test_slots_custom_response_style_replaces_default() {
363 let slots = SystemPromptSlots {
364 response_style: Some("Be concise. Use bullet points.".to_string()),
365 ..Default::default()
366 };
367 let built = slots.build();
368 assert!(built.contains("Be concise. Use bullet points."));
369 assert!(!built.contains("emit tool calls, no prose"));
371 assert!(built.contains("Core Behaviour"));
373 }
374
375 #[test]
376 fn test_slots_extra_appended() {
377 let slots = SystemPromptSlots {
378 extra: Some("Remember: always write tests first.".to_string()),
379 ..Default::default()
380 };
381 let built = slots.build();
382 assert!(built.contains("Remember: always write tests first."));
383 assert!(built.contains("Core Behaviour"));
384 }
385
386 #[test]
387 fn test_slots_from_legacy() {
388 let slots = SystemPromptSlots::from_legacy("You are a helpful assistant.".to_string());
389 let built = slots.build();
390 assert!(built.contains("You are a helpful assistant."));
392 assert!(built.contains("Core Behaviour"));
393 assert!(built.contains("Tool Usage Strategy"));
394 }
395
396 #[test]
397 fn test_slots_all_slots_combined() {
398 let slots = SystemPromptSlots {
399 role: Some("You are a Rust expert".to_string()),
400 guidelines: Some("Use clippy. No unwrap.".to_string()),
401 response_style: Some("Short answers only.".to_string()),
402 extra: Some("Project uses tokio.".to_string()),
403 };
404 let built = slots.build();
405 assert!(built.contains("You are a Rust expert"));
406 assert!(built.contains("Core Behaviour"));
407 assert!(built.contains("## Guidelines"));
408 assert!(built.contains("Use clippy"));
409 assert!(built.contains("Short answers only"));
410 assert!(built.contains("Project uses tokio"));
411 assert!(!built.contains("emit tool calls, no prose"));
413 }
414
415 #[test]
416 fn test_slots_is_empty() {
417 assert!(SystemPromptSlots::default().is_empty());
418 assert!(!SystemPromptSlots {
419 role: Some("test".to_string()),
420 ..Default::default()
421 }
422 .is_empty());
423 }
424}