brainwires_agent/
roles.rs1use brainwires_core::Tool;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
22#[serde(rename_all = "snake_case")]
23pub enum AgentRole {
24 Exploration,
29
30 Planning,
35
36 Verification,
41
42 Execution,
47}
48
49impl AgentRole {
50 pub fn allowed_tools(self) -> Option<&'static [&'static str]> {
52 match self {
53 Self::Exploration => Some(&[
54 "read_file",
55 "list_directory",
56 "search_code",
57 "query_codebase",
58 "fetch_url",
59 "web_search",
60 "glob",
61 "grep",
62 "context_recall",
63 "task_get",
64 "task_list",
65 ]),
66 Self::Planning => Some(&[
67 "read_file",
68 "list_directory",
69 "glob",
70 "grep",
71 "task_create",
72 "task_update",
73 "task_add_subtask",
74 "task_list",
75 "task_get",
76 "plan_task",
77 "context_recall",
78 ]),
79 Self::Verification => Some(&[
80 "read_file",
81 "list_directory",
82 "glob",
83 "grep",
84 "execute_command",
85 "check_duplicates",
86 "verify_build",
87 "check_syntax",
88 "task_get",
89 "task_list",
90 "context_recall",
91 ]),
92 Self::Execution => None,
93 }
94 }
95
96 pub fn filter_tools(self, tools: &[Tool]) -> Vec<Tool> {
101 match self.allowed_tools() {
102 None => tools.to_vec(),
103 Some(allow) => tools
104 .iter()
105 .filter(|t| allow.contains(&t.name.as_str()))
106 .cloned()
107 .collect(),
108 }
109 }
110
111 pub fn system_prompt_suffix(self) -> &'static str {
113 match self {
114 Self::Exploration => {
115 "\n\n[ROLE: Exploration] You may only read files and search. \
116 Do not attempt to write files, run commands, or spawn agents."
117 }
118 Self::Planning => {
119 "\n\n[ROLE: Planning] You may read files and manage tasks. \
120 Do not write files or execute code — produce a plan only."
121 }
122 Self::Verification => {
123 "\n\n[ROLE: Verification] You may read files and run build/test commands. \
124 Do not write or delete files."
125 }
126 Self::Execution => "",
127 }
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134
135 fn fake_tool(name: &str) -> Tool {
136 Tool {
137 name: name.to_string(),
138 description: String::new(),
139 input_schema: brainwires_core::ToolInputSchema::default(),
140 requires_approval: false,
141 defer_loading: false,
142 allowed_callers: vec![],
143 input_examples: vec![],
144 serialize: false,
145 }
146 }
147
148 #[test]
149 fn exploration_filters_write_tools() {
150 let tools = vec![
151 fake_tool("read_file"),
152 fake_tool("write_file"),
153 fake_tool("execute_command"),
154 fake_tool("glob"),
155 ];
156 let filtered = AgentRole::Exploration.filter_tools(&tools);
157 let names: Vec<&str> = filtered.iter().map(|t| t.name.as_str()).collect();
158 assert!(names.contains(&"read_file"));
159 assert!(names.contains(&"glob"));
160 assert!(!names.contains(&"write_file"));
161 assert!(!names.contains(&"execute_command"));
162 }
163
164 #[test]
165 fn execution_passes_all_tools() {
166 let tools = vec![fake_tool("read_file"), fake_tool("write_file")];
167 let filtered = AgentRole::Execution.filter_tools(&tools);
168 assert_eq!(filtered.len(), 2);
169 }
170
171 #[test]
172 fn planning_allows_task_tools_not_write_or_execute() {
173 let tools = vec![
174 fake_tool("read_file"),
175 fake_tool("task_create"),
176 fake_tool("task_update"),
177 fake_tool("plan_task"),
178 fake_tool("write_file"),
179 fake_tool("execute_command"),
180 ];
181 let filtered = AgentRole::Planning.filter_tools(&tools);
182 let names: Vec<&str> = filtered.iter().map(|t| t.name.as_str()).collect();
183 assert!(names.contains(&"read_file"));
184 assert!(names.contains(&"task_create"));
185 assert!(names.contains(&"task_update"));
186 assert!(names.contains(&"plan_task"));
187 assert!(!names.contains(&"write_file"));
188 assert!(!names.contains(&"execute_command"));
189 }
190
191 #[test]
192 fn verification_allows_execute_command_not_write() {
193 let tools = vec![
194 fake_tool("read_file"),
195 fake_tool("execute_command"),
196 fake_tool("verify_build"),
197 fake_tool("write_file"),
198 fake_tool("task_create"),
199 ];
200 let filtered = AgentRole::Verification.filter_tools(&tools);
201 let names: Vec<&str> = filtered.iter().map(|t| t.name.as_str()).collect();
202 assert!(names.contains(&"read_file"));
203 assert!(names.contains(&"execute_command"));
204 assert!(names.contains(&"verify_build"));
205 assert!(!names.contains(&"write_file"));
206 assert!(!names.contains(&"task_create"));
207 }
208
209 #[test]
210 fn system_prompt_suffix_non_empty_for_constrained_roles() {
211 assert!(!AgentRole::Exploration.system_prompt_suffix().is_empty());
212 assert!(!AgentRole::Planning.system_prompt_suffix().is_empty());
213 assert!(!AgentRole::Verification.system_prompt_suffix().is_empty());
214 assert_eq!(AgentRole::Execution.system_prompt_suffix(), "");
215 }
216}