Skip to main content

phi_agent/prompt/fragments/
mod.rs

1//! phi-agent specific prompt fragments.
2//!
3//! These fragments implement the phi-agent system prompt using the
4//! [`PromptFragment`] trait from agent-works.
5//!
6//! # Priority map
7//!
8//! | Fragment                     | Priority | Content                     |
9//! |------------------------------|----------|-----------------------------|
10//! | `CoreInstructionsFragment`   | 10       | Role, guidelines, wrap-up   |
11//! | `MemoryFragment`             | 95       | Memory system instructions  |
12//! | `NetworkEnvironmentFragment` | 90       | China GFW-aware hints       |
13
14use agent_works::prompt::{FragmentContext, PromptFragment};
15
16// ── Core Instructions ──────────────────────────────────────────────────────
17
18/// The main system prompt content — role, conversation detection, thinking
19/// approach, execution guidelines, file operation guidelines, wrap-up.
20///
21/// Priority: 10 (renders first).
22#[derive(Clone)]
23pub struct CoreInstructionsFragment;
24
25impl PromptFragment for CoreInstructionsFragment {
26    fn name(&self) -> &str {
27        "core_instructions"
28    }
29
30    fn priority(&self) -> i32 {
31        10
32    }
33
34    fn render(&self, _ctx: &FragmentContext) -> Option<String> {
35        Some(CORE_INSTRUCTIONS.to_string())
36    }
37}
38
39const CORE_INSTRUCTIONS: &str = r#"You are a versatile AI assistant with strong autonomous problem-solving abilities.
40
41[Role]
42You get things done, not chat. Take initiative — don't ask for confirmation repeatedly. Reply with conclusions only.
43
44[Conversation Type Detection]
45- Greetings / small talk (hello, thanks, goodbye) → Friendly response, no tools.
46- Questions / discussion → Give analysis and advice, don't execute destructive actions directly.
47- Dev / ops tasks → Take action directly.
48
49[Thinking Approach]
50Each turn, quickly assess: what phase am I in → what's the next step → do it.
51For complex tasks (3+ steps), use update_plan to show the plan and let the user see progress.
52
53[Execution Guidelines]
54- Check state before acting. Probe current state before making changes.
55- Verify results after operations.
56- Text replies should only contain analysis and conclusions — don't repeat tool output.
57- When your current task is active and not fully complete, each reply must include tool calls that advance the work — a text-only reply then ends the run. Reserve text-only replies for: (a) reporting results after the task is complete, (b) genuinely needing the user's input or confirmation before proceeding, or (c) handling a hard blocker that requires handing control back to the user.
58- Avoid narrating next steps ("let me check X", "next I'll do Y") without emitting the matching tool call in the same reply — think aloud in your reasoning block, not in the visible reply.
59- If more work is needed, keep calling tools — don't stop after one step.
60- Independent operations can run in parallel; dependent ones must be serial.
61- On error: analyze the cause, find a fix, and apply it directly. Stop after 2 consecutive failures of the same approach and explain to the user.
62
63[File Operation Guidelines]
64- Confirm the file exists before reading.
65- Confirm the directory exists before writing (create if needed).
66- Back up or verify content before modifying files.
67- Verify file state after operations.
68
69[Wrap-Up]
70Report a final conclusion once the entire user request is complete. If work remains and you can proceed without user input, keep calling tools instead of wrapping up. After confirming results, report the conclusion concisely."#;
71
72// ── Memory ─────────────────────────────────────────────────────────────────
73
74/// Memory system prompt from agent-works.
75///
76/// Priority: 95 (renders near the end).
77#[derive(Clone)]
78pub struct MemoryFragment;
79
80impl PromptFragment for MemoryFragment {
81    fn name(&self) -> &str {
82        "memory"
83    }
84
85    fn priority(&self) -> i32 {
86        95
87    }
88
89    fn render(&self, _ctx: &FragmentContext) -> Option<String> {
90        Some(agent_works::build_memory_system_prompt())
91    }
92}
93
94// ── Network Environment (China) ────────────────────────────────────────────
95
96/// GFW-aware network hints for users in mainland China.
97///
98/// Priority: 90 (after core instructions, before memory).
99/// Only injected when `include_cn` is true.
100#[derive(Clone)]
101pub struct NetworkEnvironmentFragment;
102
103impl PromptFragment for NetworkEnvironmentFragment {
104    fn name(&self) -> &str {
105        "network_environment_cn"
106    }
107
108    fn priority(&self) -> i32 {
109        90
110    }
111
112    fn render(&self, _ctx: &FragmentContext) -> Option<String> {
113        Some(NETWORK_ENV_CN.to_string())
114    }
115}
116
117const NETWORK_ENV_CN: &str = r#"[Network Environment]
118You are operating in mainland China. Google, YouTube, Twitter, BBC, and many foreign sites are inaccessible. Prefer domestic alternatives:
119- Search: Bing (cn.bing.com) or Baidu (baidu.com)
120- News: Toutiao, Baidu News, The Paper (thepaper.cn), Zaobao (zaobao.com)
121- Dev: mirrors.tuna.tsinghua.edu.cn, gitee.com
122When a foreign site times out, switch to a domestic alternative immediately — don't retry."#;
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127    use agent_works::prompt::compose_fragments;
128
129    #[test]
130    fn test_core_instructions_fragment() {
131        let frag = CoreInstructionsFragment;
132        let ctx = FragmentContext { tool_definitions: &[], session_id: "test" };
133        let output = frag.render(&ctx).unwrap();
134        assert!(output.contains("versatile AI assistant"));
135        assert!(output.contains("[Conversation Type Detection]"));
136        assert!(output.contains("[Execution Guidelines]"));
137        assert!(output.contains("[Wrap-Up]"));
138    }
139
140    #[test]
141    fn test_memory_fragment() {
142        let frag = MemoryFragment;
143        let ctx = FragmentContext { tool_definitions: &[], session_id: "test" };
144        let output = frag.render(&ctx).unwrap();
145        assert!(output.contains("Memory"));
146        assert!(output.contains("MEMORY.md"));
147    }
148
149    #[test]
150    fn test_network_environment_fragment() {
151        let frag = NetworkEnvironmentFragment;
152        let ctx = FragmentContext { tool_definitions: &[], session_id: "test" };
153        let output = frag.render(&ctx).unwrap();
154        assert!(output.contains("mainland China"));
155        assert!(output.contains("Baidu"));
156        assert!(output.contains("gitee.com"));
157    }
158
159    #[test]
160    fn test_all_fragments_compose() {
161        let fragments: Vec<Box<dyn PromptFragment>> =
162            vec![Box::new(CoreInstructionsFragment), Box::new(MemoryFragment), Box::new(NetworkEnvironmentFragment)];
163        let ctx = FragmentContext { tool_definitions: &[], session_id: "test" };
164        let result = compose_fragments(&fragments, &ctx);
165        // Core (10) comes first, then Network (90), then Memory (95)
166        let core_pos = result.find("versatile AI assistant").unwrap();
167        let cn_pos = result.find("mainland China").unwrap();
168        let mem_pos = result.find("MEMORY.md").unwrap();
169        assert!(core_pos < cn_pos);
170        assert!(cn_pos < mem_pos);
171    }
172
173    #[test]
174    fn test_fragment_names() {
175        assert_eq!(CoreInstructionsFragment.name(), "core_instructions");
176        assert_eq!(MemoryFragment.name(), "memory");
177        assert_eq!(NetworkEnvironmentFragment.name(), "network_environment_cn");
178    }
179
180    #[test]
181    fn test_fragment_priorities() {
182        assert_eq!(CoreInstructionsFragment.priority(), 10);
183        assert_eq!(NetworkEnvironmentFragment.priority(), 90);
184        assert_eq!(MemoryFragment.priority(), 95);
185    }
186}