Skip to main content

rightkit_memory_client/backend/
contract.rs

1//! Async contract shared by all the host app memory implementations.
2
3use crate::backend::error::MemoryBackendError;
4use crate::backend::types::{
5    BackendBinding, FullMemoryRecord, JsonValue, MemoryEntry, MemoryId, MemoryListRow, MemoryTier,
6    Score,
7};
8use async_trait::async_trait;
9use std::time::{Duration, Instant};
10use tokio_util::sync::CancellationToken;
11
12/// Per-call deadline and cancellation state used by concrete backends.
13#[derive(Clone, Debug)]
14pub struct CallContext {
15    deadline: Option<Instant>,
16    cancellation: CancellationToken,
17}
18
19impl CallContext {
20    pub fn unbounded() -> Self {
21        Self {
22            deadline: None,
23            cancellation: CancellationToken::new(),
24        }
25    }
26
27    pub fn after(duration: Duration) -> Self {
28        Self {
29            deadline: Some(Instant::now() + duration),
30            cancellation: CancellationToken::new(),
31        }
32    }
33
34    pub fn with_cancellation(mut self, cancellation: CancellationToken) -> Self {
35        self.cancellation = cancellation;
36        self
37    }
38
39    pub fn cancellation(&self) -> CancellationToken {
40        self.cancellation.clone()
41    }
42
43    pub fn deadline(&self) -> Option<Instant> {
44        self.deadline
45    }
46
47    pub fn check(&self) -> Result<(), MemoryBackendError> {
48        if self.cancellation.is_cancelled() {
49            return Err(MemoryBackendError::cancelled("memory operation cancelled"));
50        }
51        if self
52            .deadline
53            .is_some_and(|deadline| Instant::now() >= deadline)
54        {
55            return Err(MemoryBackendError::timeout(
56                "memory operation deadline elapsed",
57            ));
58        }
59        Ok(())
60    }
61}
62
63/// The single memory surface consumed by the host app routes, tools, and Legion adapters.
64///
65/// Methods retain source-compatible arguments and return aliases while making failure
66/// explicit for service, embedded, and future migration implementations.
67#[async_trait]
68pub trait MemoryBackend: Send + Sync {
69    fn binding(&self) -> &BackendBinding;
70
71    async fn activity_json(
72        &self,
73        context: &CallContext,
74        limit: usize,
75    ) -> Result<JsonValue, MemoryBackendError>;
76    async fn delete(&self, context: &CallContext, id: &str) -> Result<bool, MemoryBackendError>;
77    async fn embedder_dim(&self, context: &CallContext) -> Result<usize, MemoryBackendError>;
78    async fn entries(
79        &self,
80        context: &CallContext,
81        limit: usize,
82    ) -> Result<Vec<MemoryEntry>, MemoryBackendError>;
83    async fn get_full(
84        &self,
85        context: &CallContext,
86        id: &str,
87    ) -> Result<FullMemoryRecord, MemoryBackendError>;
88    #[allow(clippy::too_many_arguments)]
89    async fn log_recall_with_replay(
90        &self,
91        context: &CallContext,
92        timestamp: &str,
93        scope: Option<&str>,
94        query_chars: usize,
95        hit_count: usize,
96        full_chars: usize,
97        injected_chars: usize,
98        source: &str,
99        query_excerpt: Option<&str>,
100        client: Option<&str>,
101        model: Option<&str>,
102        session: Option<&str>,
103        turn: Option<&str>,
104        task: Option<&str>,
105        actor: Option<&str>,
106        mode: &str,
107        replay_hits: &str,
108        replay_context: &str,
109    ) -> Result<(), MemoryBackendError>;
110    async fn metrics_json(&self, context: &CallContext) -> Result<JsonValue, MemoryBackendError>;
111    async fn put(
112        &self,
113        context: &CallContext,
114        name: &str,
115        content: &str,
116        scope: &str,
117        tier: MemoryTier,
118    ) -> Result<MemoryId, MemoryBackendError>;
119    async fn recall_scored(
120        &self,
121        context: &CallContext,
122        query: &str,
123        limit: usize,
124        scopes: &[String],
125    ) -> Result<Vec<(MemoryEntry, Score)>, MemoryBackendError>;
126    async fn record_injections(
127        &self,
128        context: &CallContext,
129        ids: &[String],
130    ) -> Result<(), MemoryBackendError>;
131    async fn remember(
132        &self,
133        context: &CallContext,
134        content: &str,
135        keywords: Vec<String>,
136    ) -> Result<MemoryEntry, MemoryBackendError>;
137    async fn scopes(&self, context: &CallContext) -> Result<Vec<String>, MemoryBackendError>;
138    async fn search(
139        &self,
140        context: &CallContext,
141        query: &str,
142        limit: usize,
143    ) -> Result<Vec<MemoryEntry>, MemoryBackendError>;
144    async fn try_put(
145        &self,
146        context: &CallContext,
147        name: &str,
148        content: &str,
149        scope: &str,
150        tier: MemoryTier,
151    ) -> Result<MemoryId, MemoryBackendError>;
152
153    /// Production `/list` operation recorded by CR-000, retained for route migration.
154    async fn list(
155        &self,
156        context: &CallContext,
157        scope: Option<&str>,
158    ) -> Result<Vec<MemoryListRow>, MemoryBackendError>;
159
160    /// Production consolidation operation recorded by CR-000, retained for learning migration.
161    async fn remember_consolidated(
162        &self,
163        context: &CallContext,
164        name: &str,
165        content: &str,
166        keywords: Vec<String>,
167        threshold: Score,
168    ) -> Result<Option<MemoryEntry>, MemoryBackendError>;
169}