use std::collections::BTreeMap;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{SystemTime, UNIX_EPOCH};
use serde_json::{Map, Number, Value};
use super::{positive_ttl, MemoryService, Metadata, HUB_FIELD};
use crate::context::model::{
CompileRequest, CompiledContext, ContextFragment, ContextSavings, MemoryScope, WorkingContext,
};
use crate::context::{provenance, ContextCompiler};
use crate::embedder::Embedder;
use crate::error::MemoryError;
use crate::id::stable_id;
use crate::model::FusionOptions;
use crate::storage::MemoryStore;
const SOURCE_ID_SALT: &str = "veles-ctx-source:";
const EVENT_ID_SALT: &str = "veles-ctx-event:";
const WORKING_ID_SALT: &str = "veles-ctx-working:";
const EVENT_ANCHOR: &str = "veles context compilation event";
const CTX_EVENT_FIELD: &str = "_veles_ctx_event";
const CTX_PROJECT_FIELD: &str = "_veles_ctx_project";
const CTX_MODEL_FIELD: &str = "_veles_ctx_model";
const CTX_SOURCE_FIELD: &str = "_veles_ctx_source";
const CTX_WORKING_FIELD: &str = "_veles_ctx_working";
const CTX_SESSION_FIELD: &str = "_veles_ctx_session";
const CTX_TOKENS_IN_FIELD: &str = "_veles_ctx_tokens_in";
const CTX_TOKENS_OUT_FIELD: &str = "_veles_ctx_tokens_out";
const CTX_TOKENS_SAVED_FIELD: &str = "_veles_ctx_tokens_saved";
const CTX_COST_FIELD: &str = "_veles_ctx_cost_micros";
const CTX_CURRENCY_FIELD: &str = "_veles_ctx_currency";
const CTX_AT_FIELD: &str = "_veles_ctx_at";
static EVENT_SEQ: AtomicU64 = AtomicU64::new(0);
impl<E: Embedder, S: MemoryStore> MemoryService<E, S> {
pub fn compile_context(
&self,
compiler: &ContextCompiler,
request: &CompileRequest,
) -> Result<CompiledContext, MemoryError> {
let memories = self.context_memories(request)?;
self.compile_with_memories(compiler, request, memories)
}
pub fn compile_context_reranked<R: crate::Reranker>(
&self,
compiler: &ContextCompiler,
request: &CompileRequest,
reranker: &R,
) -> Result<CompiledContext, MemoryError> {
let memories = self.context_memories_reranked(request, reranker)?;
self.compile_with_memories(compiler, request, memories)
}
fn compile_with_memories(
&self,
compiler: &ContextCompiler,
request: &CompileRequest,
memories: Vec<PulledMemory>,
) -> Result<CompiledContext, MemoryError> {
let mut augmented = request.clone();
let mut pulled: BTreeMap<u64, PulledMemory> = BTreeMap::new();
for memory in memories {
augmented.fragments.push(memory.fragment.clone());
pulled.insert(stable_id(&memory.fragment.content), memory);
}
let mut out = compiler.compile(&augmented)?;
annotate_memory_provenance(&mut out, &pulled);
let policy = compiler.effective_policy(request);
if policy.store_sources {
self.store_context_sources(&augmented, &out, policy.source_ttl_seconds)?;
}
if policy.record_events {
self.record_context_event(request, &out, policy.event_ttl_seconds)?;
}
Ok(out)
}
fn context_memories(&self, request: &CompileRequest) -> Result<Vec<PulledMemory>, MemoryError> {
let Some((scope, k)) = scope_and_k(request) else {
return Ok(Vec::new());
};
let filter = scope_filter(scope);
let opts = FusionOptions::from_knobs(scope.hops, scope.graph_boost, None);
let scored = self.recall_fused_scored(&request.query, k, filter.as_ref(), opts)?;
let max_fused = scored
.iter()
.map(|s| s.fused)
.fold(f64::MIN, f64::max)
.max(f64::EPSILON);
Ok(scored
.into_iter()
.map(|scored| {
let memory_id = scored.recollection.id;
let fused = if scored.fused.is_finite() {
scored.fused
} else {
0.0
};
#[allow(clippy::cast_possible_truncation)] let relevance = (fused / max_fused).clamp(0.0, 1.0) as f32;
let fragment = ContextFragment {
id: None,
content: scored.recollection.content,
kind: Some("memory".to_owned()),
priority: None,
metadata: None,
};
PulledMemory {
fragment,
memory_id,
relevance,
vector_norm: scored.vector_norm,
graph_weight: scored.graph_weight,
}
})
.collect())
}
fn context_memories_reranked<R: crate::Reranker>(
&self,
request: &CompileRequest,
reranker: &R,
) -> Result<Vec<PulledMemory>, MemoryError> {
let Some((scope, k)) = scope_and_k(request) else {
return Ok(Vec::new());
};
let filter = scope_filter(scope);
let opts = FusionOptions::from_knobs(scope.hops, scope.graph_boost, None);
let ranked =
self.recall_fused_reranked(&request.query, k, filter.as_ref(), opts, reranker)?;
let count = ranked.len().max(1);
Ok(ranked
.into_iter()
.enumerate()
.map(|(rank, recollection)| {
#[allow(clippy::cast_precision_loss)] let relevance = 1.0 - (rank as f32 / count as f32);
PulledMemory {
fragment: ContextFragment {
id: None,
content: recollection.content,
kind: Some("memory".to_owned()),
priority: None,
metadata: None,
},
memory_id: recollection.id,
relevance,
vector_norm: 0.0,
graph_weight: 0.0,
}
})
.collect())
}
fn store_context_sources(
&self,
augmented: &CompileRequest,
out: &CompiledContext,
ttl_seconds: Option<u64>,
) -> Result<(), MemoryError> {
let by_hash: BTreeMap<u64, &str> = augmented
.fragments
.iter()
.map(|fragment| (stable_id(&fragment.content), fragment.content.as_str()))
.collect();
let ttl_seconds = positive_ttl(ttl_seconds);
for source in &out.sources {
let Some(hash) = provenance::parse_handle(&source.handle) else {
continue;
};
let Some(content) = by_hash.get(&hash) else {
continue;
};
let slot = source_id(hash);
if self.store.get(slot)?.is_some() {
continue;
}
let embedding = self.embedder.embed(content)?;
self.store_fact(
slot,
content,
&embedding,
Some(&system_meta(&[(CTX_SOURCE_FIELD, Value::Bool(true))])),
ttl_seconds,
)?;
}
Ok(())
}
fn slot_is_context_source(&self, slot: u64) -> Result<bool, MemoryError> {
let payloads = self.store.get_metadata_batch(&[slot])?;
Ok(payloads.first().is_some_and(|payload| {
payload
.as_ref()
.is_some_and(|meta| meta.get(CTX_SOURCE_FIELD) == Some(&Value::Bool(true)))
}))
}
pub fn retrieve_context_source(&self, handle: &str) -> Result<String, MemoryError> {
let unknown = || MemoryError::UnknownHandle(handle.to_owned());
let hash = provenance::parse_handle(handle).ok_or_else(unknown)?;
let slot = source_id(hash);
if !self.slot_is_context_source(slot)? {
return Err(unknown());
}
self.store
.get(slot)?
.map(|(content, _)| content)
.ok_or_else(unknown)
}
fn record_context_event(
&self,
request: &CompileRequest,
out: &CompiledContext,
ttl_seconds: Option<u64>,
) -> Result<(), MemoryError> {
let occurred_at_nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|elapsed| elapsed.as_nanos())
.unwrap_or(0);
let seq = EVENT_SEQ.fetch_add(1, Ordering::Relaxed);
let content = format!("{EVENT_ANCHOR} {occurred_at_nanos}-{seq}");
let id = stable_id(&format!("{EVENT_ID_SALT}{occurred_at_nanos}:{seq}"));
let embedding = self.embedder.embed(&content)?;
let meta = event_meta(request, out, occurred_at_nanos);
self.store_fact(
id,
&content,
&embedding,
Some(&meta),
positive_ttl(ttl_seconds),
)?;
Ok(())
}
pub fn context_savings(&self, project: Option<&str>) -> Result<ContextSavings, MemoryError> {
let mut filter = Map::new();
filter.insert(CTX_EVENT_FIELD.to_owned(), Value::Bool(true));
if let Some(project) = project {
filter.insert(
CTX_PROJECT_FIELD.to_owned(),
Value::String(project.to_owned()),
);
}
let embedding = self.embedder.embed(EVENT_ANCHOR)?;
let hits =
self.store
.query_filtered(&embedding, crate::limits::MAX_RECALL_LIMIT, &filter, 0)?;
let ids: Vec<u64> = hits.iter().map(|(id, _, _)| *id).collect();
let payloads = self.store.get_metadata_batch(&ids)?;
Ok(aggregate_events(&payloads))
}
pub fn save_working_context(
&self,
project: &str,
session: &str,
working: &WorkingContext,
) -> Result<u64, MemoryError> {
let content = serde_json::to_string(working)
.map_err(|err| MemoryError::WorkingContextCodec(err.to_string()))?;
let id = working_id(project, session);
let embedding = self
.embedder
.embed(&format!("working context {project} {session}"))?;
let meta = system_meta(&[
(CTX_WORKING_FIELD, Value::Bool(true)),
(CTX_PROJECT_FIELD, Value::String(project.to_owned())),
(CTX_SESSION_FIELD, Value::String(session.to_owned())),
]);
self.store_fact(id, &content, &embedding, Some(&meta), None)?;
Ok(id)
}
pub fn load_working_context(
&self,
project: &str,
session: &str,
) -> Result<Option<WorkingContext>, MemoryError> {
match self.store.get(working_id(project, session))? {
Some((content, _)) => serde_json::from_str(&content)
.map(Some)
.map_err(|err| MemoryError::WorkingContextCodec(err.to_string())),
None => Ok(None),
}
}
}
const DEFAULT_MEMORY_K: usize = 5;
fn scope_and_k(request: &CompileRequest) -> Option<(&MemoryScope, usize)> {
let scope = request.memory_scope.as_ref()?;
let room = crate::limits::MAX_FRAGMENTS.saturating_sub(request.fragments.len());
let k = crate::limits::clamp_recall_limit(scope.k.unwrap_or(DEFAULT_MEMORY_K)).min(room);
(k > 0).then_some((scope, k))
}
fn scope_filter(scope: &MemoryScope) -> Option<Metadata> {
scope.project.as_ref().map(|project| {
let mut meta = Map::new();
meta.insert("project".to_owned(), Value::String(project.clone()));
meta
})
}
struct PulledMemory {
fragment: ContextFragment,
memory_id: u64,
relevance: f32,
vector_norm: f64,
graph_weight: f64,
}
fn annotate_memory_provenance(out: &mut CompiledContext, pulled: &BTreeMap<u64, PulledMemory>) {
for decision in &mut out.decisions {
if let Some(memory) = pulled.get(&decision.content_hash) {
decision.memory_id = Some(memory.memory_id);
decision.relevance = memory.relevance;
decision.reason = format!(
"{} — pulled from memory {} (vector {:.2}, graph {:.2})",
decision.reason, memory.memory_id, memory.vector_norm, memory.graph_weight
);
}
}
for source in &mut out.sources {
if let Some(hash) = provenance::parse_handle(&source.handle) {
if let Some(memory) = pulled.get(&hash) {
source.memory_id = Some(memory.memory_id);
}
}
}
}
fn system_meta(extra: &[(&str, Value)]) -> Metadata {
let mut meta = Map::new();
meta.insert(HUB_FIELD.to_owned(), Value::Bool(true));
for (key, value) in extra {
meta.insert((*key).to_owned(), value.clone());
}
meta
}
fn event_meta(request: &CompileRequest, out: &CompiledContext, nanos: u128) -> Metadata {
let mut extra: Vec<(&str, Value)> = vec![
(CTX_EVENT_FIELD, Value::Bool(true)),
(
CTX_TOKENS_IN_FIELD,
Value::Number(out.insights.tokens_in.into()),
),
(
CTX_TOKENS_OUT_FIELD,
Value::Number(out.insights.tokens_out.into()),
),
(
CTX_TOKENS_SAVED_FIELD,
Value::Number(out.insights.tokens_saved.into()),
),
(
CTX_AT_FIELD,
Value::Number(Number::from(
u64::try_from(nanos / 1_000_000_000).unwrap_or(u64::MAX),
)),
),
];
if let Some(project) = &request.project {
extra.push((CTX_PROJECT_FIELD, Value::String(project.clone())));
}
if let Some(model) = &request.target_model {
extra.push((CTX_MODEL_FIELD, Value::String(model.clone())));
}
if let (Some(micros), Some(currency)) = (
out.insights.estimated_cost_saved_micros,
out.insights.currency.as_ref(),
) {
extra.push((CTX_COST_FIELD, Value::Number(micros.into())));
extra.push((CTX_CURRENCY_FIELD, Value::String(currency.clone())));
}
system_meta(&extra)
}
fn aggregate_events(payloads: &[Option<Metadata>]) -> ContextSavings {
let mut savings = ContextSavings {
events: payloads.len() as u64,
truncated: payloads.len() >= crate::limits::MAX_RECALL_LIMIT,
..ContextSavings::default()
};
for payload in payloads {
let Some(meta) = payload else { continue };
savings.tokens_in = savings
.tokens_in
.saturating_add(meta_u64(meta, CTX_TOKENS_IN_FIELD));
savings.tokens_out = savings
.tokens_out
.saturating_add(meta_u64(meta, CTX_TOKENS_OUT_FIELD));
savings.tokens_saved = savings
.tokens_saved
.saturating_add(meta_u64(meta, CTX_TOKENS_SAVED_FIELD));
if let (Some(Value::String(currency)), micros) =
(meta.get(CTX_CURRENCY_FIELD), meta_u64(meta, CTX_COST_FIELD))
{
if micros > 0 {
let entry = savings
.cost_saved_micros_by_currency
.entry(currency.clone())
.or_insert(0);
*entry = entry.saturating_add(micros);
}
}
}
savings
}
fn meta_u64(meta: &Metadata, key: &str) -> u64 {
meta.get(key).and_then(Value::as_u64).unwrap_or(0)
}
fn source_id(content_hash: u64) -> u64 {
stable_id(&format!("{SOURCE_ID_SALT}{content_hash}"))
}
fn working_id(project: &str, session: &str) -> u64 {
stable_id(&format!("{WORKING_ID_SALT}{project}\u{1f}{session}"))
}