Skip to main content

context_forge/distill/
mod.rs

1//! Distillation: turning a raw conversation transcript into durable memory.
2//!
3//! This module defines the [`Distiller`] trait and the data types it
4//! produces. The trait itself is always available (not feature-gated) so
5//! that callers can implement their own distillers — for example, a remote
6//! API client, a different local model runtime, or a test stub.
7//!
8//! The `OpenAiCompatDistiller` implementation in the `openai_compat`
9//! submodule (behind the `distill-http` feature) talks to an
10//! OpenAI-compatible chat completions endpoint such as Ollama or
11//! llama-server. It is the only place in this crate that performs HTTP, and
12//! only when that feature is enabled.
13
14#[cfg(feature = "distill-http")]
15pub mod openai_compat;
16
17use serde::{Deserialize, Serialize};
18
19use crate::traits::Result;
20
21/// The result of distilling a conversation transcript into durable memory.
22#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct DistilledMemory {
24    /// A summary of the conversation, intended to be under 150 words.
25    pub summary: String,
26    /// Individual facts worth remembering across future sessions.
27    pub facts: Vec<Fact>,
28}
29
30/// A single distilled fact extracted from a transcript.
31#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct Fact {
33    /// The category of this fact.
34    pub kind: FactKind,
35    /// One self-contained sentence describing the fact, understandable
36    /// without the original transcript.
37    pub text: String,
38}
39
40/// The category of a distilled [`Fact`].
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
42#[serde(rename_all = "lowercase")]
43#[non_exhaustive]
44pub enum FactKind {
45    /// A decision that was made, and (ideally) why.
46    Decision,
47    /// A correction the user gave.
48    Correction,
49    /// A user preference.
50    Preference,
51    /// A state change ("X is now Y").
52    State,
53}
54
55/// Produces [`DistilledMemory`] from a raw conversation transcript.
56///
57/// Implementations must be thread-safe (`Send + Sync`) so a single
58/// distiller instance can be shared across worker threads.
59pub trait Distiller: Send + Sync {
60    /// Distill `transcript` into a summary and a list of durable facts.
61    ///
62    /// # Security
63    ///
64    /// Implementations transmit `transcript` verbatim to the underlying
65    /// model or service — no secret scrubbing is applied at this layer.
66    /// [`ContextForge::distill_and_save`](crate::ContextForge::distill_and_save)
67    /// is the only entry point that scrubs secrets (via
68    /// [`scrub_secrets`](crate::scrub_secrets)) before a transcript reaches
69    /// a [`Distiller`]; callers invoking [`Distiller::distill`] directly are
70    /// responsible for scrubbing first.
71    ///
72    /// # Errors
73    ///
74    /// Returns an error if distillation fails (e.g. the backing model or
75    /// service is unavailable, or its response cannot be parsed).
76    fn distill(&self, transcript: &str) -> Result<DistilledMemory>;
77}