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}