Skip to main content

a3s_code_core/durable_memory/
policy.rs

1use super::invalid;
2use a3s_memory::repository::{DurableMemoryKind, MemoryRepositoryError, MAX_QUERY_LIMIT};
3use serde::{Deserialize, Serialize};
4
5/// Runtime behavior enabled for one durable-memory binding.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
7#[serde(rename_all = "snake_case")]
8#[non_exhaustive]
9pub enum DurableMemoryMode {
10    /// Mirror successful V1 extractions as evidence-backed V2 candidates.
11    ShadowCandidates,
12    /// Mirror candidates and recall only explicitly activated V2 nodes.
13    ActiveRecall,
14}
15
16/// Bounded policy for opt-in active V2 recall.
17#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
18#[serde(rename_all = "camelCase")]
19pub struct DurableMemoryRecallPolicy {
20    max_results: usize,
21    min_lexical_score: f32,
22    max_related_lookups: usize,
23}
24
25impl DurableMemoryRecallPolicy {
26    pub fn try_new(
27        max_results: usize,
28        min_lexical_score: f32,
29    ) -> Result<Self, MemoryRepositoryError> {
30        if !(1..=MAX_QUERY_LIMIT).contains(&max_results) {
31            return Err(invalid(
32                "recallPolicy.maxResults",
33                format!("must be between 1 and {MAX_QUERY_LIMIT}"),
34            ));
35        }
36        if !min_lexical_score.is_finite() || !(0.0..=1.0).contains(&min_lexical_score) {
37            return Err(invalid(
38                "recallPolicy.minLexicalScore",
39                "must be finite and between 0 and 1",
40            ));
41        }
42        Ok(Self {
43            max_results,
44            min_lexical_score,
45            max_related_lookups: 0,
46        })
47    }
48
49    /// Enable a bounded number of exact `RelatedTo` target reads after lexical
50    /// seeding. Final results remain capped by `max_results`.
51    pub fn try_with_related_lookups(
52        mut self,
53        max_related_lookups: usize,
54    ) -> Result<Self, MemoryRepositoryError> {
55        if max_related_lookups > MAX_QUERY_LIMIT {
56            return Err(invalid(
57                "recallPolicy.maxRelatedLookups",
58                format!("must not exceed {MAX_QUERY_LIMIT}"),
59            ));
60        }
61        self.max_related_lookups = max_related_lookups;
62        Ok(self)
63    }
64
65    pub fn max_results(self) -> usize {
66        self.max_results
67    }
68
69    pub fn min_lexical_score(self) -> f32 {
70        self.min_lexical_score
71    }
72
73    pub fn max_related_lookups(self) -> usize {
74        self.max_related_lookups
75    }
76}
77
78/// Retrieval branch that produced one pure recall preview hit.
79#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
80#[serde(rename_all = "snake_case")]
81#[non_exhaustive]
82pub enum DurableMemoryRecallChannel {
83    Lexical,
84    Semantic,
85    Hybrid,
86    Related,
87}
88
89/// One active V2 hit returned by a pure diagnostic recall preview.
90#[derive(Debug, Clone, PartialEq, Serialize)]
91#[serde(rename_all = "camelCase")]
92#[non_exhaustive]
93pub struct DurableMemoryRecallHit {
94    pub node_id: String,
95    pub node_revision: u64,
96    pub kind: DurableMemoryKind,
97    pub content: String,
98    pub score: f32,
99    pub channel: DurableMemoryRecallChannel,
100    pub related_from: Option<String>,
101}
102
103/// Pure, bounded active-memory recall result. Previewing does not record an
104/// admission or use event and therefore cannot authorize prompt injection.
105#[derive(Debug, Clone, PartialEq, Serialize)]
106#[serde(rename_all = "camelCase")]
107#[non_exhaustive]
108pub struct DurableMemoryRecallPreview {
109    pub hits: Vec<DurableMemoryRecallHit>,
110}