subcog/mcp/prompts/
generators.rs

1//! Prompt message generation logic.
2//!
3//! Contains the implementation of prompt generation methods.
4
5use serde_json::Value;
6
7use super::templates::{
8    BROWSE_DASHBOARD_INSTRUCTIONS, BROWSE_SYSTEM_RESPONSE, CAPTURE_ASSISTANT_SYSTEM,
9    CONTEXT_CAPTURE_INSTRUCTIONS, CONTEXT_CAPTURE_RESPONSE, DISCOVER_INSTRUCTIONS,
10    DISCOVER_RESPONSE, GENERATE_TUTORIAL_RESPONSE, GENERATE_TUTORIAL_STRUCTURE,
11    INTENT_SEARCH_INSTRUCTIONS, INTENT_SEARCH_RESPONSE, LIST_FORMAT_INSTRUCTIONS,
12    QUERY_SUGGEST_INSTRUCTIONS, QUERY_SUGGEST_RESPONSE, SEARCH_HELP_SYSTEM,
13    SESSION_START_INSTRUCTIONS, SESSION_START_RESPONSE, TUTORIAL_BEST_PRACTICES, TUTORIAL_CAPTURE,
14    TUTORIAL_NAMESPACES, TUTORIAL_OVERVIEW, TUTORIAL_SEARCH, TUTORIAL_WORKFLOWS,
15};
16use super::types::{PromptContent, PromptMessage};
17
18/// Generator for tutorial prompts.
19pub fn generate_tutorial_prompt(arguments: &Value) -> Vec<PromptMessage> {
20    let familiarity = arguments
21        .get("familiarity")
22        .and_then(|v| v.as_str())
23        .unwrap_or("beginner");
24
25    let focus = arguments
26        .get("focus")
27        .and_then(|v| v.as_str())
28        .unwrap_or("overview");
29
30    let intro = match familiarity {
31        "advanced" => {
32            "I see you're experienced with memory systems. Let me show you Subcog's advanced features."
33        },
34        "intermediate" => {
35            "Great, you have some familiarity with memory systems. Let me explain Subcog's key concepts."
36        },
37        _ => "Welcome to Subcog! I'll guide you through the basics of the memory system.",
38    };
39
40    let focus_content = match focus {
41        "capture" => TUTORIAL_CAPTURE,
42        "recall" | "search" => TUTORIAL_SEARCH,
43        "namespaces" => TUTORIAL_NAMESPACES,
44        "workflows" => TUTORIAL_WORKFLOWS,
45        "best-practices" => TUTORIAL_BEST_PRACTICES,
46        _ => TUTORIAL_OVERVIEW,
47    };
48
49    vec![
50        PromptMessage {
51            role: "user".to_string(),
52            content: PromptContent::Text {
53                text: format!(
54                    "I'd like to learn about Subcog. My familiarity level is '{familiarity}' and I want to focus on '{focus}'."
55                ),
56            },
57        },
58        PromptMessage {
59            role: "assistant".to_string(),
60            content: PromptContent::Text {
61                text: format!("{intro}\n\n{focus_content}"),
62            },
63        },
64    ]
65}
66
67/// Generates the `generate_tutorial` prompt messages.
68///
69/// Creates a tutorial on any topic using memories as source material.
70pub fn generate_generate_tutorial_messages(arguments: &Value) -> Vec<PromptMessage> {
71    let topic = arguments
72        .get("topic")
73        .and_then(|v| v.as_str())
74        .unwrap_or("general");
75
76    let level = arguments
77        .get("level")
78        .and_then(|v| v.as_str())
79        .unwrap_or("beginner");
80
81    let format = arguments
82        .get("format")
83        .and_then(|v| v.as_str())
84        .unwrap_or("markdown");
85
86    let level_description = match level {
87        "advanced" => "for an experienced developer who knows the fundamentals",
88        "intermediate" => "for a developer with some experience",
89        _ => "for someone new to this topic",
90    };
91
92    let format_instruction = match format {
93        "outline" => {
94            "Present the tutorial as a structured outline with main sections and sub-points."
95        },
96        "steps" => "Present the tutorial as numbered step-by-step instructions.",
97        _ => "Present the tutorial in full markdown with headings, examples, and explanations.",
98    };
99
100    let prompt = format!(
101        "Generate a comprehensive tutorial about **{topic}** {level_description}.\n\n\
102        **Instructions**:\n\
103        1. First, search for relevant memories using `mcp__plugin_subcog_subcog__subcog_recall` with query: \"{topic}\"\n\
104        2. Incorporate insights, decisions, and patterns from the memories found\n\
105        3. Structure the tutorial with clear sections\n\
106        4. Include practical examples where applicable\n\
107        5. Reference specific memories that inform the content\n\n\
108        **Format**: {format_instruction}\n\n\
109        {GENERATE_TUTORIAL_STRUCTURE}"
110    );
111
112    vec![
113        PromptMessage {
114            role: "user".to_string(),
115            content: PromptContent::Text { text: prompt },
116        },
117        PromptMessage {
118            role: "assistant".to_string(),
119            content: PromptContent::Text {
120                text: GENERATE_TUTORIAL_RESPONSE.to_string(),
121            },
122        },
123    ]
124}
125
126/// Generates the capture assistant prompt.
127pub fn generate_capture_assistant_prompt(arguments: &Value) -> Vec<PromptMessage> {
128    let context = arguments
129        .get("context")
130        .or_else(|| arguments.get("content"))
131        .or_else(|| arguments.get("conversation"))
132        .and_then(|v| v.as_str())
133        .unwrap_or("");
134
135    vec![
136        PromptMessage {
137            role: "user".to_string(),
138            content: PromptContent::Text {
139                text: format!(
140                    "Please analyze this context and suggest what memories to capture:\n\n{context}"
141                ),
142            },
143        },
144        PromptMessage {
145            role: "assistant".to_string(),
146            content: PromptContent::Text {
147                text: CAPTURE_ASSISTANT_SYSTEM.to_string(),
148            },
149        },
150    ]
151}
152
153/// Generates the review prompt.
154pub fn generate_review_prompt(arguments: &Value) -> Vec<PromptMessage> {
155    let namespace = arguments
156        .get("namespace")
157        .and_then(|v| v.as_str())
158        .unwrap_or("all");
159
160    let action = arguments
161        .get("action")
162        .and_then(|v| v.as_str())
163        .unwrap_or("summarize");
164
165    vec![PromptMessage {
166        role: "user".to_string(),
167        content: PromptContent::Text {
168            text: format!(
169                "Please {action} the memories in the '{namespace}' namespace. Help me understand what we have and identify any gaps or improvements."
170            ),
171        },
172    }]
173}
174
175/// Generates the decision documentation prompt.
176pub fn generate_decision_prompt(arguments: &Value) -> Vec<PromptMessage> {
177    let decision = arguments
178        .get("decision")
179        .and_then(|v| v.as_str())
180        .unwrap_or("");
181
182    let context = arguments
183        .get("context")
184        .and_then(|v| v.as_str())
185        .unwrap_or("");
186
187    let alternatives = arguments
188        .get("alternatives")
189        .and_then(|v| v.as_str())
190        .unwrap_or("");
191
192    let mut prompt =
193        format!("I need to document the following decision:\n\n**Decision**: {decision}\n");
194
195    if !context.is_empty() {
196        prompt.push_str(&format!("\n**Context**: {context}\n"));
197    }
198
199    if !alternatives.is_empty() {
200        prompt.push_str(&format!("\n**Alternatives considered**: {alternatives}\n"));
201    }
202
203    prompt.push_str(
204        "\nPlease help me document this decision in a structured way that captures:\n\
205        1. The context and problem being solved\n\
206        2. The decision and rationale\n\
207        3. Consequences and trade-offs\n\
208        4. Suggested tags for searchability",
209    );
210
211    vec![PromptMessage {
212        role: "user".to_string(),
213        content: PromptContent::Text { text: prompt },
214    }]
215}
216
217/// Generates the search help prompt.
218pub fn generate_search_help_prompt(arguments: &Value) -> Vec<PromptMessage> {
219    let goal = arguments
220        .get("goal")
221        .or_else(|| arguments.get("query"))
222        .and_then(|v| v.as_str())
223        .unwrap_or("");
224
225    vec![
226        PromptMessage {
227            role: "user".to_string(),
228            content: PromptContent::Text {
229                text: format!(
230                    "I'm trying to find memories related to: {goal}\n\nHelp me craft effective search queries."
231                ),
232            },
233        },
234        PromptMessage {
235            role: "assistant".to_string(),
236            content: PromptContent::Text {
237                text: SEARCH_HELP_SYSTEM.to_string(),
238            },
239        },
240    ]
241}
242
243/// Generates the browse prompt (discovery dashboard).
244pub fn generate_browse_prompt(arguments: &Value) -> Vec<PromptMessage> {
245    let filter = arguments
246        .get("filter")
247        .and_then(|v| v.as_str())
248        .unwrap_or("");
249
250    let view = arguments
251        .get("view")
252        .and_then(|v| v.as_str())
253        .unwrap_or("dashboard");
254
255    let top = arguments
256        .get("top")
257        .and_then(|v| v.as_str())
258        .unwrap_or("10");
259
260    let mut prompt = String::from(
261        "Show me a memory browser dashboard.\n\n**IMPORTANT**: Use the `mcp__plugin_subcog_subcog__subcog_recall` tool to fetch memories with server-side filtering:\n",
262    );
263
264    if filter.is_empty() {
265        prompt.push_str(
266            "```json\n{ \"query\": \"*\", \"limit\": 100, \"detail\": \"medium\" }\n```\n\n",
267        );
268        prompt.push_str("No filters applied - show the full dashboard with:\n");
269    } else {
270        prompt.push_str(&format!(
271            "```json\n{{ \"query\": \"*\", \"filter\": \"{filter}\", \"limit\": 100, \"detail\": \"medium\" }}\n```\n\n"
272        ));
273    }
274
275    prompt.push_str(&format!(
276        "View mode: {view}\nShow top {top} items per facet.\n\n"
277    ));
278
279    prompt.push_str(BROWSE_DASHBOARD_INSTRUCTIONS);
280
281    vec![
282        PromptMessage {
283            role: "user".to_string(),
284            content: PromptContent::Text { text: prompt },
285        },
286        PromptMessage {
287            role: "assistant".to_string(),
288            content: PromptContent::Text {
289                text: BROWSE_SYSTEM_RESPONSE.to_string(),
290            },
291        },
292    ]
293}
294
295/// Generates the list prompt (formatted inventory).
296pub fn generate_list_prompt(arguments: &Value) -> Vec<PromptMessage> {
297    let filter = arguments
298        .get("filter")
299        .and_then(|v| v.as_str())
300        .unwrap_or("");
301
302    let format = arguments
303        .get("format")
304        .and_then(|v| v.as_str())
305        .unwrap_or("table");
306
307    let limit = arguments
308        .get("limit")
309        .and_then(|v| v.as_str())
310        .unwrap_or("50");
311
312    let mut prompt = String::from(
313        "List memories from Subcog.\n\n**IMPORTANT**: Use the `mcp__plugin_subcog_subcog__subcog_recall` tool to fetch memories with server-side filtering:\n",
314    );
315
316    if filter.is_empty() {
317        prompt.push_str(&format!(
318            "```json\n{{ \"query\": \"*\", \"limit\": {limit}, \"detail\": \"medium\" }}\n```\n\n"
319        ));
320    } else {
321        prompt.push_str(&format!(
322            "```json\n{{ \"query\": \"*\", \"filter\": \"{filter}\", \"limit\": {limit}, \"detail\": \"medium\" }}\n```\n\n"
323        ));
324    }
325
326    prompt.push_str(&format!("Format: {format}\n\n"));
327
328    prompt.push_str(LIST_FORMAT_INSTRUCTIONS);
329
330    vec![PromptMessage {
331        role: "user".to_string(),
332        content: PromptContent::Text { text: prompt },
333    }]
334}
335
336// Phase 4: Intent-aware prompt generators
337
338/// Generates the intent search prompt.
339pub fn generate_intent_search_prompt(arguments: &Value) -> Vec<PromptMessage> {
340    let query = arguments
341        .get("query")
342        .and_then(|v| v.as_str())
343        .unwrap_or("");
344
345    let intent = arguments
346        .get("intent")
347        .and_then(|v| v.as_str())
348        .unwrap_or("");
349
350    let context = arguments
351        .get("context")
352        .and_then(|v| v.as_str())
353        .unwrap_or("");
354
355    let mut prompt = format!("Search for memories related to: **{query}**\n\n");
356
357    if !intent.is_empty() {
358        prompt.push_str(&format!("Intent hint: {intent}\n\n"));
359    }
360
361    if !context.is_empty() {
362        prompt.push_str(&format!("Current context: {context}\n\n"));
363    }
364
365    prompt.push_str(INTENT_SEARCH_INSTRUCTIONS);
366
367    vec![
368        PromptMessage {
369            role: "user".to_string(),
370            content: PromptContent::Text { text: prompt },
371        },
372        PromptMessage {
373            role: "assistant".to_string(),
374            content: PromptContent::Text {
375                text: INTENT_SEARCH_RESPONSE.to_string(),
376            },
377        },
378    ]
379}
380
381/// Generates the query suggest prompt.
382pub fn generate_query_suggest_prompt(arguments: &Value) -> Vec<PromptMessage> {
383    let topic = arguments
384        .get("topic")
385        .or_else(|| arguments.get("query"))
386        .and_then(|v| v.as_str())
387        .unwrap_or("");
388
389    let namespace = arguments
390        .get("namespace")
391        .and_then(|v| v.as_str())
392        .unwrap_or("");
393
394    let mut prompt = String::from("Help me explore my memory collection.\n\n");
395
396    if !topic.is_empty() {
397        prompt.push_str(&format!("Topic area: **{topic}**\n"));
398    }
399
400    if !namespace.is_empty() {
401        prompt.push_str(&format!("Focus namespace: **{namespace}**\n"));
402    }
403
404    prompt.push('\n');
405    prompt.push_str(QUERY_SUGGEST_INSTRUCTIONS);
406
407    vec![
408        PromptMessage {
409            role: "user".to_string(),
410            content: PromptContent::Text { text: prompt },
411        },
412        PromptMessage {
413            role: "assistant".to_string(),
414            content: PromptContent::Text {
415                text: QUERY_SUGGEST_RESPONSE.to_string(),
416            },
417        },
418    ]
419}
420
421/// Generates the context capture prompt.
422pub fn generate_context_capture_prompt(arguments: &Value) -> Vec<PromptMessage> {
423    let conversation = arguments
424        .get("conversation")
425        .or_else(|| arguments.get("context"))
426        .and_then(|v| v.as_str())
427        .unwrap_or("");
428
429    let threshold = arguments
430        .get("threshold")
431        .and_then(|v| v.as_str())
432        .unwrap_or("0.7");
433
434    let prompt = format!(
435        "Analyze this conversation/context and suggest memories to capture:\n\n\
436        ---\n{conversation}\n---\n\n\
437        Confidence threshold: {threshold}\n\n\
438        {CONTEXT_CAPTURE_INSTRUCTIONS}"
439    );
440
441    vec![
442        PromptMessage {
443            role: "user".to_string(),
444            content: PromptContent::Text { text: prompt },
445        },
446        PromptMessage {
447            role: "assistant".to_string(),
448            content: PromptContent::Text {
449                text: CONTEXT_CAPTURE_RESPONSE.to_string(),
450            },
451        },
452    ]
453}
454
455/// Generates the discover prompt.
456pub fn generate_discover_prompt(arguments: &Value) -> Vec<PromptMessage> {
457    let start = arguments
458        .get("start")
459        .or_else(|| arguments.get("topic"))
460        .or_else(|| arguments.get("tag"))
461        .and_then(|v| v.as_str())
462        .unwrap_or("");
463
464    let depth = arguments
465        .get("depth")
466        .and_then(|v| v.as_str())
467        .unwrap_or("2");
468
469    let mut prompt = String::from("Explore related memories and topics.\n\n");
470
471    if start.is_empty() {
472        prompt.push_str("No starting point specified - show an overview of available topics.\n");
473    } else {
474        prompt.push_str(&format!("Starting point: **{start}**\n"));
475    }
476
477    prompt.push_str(&format!("Exploration depth: {depth} hops\n\n"));
478    prompt.push_str(DISCOVER_INSTRUCTIONS);
479
480    vec![
481        PromptMessage {
482            role: "user".to_string(),
483            content: PromptContent::Text { text: prompt },
484        },
485        PromptMessage {
486            role: "assistant".to_string(),
487            content: PromptContent::Text {
488                text: DISCOVER_RESPONSE.to_string(),
489            },
490        },
491    ]
492}
493
494/// Generates the session start prompt.
495///
496/// This prompt guides AI clients through proper session initialization.
497pub fn generate_session_start_prompt(arguments: &Value) -> Vec<PromptMessage> {
498    let include_recall = arguments
499        .get("include_recall")
500        .and_then(serde_json::Value::as_bool)
501        .unwrap_or(true);
502
503    let project = arguments
504        .get("project")
505        .and_then(|v| v.as_str())
506        .unwrap_or("");
507
508    let mut prompt = String::from("Initialize my Subcog session.\n\n");
509
510    if include_recall {
511        prompt.push_str(
512            "**Include context recall**: Yes - search for project setup and architecture.\n",
513        );
514    } else {
515        prompt.push_str("**Include context recall**: No - skip initial recall.\n");
516    }
517
518    if !project.is_empty() {
519        prompt.push_str(&format!("**Project context**: {project}\n"));
520    }
521
522    prompt.push('\n');
523    prompt.push_str(SESSION_START_INSTRUCTIONS);
524
525    vec![
526        PromptMessage {
527            role: "user".to_string(),
528            content: PromptContent::Text { text: prompt },
529        },
530        PromptMessage {
531            role: "assistant".to_string(),
532            content: PromptContent::Text {
533                text: SESSION_START_RESPONSE.to_string(),
534            },
535        },
536    ]
537}