Skip to main content

agent_trace/llm/
synthesis_engine.rs

1use crate::llm::trace_insights::TraceDocument;
2
3/// Unified synthesis interface for trace artifacts (running summary, context, logs, recaps).
4pub trait SynthesisEngine: Send + Sync {
5    fn summarize_change(&self, path: &str, doc_type: &str, diff: &str) -> Result<String, String>;
6    fn synthesize_context(
7        &self,
8        documents: &[TraceDocument],
9        updates: &[String],
10    ) -> Result<String, String>;
11    fn update_running_summary(
12        &self,
13        previous: &str,
14        events: &str,
15        plan: &str,
16    ) -> Result<String, String>;
17    fn summarize_session(&self, session_id: &str, events: &[String]) -> Result<String, String>;
18    fn summarize_event_history(&self, events: &str) -> Result<String, String>;
19    fn backend_label(&self) -> &str;
20}
21
22/// Mechanical fallback when no remote or Ollama backend is available.
23pub struct DegradedBackend;
24
25impl DegradedBackend {
26    fn line_stats_summary(path: &str, diff: &str) -> String {
27        let added = diff.lines().filter(|l| l.starts_with('+')).count();
28        let removed = diff.lines().filter(|l| l.starts_with('-')).count();
29        format!("{path}: +{added} lines, -{removed} lines.")
30    }
31}
32
33impl SynthesisEngine for DegradedBackend {
34    fn summarize_change(&self, path: &str, _doc_type: &str, diff: &str) -> Result<String, String> {
35        Ok(Self::line_stats_summary(path, diff))
36    }
37
38    fn synthesize_context(
39        &self,
40        documents: &[TraceDocument],
41        _updates: &[String],
42    ) -> Result<String, String> {
43        let mut out =
44            String::from("# Project Context\n\n*(Synthesis degraded — configure `agent-trace model setup`)*\n\n## Documents\n\n");
45        for doc in documents {
46            out.push_str(&format!("- `{}` [{}]\n", doc.path, doc.doc_type));
47        }
48        Ok(out)
49    }
50
51    fn update_running_summary(
52        &self,
53        _previous: &str,
54        events: &str,
55        _plan: &str,
56    ) -> Result<String, String> {
57        let mut out = String::from(
58            "# Running Summary\n\n*(Synthesis degraded — configure `agent-trace model setup`)*\n\n## Recent Activity\n\n",
59        );
60        for line in events.lines().take(15) {
61            out.push_str(&format!("- {line}\n"));
62        }
63        Ok(out)
64    }
65
66    fn summarize_session(&self, session_id: &str, events: &[String]) -> Result<String, String> {
67        Ok(format!(
68            "Session {session_id}: {} event(s) recorded (degraded summary).",
69            events.len()
70        ))
71    }
72
73    fn summarize_event_history(&self, events: &str) -> Result<String, String> {
74        let n = events.lines().filter(|l| !l.trim().is_empty()).count();
75        Ok(format!(
76            "{n} earlier events recorded (degraded summary); see plan.md for phase checklist."
77        ))
78    }
79
80    fn backend_label(&self) -> &str {
81        "degraded"
82    }
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88    use crate::types::DocType;
89
90    #[test]
91    fn degraded_summarize_change_counts_lines() {
92        let b = DegradedBackend;
93        let s = b.summarize_change("a.md", "plan", "+a\n+b\n-c").unwrap();
94        assert!(s.contains("+2"));
95        assert!(s.contains("-1"));
96    }
97
98    #[test]
99    fn degraded_context_lists_documents() {
100        let b = DegradedBackend;
101        let docs = vec![TraceDocument {
102            path: "plan.md".into(),
103            doc_type: DocType::Plan,
104            content_snippet: "goals".into(),
105        }];
106        let out = b.synthesize_context(&docs, &[]).unwrap();
107        assert!(out.contains("plan.md"));
108    }
109}