1use std::collections::BTreeMap;
2use std::sync::Arc;
3
4use serde::{Deserialize, Serialize};
5use time::OffsetDateTime;
6
7use crate::extension::{EmbeddingProviderId, MemoryStoreId};
8
9pub type MemoryId = String;
10
11#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
12pub enum MemoryScope {
13 Global,
14 User(String),
15 Workspace(String),
16 Project(String),
17 Thread(String),
18}
19
20impl MemoryScope {
21 pub fn stable_id(&self) -> String {
22 match self {
23 MemoryScope::Global => "global".to_string(),
24 MemoryScope::User(id) => format!("user:{id}"),
25 MemoryScope::Workspace(id) => format!("workspace:{id}"),
26 MemoryScope::Project(id) => format!("project:{id}"),
27 MemoryScope::Thread(id) => format!("thread:{id}"),
28 }
29 }
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
33#[serde(rename_all = "camelCase")]
34pub struct MemoryScopeDescriptor {
35 pub id: String,
36 pub scope: MemoryScope,
37 pub label: String,
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
41#[serde(rename_all = "camelCase")]
42pub struct MemoryUsageMetadata {
43 pub use_count: u64,
44 #[serde(default, with = "time::serde::rfc3339::option")]
45 pub last_used_at: Option<OffsetDateTime>,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
49#[serde(rename_all = "camelCase")]
50pub struct MemoryCitation {
51 pub memory_id: MemoryId,
52 pub scope_id: String,
53 pub snippet: String,
54 pub score_millis: u32,
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
58#[serde(rename_all = "camelCase")]
59pub struct MemoryRecord {
60 pub id: Option<MemoryId>,
61 pub scope: MemoryScope,
62 pub text: String,
63 #[serde(default)]
64 pub content_hash: Option<String>,
65 pub metadata: serde_json::Value,
66 #[serde(default)]
67 pub usage: Option<MemoryUsageMetadata>,
68 #[serde(default)]
69 pub deleted: bool,
70 #[serde(with = "time::serde::rfc3339")]
71 pub created_at: OffsetDateTime,
72 #[serde(with = "time::serde::rfc3339")]
73 pub updated_at: OffsetDateTime,
74}
75
76#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
77pub struct MemoryQuery {
78 pub scope: Option<MemoryScope>,
79 pub text: String,
80 pub limit: usize,
81 #[serde(default)]
82 pub include_global: bool,
83 #[serde(default)]
84 pub provider_id: Option<EmbeddingProviderId>,
85 #[serde(default)]
86 pub model: Option<String>,
87}
88
89#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
90#[serde(rename_all = "camelCase")]
91pub struct MemorySearchResult {
92 pub record: MemoryRecord,
93 pub score: f32,
94 #[serde(default)]
95 pub citation: Option<MemoryCitation>,
96}
97
98#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
99#[serde(rename_all = "camelCase")]
100pub struct MemorySaveRequest {
101 pub scope: MemoryScope,
102 pub text: String,
103 #[serde(default)]
104 pub metadata: serde_json::Value,
105}
106
107#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
108#[serde(rename_all = "camelCase")]
109pub struct MemoryUpdateRequest {
110 pub id: MemoryId,
111 pub text: String,
112 #[serde(default)]
113 pub metadata: serde_json::Value,
114}
115
116#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
117#[serde(rename_all = "camelCase")]
118pub struct MemoryProviderSelection {
119 pub provider_id: EmbeddingProviderId,
120 pub model: String,
121}
122
123#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
124#[serde(rename_all = "camelCase")]
125pub struct MemoryJobLease {
126 pub id: String,
127 pub scope_id: String,
128 pub provider_id: EmbeddingProviderId,
129 pub model: String,
130 #[serde(with = "time::serde::rfc3339")]
131 pub leased_until: OffsetDateTime,
132 #[serde(default)]
133 pub attempts: u32,
134 #[serde(default)]
135 pub metadata: BTreeMap<String, String>,
136}
137
138#[async_trait::async_trait]
139pub trait MemoryStore: Send + Sync {
140 fn id(&self) -> MemoryStoreId;
141
142 async fn put(&self, record: MemoryRecord) -> anyhow::Result<MemoryId>;
143 async fn get(&self, id: &MemoryId) -> anyhow::Result<Option<MemoryRecord>>;
144 async fn search(&self, query: MemoryQuery) -> anyhow::Result<Vec<MemorySearchResult>>;
145 async fn delete(&self, id: &MemoryId) -> anyhow::Result<()>;
146 async fn list(
147 &self,
148 scope: Option<MemoryScope>,
149 limit: usize,
150 ) -> anyhow::Result<Vec<MemoryRecord>> {
151 let text = String::new();
152 let results = self
153 .search(MemoryQuery {
154 scope,
155 text,
156 limit,
157 include_global: false,
158 provider_id: None,
159 model: None,
160 })
161 .await?;
162 Ok(results.into_iter().map(|result| result.record).collect())
163 }
164}
165
166pub trait MemoryStoreFactory: Send + Sync + 'static {
167 fn id(&self) -> MemoryStoreId;
168 fn create(&self) -> Arc<dyn MemoryStore>;
169}