rename agent: fix tool calls and target override

- Add memory_rename tool (in-place rename, preserves content and links)
- Update rename.agent prompt to use memory_rename() instead of text output
- Fix {{rename}} placeholder to respect --target keys when provided
- Add format_rename_targets() for targeted rename runs
This commit is contained in:
ProofOfConcept 2026-03-27 15:10:55 -04:00
parent bb2e3b9fbb
commit 37acb9502d
4 changed files with 62 additions and 8 deletions

View file

@ -35,6 +35,9 @@ pub fn definitions() -> Vec<ToolDef> {
ToolDef::new("memory_weight_set", ToolDef::new("memory_weight_set",
"Set a node's weight directly (0.01 to 1.0).", "Set a node's weight directly (0.01 to 1.0).",
json!({"type":"object","properties":{"key":{"type":"string"},"weight":{"type":"number","description":"0.01 to 1.0"}},"required":["key","weight"]})), json!({"type":"object","properties":{"key":{"type":"string"},"weight":{"type":"number","description":"0.01 to 1.0"}},"required":["key","weight"]})),
ToolDef::new("memory_rename",
"Rename a node key in place. Same content, same links, new key.",
json!({"type":"object","properties":{"old_key":{"type":"string"},"new_key":{"type":"string"}},"required":["old_key","new_key"]})),
ToolDef::new("memory_supersede", ToolDef::new("memory_supersede",
"Mark a node as superseded by another (sets weight to 0.01).", "Mark a node as superseded by another (sets weight to 0.01).",
json!({"type":"object","properties":{"old_key":{"type":"string"},"new_key":{"type":"string"},"reason":{"type":"string"}},"required":["old_key","new_key"]})), json!({"type":"object","properties":{"old_key":{"type":"string"},"new_key":{"type":"string"},"reason":{"type":"string"}},"required":["old_key","new_key"]})),
@ -116,6 +119,15 @@ pub fn dispatch(name: &str, args: &serde_json::Value, provenance: Option<&str>)
"memory_link_set" | "memory_link_add" | "memory_used" | "memory_weight_set" => { "memory_link_set" | "memory_link_add" | "memory_used" | "memory_weight_set" => {
with_store(name, args, prov) with_store(name, args, prov)
} }
"memory_rename" => {
let old_key = get_str(args, "old_key")?;
let new_key = get_str(args, "new_key")?;
let mut store = Store::load().map_err(|e| anyhow::anyhow!("{}", e))?;
let resolved = store.resolve_key(old_key).map_err(|e| anyhow::anyhow!("{}", e))?;
store.rename_node(&resolved, new_key).map_err(|e| anyhow::anyhow!("{}", e))?;
store.save().map_err(|e| anyhow::anyhow!("{}", e))?;
Ok(format!("Renamed '{}' → '{}'", resolved, new_key))
}
"memory_supersede" => { "memory_supersede" => {
let old_key = get_str(args, "old_key")?; let old_key = get_str(args, "old_key")?;
let new_key = get_str(args, "new_key")?; let new_key = get_str(args, "new_key")?;

View file

@ -52,13 +52,17 @@ search for — `bcachefs-transaction-restart`, `emotional-regulation-gap`,
- Keys shorter than 60 characters - Keys shorter than 60 characters
- System keys (_consolidation-*) - System keys (_consolidation-*)
## What to output ## How to rename
``` Use the `memory_rename` tool:
RENAME old_key new_key
```
If a node already has a reasonable name, skip it. memory_rename(old_key, new_key)
This renames the node in place — same content, same links, new key.
Do NOT use `memory_write` or `memory_supersede` — just rename.
If a node already has a reasonable name, skip it. When in doubt, skip.
A bad rename is worse than an auto-slug.
## Guidelines ## Guidelines
@ -66,7 +70,7 @@ If a node already has a reasonable name, skip it.
- **Be specific.** `journal#2026-02-14-session` is useless. - **Be specific.** `journal#2026-02-14-session` is useless.
- **Use domain terms.** Use the words someone would search for. - **Use domain terms.** Use the words someone would search for.
- **Don't rename to something longer than the original.** - **Don't rename to something longer than the original.**
- **Preserve the date.** Always keep YYYY-MM-DD. - **Preserve the date.** Always keep YYYY-MM-DD for journal entries.
- **When in doubt, skip.** A bad rename is worse than an auto-slug. - **When in doubt, skip.** A bad rename is worse than an auto-slug.
- **Respect search hits.** Nodes marked "actively found by search" are - **Respect search hits.** Nodes marked "actively found by search" are
being retrieved by their current name. Skip these unless the rename being retrieved by their current name. Skip these unless the rename

View file

@ -237,8 +237,14 @@ fn resolve(
} }
"rename" => { "rename" => {
let (rename_keys, section) = super::prompts::format_rename_candidates(store, count); if !keys.is_empty() {
Some(Resolved { text: section, keys: rename_keys }) // --target provided: present those keys as candidates
let section = super::prompts::format_rename_targets(store, keys);
Some(Resolved { text: section, keys: vec![] })
} else {
let (rename_keys, section) = super::prompts::format_rename_candidates(store, count);
Some(Resolved { text: section, keys: rename_keys })
}
} }
"split" => { "split" => {

View file

@ -303,6 +303,38 @@ pub fn format_rename_candidates(store: &Store, count: usize) -> (Vec<String>, St
(keys, out) (keys, out)
} }
/// Format specific target keys as rename candidates (for --target mode)
pub fn format_rename_targets(store: &Store, keys: &[String]) -> String {
let mut out = String::new();
out.push_str(&format!("## Nodes to rename ({} targets)\n\n", keys.len()));
for key in keys {
let Some(node) = store.nodes.get(key) else {
out.push_str(&format!("### {}\n\n(node not found)\n\n---\n\n", key));
continue;
};
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
}
/// Get split candidates sorted by size (largest first) /// Get split candidates sorted by size (largest first)
pub fn split_candidates(store: &Store) -> Vec<String> { pub fn split_candidates(store: &Store) -> Vec<String> {
let mut candidates: Vec<(&str, usize)> = store.nodes.iter() let mut candidates: Vec<(&str, usize)> = store.nodes.iter()