Skip to main content

telos_agent/agent/prompt/
builtins.rs

1use async_trait::async_trait;
2use std::sync::Arc;
3
4use crate::agent::prompt::{PromptSection, PromptStability};
5use crate::config::TaskPath;
6use crate::tools::api::ToolRegistry;
7
8mod context;
9
10pub use context::{
11    CwdSection, DateSection, GitStatusSection, McpSection, MemorySection, ProfileSection,
12    SkillsSection,
13};
14
15// ── Identity ──────────────────────────────────────────────
16
17macro_rules! prompt_template {
18    ($name:expr) => {
19        include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../prompt/", $name))
20    };
21}
22
23static IDENTITY_TEMPLATE: &str = prompt_template!("identity.md");
24static TONE_STYLE_TEMPLATE: &str = prompt_template!("tone_style.md");
25static TASK_GUIDANCE_TEMPLATE: &str = prompt_template!("task_guidance.md");
26static SAFETY_TEMPLATE: &str = prompt_template!("safety.md");
27static PATH_FAST_TEMPLATE: &str = prompt_template!("path_fast.md");
28static PATH_STANDARD_TEMPLATE: &str = prompt_template!("path_standard.md");
29static PATH_HEAVY_TEMPLATE: &str = prompt_template!("path_heavy.md");
30static TOOL_USAGE_TEMPLATE: &str = prompt_template!("tool_usage.md");
31
32/// Core identity, security policy, and system-level rules.
33///
34/// Topics:
35///   - identity and role
36///   - security testing policy
37///   - URL generation rule
38///   - output display rules
39///   - permission mode / policy / compaction notes
40pub struct IdentitySection {
41    base: Option<String>,
42}
43
44impl IdentitySection {
45    pub fn new(base_prompt: Option<String>) -> Self {
46        Self { base: base_prompt }
47    }
48}
49
50#[async_trait]
51impl PromptSection for IdentitySection {
52    fn name(&self) -> &str {
53        "identity"
54    }
55    fn stability(&self) -> PromptStability {
56        PromptStability::Static
57    }
58
59    async fn render(&self, _ctx: &()) -> String {
60        let base_block = match &self.base {
61            Some(base) => format!("\nAdditional instructions from the user:\n{base}"),
62            None => String::new(),
63        };
64        IDENTITY_TEMPLATE.replace("{{BASE}}", &base_block)
65    }
66}
67
68// ── Tone and Style ────────────────────────────────────────
69
70/// Output style guidance for a terminal coding assistant.
71pub struct ToneStyleSection;
72
73#[async_trait]
74impl PromptSection for ToneStyleSection {
75    fn name(&self) -> &str {
76        "tone_style"
77    }
78    fn stability(&self) -> PromptStability {
79        PromptStability::Static
80    }
81
82    async fn render(&self, _ctx: &()) -> String {
83        TONE_STYLE_TEMPLATE.to_string()
84    }
85}
86
87// ── Task Guidance ─────────────────────────────────────────
88
89/// Recommended workflow for software-engineering tasks.
90pub struct TaskGuidanceSection;
91
92#[async_trait]
93impl PromptSection for TaskGuidanceSection {
94    fn name(&self) -> &str {
95        "task_guidance"
96    }
97    fn stability(&self) -> PromptStability {
98        PromptStability::Static
99    }
100
101    async fn render(&self, _ctx: &()) -> String {
102        TASK_GUIDANCE_TEMPLATE.to_string()
103    }
104}
105
106// ── Safety ────────────────────────────────────────────────
107
108/// Safety, reversibility, and honest reporting guidance.
109pub struct SafetySection;
110
111#[async_trait]
112impl PromptSection for SafetySection {
113    fn name(&self) -> &str {
114        "safety"
115    }
116    fn stability(&self) -> PromptStability {
117        PromptStability::Static
118    }
119
120    async fn render(&self, _ctx: &()) -> String {
121        SAFETY_TEMPLATE.to_string()
122    }
123}
124
125// ── Task Path ──────────────────────────────────────────────
126
127/// Injects path-appropriate behavioural guidance based on the configured
128/// [`TaskPath`]. Fast = work directly, Standard = map context + verify,
129/// Heavy = design → plan → phased execution.
130pub struct PathSection {
131    path: TaskPath,
132}
133
134impl PathSection {
135    pub fn new(path: TaskPath) -> Self {
136        Self { path }
137    }
138}
139
140#[async_trait]
141impl PromptSection for PathSection {
142    fn name(&self) -> &str {
143        "task_path"
144    }
145    fn stability(&self) -> PromptStability {
146        PromptStability::Static
147    }
148
149    async fn render(&self, _ctx: &()) -> String {
150        match self.path {
151            TaskPath::Fast => PATH_FAST_TEMPLATE.to_string(),
152            TaskPath::Standard => PATH_STANDARD_TEMPLATE.to_string(),
153            TaskPath::Heavy => PATH_HEAVY_TEMPLATE.to_string(),
154        }
155    }
156}
157
158// ── Tool Usage ────────────────────────────────────────────
159
160/// General tool-selection and parallelism guidance.
161pub struct ToolUsageSection;
162
163/// Tool-selection guidance that reflects the registered default shell.
164pub struct ShellAwareToolUsageSection {
165    tools: Arc<ToolRegistry>,
166}
167
168impl ShellAwareToolUsageSection {
169    pub fn new(tools: Arc<ToolRegistry>) -> Self {
170        Self { tools }
171    }
172
173    fn default_shell(&self) -> &'static str {
174        if self.tools.definitions().iter().any(|definition| definition.name == "PowerShell") {
175            "PowerShell"
176        } else {
177            "Bash"
178        }
179    }
180}
181
182fn render_tool_usage(shell_tool: &str) -> String {
183    TOOL_USAGE_TEMPLATE.replace("{{SHELL}}", shell_tool)
184}
185
186#[async_trait]
187impl PromptSection for ToolUsageSection {
188    fn name(&self) -> &str {
189        "tool_usage"
190    }
191    fn stability(&self) -> PromptStability {
192        PromptStability::Static
193    }
194
195    async fn render(&self, _ctx: &()) -> String {
196        render_tool_usage("Bash")
197    }
198}
199
200#[async_trait]
201impl PromptSection for ShellAwareToolUsageSection {
202    fn name(&self) -> &str {
203        "tool_usage"
204    }
205    fn stability(&self) -> PromptStability {
206        PromptStability::Static
207    }
208
209    async fn render(&self, _ctx: &()) -> String {
210        render_tool_usage(self.default_shell())
211    }
212}
213
214// ── Tools ─────────────────────────────────────────────────
215
216pub struct ToolsSection {
217    tools: Arc<ToolRegistry>,
218}
219
220impl ToolsSection {
221    pub fn new(tools: Arc<ToolRegistry>) -> Self {
222        Self { tools }
223    }
224}
225
226#[async_trait]
227impl PromptSection for ToolsSection {
228    fn name(&self) -> &str {
229        "tools"
230    }
231    fn stability(&self) -> PromptStability {
232        PromptStability::Static
233    }
234
235    async fn render(&self, _ctx: &()) -> String {
236        let defs = self.tools.definitions();
237        if defs.is_empty() {
238            return String::new();
239        }
240        let mut lines = vec!["## Available Tools".to_string()];
241        for def in &defs {
242            lines.push(format!("- **{}**: {}", def.name, def.description));
243        }
244        lines.join("\n")
245    }
246}
247
248// ── Tool Prompts ──────────────────────────────────────────
249
250/// Per-tool behavioral guidance collected from `Tool::prompt_text()`.
251pub struct ToolPromptsSection {
252    tools: Arc<ToolRegistry>,
253}
254
255impl ToolPromptsSection {
256    pub fn new(tools: Arc<ToolRegistry>) -> Self {
257        Self { tools }
258    }
259}
260
261#[async_trait]
262impl PromptSection for ToolPromptsSection {
263    fn name(&self) -> &str {
264        "tool_prompts"
265    }
266    fn stability(&self) -> PromptStability {
267        PromptStability::Static
268    }
269
270    async fn render(&self, _ctx: &()) -> String {
271        let mut entries: Vec<(String, String)> = self
272            .tools
273            .iter()
274            .filter_map(|(name, tool)| {
275                tool.prompt_text().map(|text| (name.clone(), text.to_string()))
276            })
277            .collect();
278        if entries.is_empty() {
279            return String::new();
280        }
281        entries.sort_by(|a, b| a.0.cmp(&b.0));
282        let mut lines = vec!["## Tool-specific guidance".to_string()];
283        for (name, text) in entries {
284            lines.push(format!("### {name}"));
285            for line in text.lines() {
286                lines.push(line.to_string());
287            }
288        }
289        lines.join("\n")
290    }
291}