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,
|
_ => 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.
|
/// Resolve all {{placeholder}} patterns in a prompt template.
|
||||||
/// Returns the resolved text and all node keys collected from placeholders.
|
/// Returns the resolved text and all node keys collected from placeholders.
|
||||||
pub fn resolve_placeholders(
|
pub fn resolve_placeholders(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue