Skip to main content

entelix_memory/
summary.rs

1//! `SummaryMemory` — single-string running summary keyed by namespace.
2//!
3//! Use case: rolling conversation summaries that fit a fixed token budget.
4//! The summarization logic itself is the caller's responsibility — pair
5//! with an LLM call after `messages()` to produce the new summary, then
6//! `set` it.
7
8use std::sync::Arc;
9
10use entelix_core::{ExecutionContext, Result};
11
12use crate::namespace::Namespace;
13use crate::store::Store;
14
15const DEFAULT_KEY: &str = "summary";
16
17/// Single-string summary keyed by `Namespace`.
18pub struct SummaryMemory {
19    store: Arc<dyn Store<String>>,
20    namespace: Namespace,
21}
22
23impl SummaryMemory {
24    /// Build a summary memory over `store` scoped to `namespace`.
25    pub fn new(store: Arc<dyn Store<String>>, namespace: Namespace) -> Self {
26        Self { store, namespace }
27    }
28
29    /// Borrow the bound namespace.
30    pub const fn namespace(&self) -> &Namespace {
31        &self.namespace
32    }
33
34    /// Replace the current summary.
35    pub async fn set(&self, ctx: &ExecutionContext, summary: impl Into<String>) -> Result<()> {
36        self.store
37            .put(ctx, &self.namespace, DEFAULT_KEY, summary.into())
38            .await
39    }
40
41    /// Append `addition` to the existing summary, separated by a blank
42    /// line. If no summary exists, `addition` becomes the summary.
43    pub async fn append(&self, ctx: &ExecutionContext, addition: &str) -> Result<()> {
44        let merged = match self.store.get(ctx, &self.namespace, DEFAULT_KEY).await? {
45            Some(existing) if !existing.is_empty() => format!("{existing}\n\n{addition}"),
46            _ => addition.to_owned(),
47        };
48        self.store
49            .put(ctx, &self.namespace, DEFAULT_KEY, merged)
50            .await
51    }
52
53    /// Read the current summary.
54    pub async fn get(&self, ctx: &ExecutionContext) -> Result<Option<String>> {
55        self.store.get(ctx, &self.namespace, DEFAULT_KEY).await
56    }
57
58    /// Delete the summary.
59    pub async fn clear(&self, ctx: &ExecutionContext) -> Result<()> {
60        self.store.delete(ctx, &self.namespace, DEFAULT_KEY).await
61    }
62}