use parking_lot::RwLock;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::time::{SystemTime, UNIX_EPOCH};
use crate::error::Result;
use crate::types::{Metadata, VectorId};
use crate::Config;
use crate::Filter;
use crate::VectorDB;
pub trait DomainClassifier: Send + Sync + Default {
fn domain_type_name(&self) -> &'static str;
fn available_domains(&self) -> Vec<&'static str>;
fn classify(&self, content: &str) -> String;
fn related(&self, domain1: &str, domain2: &str) -> bool;
fn relatedness_score(&self, domain1: &str, domain2: &str) -> f32 {
if domain1 == domain2 {
1.0
} else if self.related(domain1, domain2) {
0.7
} else {
0.3
}
}
}
pub trait ConceptExtractor: Send + Sync + Default {
fn extract(&self, description: &str, content: &str) -> Vec<String>;
fn is_universal(&self, concept: &str) -> bool;
fn universal_concepts(&self) -> Vec<&'static str>;
}
pub trait ContextMatcher: Send + Sync + Default {
fn context_type_name(&self) -> &'static str;
fn available_contexts(&self) -> Vec<&'static str>;
fn compatibility(&self, context1: &str, context2: &str) -> f32;
fn context_family(&self, context: &str) -> Option<&'static str>;
}
pub trait DomainPreset: Send + Sync + 'static {
type Domain: DomainClassifier;
type Concepts: ConceptExtractor;
type Context: ContextMatcher;
type Priority: PriorityCalculator;
fn name() -> &'static str;
fn description() -> &'static str;
fn default_decay() -> DecayConfig {
DecayConfig::default()
}
fn default_weights() -> PriorityWeights {
PriorityWeights::default()
}
fn create() -> (Self::Domain, Self::Concepts, Self::Context, Self::Priority) {
(
Self::Domain::default(),
Self::Concepts::default(),
Self::Context::default(),
Self::Priority::default(),
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[repr(u8)]
#[derive(Default)]
pub enum TransferLevel {
#[default]
Instance = 1,
Context = 2,
Domain = 3,
Universal = 4,
}
impl TransferLevel {
pub fn as_str(&self) -> &'static str {
match self {
Self::Instance => "instance",
Self::Context => "context",
Self::Domain => "domain",
Self::Universal => "universal",
}
}
pub fn from_str(s: &str) -> Option<Self> {
match s.to_lowercase().as_str() {
"instance" | "project" | "specific" => Some(Self::Instance),
"context" | "stack" | "language" => Some(Self::Context),
"domain" | "area" => Some(Self::Domain),
"universal" | "global" | "always" => Some(Self::Universal),
_ => None,
}
}
pub fn transfer_score(&self) -> f32 {
match self {
Self::Instance => 0.25,
Self::Context => 0.5,
Self::Domain => 0.75,
Self::Universal => 1.0,
}
}
pub fn transfers_to(&self, target: TransferLevel) -> bool {
*self >= target
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[repr(u8)]
#[derive(Default)]
pub enum Priority {
Low = 1,
#[default]
Normal = 2,
High = 3,
Critical = 4,
}
impl Priority {
pub fn as_str(&self) -> &'static str {
match self {
Self::Low => "low",
Self::Normal => "normal",
Self::High => "high",
Self::Critical => "critical",
}
}
pub fn from_str(s: &str) -> Option<Self> {
match s.to_lowercase().as_str() {
"low" | "minor" | "trivial" => Some(Self::Low),
"normal" | "medium" | "default" => Some(Self::Normal),
"high" | "important" | "major" => Some(Self::High),
"critical" | "urgent" | "essential" | "security" => Some(Self::Critical),
_ => None,
}
}
pub fn base_score(&self) -> f32 {
match self {
Self::Low => 0.25,
Self::Normal => 0.5,
Self::High => 0.75,
Self::Critical => 1.0,
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct UsageStats {
pub access_count: u32,
pub last_accessed: i64,
pub created_at: i64,
pub useful_count: u32,
}
impl UsageStats {
pub fn new() -> Self {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs() as i64;
Self {
access_count: 0,
last_accessed: now,
created_at: now,
useful_count: 0,
}
}
pub fn record_access(&mut self) {
self.access_count += 1;
self.last_accessed = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs() as i64;
}
pub fn record_useful(&mut self) {
self.useful_count += 1;
}
pub fn frequency_score(&self) -> f32 {
if self.access_count == 0 {
0.0
} else {
((self.access_count as f32 + 1.0).log2() / 10.0).min(1.0)
}
}
pub fn usefulness_score(&self) -> f32 {
if self.access_count == 0 {
0.5 } else {
self.useful_count as f32 / self.access_count as f32
}
}
pub fn age_seconds(&self) -> i64 {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs() as i64;
now - self.created_at
}
pub fn staleness_seconds(&self) -> i64 {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs() as i64;
now - self.last_accessed
}
pub fn save_to_metadata(&self, meta: &mut Metadata) {
meta.insert("_access_count", self.access_count as i64);
meta.insert("_last_accessed", self.last_accessed);
meta.insert("_created_at", self.created_at);
meta.insert("_useful_count", self.useful_count as i64);
}
pub fn load_from_metadata(meta: &Metadata) -> Self {
let access_count = meta
.get("_access_count")
.and_then(|v| v.as_i64())
.unwrap_or(0) as u32;
let last_accessed = meta
.get("_last_accessed")
.and_then(|v| v.as_i64())
.unwrap_or(0);
let created_at = meta
.get("_created_at")
.and_then(|v| v.as_i64())
.unwrap_or(0);
let useful_count = meta
.get("_useful_count")
.and_then(|v| v.as_i64())
.unwrap_or(0) as u32;
Self {
access_count,
last_accessed,
created_at,
useful_count,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DecayConfig {
pub enabled: bool,
pub half_life_seconds: i64,
pub min_decay: f32,
pub immune_priorities: Vec<Priority>,
}
impl Default for DecayConfig {
fn default() -> Self {
Self {
enabled: true,
half_life_seconds: 30 * 24 * 60 * 60, min_decay: 0.1,
immune_priorities: vec![Priority::Critical],
}
}
}
impl DecayConfig {
pub fn no_decay() -> Self {
Self {
enabled: false,
..Default::default()
}
}
pub fn fast() -> Self {
Self {
enabled: true,
half_life_seconds: 7 * 24 * 60 * 60,
min_decay: 0.2,
immune_priorities: vec![Priority::Critical, Priority::High],
}
}
pub fn slow() -> Self {
Self {
enabled: true,
half_life_seconds: 90 * 24 * 60 * 60,
min_decay: 0.05,
immune_priorities: vec![Priority::Critical],
}
}
pub fn calculate_decay(&self, age_seconds: i64, priority: Priority) -> f32 {
if !self.enabled || self.immune_priorities.contains(&priority) {
return 1.0;
}
let decay = 0.5_f32.powf(age_seconds as f32 / self.half_life_seconds as f32);
decay.max(self.min_decay)
}
}
pub trait PriorityCalculator: Send + Sync + Default {
fn calculate(&self, description: &str, content: &str, outcome: &str) -> Priority;
fn critical_keywords(&self) -> Vec<&'static str>;
fn high_keywords(&self) -> Vec<&'static str>;
fn low_keywords(&self) -> Vec<&'static str>;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PriorityWeights {
pub base_priority: f32,
pub frequency: f32,
pub usefulness: f32,
pub recency: f32,
}
impl Default for PriorityWeights {
fn default() -> Self {
Self {
base_priority: 0.4,
frequency: 0.2,
usefulness: 0.25,
recency: 0.15,
}
}
}
impl PriorityWeights {
pub fn manual_focused() -> Self {
Self {
base_priority: 0.6,
frequency: 0.15,
usefulness: 0.15,
recency: 0.1,
}
}
pub fn usage_focused() -> Self {
Self {
base_priority: 0.2,
frequency: 0.4,
usefulness: 0.25,
recency: 0.15,
}
}
pub fn recency_focused() -> Self {
Self {
base_priority: 0.25,
frequency: 0.15,
usefulness: 0.2,
recency: 0.4,
}
}
pub fn calculate_score(&self, base: f32, frequency: f32, usefulness: f32, recency: f32) -> f32 {
(self.base_priority * base
+ self.frequency * frequency
+ self.usefulness * usefulness
+ self.recency * recency)
.clamp(0.0, 1.0)
}
}
pub fn recency_score(staleness_seconds: i64) -> f32 {
let hours = staleness_seconds as f32 / 3600.0;
(-hours / 168.0).exp() }
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct InstanceContext {
pub instance_id: String,
pub context: String,
pub domain: String,
pub extra: HashMap<String, String>,
}
impl InstanceContext {
pub fn new(instance_id: impl Into<String>) -> Self {
Self {
instance_id: instance_id.into(),
..Default::default()
}
}
pub fn with_context(mut self, context: impl Into<String>) -> Self {
self.context = context.into();
self
}
pub fn with_domain(mut self, domain: impl Into<String>) -> Self {
self.domain = domain.into();
self
}
pub fn with_extra(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.extra.insert(key.into(), value.into());
self
}
pub fn from_project_context(ctx: &ProjectContext) -> Self {
let mut ic = Self::new(&ctx.name)
.with_context(&ctx.language)
.with_domain(ctx.domain.as_str());
for (i, fw) in ctx.frameworks.iter().enumerate() {
ic.extra
.insert(format!("framework_{}", i), fw.clone());
}
for (i, pat) in ctx.patterns.iter().enumerate() {
ic.extra
.insert(format!("pattern_{}", i), pat.clone());
}
for (i, tag) in ctx.tags.iter().enumerate() {
ic.extra.insert(format!("tag_{}", i), tag.clone());
}
ic
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum KnowledgeDomain {
WebBackend,
WebFrontend,
CLI,
DataScience,
Systems,
Mobile,
DevOps,
Security,
Database,
GameDev,
Embedded,
General,
}
impl KnowledgeDomain {
pub fn as_str(&self) -> &'static str {
match self {
KnowledgeDomain::WebBackend => "web_backend",
KnowledgeDomain::WebFrontend => "web_frontend",
KnowledgeDomain::CLI => "cli",
KnowledgeDomain::DataScience => "data_science",
KnowledgeDomain::Systems => "systems",
KnowledgeDomain::Mobile => "mobile",
KnowledgeDomain::DevOps => "devops",
KnowledgeDomain::Security => "security",
KnowledgeDomain::Database => "database",
KnowledgeDomain::GameDev => "gamedev",
KnowledgeDomain::Embedded => "embedded",
KnowledgeDomain::General => "general",
}
}
pub fn from_str(s: &str) -> Self {
match s.to_lowercase().as_str() {
"web_backend" | "webbackend" | "backend" => KnowledgeDomain::WebBackend,
"web_frontend" | "webfrontend" | "frontend" => KnowledgeDomain::WebFrontend,
"cli" | "command_line" => KnowledgeDomain::CLI,
"data_science" | "datascience" | "data" | "ml" => KnowledgeDomain::DataScience,
"systems" | "system" => KnowledgeDomain::Systems,
"mobile" | "ios" | "android" => KnowledgeDomain::Mobile,
"devops" | "ops" | "infra" => KnowledgeDomain::DevOps,
"security" | "sec" => KnowledgeDomain::Security,
"database" | "db" => KnowledgeDomain::Database,
"gamedev" | "game" | "games" => KnowledgeDomain::GameDev,
"embedded" | "iot" => KnowledgeDomain::Embedded,
_ => KnowledgeDomain::General,
}
}
pub fn related_domains(&self) -> Vec<KnowledgeDomain> {
match self {
KnowledgeDomain::WebBackend => vec![
KnowledgeDomain::Database,
KnowledgeDomain::Security,
KnowledgeDomain::DevOps,
],
KnowledgeDomain::WebFrontend => {
vec![KnowledgeDomain::Mobile, KnowledgeDomain::WebBackend]
}
KnowledgeDomain::CLI => vec![KnowledgeDomain::Systems, KnowledgeDomain::DevOps],
KnowledgeDomain::DataScience => {
vec![KnowledgeDomain::Database, KnowledgeDomain::Systems]
}
KnowledgeDomain::Systems => {
vec![KnowledgeDomain::Embedded, KnowledgeDomain::Security]
}
KnowledgeDomain::Mobile => vec![KnowledgeDomain::WebFrontend],
KnowledgeDomain::DevOps => {
vec![KnowledgeDomain::Security, KnowledgeDomain::Systems]
}
KnowledgeDomain::Security => {
vec![KnowledgeDomain::WebBackend, KnowledgeDomain::Systems]
}
KnowledgeDomain::Database => {
vec![KnowledgeDomain::WebBackend, KnowledgeDomain::DataScience]
}
KnowledgeDomain::GameDev => {
vec![KnowledgeDomain::Systems, KnowledgeDomain::WebFrontend]
}
KnowledgeDomain::Embedded => vec![KnowledgeDomain::Systems],
KnowledgeDomain::General => vec![],
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProjectContext {
pub name: String,
pub language: String,
pub domain: KnowledgeDomain,
pub frameworks: Vec<String>,
pub patterns: Vec<String>,
pub tags: Vec<String>,
}
impl ProjectContext {
pub fn new(
name: impl Into<String>,
language: impl Into<String>,
domain: KnowledgeDomain,
) -> Self {
Self {
name: name.into(),
language: language.into(),
domain,
frameworks: Vec::new(),
patterns: Vec::new(),
tags: Vec::new(),
}
}
pub fn with_frameworks(mut self, frameworks: Vec<String>) -> Self {
self.frameworks = frameworks;
self
}
pub fn with_patterns(mut self, patterns: Vec<String>) -> Self {
self.patterns = patterns;
self
}
pub fn with_tags(mut self, tags: Vec<String>) -> Self {
self.tags = tags;
self
}
}
pub struct LanguageCompatibility;
impl LanguageCompatibility {
const LANGUAGE_GROUPS: &'static [&'static [&'static str]] = &[
&["typescript", "javascript", "js", "ts"],
&["python", "ruby", "perl"],
&["rust", "go", "c", "cpp", "c++", "zig"],
&["java", "kotlin", "scala", "groovy"],
&["csharp", "c#", "fsharp", "f#"],
&["swift", "objective-c", "objc"],
&["haskell", "ocaml", "elm", "purescript"],
&["clojure", "lisp", "scheme", "racket"],
&["php", "hack"],
&["elixir", "erlang"],
];
pub fn compatibility(lang_a: &str, lang_b: &str) -> f32 {
let a = lang_a.to_lowercase();
let b = lang_b.to_lowercase();
if a == b {
return 1.0;
}
for group in Self::LANGUAGE_GROUPS {
let a_in = group.contains(&a.as_str());
let b_in = group.contains(&b.as_str());
if a_in && b_in {
return 0.7;
}
}
0.2
}
pub fn adaptation_description(from: &str, to: &str) -> Option<String> {
let from_lower = from.to_lowercase();
let to_lower = to.to_lowercase();
if from_lower == to_lower {
return None;
}
let desc = match (from_lower.as_str(), to_lower.as_str()) {
("python", "rust") => {
"Cambiar a tipado estatico, usar Result para errores, ownership"
}
("javascript", "typescript") => "Anadir tipos, interfaces",
("typescript", "javascript") => "Remover tipos",
("java", "kotlin") => "Simplificar sintaxis, usar null-safety",
("python", "javascript") | ("javascript", "python") => {
"Adaptar sintaxis y async model"
}
("rust", "go") => "Simplificar ownership, usar goroutines",
("go", "rust") => "Anadir ownership, Result types, macros",
_ => return Some(format!("Adaptar sintaxis de {} a {}", from, to)),
};
Some(desc.to_string())
}
}
#[derive(Debug, Clone)]
pub struct GenericRecall {
pub id: VectorId,
pub relevance: f32,
pub transfer_level: TransferLevel,
pub priority: Priority,
pub priority_score: f32,
pub combined_score: f32,
pub concepts: Vec<String>,
pub usage: UsageStats,
pub metadata: Metadata,
}
pub struct GenericMemory<P: DomainPreset> {
db: VectorDB,
domain_classifier: P::Domain,
concept_extractor: P::Concepts,
context_matcher: P::Context,
priority_calculator: P::Priority,
current_context: RwLock<Option<InstanceContext>>,
usage_stats: RwLock<HashMap<String, UsageStats>>,
decay_config: DecayConfig,
priority_weights: PriorityWeights,
relevance_weight: f32,
transfer_weight: f32,
priority_weight: f32,
transfer_threshold: f32,
}
impl<P: DomainPreset> GenericMemory<P> {
pub fn new(dimensions: usize) -> Result<Self> {
let config = Config::new(dimensions);
let db = VectorDB::with_fulltext(config, vec!["content".into(), "description".into()])?;
let (domain_classifier, concept_extractor, context_matcher, priority_calculator) =
P::create();
Ok(Self {
db,
domain_classifier,
concept_extractor,
context_matcher,
priority_calculator,
current_context: RwLock::new(None),
usage_stats: RwLock::new(HashMap::new()),
decay_config: P::default_decay(),
priority_weights: P::default_weights(),
relevance_weight: 0.4,
transfer_weight: 0.3,
priority_weight: 0.3,
transfer_threshold: 0.3,
})
}
pub fn with_config(config: Config) -> Result<Self> {
let db = VectorDB::with_fulltext(config, vec!["content".into(), "description".into()])?;
let (domain_classifier, concept_extractor, context_matcher, priority_calculator) =
P::create();
Ok(Self {
db,
domain_classifier,
concept_extractor,
context_matcher,
priority_calculator,
current_context: RwLock::new(None),
usage_stats: RwLock::new(HashMap::new()),
decay_config: P::default_decay(),
priority_weights: P::default_weights(),
relevance_weight: 0.4,
transfer_weight: 0.3,
priority_weight: 0.3,
transfer_threshold: 0.3,
})
}
pub fn with_db(db: VectorDB) -> Self {
let (domain_classifier, concept_extractor, context_matcher, priority_calculator) =
P::create();
Self {
db,
domain_classifier,
concept_extractor,
context_matcher,
priority_calculator,
current_context: RwLock::new(None),
usage_stats: RwLock::new(HashMap::new()),
decay_config: P::default_decay(),
priority_weights: P::default_weights(),
relevance_weight: 0.4,
transfer_weight: 0.3,
priority_weight: 0.3,
transfer_threshold: 0.3,
}
}
pub fn db(&self) -> &VectorDB {
&self.db
}
pub fn set_decay_config(&mut self, config: DecayConfig) {
self.decay_config = config;
}
pub fn set_priority_weights(&mut self, weights: PriorityWeights) {
self.priority_weights = weights;
}
pub fn set_score_weights(&mut self, relevance: f32, transfer: f32, priority: f32) {
let total = relevance + transfer + priority;
self.relevance_weight = relevance / total;
self.transfer_weight = transfer / total;
self.priority_weight = priority / total;
}
pub fn set_transfer_threshold(&mut self, threshold: f32) {
self.transfer_threshold = threshold.clamp(0.0, 1.0);
}
pub fn set_context(&self, context: InstanceContext) {
*self.current_context.write() = Some(context);
}
pub fn set_instance(
&self,
instance_id: impl Into<String>,
context: impl Into<String>,
domain: impl Into<String>,
) {
self.set_context(
InstanceContext::new(instance_id)
.with_context(context)
.with_domain(domain),
);
}
pub fn set_project_context(&self, ctx: &ProjectContext) {
self.set_context(InstanceContext::from_project_context(ctx));
}
pub fn current_context(&self) -> Option<InstanceContext> {
self.current_context.read().clone()
}
pub fn learn(
&self,
id: &str,
embedding: &[f32],
content: &str,
description: &str,
outcome: &str,
) -> Result<VectorId> {
let priority = self
.priority_calculator
.calculate(description, content, outcome);
self.learn_with_priority(id, embedding, content, description, outcome, priority)
}
pub fn learn_with_priority(
&self,
id: &str,
embedding: &[f32],
content: &str,
description: &str,
outcome: &str,
priority: Priority,
) -> Result<VectorId> {
let ctx = self.current_context.read().clone();
let concepts = self.concept_extractor.extract(description, content);
let transfer_level = self.infer_transfer_level(&concepts, content);
let usage = UsageStats::new();
self.usage_stats.write().insert(id.to_string(), usage.clone());
let mut meta = Metadata::new();
meta.insert("content", content);
meta.insert("description", description);
meta.insert("outcome", outcome);
meta.insert("transfer_level", transfer_level.as_str());
meta.insert("priority", priority.as_str());
meta.insert("concepts", concepts.join(","));
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs() as i64;
meta.insert("timestamp", timestamp);
if let Some(ref ctx) = ctx {
meta.insert("instance_id", ctx.instance_id.as_str());
meta.insert("context", ctx.context.as_str());
meta.insert("domain", ctx.domain.as_str());
for (k, v) in &ctx.extra {
meta.insert(k.as_str(), v.as_str());
}
}
if ctx.as_ref().is_none_or(|c| c.domain.is_empty()) {
let domain = self.domain_classifier.classify(content);
meta.insert("domain", domain.as_str());
}
usage.save_to_metadata(&mut meta);
self.db.insert(id, embedding, Some(meta))?;
Ok(id.to_string())
}
pub fn learn_raw(
&self,
id: &str,
embedding: &[f32],
meta: Metadata,
content_for_analysis: &str,
) -> Result<VectorId> {
let description = meta
.get("description")
.and_then(|v| v.as_str())
.unwrap_or("");
let outcome = meta.get("outcome").and_then(|v| v.as_str()).unwrap_or("");
let priority = self
.priority_calculator
.calculate(description, content_for_analysis, outcome);
self.learn_raw_with_priority(id, embedding, meta, content_for_analysis, priority)
}
pub fn learn_raw_with_priority(
&self,
id: &str,
embedding: &[f32],
mut meta: Metadata,
content_for_analysis: &str,
priority: Priority,
) -> Result<VectorId> {
let ctx = self.current_context.read().clone();
let description = meta
.get("description")
.and_then(|v| v.as_str())
.unwrap_or("");
let concepts = self
.concept_extractor
.extract(description, content_for_analysis);
let transfer_level = self.infer_transfer_level(&concepts, content_for_analysis);
let usage = UsageStats::new();
self.usage_stats
.write()
.insert(id.to_string(), usage.clone());
if meta.get("transfer_level").is_none() {
meta.insert("transfer_level", transfer_level.as_str());
}
if meta.get("priority").is_none() {
meta.insert("priority", priority.as_str());
}
if meta.get("concepts").is_none() {
meta.insert("concepts", concepts.join(","));
}
if meta.get("timestamp").is_none() {
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs() as i64;
meta.insert("timestamp", timestamp);
}
if let Some(ref ctx) = ctx {
if meta.get("instance_id").is_none() && !ctx.instance_id.is_empty() {
meta.insert("instance_id", ctx.instance_id.as_str());
}
if meta.get("context").is_none() && !ctx.context.is_empty() {
meta.insert("context", ctx.context.as_str());
}
if meta.get("domain").is_none() && !ctx.domain.is_empty() {
meta.insert("domain", ctx.domain.as_str());
}
}
let has_domain = meta
.get("domain")
.and_then(|v| v.as_str())
.is_some_and(|s| !s.is_empty());
if !has_domain {
let domain = self.domain_classifier.classify(content_for_analysis);
meta.insert("domain", domain.as_str());
}
usage.save_to_metadata(&mut meta);
self.db.insert(id, embedding, Some(meta))?;
Ok(id.to_string())
}
pub fn mark_useful(&self, id: &str) {
let mut stats_map = self.usage_stats.write();
let stats = if let Some(s) = stats_map.get_mut(id) {
s.record_useful();
s.clone()
} else {
if let Ok(Some((_, Some(meta)))) = self.db.get(id) {
let mut s = UsageStats::load_from_metadata(&meta);
s.record_useful();
stats_map.insert(id.to_string(), s.clone());
s
} else {
return;
}
};
drop(stats_map);
if let Ok(Some((vec_opt, meta_opt))) = self.db.get(id) {
if let (Some(vec), Some(mut meta)) = (vec_opt, meta_opt) {
stats.save_to_metadata(&mut meta);
let _ = self.db.update(id, &vec, Some(meta));
}
}
}
pub fn update_priority(&self, id: &str, priority: Priority) -> Result<()> {
if let Some((vec_opt, meta_opt)) = self.db.get(id)? {
if let (Some(vec), Some(mut meta)) = (vec_opt, meta_opt) {
meta.insert("priority", priority.as_str());
self.db.update(id, &vec, Some(meta))?;
}
}
Ok(())
}
fn infer_transfer_level(&self, concepts: &[String], content: &str) -> TransferLevel {
let universal_count = concepts
.iter()
.filter(|c| self.concept_extractor.is_universal(c))
.count();
if universal_count >= 2 {
return TransferLevel::Universal;
}
let content_lower = content.to_lowercase();
let instance_patterns = [
"this project",
"este proyecto",
"specific to",
"only here",
"custom",
"our",
"nuestra",
];
if instance_patterns.iter().any(|p| content_lower.contains(p)) {
return TransferLevel::Instance;
}
if universal_count >= 1 {
TransferLevel::Domain
} else {
TransferLevel::Context
}
}
pub fn recall(&self, query_embedding: &[f32], k: usize) -> Result<Vec<GenericRecall>> {
let ctx = self.current_context.read().clone();
let results = self.db.search(query_embedding, k * 3)?;
let mut recalls: Vec<GenericRecall> = results
.into_iter()
.filter_map(|r| {
let meta = r.metadata?;
let id = r.id.clone();
let transfer_level = meta
.get("transfer_level")
.and_then(|v| v.as_str())
.and_then(TransferLevel::from_str)
.unwrap_or(TransferLevel::Instance);
let transfer_score = self.calculate_transfer_score(&ctx, &meta, transfer_level);
if transfer_score < self.transfer_threshold {
return None;
}
let priority = meta
.get("priority")
.and_then(|v| v.as_str())
.and_then(Priority::from_str)
.unwrap_or(Priority::Normal);
let usage = {
let cached = self.usage_stats.read().get(&id).cloned();
cached.unwrap_or_else(|| UsageStats::load_from_metadata(&meta))
};
let priority_score = self.calculate_priority_score(&usage, priority);
let relevance = 1.0 - r.distance; let combined_score = relevance * self.relevance_weight
+ transfer_score * self.transfer_weight
+ priority_score * self.priority_weight;
let concepts = meta
.get("concepts")
.and_then(|v| v.as_str())
.map(|s: &str| s.split(',').map(String::from).collect())
.unwrap_or_default();
Some(GenericRecall {
id,
relevance,
transfer_level,
priority,
priority_score,
combined_score,
concepts,
usage,
metadata: meta,
})
})
.collect();
{
let mut stats = self.usage_stats.write();
for recall in &recalls {
let entry = stats
.entry(recall.id.clone())
.or_insert_with(|| UsageStats::load_from_metadata(&recall.metadata));
entry.record_access();
if let Ok(Some((vec_opt, meta_opt))) = self.db.get(&recall.id) {
if let (Some(vec), Some(mut meta)) = (vec_opt, meta_opt) {
entry.save_to_metadata(&mut meta);
let _ = self.db.update(&recall.id, &vec, Some(meta));
}
}
}
}
recalls.sort_by(|a, b| b.combined_score.partial_cmp(&a.combined_score).unwrap());
recalls.truncate(k);
Ok(recalls)
}
fn calculate_priority_score(&self, usage: &UsageStats, priority: Priority) -> f32 {
let base = priority.base_score();
let frequency = usage.frequency_score();
let usefulness = usage.usefulness_score();
let recency = recency_score(usage.staleness_seconds());
let raw_score = self
.priority_weights
.calculate_score(base, frequency, usefulness, recency);
let age = usage.age_seconds();
let decay = self.decay_config.calculate_decay(age, priority);
raw_score * decay
}
fn calculate_transfer_score(
&self,
current_ctx: &Option<InstanceContext>,
meta: &Metadata,
level: TransferLevel,
) -> f32 {
if level == TransferLevel::Universal {
return 1.0;
}
let Some(ctx) = current_ctx else {
return level.transfer_score();
};
let stored_instance = meta
.get("instance_id")
.and_then(|v| v.as_str())
.unwrap_or("");
let stored_context = meta.get("context").and_then(|v| v.as_str()).unwrap_or("");
let stored_domain = meta.get("domain").and_then(|v| v.as_str()).unwrap_or("");
let instance_match = if ctx.instance_id == stored_instance {
1.0
} else {
0.0
};
let context_compat = if !ctx.context.is_empty() && !stored_context.is_empty() {
let lang_compat = LanguageCompatibility::compatibility(&ctx.context, stored_context);
let matcher_compat = self.context_matcher.compatibility(&ctx.context, stored_context);
lang_compat.max(matcher_compat)
} else {
self.context_matcher
.compatibility(&ctx.context, stored_context)
};
let domain_compat = if !ctx.domain.is_empty() && !stored_domain.is_empty() {
let current_kd = KnowledgeDomain::from_str(&ctx.domain);
let stored_kd = KnowledgeDomain::from_str(stored_domain);
if current_kd == stored_kd {
1.0
} else if current_kd.related_domains().contains(&stored_kd) {
0.7
} else {
self.domain_classifier
.relatedness_score(&ctx.domain, stored_domain)
}
} else {
self.domain_classifier
.relatedness_score(&ctx.domain, stored_domain)
};
match level {
TransferLevel::Instance => {
instance_match * 0.6 + context_compat * 0.2 + domain_compat * 0.2
}
TransferLevel::Context => context_compat * 0.5 + domain_compat * 0.3 + 0.2,
TransferLevel::Domain => domain_compat * 0.6 + 0.4,
TransferLevel::Universal => 1.0,
}
}
fn make_recall(&self, id: String, distance: f32, meta: Metadata) -> GenericRecall {
let transfer_level = meta
.get("transfer_level")
.and_then(|v| v.as_str())
.and_then(TransferLevel::from_str)
.unwrap_or(TransferLevel::Instance);
let priority = meta
.get("priority")
.and_then(|v| v.as_str())
.and_then(Priority::from_str)
.unwrap_or(Priority::Normal);
let usage = self
.usage_stats
.read()
.get(&id)
.cloned()
.unwrap_or_default();
let priority_score = self.calculate_priority_score(&usage, priority);
let relevance = 1.0 - distance;
let concepts = meta
.get("concepts")
.and_then(|v| v.as_str())
.map(|s: &str| s.split(',').map(String::from).collect())
.unwrap_or_default();
GenericRecall {
id,
relevance,
transfer_level,
priority,
priority_score,
combined_score: relevance, concepts,
usage,
metadata: meta,
}
}
pub fn recall_universal(
&self,
query_embedding: &[f32],
k: usize,
) -> Result<Vec<GenericRecall>> {
let results = self.db.search_with_filter(
query_embedding,
k,
Filter::eq("transfer_level", "universal"),
)?;
Ok(results
.into_iter()
.filter_map(|r| {
let meta = r.metadata?;
Some(self.make_recall(r.id, r.distance, meta))
})
.collect())
}
pub fn recall_same_domain(
&self,
query_embedding: &[f32],
k: usize,
) -> Result<Vec<GenericRecall>> {
let ctx = self.current_context.read().clone();
let domain = ctx.map(|c| c.domain).unwrap_or_default();
if domain.is_empty() {
return self.recall(query_embedding, k);
}
let results = self.db.search_with_filter(
query_embedding,
k,
Filter::eq("domain", domain.as_str()),
)?;
Ok(results
.into_iter()
.filter_map(|r| {
let meta = r.metadata?;
Some(self.make_recall(r.id, r.distance, meta))
})
.collect())
}
pub fn recall_same_context(
&self,
query_embedding: &[f32],
k: usize,
) -> Result<Vec<GenericRecall>> {
let ctx = self.current_context.read().clone();
let context = ctx.map(|c| c.context).unwrap_or_default();
if context.is_empty() {
return self.recall(query_embedding, k);
}
let results = self.db.search_with_filter(
query_embedding,
k,
Filter::eq("context", context.as_str()),
)?;
Ok(results
.into_iter()
.filter_map(|r| {
let meta = r.metadata?;
Some(self.make_recall(r.id, r.distance, meta))
})
.collect())
}
pub fn recall_critical(&self, query_embedding: &[f32], k: usize) -> Result<Vec<GenericRecall>> {
let results =
self.db
.search_with_filter(query_embedding, k, Filter::eq("priority", "critical"))?;
Ok(results
.into_iter()
.filter_map(|r| {
let meta = r.metadata?;
Some(self.make_recall(r.id, r.distance, meta))
})
.collect())
}
pub fn recall_high_priority(
&self,
query_embedding: &[f32],
k: usize,
) -> Result<Vec<GenericRecall>> {
let results = self.db.search_with_filter(
query_embedding,
k * 2,
Filter::any(vec![
Filter::eq("priority", "critical"),
Filter::eq("priority", "high"),
]),
)?;
let mut recalls: Vec<GenericRecall> = results
.into_iter()
.filter_map(|r| {
let meta = r.metadata?;
Some(self.make_recall(r.id, r.distance, meta))
})
.collect();
recalls.sort_by(|a, b| b.priority_score.partial_cmp(&a.priority_score).unwrap());
recalls.truncate(k);
Ok(recalls)
}
pub fn recall_by_keywords(&self, keywords: &str, k: usize) -> Result<Vec<GenericRecall>> {
let results = self.db.keyword_search(keywords, k)?;
Ok(results
.into_iter()
.filter_map(|r| {
let meta = r.metadata?;
let transfer_level = meta
.get("transfer_level")
.and_then(|v| v.as_str())
.and_then(TransferLevel::from_str)
.unwrap_or(TransferLevel::Instance);
let priority = meta
.get("priority")
.and_then(|v| v.as_str())
.and_then(Priority::from_str)
.unwrap_or(Priority::Normal);
let id = r.id.clone();
let usage = self
.usage_stats
.read()
.get(&id)
.cloned()
.unwrap_or_default();
let priority_score = self.calculate_priority_score(&usage, priority);
let concepts = meta
.get("concepts")
.and_then(|v| v.as_str())
.map(|s: &str| s.split(',').map(String::from).collect())
.unwrap_or_default();
Some(GenericRecall {
id,
relevance: r.score,
transfer_level,
priority,
priority_score,
combined_score: r.score,
concepts,
usage,
metadata: meta,
})
})
.collect())
}
pub fn stats(&self) -> MemoryStats {
let usage_stats = self.usage_stats.read();
let total_accesses: u32 = usage_stats.values().map(|u| u.access_count).sum();
let avg_usefulness = if usage_stats.is_empty() {
0.0
} else {
usage_stats
.values()
.map(|u| u.usefulness_score())
.sum::<f32>()
/ usage_stats.len() as f32
};
MemoryStats {
total_memories: self.db.len(),
preset_name: P::name().to_string(),
has_context: self.current_context.read().is_some(),
total_accesses,
avg_usefulness,
}
}
}
#[derive(Debug, Clone)]
pub struct MemoryStats {
pub total_memories: usize,
pub preset_name: String,
pub has_context: bool,
pub total_accesses: u32,
pub avg_usefulness: f32,
}
pub mod presets {
use super::*;
#[derive(Debug, Default)]
pub struct SoftwareDomainClassifier;
impl DomainClassifier for SoftwareDomainClassifier {
fn domain_type_name(&self) -> &'static str {
"software_domain"
}
fn available_domains(&self) -> Vec<&'static str> {
vec![
"web_backend",
"web_frontend",
"cli",
"data_science",
"systems",
"mobile",
"devops",
"security",
"database",
"gamedev",
"embedded",
"general",
]
}
fn classify(&self, content: &str) -> String {
let lower = content.to_lowercase();
if lower.contains("api") || lower.contains("endpoint") || lower.contains("rest") {
"web_backend".into()
} else if lower.contains("react") || lower.contains("vue") || lower.contains("css") {
"web_frontend".into()
} else if lower.contains("cli")
|| lower.contains("terminal")
|| lower.contains("command")
{
"cli".into()
} else if lower.contains("pandas") || lower.contains("numpy") || lower.contains("ml") {
"data_science".into()
} else if lower.contains("docker")
|| lower.contains("kubernetes")
|| lower.contains("ci/cd")
{
"devops".into()
} else if lower.contains("auth")
|| lower.contains("security")
|| lower.contains("encrypt")
{
"security".into()
} else if lower.contains("sql") || lower.contains("database") || lower.contains("query")
{
"database".into()
} else if lower.contains("android") || lower.contains("ios") || lower.contains("mobile")
{
"mobile".into()
} else if lower.contains("kernel")
|| lower.contains("memory")
|| lower.contains("syscall")
{
"systems".into()
} else if lower.contains("game") || lower.contains("render") || lower.contains("sprite")
{
"gamedev".into()
} else if lower.contains("embedded")
|| lower.contains("mcu")
|| lower.contains("firmware")
{
"embedded".into()
} else {
"general".into()
}
}
fn related(&self, domain1: &str, domain2: &str) -> bool {
let web = ["web_backend", "web_frontend"];
let low_level = ["systems", "embedded"];
let data = ["data_science", "database"];
(web.contains(&domain1) && web.contains(&domain2))
|| (low_level.contains(&domain1) && low_level.contains(&domain2))
|| (data.contains(&domain1) && data.contains(&domain2))
}
}
#[derive(Debug, Default)]
pub struct SoftwareConceptExtractor;
impl ConceptExtractor for SoftwareConceptExtractor {
fn extract(&self, description: &str, content: &str) -> Vec<String> {
let text = format!("{} {}", description, content).to_lowercase();
let mut concepts = Vec::new();
let patterns = [
(
"error handling",
&["error", "exception", "try", "catch", "result"][..],
),
("validation", &["valid", "check", "verify", "sanitize"]),
("caching", &["cache", "memoize", "ttl"]),
("async", &["async", "await", "future", "promise"]),
("testing", &["test", "mock", "assert", "spec"]),
("logging", &["log", "trace", "debug", "info"]),
("authentication", &["auth", "login", "jwt", "token"]),
("pagination", &["page", "limit", "offset", "cursor"]),
("rate limiting", &["rate", "throttle", "limit"]),
("middleware", &["middleware", "interceptor", "filter"]),
];
for (concept, keywords) in patterns {
if keywords.iter().any(|k| text.contains(k)) {
concepts.push(concept.to_string());
}
}
concepts
}
fn is_universal(&self, concept: &str) -> bool {
self.universal_concepts().contains(&concept)
}
fn universal_concepts(&self) -> Vec<&'static str> {
vec![
"error handling",
"validation",
"caching",
"logging",
"testing",
]
}
}
#[derive(Debug, Default)]
pub struct ProgrammingLanguageMatcher;
impl ContextMatcher for ProgrammingLanguageMatcher {
fn context_type_name(&self) -> &'static str {
"programming_language"
}
fn available_contexts(&self) -> Vec<&'static str> {
vec![
"rust",
"python",
"javascript",
"typescript",
"go",
"java",
"c",
"cpp",
"csharp",
"ruby",
"php",
"swift",
"kotlin",
]
}
fn compatibility(&self, ctx1: &str, ctx2: &str) -> f32 {
if ctx1 == ctx2 {
return 1.0;
}
let family1 = self.context_family(ctx1);
let family2 = self.context_family(ctx2);
if family1.is_some() && family1 == family2 {
0.8
} else {
0.3
}
}
fn context_family(&self, context: &str) -> Option<&'static str> {
match context.to_lowercase().as_str() {
"javascript" | "typescript" => Some("js_family"),
"c" | "cpp" | "rust" => Some("systems"),
"java" | "kotlin" | "scala" => Some("jvm"),
"python" | "ruby" => Some("dynamic"),
"swift" | "objective-c" => Some("apple"),
_ => None,
}
}
}
#[derive(Debug, Default)]
pub struct SoftwarePriorityCalculator;
impl PriorityCalculator for SoftwarePriorityCalculator {
fn calculate(&self, description: &str, content: &str, outcome: &str) -> Priority {
let text = format!("{} {} {}", description, content, outcome).to_lowercase();
if self.critical_keywords().iter().any(|k| text.contains(k)) {
return Priority::Critical;
}
if self.high_keywords().iter().any(|k| text.contains(k)) {
return Priority::High;
}
if self.low_keywords().iter().any(|k| text.contains(k)) {
return Priority::Low;
}
Priority::Normal
}
fn critical_keywords(&self) -> Vec<&'static str> {
vec![
"security",
"vulnerability",
"cve",
"injection",
"xss",
"csrf",
"production",
"outage",
"data loss",
"corruption",
"breach",
"critical",
"urgent",
"emergency",
"hotfix",
]
}
fn high_keywords(&self) -> Vec<&'static str> {
vec![
"bug",
"error",
"exception",
"crash",
"failure",
"broken",
"performance",
"slow",
"memory leak",
"timeout",
"important",
"priority",
"blocking",
]
}
fn low_keywords(&self) -> Vec<&'static str> {
vec![
"style",
"formatting",
"comment",
"typo",
"rename",
"refactor",
"cleanup",
"todo",
"nice to have",
]
}
}
pub struct SoftwareDevelopment;
impl DomainPreset for SoftwareDevelopment {
type Domain = SoftwareDomainClassifier;
type Concepts = SoftwareConceptExtractor;
type Context = ProgrammingLanguageMatcher;
type Priority = SoftwarePriorityCalculator;
fn name() -> &'static str {
"Software Development"
}
fn description() -> &'static str {
"Memory system for software development agents with code-aware transfer"
}
fn default_decay() -> DecayConfig {
DecayConfig::slow()
}
fn default_weights() -> PriorityWeights {
PriorityWeights {
base_priority: 0.3,
frequency: 0.25,
usefulness: 0.35,
recency: 0.1,
}
}
}
#[derive(Debug, Default)]
pub struct ConversationalDomainClassifier;
impl DomainClassifier for ConversationalDomainClassifier {
fn domain_type_name(&self) -> &'static str {
"conversation_domain"
}
fn available_domains(&self) -> Vec<&'static str> {
vec![
"support",
"sales",
"entertainment",
"education",
"casual",
"professional",
]
}
fn classify(&self, content: &str) -> String {
let lower = content.to_lowercase();
if lower.contains("help") || lower.contains("problem") || lower.contains("issue") {
"support".into()
} else if lower.contains("buy")
|| lower.contains("price")
|| lower.contains("offer")
|| lower.contains("cost")
{
"sales".into()
} else if lower.contains("joke") || lower.contains("fun") || lower.contains("play") {
"entertainment".into()
} else if lower.contains("learn") || lower.contains("explain") || lower.contains("how")
{
"education".into()
} else if lower.contains("meeting")
|| lower.contains("report")
|| lower.contains("deadline")
{
"professional".into()
} else {
"casual".into()
}
}
fn related(&self, domain1: &str, domain2: &str) -> bool {
let formal = ["support", "sales", "professional"];
let informal = ["entertainment", "casual"];
let learning = ["education", "support"];
(formal.contains(&domain1) && formal.contains(&domain2))
|| (informal.contains(&domain1) && informal.contains(&domain2))
|| (learning.contains(&domain1) && learning.contains(&domain2))
}
}
#[derive(Debug, Default)]
pub struct ConversationalConceptExtractor;
impl ConceptExtractor for ConversationalConceptExtractor {
fn extract(&self, description: &str, content: &str) -> Vec<String> {
let text = format!("{} {}", description, content).to_lowercase();
let mut concepts = Vec::new();
let patterns = [
(
"greeting",
&["hello", "hi", "hey", "good morning", "hola"][..],
),
("farewell", &["bye", "goodbye", "see you", "adios"]),
("gratitude", &["thank", "thanks", "gracias", "appreciate"]),
("apology", &["sorry", "apologize", "excuse", "disculpa"]),
("empathy", &["understand", "feel", "sorry to hear"]),
(
"clarification",
&["mean", "clarify", "explain", "what do you"],
),
("confirmation", &["yes", "correct", "right", "exactly"]),
("negation", &["no", "not", "don't", "can't"]),
("urgency", &["urgent", "asap", "immediately", "now"]),
("frustration", &["angry", "upset", "frustrated", "annoyed"]),
];
for (concept, keywords) in patterns {
if keywords.iter().any(|k| text.contains(k)) {
concepts.push(concept.to_string());
}
}
concepts
}
fn is_universal(&self, concept: &str) -> bool {
self.universal_concepts().contains(&concept)
}
fn universal_concepts(&self) -> Vec<&'static str> {
vec![
"greeting",
"farewell",
"gratitude",
"empathy",
"clarification",
]
}
}
#[derive(Debug, Default)]
pub struct ConversationalToneMatcher;
impl ContextMatcher for ConversationalToneMatcher {
fn context_type_name(&self) -> &'static str {
"conversational_tone"
}
fn available_contexts(&self) -> Vec<&'static str> {
vec![
"formal",
"informal",
"friendly",
"professional",
"technical",
"casual",
"empathetic",
]
}
fn compatibility(&self, ctx1: &str, ctx2: &str) -> f32 {
if ctx1 == ctx2 {
return 1.0;
}
let family1 = self.context_family(ctx1);
let family2 = self.context_family(ctx2);
if family1.is_some() && family1 == family2 {
0.8
} else {
0.4
}
}
fn context_family(&self, context: &str) -> Option<&'static str> {
match context.to_lowercase().as_str() {
"formal" | "professional" | "technical" => Some("formal"),
"informal" | "friendly" | "casual" => Some("informal"),
"empathetic" => Some("supportive"),
_ => None,
}
}
}
#[derive(Debug, Default)]
pub struct ConversationalPriorityCalculator;
impl PriorityCalculator for ConversationalPriorityCalculator {
fn calculate(&self, description: &str, content: &str, outcome: &str) -> Priority {
let text = format!("{} {} {}", description, content, outcome).to_lowercase();
if self.critical_keywords().iter().any(|k| text.contains(k)) {
return Priority::Critical;
}
if self.high_keywords().iter().any(|k| text.contains(k)) {
return Priority::High;
}
if self.low_keywords().iter().any(|k| text.contains(k)) {
return Priority::Low;
}
Priority::Normal
}
fn critical_keywords(&self) -> Vec<&'static str> {
vec![
"never",
"always",
"hate",
"love",
"allergy",
"allergic",
"important",
"remember",
"don't forget",
"must",
"preference",
"please don't",
"stop",
]
}
fn high_keywords(&self) -> Vec<&'static str> {
vec![
"frustrated",
"angry",
"upset",
"disappointed",
"happy",
"excited",
"grateful",
"thank",
"favorite",
"prefer",
"like",
"dislike",
]
}
fn low_keywords(&self) -> Vec<&'static str> {
vec![
"ok", "fine", "sure", "maybe", "whatever", "casual", "just", "random",
]
}
}
pub struct Conversational;
impl DomainPreset for Conversational {
type Domain = ConversationalDomainClassifier;
type Concepts = ConversationalConceptExtractor;
type Context = ConversationalToneMatcher;
type Priority = ConversationalPriorityCalculator;
fn name() -> &'static str {
"Conversational"
}
fn description() -> &'static str {
"Memory system for chatbots and conversational agents"
}
fn default_decay() -> DecayConfig {
DecayConfig::fast()
}
fn default_weights() -> PriorityWeights {
PriorityWeights::recency_focused()
}
}
#[derive(Debug, Default)]
pub struct CustomerServiceDomainClassifier;
impl DomainClassifier for CustomerServiceDomainClassifier {
fn domain_type_name(&self) -> &'static str {
"service_domain"
}
fn available_domains(&self) -> Vec<&'static str> {
vec![
"billing",
"technical",
"returns",
"shipping",
"account",
"product_info",
"complaints",
"general",
]
}
fn classify(&self, content: &str) -> String {
let lower = content.to_lowercase();
if lower.contains("bill") || lower.contains("charge") || lower.contains("payment") {
"billing".into()
} else if lower.contains("broken")
|| lower.contains("not working")
|| lower.contains("bug")
{
"technical".into()
} else if lower.contains("return")
|| lower.contains("refund")
|| lower.contains("exchange")
{
"returns".into()
} else if lower.contains("ship")
|| lower.contains("deliver")
|| lower.contains("tracking")
{
"shipping".into()
} else if lower.contains("account")
|| lower.contains("password")
|| lower.contains("login")
{
"account".into()
} else if lower.contains("product")
|| lower.contains("feature")
|| lower.contains("spec")
{
"product_info".into()
} else if lower.contains("complain")
|| lower.contains("unhappy")
|| lower.contains("terrible")
{
"complaints".into()
} else {
"general".into()
}
}
fn related(&self, domain1: &str, domain2: &str) -> bool {
let money = ["billing", "returns"];
let logistics = ["shipping", "returns"];
let tech = ["technical", "account"];
(money.contains(&domain1) && money.contains(&domain2))
|| (logistics.contains(&domain1) && logistics.contains(&domain2))
|| (tech.contains(&domain1) && tech.contains(&domain2))
}
}
#[derive(Debug, Default)]
pub struct CustomerServiceConceptExtractor;
impl ConceptExtractor for CustomerServiceConceptExtractor {
fn extract(&self, description: &str, content: &str) -> Vec<String> {
let text = format!("{} {}", description, content).to_lowercase();
let mut concepts = Vec::new();
let patterns = [
(
"escalation needed",
&["manager", "supervisor", "escalate"][..],
),
("resolution", &["solved", "fixed", "resolved", "done"]),
("compensation", &["refund", "credit", "discount", "free"]),
("verification", &["verify", "confirm", "check identity"]),
("policy reference", &["policy", "terms", "conditions"]),
("empathy response", &["understand", "sorry", "apologize"]),
(
"follow up needed",
&["follow up", "callback", "contact again"],
),
("urgent", &["urgent", "emergency", "asap"]),
];
for (concept, keywords) in patterns {
if keywords.iter().any(|k| text.contains(k)) {
concepts.push(concept.to_string());
}
}
concepts
}
fn is_universal(&self, concept: &str) -> bool {
self.universal_concepts().contains(&concept)
}
fn universal_concepts(&self) -> Vec<&'static str> {
vec![
"empathy response",
"verification",
"resolution",
"follow up needed",
]
}
}
#[derive(Debug, Default)]
pub struct CustomerTierMatcher;
impl ContextMatcher for CustomerTierMatcher {
fn context_type_name(&self) -> &'static str {
"customer_tier"
}
fn available_contexts(&self) -> Vec<&'static str> {
vec!["vip", "premium", "standard", "new", "at_risk", "churned"]
}
fn compatibility(&self, ctx1: &str, ctx2: &str) -> f32 {
if ctx1 == ctx2 {
return 1.0;
}
let family1 = self.context_family(ctx1);
let family2 = self.context_family(ctx2);
if family1.is_some() && family1 == family2 {
0.7
} else {
0.5 }
}
fn context_family(&self, context: &str) -> Option<&'static str> {
match context.to_lowercase().as_str() {
"vip" | "premium" => Some("high_value"),
"standard" | "new" => Some("regular"),
"at_risk" | "churned" => Some("retention"),
_ => None,
}
}
}
#[derive(Debug, Default)]
pub struct CustomerServicePriorityCalculator;
impl PriorityCalculator for CustomerServicePriorityCalculator {
fn calculate(&self, description: &str, content: &str, outcome: &str) -> Priority {
let text = format!("{} {} {}", description, content, outcome).to_lowercase();
if self.critical_keywords().iter().any(|k| text.contains(k)) {
return Priority::Critical;
}
if self.high_keywords().iter().any(|k| text.contains(k)) {
return Priority::High;
}
if self.low_keywords().iter().any(|k| text.contains(k)) {
return Priority::Low;
}
Priority::Normal
}
fn critical_keywords(&self) -> Vec<&'static str> {
vec![
"vip",
"enterprise",
"legal",
"lawyer",
"sue",
"escalate",
"manager",
"supervisor",
"ceo",
"fraud",
"breach",
"unauthorized",
]
}
fn high_keywords(&self) -> Vec<&'static str> {
vec![
"complaint",
"unhappy",
"refund",
"cancel",
"broken",
"defective",
"wrong",
"missing",
"urgent",
"immediately",
"asap",
]
}
fn low_keywords(&self) -> Vec<&'static str> {
vec![
"question",
"inquiry",
"information",
"how to",
"general",
"routine",
"standard",
]
}
}
pub struct CustomerService;
impl DomainPreset for CustomerService {
type Domain = CustomerServiceDomainClassifier;
type Concepts = CustomerServiceConceptExtractor;
type Context = CustomerTierMatcher;
type Priority = CustomerServicePriorityCalculator;
fn name() -> &'static str {
"Customer Service"
}
fn description() -> &'static str {
"Memory system for customer service agents"
}
fn default_decay() -> DecayConfig {
DecayConfig::default()
}
fn default_weights() -> PriorityWeights {
PriorityWeights::manual_focused()
}
}
}
#[cfg(test)]
mod tests {
use super::presets::*;
use super::*;
#[test]
fn test_transfer_level_ordering() {
assert!(TransferLevel::Universal > TransferLevel::Domain);
assert!(TransferLevel::Domain > TransferLevel::Context);
assert!(TransferLevel::Context > TransferLevel::Instance);
}
#[test]
fn test_transfer_level_scores() {
assert_eq!(TransferLevel::Universal.transfer_score(), 1.0);
assert_eq!(TransferLevel::Domain.transfer_score(), 0.75);
assert_eq!(TransferLevel::Context.transfer_score(), 0.5);
assert_eq!(TransferLevel::Instance.transfer_score(), 0.25);
}
#[test]
fn test_instance_context_builder() {
let ctx = InstanceContext::new("my-project")
.with_context("rust")
.with_domain("web_backend")
.with_extra("framework", "actix");
assert_eq!(ctx.instance_id, "my-project");
assert_eq!(ctx.context, "rust");
assert_eq!(ctx.domain, "web_backend");
assert_eq!(ctx.extra.get("framework"), Some(&"actix".to_string()));
}
#[test]
fn test_software_domain_classifier() {
let classifier = SoftwareDomainClassifier;
assert_eq!(classifier.classify("REST API endpoint"), "web_backend");
assert_eq!(classifier.classify("React component"), "web_frontend");
assert_eq!(classifier.classify("CLI tool"), "cli");
assert_eq!(classifier.classify("pandas dataframe"), "data_science");
}
#[test]
fn test_software_concept_extractor() {
let extractor = SoftwareConceptExtractor;
let concepts = extractor.extract(
"JWT authentication with rate limiting",
"middleware auth jwt token",
);
assert!(concepts.contains(&"authentication".to_string()));
assert!(concepts.contains(&"rate limiting".to_string()));
assert!(concepts.contains(&"middleware".to_string()));
}
#[test]
fn test_programming_language_matcher() {
let matcher = ProgrammingLanguageMatcher;
assert_eq!(matcher.compatibility("rust", "rust"), 1.0);
assert_eq!(matcher.compatibility("javascript", "typescript"), 0.8);
assert!(matcher.compatibility("rust", "python") < 0.5);
}
#[test]
fn test_conversational_domain_classifier() {
let classifier = ConversationalDomainClassifier;
assert_eq!(classifier.classify("I need help with my order"), "support");
assert_eq!(classifier.classify("How much does it cost?"), "sales");
assert_eq!(classifier.classify("Tell me a joke"), "entertainment");
}
#[test]
fn test_conversational_concept_extractor() {
let extractor = ConversationalConceptExtractor;
let concepts = extractor.extract("User greeting", "Hello, how are you?");
assert!(concepts.contains(&"greeting".to_string()));
let concepts = extractor.extract("User frustrated", "I'm very upset about this");
assert!(concepts.contains(&"frustration".to_string()));
}
#[test]
fn test_customer_service_domain_classifier() {
let classifier = CustomerServiceDomainClassifier;
assert_eq!(classifier.classify("Wrong charge on my bill"), "billing");
assert_eq!(classifier.classify("Product not working"), "technical");
assert_eq!(classifier.classify("I want to return this"), "returns");
}
#[test]
fn test_generic_memory_creation() {
let memory = GenericMemory::<SoftwareDevelopment>::new(4).unwrap();
assert_eq!(memory.stats().preset_name, "Software Development");
assert!(!memory.stats().has_context);
}
#[test]
fn test_generic_memory_set_context() {
let memory = GenericMemory::<Conversational>::new(4).unwrap();
memory.set_instance("@user123", "casual", "support");
let ctx = memory.current_context().unwrap();
assert_eq!(ctx.instance_id, "@user123");
assert_eq!(ctx.context, "casual");
assert_eq!(ctx.domain, "support");
}
#[test]
fn test_domain_relatedness() {
let classifier = SoftwareDomainClassifier;
assert!(classifier.related("web_backend", "web_frontend"));
assert!(classifier.related("systems", "embedded"));
assert!(!classifier.related("web_backend", "gamedev"));
}
#[test]
fn test_preset_names() {
assert_eq!(SoftwareDevelopment::name(), "Software Development");
assert_eq!(Conversational::name(), "Conversational");
assert_eq!(CustomerService::name(), "Customer Service");
}
#[test]
fn test_priority_ordering() {
assert!(Priority::Critical > Priority::High);
assert!(Priority::High > Priority::Normal);
assert!(Priority::Normal > Priority::Low);
}
#[test]
fn test_priority_scores() {
assert_eq!(Priority::Critical.base_score(), 1.0);
assert_eq!(Priority::High.base_score(), 0.75);
assert_eq!(Priority::Normal.base_score(), 0.5);
assert_eq!(Priority::Low.base_score(), 0.25);
}
#[test]
fn test_priority_from_str() {
assert_eq!(Priority::from_str("critical"), Some(Priority::Critical));
assert_eq!(Priority::from_str("urgent"), Some(Priority::Critical));
assert_eq!(Priority::from_str("high"), Some(Priority::High));
assert_eq!(Priority::from_str("normal"), Some(Priority::Normal));
assert_eq!(Priority::from_str("low"), Some(Priority::Low));
assert_eq!(Priority::from_str("unknown"), None);
}
#[test]
fn test_usage_stats_frequency_score() {
let mut stats = UsageStats::new();
assert_eq!(stats.frequency_score(), 0.0);
stats.access_count = 1;
assert!(stats.frequency_score() > 0.0);
stats.access_count = 100;
assert!(stats.frequency_score() > 0.5);
assert!(stats.frequency_score() <= 1.0);
}
#[test]
fn test_usage_stats_usefulness() {
let mut stats = UsageStats::new();
assert_eq!(stats.usefulness_score(), 0.5);
stats.access_count = 10;
stats.useful_count = 8;
assert_eq!(stats.usefulness_score(), 0.8);
}
#[test]
fn test_decay_config_calculation() {
let config = DecayConfig::default();
assert_eq!(config.calculate_decay(1000000, Priority::Critical), 1.0);
let decay = config.calculate_decay(30 * 24 * 60 * 60, Priority::Normal);
assert!(decay < 1.0);
assert!(decay >= 0.4); }
#[test]
fn test_decay_config_no_decay() {
let config = DecayConfig::no_decay();
assert_eq!(config.calculate_decay(1000000, Priority::Low), 1.0);
}
#[test]
fn test_priority_weights() {
let weights = PriorityWeights::default();
let score = weights.calculate_score(0.5, 0.3, 0.8, 0.6);
assert!(score > 0.0 && score <= 1.0);
}
#[test]
fn test_recency_score() {
assert!(recency_score(0) > 0.99);
let week_old = recency_score(7 * 24 * 60 * 60);
assert!(week_old > 0.3 && week_old < 0.5);
assert!(recency_score(365 * 24 * 60 * 60) < 0.1);
}
#[test]
fn test_software_priority_calculator() {
let calc = SoftwarePriorityCalculator;
assert_eq!(
calc.calculate("XSS vulnerability fix", "sanitize input", "fixed"),
Priority::Critical
);
assert_eq!(
calc.calculate("Bug fix", "crash on startup", "resolved"),
Priority::High
);
assert_eq!(
calc.calculate("Formatting", "apply prettier", "done"),
Priority::Low
);
assert_eq!(
calc.calculate("Add feature", "new button", "completed"),
Priority::Normal
);
}
#[test]
fn test_conversational_priority_calculator() {
let calc = ConversationalPriorityCalculator;
assert_eq!(
calc.calculate("User preference", "I never want spam", "noted"),
Priority::Critical
);
assert_eq!(
calc.calculate("User upset", "I'm frustrated", "apologized"),
Priority::High
);
assert_eq!(
calc.calculate("Casual chat", "just ok", "acknowledged"),
Priority::Low
);
}
#[test]
fn test_customer_service_priority_calculator() {
let calc = CustomerServicePriorityCalculator;
assert_eq!(
calc.calculate("VIP customer", "enterprise account", "handled"),
Priority::Critical
);
assert_eq!(
calc.calculate("Customer complaint", "refund request", "processed"),
Priority::High
);
assert_eq!(
calc.calculate("General question", "product information", "answered"),
Priority::Low
);
}
#[test]
fn test_priority_weights_presets() {
let manual = PriorityWeights::manual_focused();
assert!(manual.base_priority > manual.frequency);
let usage = PriorityWeights::usage_focused();
assert!(usage.frequency > usage.base_priority);
let recency = PriorityWeights::recency_focused();
assert!(recency.recency > recency.base_priority);
}
#[test]
fn test_memory_stats_includes_usage() {
let memory = GenericMemory::<SoftwareDevelopment>::new(4).unwrap();
let stats = memory.stats();
assert_eq!(stats.total_accesses, 0);
assert_eq!(stats.avg_usefulness, 0.0);
}
}