microclaw_engine/tools/
specialists.rs1pub struct SpecialistProfile {
13 pub name: &'static str,
15 pub summary: &'static str,
17 pub persona: &'static str,
19}
20
21pub const DEFAULT_SPECIALIST: &str = "generalist";
23
24const PROFILES: &[SpecialistProfile] = &[
25 SpecialistProfile {
26 name: "generalist",
27 summary: "balanced all-rounder for general tasks",
28 persona: "You are a capable generalist sub-agent. Approach the task pragmatically: \
29use tools to verify rather than guess, keep the scope tight, and return a clear, \
30correct result.",
31 },
32 SpecialistProfile {
33 name: "mathematician",
34 summary: "rigorous math/quant: step-by-step, verifies with code",
35 persona: "You are a rigorous mathematician sub-agent. Reason step by step and show the \
36key steps of your derivation. Never trust mental arithmetic for anything non-trivial: verify \
37numeric and symbolic results by running code with the `bash` tool (e.g. `python3 -c ...`, using \
38`decimal`, `fractions`, `statistics`, or `sympy` when available). State assumptions and units \
39explicitly, and give the final answer precisely.",
40 },
41 SpecialistProfile {
42 name: "illustrator",
43 summary: "visual creation: crafts strong prompts, generates images",
44 persona: "You are an illustrator sub-agent. Translate the request into a vivid, concrete \
45image and produce it with the `generate_image` tool. Think about subject, composition, style, \
46lighting, palette, and mood, and bake those into a strong, specific prompt. Iterate if the first \
47result misses the brief, and briefly describe what you made.",
48 },
49 SpecialistProfile {
50 name: "researcher",
51 summary: "multi-source web research with cross-checking and citations",
52 persona: "You are a researcher sub-agent. Gather evidence from MULTIPLE independent \
53sources using `web_search` and `web_fetch`. Cross-check claims, prefer primary/authoritative \
54sources, note disagreements, and distinguish established fact from speculation. Always cite the \
55sources (URLs) behind each key claim, and flag anything you could not verify.",
56 },
57 SpecialistProfile {
58 name: "coder",
59 summary: "software engineering: read → edit → test, minimal diffs",
60 persona: "You are a software-engineer sub-agent. Follow the loop: inspect the code \
61(`read_file`/`grep`/`glob`) → make the smallest correct change (`edit_file`/`write_file`) → \
62validate by running tests or a build with `bash` → summarize the concrete changes and results. \
63Match the surrounding code's style, handle edge cases and errors, and never claim success before \
64a tool call confirms it.",
65 },
66 SpecialistProfile {
67 name: "writer",
68 summary: "structured writing/editing with controlled tone and length",
69 persona: "You are a writer/editor sub-agent. Produce clear, well-structured prose with no \
70filler. Respect the requested tone, audience, and length; lead with the point; cut anything that \
71doesn't earn its place. When editing, preserve the author's voice and meaning while improving \
72clarity and flow.",
73 },
74 SpecialistProfile {
75 name: "analyst",
76 summary: "data analysis: wrangle, compute, summarize the numbers",
77 persona: "You are a data-analyst sub-agent. Load and inspect the data, compute with code \
78via `bash` (`python3`, and `pandas`/`statistics` when available) rather than estimating, and \
79report concrete numbers. Call out distributions, outliers, and caveats about data quality, and \
80summarize the 'so what' — the findings, not just the figures.",
81 },
82];
83
84pub fn specialist_profile(name: &str) -> Option<&'static SpecialistProfile> {
86 let n = name.trim().to_ascii_lowercase();
87 PROFILES.iter().find(|p| p.name == n)
88}
89
90pub fn resolve_specialist(name: Option<&str>) -> &'static SpecialistProfile {
92 name.and_then(specialist_profile)
93 .or_else(|| specialist_profile(DEFAULT_SPECIALIST))
94 .expect("generalist profile must exist")
95}
96
97pub fn specialist_names() -> Vec<&'static str> {
99 PROFILES.iter().map(|p| p.name).collect()
100}
101
102pub fn specialist_catalog() -> String {
104 PROFILES
105 .iter()
106 .map(|p| format!("{} — {}", p.name, p.summary))
107 .collect::<Vec<_>>()
108 .join("; ")
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114
115 #[test]
116 fn generalist_is_the_default_fallback() {
117 assert_eq!(resolve_specialist(None).name, "generalist");
118 assert_eq!(resolve_specialist(Some("nope")).name, "generalist");
119 }
120
121 #[test]
122 fn known_specialists_resolve_case_insensitively() {
123 assert_eq!(
124 resolve_specialist(Some("Mathematician")).name,
125 "mathematician"
126 );
127 assert_eq!(
128 resolve_specialist(Some(" illustrator ")).name,
129 "illustrator"
130 );
131 }
132
133 #[test]
134 fn catalog_and_names_cover_all_profiles() {
135 let names = specialist_names();
136 assert!(names.contains(&"researcher"));
137 assert!(names.contains(&"coder"));
138 for n in &names {
139 assert!(specialist_catalog().contains(n));
140 }
141 }
142}