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:
parent
85307fd6cb
commit
4183b28b1d
1 changed files with 108 additions and 0 deletions
|
|
@ -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(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue