Detect backtracking: the agent reads/edits the same file multiple times. Pattern: any file appears in both an exploration AND a commitment more than once in the sequence, suggesting the agent revised its approach.
(
nodes: &[GraphNode],
seq: &[usize],
counter: &mut u64,
prefix: &str,
)
| 190 | /// Pattern: any file appears in both an exploration AND a commitment more |
| 191 | /// than once in the sequence, suggesting the agent revised its approach. |
| 192 | fn detect_backtracking( |
| 193 | nodes: &[GraphNode], |
| 194 | seq: &[usize], |
| 195 | counter: &mut u64, |
| 196 | prefix: &str, |
| 197 | ) -> Option<(GraphNode, Vec<GraphEdge>)> { |
| 198 | let mut file_edit_counts: std::collections::HashMap<String, u32> = |
| 199 | std::collections::HashMap::new(); |
| 200 | |
| 201 | for &idx in seq { |
| 202 | let node = &nodes[idx]; |
| 203 | if node.kind == NodeKind::Commitment { |
| 204 | if let Some(file) = extract_file(node) { |
| 205 | *file_edit_counts.entry(file).or_default() += 1; |
| 206 | } |
| 207 | } |
| 208 | } |
| 209 | |
| 210 | // Need at least one file edited more than once |
| 211 | let repeated: Vec<(&String, &u32)> = file_edit_counts |
| 212 | .iter() |
| 213 | .filter(|(_, count)| **count > 1) |
| 214 | .collect(); |
| 215 | |
| 216 | if repeated.is_empty() { |
| 217 | return None; |
| 218 | } |
| 219 | |
| 220 | let max_file = repeated |
| 221 | .iter() |
| 222 | .max_by_key(|(_, c)| *c) |
| 223 | .map(|(f, _)| f.to_string()) |
| 224 | .unwrap_or_default(); |
| 225 | let max_count = file_edit_counts.get(&max_file).copied().unwrap_or(0); |
| 226 | |
| 227 | let summary = format!( |
| 228 | "Iterated on {} ({} attempts)", |
| 229 | short_path(&max_file), |
| 230 | max_count |
| 231 | ); |
| 232 | |
| 233 | let consolidated_ids: Vec<String> = seq.iter().map(|&i| nodes[i].id.clone()).collect(); |
| 234 | |
| 235 | let detail = serde_json::json!({ |
| 236 | "pattern": "backtracking", |
| 237 | "iterations": max_count, |
| 238 | "file": max_file, |
| 239 | "files_revisited": repeated.iter().map(|(f, c)| { |
| 240 | serde_json::json!({"file": f, "edits": c}) |
| 241 | }).collect::<Vec<_>>(), |
| 242 | }); |
| 243 | |
| 244 | let id = next_id(counter, prefix); |
| 245 | let timestamp = seq.last().map(|&i| nodes[i].timestamp).unwrap_or_default(); |
| 246 | |
| 247 | let mut node = GraphNode::new(&id, NodeKind::Decision, timestamp, summary).with_detail(detail); |
| 248 | node.classified = true; |
| 249 | node.confidence = Some(0.85); |
no test coverage detected