cersei_agent/
coordinator.rs1use cersei_tools::Tool;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum AgentMode {
12 Coordinator,
14 Worker,
16 Normal,
18}
19
20pub const COORDINATOR_ONLY_TOOLS: &[&str] = &[
22 "Agent",
23 "SendMessage",
24 "TaskStop",
25 "TeamCreate",
26 "TeamDelete",
27 "SyntheticOutput",
28];
29
30pub fn is_coordinator_mode() -> bool {
32 match std::env::var("CERSEI_COORDINATOR_MODE") {
33 Ok(v) => !v.is_empty() && v != "0" && v != "false",
34 Err(_) => false,
35 }
36}
37
38pub fn filter_tools_for_mode(tools: Vec<Box<dyn Tool>>, mode: AgentMode) -> Vec<Box<dyn Tool>> {
41 match mode {
42 AgentMode::Worker => tools
43 .into_iter()
44 .filter(|t| !COORDINATOR_ONLY_TOOLS.contains(&t.name()))
45 .collect(),
46 AgentMode::Coordinator | AgentMode::Normal => tools,
47 }
48}
49
50pub fn coordinator_system_prompt() -> &'static str {
52 "## Coordinator Mode\n\n\
53 You are operating as an orchestrator. Your role is to:\n\
54 1. Break complex tasks into independent sub-tasks\n\
55 2. Spawn parallel worker agents using the Agent tool\n\
56 3. Each worker prompt must be fully self-contained\n\
57 4. Synthesize findings from all workers before responding\n\
58 5. Use TaskCreate/TaskUpdate to track parallel work\n\n\
59 Workers cannot spawn their own sub-agents. They have access to \
60 filesystem, shell, and web tools only."
61}
62
63pub fn coordinator_context(tools: &[Box<dyn Tool>]) -> String {
65 let tool_list: Vec<String> = tools
66 .iter()
67 .filter(|t| !["Agent", "SyntheticOutput"].contains(&t.name()))
68 .map(|t| format!("- {}: {}", t.name(), t.description()))
69 .collect();
70
71 format!("Available tools for workers:\n{}", tool_list.join("\n"))
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77
78 #[test]
79 fn test_filter_worker_tools() {
80 let tools = cersei_tools::all();
81 let original_count = tools.len();
82 let filtered = filter_tools_for_mode(tools, AgentMode::Worker);
83 assert!(filtered.len() <= original_count);
85 assert!(filtered
86 .iter()
87 .all(|t| !COORDINATOR_ONLY_TOOLS.contains(&t.name())));
88 }
89
90 #[test]
91 fn test_filter_coordinator_keeps_all() {
92 let tools = cersei_tools::all();
93 let count = tools.len();
94 let filtered = filter_tools_for_mode(tools, AgentMode::Coordinator);
95 assert_eq!(filtered.len(), count);
96 }
97
98 #[test]
99 fn test_coordinator_prompt() {
100 let prompt = coordinator_system_prompt();
101 assert!(prompt.contains("orchestrator"));
102 assert!(prompt.contains("sub-tasks"));
103 }
104
105 #[test]
106 fn test_coordinator_context() {
107 let tools = cersei_tools::all();
108 let ctx = coordinator_context(&tools);
109 assert!(ctx.contains("Available tools"));
110 assert!(ctx.contains("Read"));
111 assert!(ctx.contains("Bash"));
112 }
113}