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:
parent
bb2e3b9fbb
commit
37acb9502d
4 changed files with 62 additions and 8 deletions
|
|
@ -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")?;
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -237,9 +237,15 @@ fn resolve(
|
||||||
}
|
}
|
||||||
|
|
||||||
"rename" => {
|
"rename" => {
|
||||||
|
if !keys.is_empty() {
|
||||||
|
// --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);
|
let (rename_keys, section) = super::prompts::format_rename_candidates(store, count);
|
||||||
Some(Resolved { text: section, keys: rename_keys })
|
Some(Resolved { text: section, keys: rename_keys })
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
"split" => {
|
"split" => {
|
||||||
let key = keys.first()?;
|
let key = keys.first()?;
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue