Skip to main content

agent_trace/llm/
prompts.rs

1use crate::llm::trace_insights::TraceDocument;
2use crate::types::DocType;
3
4const MAX_SNIPPET_CHARS: usize = 2000;
5
6#[derive(Debug, Clone)]
7pub struct DocSummary {
8    pub path: String,
9    pub doc_type: DocType,
10    pub content_snippet: String,
11}
12
13fn truncate(s: &str, max: usize) -> &str {
14    if s.len() <= max {
15        s
16    } else {
17        &s[..max]
18    }
19}
20
21pub fn summarize_change_prompt(path: &str, doc_type: &str, diff: &str) -> String {
22    format!(
23        "Summarize this diff of a {doc_type} document '{path}' in one sentence (max 20 words).\n\n{}",
24        truncate(diff, MAX_SNIPPET_CHARS)
25    )
26}
27
28pub fn synthesize_context_prompt(documents: &[DocSummary], updates: &[String]) -> String {
29    let mut docs_str = String::new();
30    for doc in documents {
31        docs_str.push_str(&format!(
32            "[{}] {}: {}\n",
33            doc.doc_type, doc.path, doc.content_snippet
34        ));
35    }
36    format!(
37        "Given these project documents and user updates, produce a concise context document \
38         covering: goals, status, key decisions, architecture, open questions.\n\
39         Start with '# Project Context'.\n\n\
40         Documents:\n{}\n\nUser updates:\n{}",
41        truncate(&docs_str, MAX_SNIPPET_CHARS),
42        truncate(&updates.join("\n"), MAX_SNIPPET_CHARS / 2)
43    )
44}
45
46pub fn summarize_session_prompt(session_id: &str, events: &[String]) -> String {
47    format!(
48        "Summarize this agent session in 2-3 sentences for a reconnecting agent.\n\
49         Session: {session_id}\n\nEvents:\n{}",
50        truncate(&events.join("\n"), MAX_SNIPPET_CHARS)
51    )
52}
53
54pub fn summarize_event_history_prompt(events: &str) -> String {
55    format!(
56        "Summarize this agent work history in one paragraph (4-6 sentences).\n\
57         Focus on themes and progress, not per-file enumeration.\n\n{}",
58        truncate(events, MAX_SNIPPET_CHARS)
59    )
60}
61
62pub fn update_running_summary_prompt(previous: &str, events: &str, plan: &str) -> String {
63    format!(
64        "Update the running project summary given the previous version and new events.\n\
65         Keep sections: Current Status, Recent Activity, Resume Here, Key Documents, Open Items.\n\
66         Under 800 words.\n\n\
67         Previous summary:\n{}\n\nNew events:\n{}\n\nPlan excerpt:\n{}",
68        truncate(previous, 3000),
69        truncate(events, 2000),
70        truncate(plan, 2000)
71    )
72}
73
74pub fn trace_to_doc_summaries(documents: &[TraceDocument]) -> Vec<DocSummary> {
75    documents
76        .iter()
77        .map(|d| DocSummary {
78            path: d.path.clone(),
79            doc_type: d.doc_type.clone(),
80            content_snippet: d.content_snippet.clone(),
81        })
82        .collect()
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88
89    #[test]
90    fn prompts_are_non_empty() {
91        assert!(!summarize_change_prompt("a.md", "plan", "+1").is_empty());
92        assert!(!update_running_summary_prompt("prev", "ev", "plan").is_empty());
93        assert!(!summarize_session_prompt("s1", &["event".into()]).is_empty());
94    }
95}