mirror of
https://github.com/samvallad33/vestige.git
synced 2026-05-10 08:12:37 +02:00
Add Prediction Error Gating and smart_ingest tool (26 tools total)
Implements neuroscience-inspired memory gating based on prediction error: - New smart_ingest MCP tool that auto-decides CREATE/UPDATE/SUPERSEDE - PredictionErrorGate evaluates semantic similarity vs existing memories - Automatically supersedes demoted memories with similar new content - Reinforces near-identical memories instead of creating duplicates - Adds promote_memory/demote_memory/request_feedback tools Thresholds: - >0.92 similarity = Reinforce existing - >0.75 similarity = Update/Merge - <0.75 similarity = Create new - Demoted + similar = Auto-supersede Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
5337efdfa7
commit
bbd1c15b4a
12 changed files with 1705 additions and 10 deletions
231
crates/vestige-mcp/src/tools/feedback.rs
Normal file
231
crates/vestige-mcp/src/tools/feedback.rs
Normal file
|
|
@ -0,0 +1,231 @@
|
|||
//! Feedback Tools
|
||||
//!
|
||||
//! Promote and demote memories based on outcome quality.
|
||||
//! Implements preference learning for Vestige.
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde_json::Value;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use vestige_core::Storage;
|
||||
|
||||
/// Input schema for promote_memory tool
|
||||
pub fn promote_schema() -> Value {
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "The ID of the memory to promote"
|
||||
},
|
||||
"reason": {
|
||||
"type": "string",
|
||||
"description": "Why this memory was helpful (optional, for logging)"
|
||||
}
|
||||
},
|
||||
"required": ["id"]
|
||||
})
|
||||
}
|
||||
|
||||
/// Input schema for demote_memory tool
|
||||
pub fn demote_schema() -> Value {
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "The ID of the memory to demote"
|
||||
},
|
||||
"reason": {
|
||||
"type": "string",
|
||||
"description": "Why this memory was unhelpful or wrong (optional, for logging)"
|
||||
}
|
||||
},
|
||||
"required": ["id"]
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct FeedbackArgs {
|
||||
id: String,
|
||||
reason: Option<String>,
|
||||
}
|
||||
|
||||
/// Promote a memory (thumbs up) - it led to a good outcome
|
||||
pub async fn execute_promote(
|
||||
storage: &Arc<Mutex<Storage>>,
|
||||
args: Option<Value>,
|
||||
) -> Result<Value, String> {
|
||||
let args: FeedbackArgs = match args {
|
||||
Some(v) => serde_json::from_value(v).map_err(|e| format!("Invalid arguments: {}", e))?,
|
||||
None => return Err("Missing arguments".to_string()),
|
||||
};
|
||||
|
||||
// Validate UUID
|
||||
uuid::Uuid::parse_str(&args.id).map_err(|_| "Invalid node ID format".to_string())?;
|
||||
|
||||
let storage = storage.lock().await;
|
||||
|
||||
// Get node before for comparison
|
||||
let before = storage.get_node(&args.id).map_err(|e| e.to_string())?
|
||||
.ok_or_else(|| format!("Node not found: {}", args.id))?;
|
||||
|
||||
let node = storage.promote_memory(&args.id).map_err(|e| e.to_string())?;
|
||||
|
||||
Ok(serde_json::json!({
|
||||
"success": true,
|
||||
"action": "promoted",
|
||||
"nodeId": node.id,
|
||||
"reason": args.reason,
|
||||
"changes": {
|
||||
"retrievalStrength": {
|
||||
"before": before.retrieval_strength,
|
||||
"after": node.retrieval_strength,
|
||||
"delta": "+0.20"
|
||||
},
|
||||
"retentionStrength": {
|
||||
"before": before.retention_strength,
|
||||
"after": node.retention_strength,
|
||||
"delta": "+0.10"
|
||||
},
|
||||
"stability": {
|
||||
"before": before.stability,
|
||||
"after": node.stability,
|
||||
"multiplier": "1.5x"
|
||||
}
|
||||
},
|
||||
"message": format!("Memory promoted. It will now surface more often in searches. Retrieval: {:.2} -> {:.2}",
|
||||
before.retrieval_strength, node.retrieval_strength),
|
||||
}))
|
||||
}
|
||||
|
||||
/// Demote a memory (thumbs down) - it led to a bad outcome
|
||||
pub async fn execute_demote(
|
||||
storage: &Arc<Mutex<Storage>>,
|
||||
args: Option<Value>,
|
||||
) -> Result<Value, String> {
|
||||
let args: FeedbackArgs = match args {
|
||||
Some(v) => serde_json::from_value(v).map_err(|e| format!("Invalid arguments: {}", e))?,
|
||||
None => return Err("Missing arguments".to_string()),
|
||||
};
|
||||
|
||||
// Validate UUID
|
||||
uuid::Uuid::parse_str(&args.id).map_err(|_| "Invalid node ID format".to_string())?;
|
||||
|
||||
let storage = storage.lock().await;
|
||||
|
||||
// Get node before for comparison
|
||||
let before = storage.get_node(&args.id).map_err(|e| e.to_string())?
|
||||
.ok_or_else(|| format!("Node not found: {}", args.id))?;
|
||||
|
||||
let node = storage.demote_memory(&args.id).map_err(|e| e.to_string())?;
|
||||
|
||||
Ok(serde_json::json!({
|
||||
"success": true,
|
||||
"action": "demoted",
|
||||
"nodeId": node.id,
|
||||
"reason": args.reason,
|
||||
"changes": {
|
||||
"retrievalStrength": {
|
||||
"before": before.retrieval_strength,
|
||||
"after": node.retrieval_strength,
|
||||
"delta": "-0.30"
|
||||
},
|
||||
"retentionStrength": {
|
||||
"before": before.retention_strength,
|
||||
"after": node.retention_strength,
|
||||
"delta": "-0.15"
|
||||
},
|
||||
"stability": {
|
||||
"before": before.stability,
|
||||
"after": node.stability,
|
||||
"multiplier": "0.5x"
|
||||
}
|
||||
},
|
||||
"message": format!("Memory demoted. Better alternatives will now surface instead. Retrieval: {:.2} -> {:.2}",
|
||||
before.retrieval_strength, node.retrieval_strength),
|
||||
"note": "Memory is NOT deleted - it remains searchable but ranks lower."
|
||||
}))
|
||||
}
|
||||
|
||||
/// Input schema for request_feedback tool
|
||||
pub fn request_feedback_schema() -> Value {
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "The ID of the memory to request feedback on"
|
||||
},
|
||||
"context": {
|
||||
"type": "string",
|
||||
"description": "What the memory was used for (e.g., 'error handling advice')"
|
||||
}
|
||||
},
|
||||
"required": ["id"]
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct RequestFeedbackArgs {
|
||||
id: String,
|
||||
context: Option<String>,
|
||||
}
|
||||
|
||||
/// Request feedback from the user about a memory's usefulness
|
||||
/// Returns a structured prompt for Claude to ask the user
|
||||
pub async fn execute_request_feedback(
|
||||
storage: &Arc<Mutex<Storage>>,
|
||||
args: Option<Value>,
|
||||
) -> Result<Value, String> {
|
||||
let args: RequestFeedbackArgs = match args {
|
||||
Some(v) => serde_json::from_value(v).map_err(|e| format!("Invalid arguments: {}", e))?,
|
||||
None => return Err("Missing arguments".to_string()),
|
||||
};
|
||||
|
||||
// Validate UUID
|
||||
uuid::Uuid::parse_str(&args.id).map_err(|_| "Invalid node ID format".to_string())?;
|
||||
|
||||
let storage = storage.lock().await;
|
||||
|
||||
let node = storage.get_node(&args.id).map_err(|e| e.to_string())?
|
||||
.ok_or_else(|| format!("Node not found: {}", args.id))?;
|
||||
|
||||
// Truncate content for display
|
||||
let preview: String = node.content.chars().take(100).collect();
|
||||
let preview = if node.content.len() > 100 {
|
||||
format!("{}...", preview)
|
||||
} else {
|
||||
preview
|
||||
};
|
||||
|
||||
Ok(serde_json::json!({
|
||||
"action": "request_feedback",
|
||||
"nodeId": node.id,
|
||||
"memoryPreview": preview,
|
||||
"context": args.context,
|
||||
"prompt": "Was this memory helpful?",
|
||||
"options": [
|
||||
{
|
||||
"key": "A",
|
||||
"label": "Yes, helpful",
|
||||
"action": "promote",
|
||||
"description": "Memory will surface more often"
|
||||
},
|
||||
{
|
||||
"key": "B",
|
||||
"label": "No, wrong/outdated",
|
||||
"action": "demote",
|
||||
"description": "Better alternatives will surface instead"
|
||||
},
|
||||
{
|
||||
"key": "C",
|
||||
"label": "Ask Claude...",
|
||||
"action": "custom",
|
||||
"description": "Give Claude a custom instruction (e.g., 'update this memory', 'merge with X', 'add tag Y')"
|
||||
}
|
||||
],
|
||||
"instruction": "PRESENT THESE OPTIONS TO THE USER. If they choose A, call promote_memory. If B, call demote_memory. If C, they will provide a custom instruction - execute it (could be: update the memory content, delete it, merge it, add tags, research something, etc.)."
|
||||
}))
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue