mirror of
https://github.com/samvallad33/vestige.git
synced 2026-04-25 00:36:22 +02:00
Merge pull request #32 from matthias-Q/dream_eviction
Some checks failed
CI / Test (macos-latest) (push) Has been cancelled
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Release Build (aarch64-apple-darwin) (push) Has been cancelled
CI / Release Build (x86_64-unknown-linux-gnu) (push) Has been cancelled
Test Suite / Unit Tests (push) Has been cancelled
Test Suite / MCP E2E Tests (push) Has been cancelled
Test Suite / User Journey Tests (push) Has been cancelled
Test Suite / Dashboard Build (push) Has been cancelled
Test Suite / Code Coverage (push) Has been cancelled
Some checks failed
CI / Test (macos-latest) (push) Has been cancelled
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Release Build (aarch64-apple-darwin) (push) Has been cancelled
CI / Release Build (x86_64-unknown-linux-gnu) (push) Has been cancelled
Test Suite / Unit Tests (push) Has been cancelled
Test Suite / MCP E2E Tests (push) Has been cancelled
Test Suite / User Journey Tests (push) Has been cancelled
Test Suite / Dashboard Build (push) Has been cancelled
Test Suite / Code Coverage (push) Has been cancelled
feat: dream connection eviction uses composite score instead of FIFO
This commit is contained in:
commit
16fe2674ed
3 changed files with 58 additions and 10 deletions
|
|
@ -1076,6 +1076,9 @@ pub struct DiscoveredConnection {
|
||||||
pub connection_type: DiscoveredConnectionType,
|
pub connection_type: DiscoveredConnectionType,
|
||||||
/// Reasoning for this connection
|
/// Reasoning for this connection
|
||||||
pub reasoning: String,
|
pub reasoning: String,
|
||||||
|
/// When this connection was discovered (used for recency scoring during eviction)
|
||||||
|
#[serde(default = "Utc::now")]
|
||||||
|
pub discovered_at: DateTime<Utc>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Types of connections discovered during dreaming
|
/// Types of connections discovered during dreaming
|
||||||
|
|
@ -1277,6 +1280,7 @@ impl MemoryDreamer {
|
||||||
similarity,
|
similarity,
|
||||||
connection_type,
|
connection_type,
|
||||||
reasoning,
|
reasoning,
|
||||||
|
discovered_at: Utc::now(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1699,10 +1703,38 @@ impl MemoryDreamer {
|
||||||
fn store_connections(&self, connections: &[DiscoveredConnection]) {
|
fn store_connections(&self, connections: &[DiscoveredConnection]) {
|
||||||
if let Ok(mut stored) = self.connections.write() {
|
if let Ok(mut stored) = self.connections.write() {
|
||||||
stored.extend(connections.iter().cloned());
|
stored.extend(connections.iter().cloned());
|
||||||
// Keep last 1000 connections
|
// Keep the 1000 highest-scoring connections using a composite score
|
||||||
|
// that balances quality (similarity) and recency (age-based decay).
|
||||||
|
//
|
||||||
|
// score = similarity * 0.6 + recency * 0.4
|
||||||
|
//
|
||||||
|
// Recency uses exponential decay with a 7-day half-life:
|
||||||
|
// recency = 0.5 ^ (age_days / 7.0)
|
||||||
|
//
|
||||||
|
// This means:
|
||||||
|
// - A brand-new connection with similarity 0.5 scores 0.70
|
||||||
|
// - A week-old connection with similarity 0.9 scores 0.74
|
||||||
|
// - A month-old connection with similarity 0.9 scores 0.58
|
||||||
|
// Strong old connections are retained longer than weak new ones,
|
||||||
|
// but eventually yield to fresh high-quality discoveries.
|
||||||
let len = stored.len();
|
let len = stored.len();
|
||||||
if len > 1000 {
|
if len > 1000 {
|
||||||
stored.drain(0..(len - 1000));
|
let now = Utc::now();
|
||||||
|
stored.sort_unstable_by(|a, b| {
|
||||||
|
let score = |c: &DiscoveredConnection| -> f64 {
|
||||||
|
let age_days = now
|
||||||
|
.signed_duration_since(c.discovered_at)
|
||||||
|
.num_seconds()
|
||||||
|
.max(0) as f64
|
||||||
|
/ 86_400.0;
|
||||||
|
let recency = (0.5_f64).powf(age_days / 7.0);
|
||||||
|
c.similarity * 0.6 + recency * 0.4
|
||||||
|
};
|
||||||
|
score(b)
|
||||||
|
.partial_cmp(&score(a))
|
||||||
|
.unwrap_or(std::cmp::Ordering::Equal)
|
||||||
|
});
|
||||||
|
stored.truncate(1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -539,17 +539,23 @@ pub async fn trigger_dream(
|
||||||
|
|
||||||
// Run dream through CognitiveEngine
|
// Run dream through CognitiveEngine
|
||||||
let cog = cognitive.lock().await;
|
let cog = cognitive.lock().await;
|
||||||
let pre_dream_count = cog.dreamer.get_connections().len();
|
// Capture start time before the dream — composite-score eviction in store_connections
|
||||||
|
// reorders the buffer, making positional slicing (pre_dream_count..) unreliable.
|
||||||
|
let dream_start = Utc::now();
|
||||||
let dream_result = cog.dreamer.dream(&dream_memories).await;
|
let dream_result = cog.dreamer.dream(&dream_memories).await;
|
||||||
let insights = cog.dreamer.synthesize_insights(&dream_memories);
|
let insights = cog.dreamer.synthesize_insights(&dream_memories);
|
||||||
let all_connections = cog.dreamer.get_connections();
|
let all_connections = cog.dreamer.get_connections();
|
||||||
drop(cog);
|
drop(cog);
|
||||||
|
|
||||||
// Persist new connections
|
// Persist new connections
|
||||||
let new_connections = &all_connections[pre_dream_count..];
|
// Filter by timestamp — same approach as dream.rs to avoid positional index issues.
|
||||||
|
let new_connections: Vec<&vestige_core::DiscoveredConnection> = all_connections
|
||||||
|
.iter()
|
||||||
|
.filter(|c| c.discovered_at >= dream_start)
|
||||||
|
.collect();
|
||||||
let mut connections_persisted = 0u64;
|
let mut connections_persisted = 0u64;
|
||||||
let now = Utc::now();
|
let now = Utc::now();
|
||||||
for conn in new_connections {
|
for conn in new_connections.iter() {
|
||||||
let link_type = match conn.connection_type {
|
let link_type = match conn.connection_type {
|
||||||
vestige_core::DiscoveredConnectionType::Semantic => "semantic",
|
vestige_core::DiscoveredConnectionType::Semantic => "semantic",
|
||||||
vestige_core::DiscoveredConnectionType::SharedConcept => "shared_concepts",
|
vestige_core::DiscoveredConnectionType::SharedConcept => "shared_concepts",
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,12 @@ pub async fn execute(
|
||||||
}).collect();
|
}).collect();
|
||||||
|
|
||||||
let cog = cognitive.lock().await;
|
let cog = cognitive.lock().await;
|
||||||
let pre_dream_count = cog.dreamer.get_connections().len();
|
// Capture start time before the dream so we can identify newly discovered
|
||||||
|
// connections by timestamp rather than by buffer position. This is robust
|
||||||
|
// against the composite-score eviction sort in store_connections, which
|
||||||
|
// reorders the buffer and makes positional slicing (pre_dream_count..)
|
||||||
|
// unreliable.
|
||||||
|
let dream_start = Utc::now();
|
||||||
let dream_result = cog.dreamer.dream(&dream_memories).await;
|
let dream_result = cog.dreamer.dream(&dream_memories).await;
|
||||||
let insights = cog.dreamer.synthesize_insights(&dream_memories);
|
let insights = cog.dreamer.synthesize_insights(&dream_memories);
|
||||||
let all_connections = cog.dreamer.get_connections();
|
let all_connections = cog.dreamer.get_connections();
|
||||||
|
|
@ -115,12 +120,17 @@ pub async fn execute(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// v1.9.0: Persist only NEW connections from this dream (skip accumulated ones)
|
// Identify new connections from this dream by timestamp rather than buffer
|
||||||
let new_connections = all_connections.get(pre_dream_count..).unwrap_or(&[]);
|
// position — positional slicing is broken after composite-score eviction
|
||||||
|
// reorders the buffer.
|
||||||
|
let new_connections: Vec<&vestige_core::DiscoveredConnection> = all_connections
|
||||||
|
.iter()
|
||||||
|
.filter(|c| c.discovered_at >= dream_start)
|
||||||
|
.collect();
|
||||||
let mut connections_persisted = 0u64;
|
let mut connections_persisted = 0u64;
|
||||||
{
|
{
|
||||||
let now = Utc::now();
|
let now = Utc::now();
|
||||||
for conn in new_connections {
|
for conn in new_connections.iter() {
|
||||||
let link_type = match conn.connection_type {
|
let link_type = match conn.connection_type {
|
||||||
vestige_core::DiscoveredConnectionType::Semantic => "semantic",
|
vestige_core::DiscoveredConnectionType::Semantic => "semantic",
|
||||||
vestige_core::DiscoveredConnectionType::SharedConcept => "shared_concepts",
|
vestige_core::DiscoveredConnectionType::SharedConcept => "shared_concepts",
|
||||||
|
|
@ -162,7 +172,7 @@ pub async fn execute(
|
||||||
// Hydrate live cognitive engine with newly persisted connections
|
// Hydrate live cognitive engine with newly persisted connections
|
||||||
if connections_persisted > 0 {
|
if connections_persisted > 0 {
|
||||||
let mut cog = cognitive.lock().await;
|
let mut cog = cognitive.lock().await;
|
||||||
for conn in new_connections {
|
for conn in new_connections.iter() {
|
||||||
let link_type_enum = match conn.connection_type {
|
let link_type_enum = match conn.connection_type {
|
||||||
vestige_core::DiscoveredConnectionType::Semantic => LinkType::Semantic,
|
vestige_core::DiscoveredConnectionType::Semantic => LinkType::Semantic,
|
||||||
vestige_core::DiscoveredConnectionType::SharedConcept => LinkType::Semantic,
|
vestige_core::DiscoveredConnectionType::SharedConcept => LinkType::Semantic,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue