add {{conversation}} and {{seen_recent}} placeholders for surface agent

{{conversation}} reads POC_SESSION_ID, finds the transcript, extracts
the last segment (post-compaction), returns the tail ~100K chars.

{{seen_recent}} merges current + prev seen files for the session,
returns the 20 most recently surfaced memory keys with timestamps.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kent Overstreet 2026-03-22 02:27:43 -04:00
parent 85307fd6cb
commit 4183b28b1d

View file

@ -416,10 +416,118 @@ fn resolve(
})
}
// conversation — tail of the current session transcript (post-compaction)
"conversation" => {
let text = resolve_conversation();
if text.is_empty() { None }
else { Some(Resolved { text, keys: vec![] }) }
}
// seen_recent — recently surfaced memory keys for this session
"seen_recent" => {
let text = resolve_seen_recent();
Some(Resolved { text, keys: vec![] })
}
_ => None,
}
}
/// Get the tail of the current session's conversation.
/// Reads POC_SESSION_ID to find the transcript, extracts the last
/// segment (post-compaction), returns the tail (~100K chars).
fn resolve_conversation() -> String {
let session_id = std::env::var("POC_SESSION_ID").unwrap_or_default();
if session_id.is_empty() { return String::new(); }
let projects = crate::config::get().projects_dir.clone();
// Find the transcript file matching this session
let mut transcript = None;
if let Ok(dirs) = std::fs::read_dir(&projects) {
for dir in dirs.filter_map(|e| e.ok()) {
let path = dir.path().join(format!("{}.jsonl", session_id));
if path.exists() {
transcript = Some(path);
break;
}
}
}
let Some(path) = transcript else { return String::new() };
let path_str = path.to_string_lossy();
let messages = match super::enrich::extract_conversation(&path_str) {
Ok(m) => m,
Err(_) => return String::new(),
};
// Take the last segment (post-compaction)
let segments = super::enrich::split_on_compaction(messages);
let Some(segment) = segments.last() else { return String::new() };
// Format and take the tail
let cfg = crate::config::get();
let mut text = String::new();
for (_, role, content, ts) in segment {
let name = if role == "user" { &cfg.user_name } else { &cfg.assistant_name };
if !ts.is_empty() {
text.push_str(&format!("**{}** {}: {}\n\n", name, &ts[..ts.len().min(19)], content));
} else {
text.push_str(&format!("**{}:** {}\n\n", name, content));
}
}
// Tail: keep last ~100K chars
const MAX_CHARS: usize = 100_000;
if text.len() > MAX_CHARS {
// Find a clean line break near the cut point
let start = text.len() - MAX_CHARS;
let start = text[start..].find('\n').map(|i| start + i + 1).unwrap_or(start);
text[start..].to_string()
} else {
text
}
}
/// Get recently surfaced memory keys for the current session.
fn resolve_seen_recent() -> String {
let session_id = std::env::var("POC_SESSION_ID").unwrap_or_default();
if session_id.is_empty() {
return "(no session ID — cannot load seen set)".to_string();
}
let state_dir = std::path::PathBuf::from("/tmp/claude-memory-search");
let mut entries: Vec<(String, String)> = Vec::new();
for suffix in ["", "-prev"] {
let path = state_dir.join(format!("seen{}-{}", suffix, session_id));
if let Ok(content) = std::fs::read_to_string(&path) {
entries.extend(
content.lines()
.filter(|s| !s.is_empty())
.filter_map(|line| {
let (ts, key) = line.split_once('\t')?;
Some((ts.to_string(), key.to_string()))
})
);
}
}
if entries.is_empty() {
return "(no memories surfaced yet this session)".to_string();
}
// Sort newest first, dedup
entries.sort_by(|a, b| b.0.cmp(&a.0));
let mut seen = std::collections::HashSet::new();
let recent: Vec<String> = entries.into_iter()
.filter(|(_, key)| seen.insert(key.clone()))
.take(20)
.map(|(ts, key)| format!("- {} (surfaced {})", key, ts))
.collect();
recent.join("\n")
}
/// Resolve all {{placeholder}} patterns in a prompt template.
/// Returns the resolved text and all node keys collected from placeholders.
pub fn resolve_placeholders(