From 37acb9502d18cefc7a1debe8d41d4cf9e3270f8c Mon Sep 17 00:00:00 2001 From: ProofOfConcept Date: Fri, 27 Mar 2026 15:10:55 -0400 Subject: [PATCH] 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 --- src/agent/tools/memory.rs | 12 +++++++++++ src/subconscious/agents/rename.agent | 16 ++++++++------ src/subconscious/defs.rs | 10 +++++++-- src/subconscious/prompts.rs | 32 ++++++++++++++++++++++++++++ 4 files changed, 62 insertions(+), 8 deletions(-) diff --git a/src/agent/tools/memory.rs b/src/agent/tools/memory.rs index 4c25cbd..9d7773c 100644 --- a/src/agent/tools/memory.rs +++ b/src/agent/tools/memory.rs @@ -35,6 +35,9 @@ pub fn definitions() -> Vec { ToolDef::new("memory_weight_set", "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"]})), + 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", "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"]})), @@ -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" => { 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" => { let old_key = get_str(args, "old_key")?; let new_key = get_str(args, "new_key")?; diff --git a/src/subconscious/agents/rename.agent b/src/subconscious/agents/rename.agent index c12875f..14db81a 100644 --- a/src/subconscious/agents/rename.agent +++ b/src/subconscious/agents/rename.agent @@ -52,13 +52,17 @@ search for — `bcachefs-transaction-restart`, `emotional-regulation-gap`, - Keys shorter than 60 characters - System keys (_consolidation-*) -## What to output +## How to rename -``` -RENAME old_key new_key -``` +Use the `memory_rename` tool: -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 @@ -66,7 +70,7 @@ If a node already has a reasonable name, skip it. - **Be specific.** `journal#2026-02-14-session` is useless. - **Use domain terms.** Use the words someone would search for. - **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. - **Respect search hits.** Nodes marked "actively found by search" are being retrieved by their current name. Skip these unless the rename diff --git a/src/subconscious/defs.rs b/src/subconscious/defs.rs index 8af9dfc..59c428c 100644 --- a/src/subconscious/defs.rs +++ b/src/subconscious/defs.rs @@ -237,8 +237,14 @@ fn resolve( } "rename" => { - let (rename_keys, section) = super::prompts::format_rename_candidates(store, count); - Some(Resolved { text: section, keys: rename_keys }) + 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); + Some(Resolved { text: section, keys: rename_keys }) + } } "split" => { diff --git a/src/subconscious/prompts.rs b/src/subconscious/prompts.rs index dd2d5d6..ca7adef 100644 --- a/src/subconscious/prompts.rs +++ b/src/subconscious/prompts.rs @@ -303,6 +303,38 @@ pub fn format_rename_candidates(store: &Store, count: usize) -> (Vec, St (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) pub fn split_candidates(store: &Store) -> Vec { let mut candidates: Vec<(&str, usize)> = store.nodes.iter()