mirror of
https://github.com/samvallad33/vestige.git
synced 2026-05-08 07:12:37 +02:00
Retention Target System: auto-GC low-retention memories during consolidation (VESTIGE_RETENTION_TARGET env var, default 0.8). Auto-Promote: memories accessed 3+ times in 24h get frequency-dependent potentiation. Waking SWR Tagging: promoted memories get preferential 70/30 dream replay. Improved Consolidation Scheduler: triggers on 6h staleness or 2h active use. New tools: memory_health (retention dashboard with distribution buckets, trend tracking, recommendations) and memory_graph (subgraph export with Fruchterman-Reingold force-directed layout, up to 200 nodes). Dream connections now persist to database via save_connection(), enabling memory_graph traversal. Schema Migration V8 adds waking_tag, utility_score, times_retrieved/useful columns and retention_snapshots table. 21 MCP tools. v1.9.1 fixes: ConnectionRecord export, UTF-8 safe truncation, link_type normalization, utility_score clamping, only-new-connections persistence, 70/30 split capacity fill, nonexistent center_id error handling. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
172 lines
5.7 KiB
Rust
172 lines
5.7 KiB
Rust
//! Context-Dependent Memory Tool (Deprecated)
|
|
//!
|
|
//! Retrieval based on encoding context match.
|
|
//! Based on Tulving & Thomson's Encoding Specificity Principle (1973).
|
|
|
|
use chrono::Utc;
|
|
use serde_json::Value;
|
|
use std::sync::Arc;
|
|
|
|
|
|
use vestige_core::{RecallInput, SearchMode, Storage};
|
|
|
|
/// Input schema for match_context tool
|
|
pub fn schema() -> Value {
|
|
serde_json::json!({
|
|
"type": "object",
|
|
"properties": {
|
|
"query": {
|
|
"type": "string",
|
|
"description": "Search query for content matching"
|
|
},
|
|
"topics": {
|
|
"type": "array",
|
|
"items": { "type": "string" },
|
|
"description": "Active topics in current context"
|
|
},
|
|
"project": {
|
|
"type": "string",
|
|
"description": "Current project name"
|
|
},
|
|
"mood": {
|
|
"type": "string",
|
|
"enum": ["positive", "negative", "neutral"],
|
|
"description": "Current emotional state"
|
|
},
|
|
"time_weight": {
|
|
"type": "number",
|
|
"description": "Weight for temporal context (0.0-1.0, default: 0.3)"
|
|
},
|
|
"topic_weight": {
|
|
"type": "number",
|
|
"description": "Weight for topical context (0.0-1.0, default: 0.4)"
|
|
},
|
|
"limit": {
|
|
"type": "integer",
|
|
"description": "Maximum results (default: 10)"
|
|
}
|
|
},
|
|
"required": ["query"]
|
|
})
|
|
}
|
|
|
|
pub async fn execute(
|
|
storage: &Arc<Storage>,
|
|
args: Option<Value>,
|
|
) -> Result<Value, String> {
|
|
let args = args.ok_or("Missing arguments")?;
|
|
|
|
let query = args["query"]
|
|
.as_str()
|
|
.ok_or("query is required")?;
|
|
|
|
let topics: Vec<String> = args["topics"]
|
|
.as_array()
|
|
.map(|arr| arr.iter().filter_map(|v| v.as_str().map(String::from)).collect())
|
|
.unwrap_or_default();
|
|
|
|
let project = args["project"].as_str().map(String::from);
|
|
let mood = args["mood"].as_str().unwrap_or("neutral");
|
|
|
|
let time_weight = args["time_weight"].as_f64().unwrap_or(0.3);
|
|
let topic_weight = args["topic_weight"].as_f64().unwrap_or(0.4);
|
|
|
|
let limit = args["limit"].as_i64().unwrap_or(10) as i32;
|
|
|
|
let now = Utc::now();
|
|
|
|
// Get candidate memories
|
|
let recall_input = RecallInput {
|
|
query: query.to_string(),
|
|
limit: limit * 2, // Get more, then filter
|
|
min_retention: 0.0,
|
|
search_mode: SearchMode::Hybrid,
|
|
valid_at: None,
|
|
};
|
|
let candidates = storage.recall(recall_input)
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
// Score by context match (simplified implementation)
|
|
let mut scored_results: Vec<_> = candidates.into_iter()
|
|
.map(|mem| {
|
|
// Calculate context score based on:
|
|
// 1. Temporal proximity (how recent)
|
|
let hours_ago = (now - mem.created_at).num_hours() as f64;
|
|
let temporal_score = 1.0 / (1.0 + hours_ago / 24.0); // Decay over days
|
|
|
|
// 2. Tag overlap with topics
|
|
let tag_overlap = if topics.is_empty() {
|
|
0.5 // Neutral if no topics specified
|
|
} else {
|
|
let matching = mem.tags.iter()
|
|
.filter(|t| topics.iter().any(|topic| topic.to_lowercase().contains(&t.to_lowercase())))
|
|
.count();
|
|
matching as f64 / topics.len().max(1) as f64
|
|
};
|
|
|
|
// 3. Project match
|
|
let project_score = match (&project, &mem.source) {
|
|
(Some(p), Some(s)) if s.to_lowercase().contains(&p.to_lowercase()) => 1.0,
|
|
(Some(_), None) => 0.0,
|
|
(None, _) => 0.5,
|
|
_ => 0.3,
|
|
};
|
|
|
|
// 4. Emotional match (simplified)
|
|
let mood_score = match mood {
|
|
"positive" if mem.sentiment_score > 0.0 => 0.8,
|
|
"negative" if mem.sentiment_score < 0.0 => 0.8,
|
|
"neutral" if mem.sentiment_score.abs() < 0.3 => 0.8,
|
|
_ => 0.5,
|
|
};
|
|
|
|
// Combine scores
|
|
let context_score = temporal_score * time_weight
|
|
+ tag_overlap * topic_weight
|
|
+ project_score * 0.2
|
|
+ mood_score * 0.1;
|
|
|
|
let combined_score = mem.retention_strength * 0.5 + context_score * 0.5;
|
|
|
|
(mem, context_score, combined_score)
|
|
})
|
|
.collect();
|
|
|
|
// Sort by combined score (handle NaN safely)
|
|
scored_results.sort_by(|a, b| b.2.partial_cmp(&a.2).unwrap_or(std::cmp::Ordering::Equal));
|
|
scored_results.truncate(limit as usize);
|
|
|
|
let results: Vec<Value> = scored_results.into_iter()
|
|
.map(|(mem, ctx_score, combined)| {
|
|
serde_json::json!({
|
|
"id": mem.id,
|
|
"content": mem.content,
|
|
"retentionStrength": mem.retention_strength,
|
|
"contextScore": ctx_score,
|
|
"combinedScore": combined,
|
|
"tags": mem.tags,
|
|
"createdAt": mem.created_at.to_rfc3339()
|
|
})
|
|
})
|
|
.collect();
|
|
|
|
Ok(serde_json::json!({
|
|
"success": true,
|
|
"query": query,
|
|
"currentContext": {
|
|
"topics": topics,
|
|
"project": project,
|
|
"mood": mood
|
|
},
|
|
"weights": {
|
|
"temporal": time_weight,
|
|
"topical": topic_weight
|
|
},
|
|
"resultCount": results.len(),
|
|
"results": results,
|
|
"science": {
|
|
"theory": "Encoding Specificity Principle (Tulving & Thomson, 1973)",
|
|
"principle": "Memory retrieval is most effective when retrieval context matches encoding context"
|
|
}
|
|
}))
|
|
}
|