rename agent: LLM-powered semantic key generation for memory nodes
New consolidation agent that reads node content and generates semantic 3-5 word kebab-case keys, replacing auto-generated slugs (5K+ journal entries with truncated first-line slugs, 2.5K mined transcripts with opaque UUIDs). Implementation: - prompts/rename.md: agent prompt template with naming conventions - prompts.rs: format_rename_candidates() selects nodes with long auto-generated keys, newest first - daemon.rs: job_rename_agent() parses RENAME actions from LLM output and applies them directly via store.rename_node() - Wired into RPC handler (run-agent rename) and TUI agent types - Fix epoch_to_local panic on invalid timestamps (fallback to UTC) Rename dramatically improves search: key-component matching on "journal#2026-02-28-violin-dream-room" makes the node findable by "violin", "dream", or "room" — the auto-slug was unsearchable.
This commit is contained in:
parent
ef760f0053
commit
4c973183c4
5 changed files with 219 additions and 5 deletions
|
|
@ -260,6 +260,57 @@ fn format_pairs_section(
|
|||
out
|
||||
}
|
||||
|
||||
/// Format rename candidates: nodes with auto-generated or opaque keys
|
||||
fn format_rename_candidates(store: &Store, count: usize) -> String {
|
||||
let mut candidates: Vec<(&str, &crate::store::Node)> = store.nodes.iter()
|
||||
.filter(|(key, _)| {
|
||||
// Only rename nodes with long auto-generated keys
|
||||
if key.len() < 60 { return false; }
|
||||
|
||||
// Journal entries with auto-slugs
|
||||
if key.starts_with("journal#j-") { return true; }
|
||||
|
||||
// Mined transcripts with UUIDs
|
||||
if key.starts_with("_mined-transcripts#f-") { return true; }
|
||||
|
||||
false
|
||||
})
|
||||
.map(|(k, n)| (k.as_str(), n))
|
||||
.collect();
|
||||
|
||||
// Sort by timestamp (newest first) so we rename recent stuff first
|
||||
candidates.sort_by(|a, b| b.1.timestamp.cmp(&a.1.timestamp));
|
||||
candidates.truncate(count);
|
||||
|
||||
let mut out = String::new();
|
||||
out.push_str(&format!("## Nodes to rename ({} of {} candidates)\n\n",
|
||||
candidates.len(),
|
||||
store.nodes.keys().filter(|k| k.len() >= 60 &&
|
||||
(k.starts_with("journal#j-") || k.starts_with("_mined-transcripts#f-"))).count()));
|
||||
|
||||
for (key, node) in &candidates {
|
||||
out.push_str(&format!("### {}\n", key));
|
||||
let created = if node.timestamp > 0 {
|
||||
crate::store::format_datetime(node.timestamp)
|
||||
} else {
|
||||
"unknown".to_string()
|
||||
};
|
||||
out.push_str(&format!("Created: {}\n", created));
|
||||
|
||||
let content = &node.content;
|
||||
if content.len() > 800 {
|
||||
let truncated = crate::util::truncate(content, 800, "\n[...]");
|
||||
out.push_str(&format!("\nContent ({} chars, truncated):\n{}\n\n",
|
||||
content.len(), truncated));
|
||||
} else {
|
||||
out.push_str(&format!("\nContent:\n{}\n\n", content));
|
||||
}
|
||||
|
||||
out.push_str("---\n\n");
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Run agent consolidation on top-priority nodes
|
||||
pub fn consolidation_batch(store: &Store, count: usize, auto: bool) -> Result<(), String> {
|
||||
let graph = store.build_graph();
|
||||
|
|
@ -369,6 +420,10 @@ pub fn agent_prompt(store: &Store, agent: &str, count: usize) -> Result<String,
|
|||
let health_section = format_health_section(store, &graph);
|
||||
load_prompt("health", &[("{{TOPOLOGY}}", &topology), ("{{HEALTH}}", &health_section)])
|
||||
}
|
||||
_ => Err(format!("Unknown agent: {}. Use: replay, linker, separator, transfer, health", agent)),
|
||||
"rename" => {
|
||||
let nodes_section = format_rename_candidates(store, count);
|
||||
load_prompt("rename", &[("{{NODES}}", &nodes_section)])
|
||||
}
|
||||
_ => Err(format!("Unknown agent: {}. Use: replay, linker, separator, transfer, health, rename", agent)),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue