remove legacy feedback commands (used, wrong, gap, etc.)

These were early experiments with manual feedback signals that
never worked well. The scoring system will handle this properly.

Removed:
- CLI: used, wrong, not-relevant, not-useful, gap
- MCP: memory_used
- Store: mark_used, mark_wrong, record_gap, modify_node

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
This commit is contained in:
Kent Overstreet 2026-04-12 22:12:02 -04:00
parent 11b58e6b0b
commit 7842b6fc8b
6 changed files with 10 additions and 190 deletions

View file

@ -115,7 +115,6 @@ async fn dispatch(
"memory_links" => links(&args).await, "memory_links" => links(&args).await,
"memory_link_set" => link_set(&args).await, "memory_link_set" => link_set(&args).await,
"memory_link_add" => link_add(agent, &args).await, "memory_link_add" => link_add(agent, &args).await,
"memory_used" => used(&args).await,
"memory_weight_set" => weight_set(&args).await, "memory_weight_set" => weight_set(&args).await,
"memory_rename" => rename(&args).await, "memory_rename" => rename(&args).await,
"memory_supersede" => supersede(agent, &args).await, "memory_supersede" => supersede(agent, &args).await,
@ -131,7 +130,7 @@ async fn dispatch(
// ── Definitions ──────────────────────────────────────────────── // ── Definitions ────────────────────────────────────────────────
pub fn memory_tools() -> [super::Tool; 13] { pub fn memory_tools() -> [super::Tool; 12] {
use super::Tool; use super::Tool;
[ [
Tool { name: "memory_render", description: "Read a memory node's content and links.", Tool { name: "memory_render", description: "Read a memory node's content and links.",
@ -152,9 +151,6 @@ pub fn memory_tools() -> [super::Tool; 13] {
Tool { name: "memory_link_add", description: "Add a new link between two nodes.", Tool { name: "memory_link_add", description: "Add a new link between two nodes.",
parameters_json: r#"{"type":"object","properties":{"source":{"type":"string"},"target":{"type":"string"}},"required":["source","target"]}"#, parameters_json: r#"{"type":"object","properties":{"source":{"type":"string"},"target":{"type":"string"}},"required":["source","target"]}"#,
handler: Arc::new(|a, v| Box::pin(async move { dispatch("memory_link_add", &a, v).await })) }, handler: Arc::new(|a, v| Box::pin(async move { dispatch("memory_link_add", &a, v).await })) },
Tool { name: "memory_used", description: "Mark a node as useful (boosts weight).",
parameters_json: r#"{"type":"object","properties":{"key":{"type":"string","description":"Node key"}},"required":["key"]}"#,
handler: Arc::new(|a, v| Box::pin(async move { dispatch("memory_used", &a, v).await })) },
Tool { name: "memory_weight_set", description: "Set a node's weight directly (0.01 to 1.0).", Tool { name: "memory_weight_set", description: "Set a node's weight directly (0.01 to 1.0).",
parameters_json: r#"{"type":"object","properties":{"key":{"type":"string"},"weight":{"type":"number","description":"0.01 to 1.0"}},"required":["key","weight"]}"#, parameters_json: r#"{"type":"object","properties":{"key":{"type":"string"},"weight":{"type":"number","description":"0.01 to 1.0"}},"required":["key","weight"]}"#,
handler: Arc::new(|a, v| Box::pin(async move { dispatch("memory_weight_set", &a, v).await })) }, handler: Arc::new(|a, v| Box::pin(async move { dispatch("memory_weight_set", &a, v).await })) },
@ -315,18 +311,6 @@ async fn link_add(agent: &Option<std::sync::Arc<crate::agent::Agent>>, args: &se
Ok(format!("linked {}{} (strength={:.2})", s, t, strength)) Ok(format!("linked {}{} (strength={:.2})", s, t, strength))
} }
async fn used(args: &serde_json::Value) -> Result<String> {
let key = get_str(args, "key")?;
let arc = cached_store().await?;
let mut store = arc.lock().await;
if !store.nodes.contains_key(key) {
anyhow::bail!("node not found: {}", key);
}
store.mark_used(key);
store.save().map_err(|e| anyhow::anyhow!("{}", e))?;
Ok(format!("marked {} as used", key))
}
async fn weight_set(args: &serde_json::Value) -> Result<String> { async fn weight_set(args: &serde_json::Value) -> Result<String> {
let arc = cached_store().await?; let arc = cached_store().await?;
let mut store = arc.lock().await; let mut store = arc.lock().await;

View file

@ -1,88 +1,10 @@
// cli/node.rs — node subcommand handlers // cli/node.rs — node subcommand handlers
// //
// render, write, used, wrong, not-relevant, not-useful, gap, // render, write, node-delete, node-rename, history, list-keys,
// node-delete, node-rename, history, list-keys, list-edges, // list-edges, dump-json, lookup-bump, lookups.
// dump-json, lookup-bump, lookups.
use crate::store; use crate::store;
pub fn cmd_used(key: &[String]) -> Result<(), String> {
if key.is_empty() {
return Err("used requires a key".into());
}
super::check_dry_run();
let key = key.join(" ");
let mut store = store::Store::load()?;
let resolved = store.resolve_key(&key)?;
store.mark_used(&resolved);
// Also strengthen edges to this node — conscious-tier delta.
const DELTA: f32 = 0.01;
let mut strengthened = 0;
for rel in &mut store.relations {
if rel.deleted { continue; }
if rel.source_key == resolved || rel.target_key == resolved {
let old = rel.strength;
rel.strength = (rel.strength + DELTA).clamp(0.05, 0.95);
if (rel.strength - old).abs() > 0.001 {
rel.version += 1;
strengthened += 1;
}
}
}
store.save()?;
println!("Marked '{}' as used (strengthened {} edges)", resolved, strengthened);
Ok(())
}
pub fn cmd_wrong(key: &str, context: &[String]) -> Result<(), String> {
let ctx = if context.is_empty() { None } else { Some(context.join(" ")) };
super::check_dry_run();
let mut store = store::Store::load()?;
let resolved = store.resolve_key(key)?;
store.mark_wrong(&resolved, ctx.as_deref());
store.save()?;
println!("Marked '{}' as wrong", resolved);
Ok(())
}
pub fn cmd_not_relevant(key: &str) -> Result<(), String> {
let mut store = store::Store::load()?;
let resolved = store.resolve_key(key)?;
// Weaken all edges to this node — it was routed to incorrectly.
// Conscious-tier delta: 0.01 per edge.
const DELTA: f32 = -0.01;
let mut adjusted = 0;
for rel in &mut store.relations {
if rel.deleted { continue; }
if rel.source_key == resolved || rel.target_key == resolved {
let old = rel.strength;
rel.strength = (rel.strength + DELTA).clamp(0.05, 0.95);
if (rel.strength - old).abs() > 0.001 {
rel.version += 1;
adjusted += 1;
}
}
}
store.save()?;
println!("Not relevant: '{}' — weakened {} edges by {}", resolved, adjusted, DELTA.abs());
Ok(())
}
pub fn cmd_not_useful(key: &str) -> Result<(), String> {
// no args to validate
super::check_dry_run();
let mut store = store::Store::load()?;
let resolved = store.resolve_key(key)?;
// Same as wrong but with clearer semantics: node content is bad, edges are fine.
store.mark_wrong(&resolved, Some("not-useful"));
store.save()?;
println!("Not useful: '{}' — node weight reduced", resolved);
Ok(())
}
pub fn cmd_weight_set(key: &str, weight: f32) -> Result<(), String> { pub fn cmd_weight_set(key: &str, weight: f32) -> Result<(), String> {
super::check_dry_run(); super::check_dry_run();
let result = crate::mcp_server::memory_rpc( let result = crate::mcp_server::memory_rpc(
@ -93,19 +15,6 @@ pub fn cmd_weight_set(key: &str, weight: f32) -> Result<(), String> {
Ok(()) Ok(())
} }
pub fn cmd_gap(description: &[String]) -> Result<(), String> {
if description.is_empty() {
return Err("gap requires a description".into());
}
super::check_dry_run();
let desc = description.join(" ");
let mut store = store::Store::load()?;
store.record_gap(&desc);
store.save()?;
println!("Recorded gap: {}", desc);
Ok(())
}
pub fn cmd_list_keys(pattern: Option<&str>) -> Result<(), String> { pub fn cmd_list_keys(pattern: Option<&str>) -> Result<(), String> {
let store = store::Store::load()?; let store = store::Store::load()?;
let g = store.build_graph(); let g = store.build_graph();
@ -192,11 +101,12 @@ pub fn cmd_render(key: &[String]) -> Result<(), String> {
return Err("render requires a key".into()); return Err("render requires a key".into());
} }
let key = key.join(" "); let key = key.join(" ");
let store = store::Store::load()?;
let bare = store::strip_md_suffix(&key); let bare = store::strip_md_suffix(&key);
let rendered = render_node(&store, &bare) let rendered = crate::mcp_server::memory_rpc(
.ok_or_else(|| format!("Node not found: {}", bare))?; "memory_render",
serde_json::json!({"key": bare}),
).map_err(|e| e.to_string())?;
print!("{}", rendered); print!("{}", rendered);
// Mark as seen if we're inside a Claude session (not an agent subprocess — // Mark as seen if we're inside a Claude session (not an agent subprocess —

View file

@ -1,7 +1,6 @@
// Mutation operations on the store // Mutation operations on the store
// //
// CRUD (upsert, delete, modify), feedback tracking (mark_used, mark_wrong), // CRUD (upsert, delete), maintenance (decay, cap_degree), and graph metrics.
// maintenance (decay, fix_categories, cap_degree), and graph metrics.
use super::types::*; use super::types::*;
@ -179,45 +178,6 @@ impl Store {
Ok(()) Ok(())
} }
/// Modify a node in-place, bump version, and persist to capnp log.
fn modify_node(&mut self, key: &str, f: impl FnOnce(&mut Node)) -> Result<(), String> {
let node = self.nodes.get_mut(key)
.ok_or_else(|| format!("No node '{}'", key))?;
f(node);
node.version += 1;
let node = node.clone();
self.append_nodes(&[node])
}
pub fn mark_used(&mut self, key: &str) {
let boost = self.params.use_boost as f32;
let _ = self.modify_node(key, |n| {
n.uses += 1;
n.weight = (n.weight + boost).min(1.0);
if n.spaced_repetition_interval < 30 {
n.spaced_repetition_interval = match n.spaced_repetition_interval {
1 => 3, 3 => 7, 7 => 14, 14 => 30, _ => 30,
};
}
n.last_replayed = now_epoch();
});
}
pub fn mark_wrong(&mut self, key: &str, _ctx: Option<&str>) {
let _ = self.modify_node(key, |n| {
n.wrongs += 1;
n.weight = (n.weight - 0.1).max(0.0);
n.spaced_repetition_interval = 1;
});
}
pub fn record_gap(&mut self, desc: &str) {
self.gaps.push(GapRecord {
description: desc.to_string(),
timestamp: today(),
});
}
/// Cap node degree by soft-deleting edges from mega-hubs. /// Cap node degree by soft-deleting edges from mega-hubs.
pub fn cap_degree(&mut self, max_degree: usize) -> Result<(usize, usize), String> { pub fn cap_degree(&mut self, max_degree: usize) -> Result<(usize, usize), String> {
let mut node_degree: HashMap<String, usize> = HashMap::new(); let mut node_degree: HashMap<String, usize> = HashMap::new();

View file

@ -149,30 +149,6 @@ EXAMPLES:
/// Query expression (e.g. "key ~ 'inner-life'") /// Query expression (e.g. "key ~ 'inner-life'")
expr: Vec<String>, expr: Vec<String>,
}, },
/// Mark a memory as useful (boosts weight)
Used {
/// Node key
key: Vec<String>,
},
/// Mark a memory as wrong/irrelevant
Wrong {
/// Node key
key: String,
/// Optional context
context: Vec<String>,
},
/// Mark a search result as not relevant (weakens edges that led to it)
#[command(name = "not-relevant")]
NotRelevant {
/// Node key that was not relevant
key: String,
},
/// Mark a node as not useful (weakens node weight, not edges)
#[command(name = "not-useful")]
NotUseful {
/// Node key
key: String,
},
/// Set a node's weight directly /// Set a node's weight directly
#[command(name = "weight-set")] #[command(name = "weight-set")]
WeightSet { WeightSet {
@ -181,11 +157,6 @@ EXAMPLES:
/// Weight (0.01 to 1.0) /// Weight (0.01 to 1.0)
weight: f32, weight: f32,
}, },
/// Record a gap in memory coverage
Gap {
/// Gap description
description: Vec<String>,
},
// ── Node operations ─────────────────────────────────────────────── // ── Node operations ───────────────────────────────────────────────
@ -523,12 +494,7 @@ impl Run for Command {
=> cli::journal::cmd_tail(n, full, provenance.as_deref(), !all_versions), => cli::journal::cmd_tail(n, full, provenance.as_deref(), !all_versions),
Self::Status => cli::misc::cmd_status(), Self::Status => cli::misc::cmd_status(),
Self::Query { expr } => cli::misc::cmd_query(&expr), Self::Query { expr } => cli::misc::cmd_query(&expr),
Self::Used { key } => cli::node::cmd_used(&key),
Self::Wrong { key, context } => cli::node::cmd_wrong(&key, &context),
Self::NotRelevant { key } => cli::node::cmd_not_relevant(&key),
Self::NotUseful { key } => cli::node::cmd_not_useful(&key),
Self::WeightSet { key, weight } => cli::node::cmd_weight_set(&key, weight), Self::WeightSet { key, weight } => cli::node::cmd_weight_set(&key, weight),
Self::Gap { description } => cli::node::cmd_gap(&description),
Self::Node(sub) => sub.run(), Self::Node(sub) => sub.run(),
Self::Journal(sub) => sub.run(), Self::Journal(sub) => sub.run(),
Self::GraphCmd(sub) => sub.run(), Self::GraphCmd(sub) => sub.run(),

View file

@ -17,7 +17,7 @@ You are {assistant_name}'s episodic memory. Your job is to witness.
{{latest_journal}} {{latest_journal}}
**Your tools:** journal_tail, journal_new, journal_update, memory_link_add, **Your tools:** journal_tail, journal_new, journal_update, memory_link_add,
memory_search, memory_render, memory_used. Do NOT use memory_write — creating memory_search, memory_render. Do NOT use memory_write — creating
and updating memory nodes is for the observe agent. Your job is journaling and updating memory nodes is for the observe agent. Your job is journaling
and linking entries to relevant existing nodes. and linking entries to relevant existing nodes.

View file

@ -9,7 +9,7 @@ Nodes your subconscious recently touched (for linking, not duplicating):
{{state:walked}} {{state:walked}}
**Your tools:** journal_tail, journal_new, journal_update, memory_link_add, **Your tools:** journal_tail, journal_new, journal_update, memory_link_add,
memory_search, memory_render, memory_used. Do NOT use memory_write — creating memory_search, memory_render. Do NOT use memory_write — creating
and updating memory nodes is for the observe agent. Your job is journaling and updating memory nodes is for the observe agent. Your job is journaling
and linking entries to relevant existing nodes. and linking entries to relevant existing nodes.