Skip to main content

agent_memgc/rules/
cache_layout.rs

1use crate::domain::{InstructionBlock, LinterFinding, RuleCode, Severity};
2use regex::Regex;
3
4/// Check if volatile dynamic data sits at the prefix of instructions, breaking KV-cache prefix hits.
5pub fn check_cache_layout(blocks: &[InstructionBlock]) -> Vec<LinterFinding> {
6    let mut findings = Vec::new();
7
8    // Inspect first 8 non-empty instruction blocks
9    let volatile_patterns = [
10        ("timestamp", r"\b\d{4}-\d{2}-\d{2}\b"),
11        ("time", r"\b\d{2}:\d{2}:\d{2}\b"),
12        ("session_id", r"(?i)session[_\s\-]?id"),
13        ("last_updated", r"(?i)last[_\s\-]updated"),
14        ("date_marker", r"(?i)date:\s*"),
15    ];
16
17    for block in blocks.iter().take(8) {
18        let text = &block.text;
19        // Skip title heading
20        if text.starts_with("# ") {
21            continue;
22        }
23
24        for (label, pattern) in &volatile_patterns {
25            if let Ok(re) = Regex::new(pattern) {
26                if re.is_match(text) {
27                    findings.push(LinterFinding {
28                        rule: RuleCode::MGC003,
29                        severity: Severity::Info,
30                        line: block.line_start,
31                        target: format!("Volatile pattern: {label}"),
32                        message: format!(
33                            "Volatile dynamic content at line {} breaks prompt prefix caching.",
34                            block.line_start
35                        ),
36                        suggestion: Some(
37                            "Move dynamic timestamps, session IDs, and dates to the end of the prompt or into user messages so the static instruction prefix caches across turns."
38                                .to_string(),
39                        ),
40                        fixable: true,
41                    });
42                    break;
43                }
44            }
45        }
46    }
47
48    findings
49}