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