Skip to main content

cersei_agent/
coordinator.rs

1//! Coordinator mode: multi-agent orchestration.
2//!
3//! When active, the agent acts as a coordinator that spawns parallel worker
4//! agents using the Agent tool. Workers have restricted tool access (no Agent,
5//! SendMessage, TaskStop) to prevent uncontrolled recursion.
6
7use cersei_tools::Tool;
8
9/// Agent execution mode.
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum AgentMode {
12    /// Full access to all tools including Agent spawning.
13    Coordinator,
14    /// Restricted: cannot spawn sub-agents or use coordination tools.
15    Worker,
16    /// Standard mode: all tools available (no special orchestration).
17    Normal,
18}
19
20/// Tools restricted to coordinator mode only (workers can't use these).
21pub const COORDINATOR_ONLY_TOOLS: &[&str] = &[
22    "Agent",
23    "SendMessage",
24    "TaskStop",
25    "TeamCreate",
26    "TeamDelete",
27    "SyntheticOutput",
28];
29
30/// Check if coordinator mode is active via environment variable.
31pub 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
38/// Filter tools based on agent mode.
39/// Workers lose coordinator-only tools. Coordinators and Normal keep everything.
40pub 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
50/// System prompt section for coordinator mode.
51pub 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
63/// Format a context section listing available tools for the coordinator.
64pub 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        // Workers should have fewer tools (coordinator-only removed)
84        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}