1use std::collections::BTreeMap;
2use std::sync::{Arc, Mutex};
3
4use serde::{ser::SerializeStruct, Deserialize, Serialize};
5
6#[derive(Clone, Copy, Default)]
7pub struct DbState;
8
9#[derive(Clone, Default)]
10pub(super) struct StatusCache {
11 pub(super) entry: Arc<Mutex<Option<StatusCacheEntry>>>,
12}
13
14#[derive(Clone)]
15pub(super) struct StatusCacheEntry {
16 pub(super) generated_at_epoch: i64,
17 pub(super) payload: serde_json::Value,
18}
19
20#[derive(Deserialize, Default)]
21pub(super) struct StatusParams {
22 pub refresh: Option<bool>,
23}
24
25#[derive(Deserialize)]
26pub(super) struct SearchParams {
27 pub query: Option<String>,
28 pub project: Option<String>,
29 #[serde(rename = "type")]
30 pub memory_type: Option<String>,
31 pub limit: Option<i64>,
32 pub offset: Option<i64>,
33 pub include_stale: Option<bool>,
34 pub include_suppressed: Option<bool>,
35 pub branch: Option<String>,
36 pub multi_hop: Option<bool>,
37 pub explain: Option<bool>,
38}
39
40#[derive(Serialize)]
41pub(super) struct SearchResponse {
42 pub data: Vec<MemoryItem>,
43 pub meta: Meta,
44 #[serde(skip_serializing_if = "Option::is_none")]
45 pub multi_hop: Option<MultiHopInfo>,
46 #[serde(skip_serializing_if = "Vec::is_empty")]
49 pub raw_hits: Vec<RawHitItem>,
50 #[serde(skip_serializing_if = "Option::is_none")]
51 pub raw_hits_error: Option<String>,
52 #[serde(skip_serializing_if = "Option::is_none")]
53 pub explain: Option<crate::retrieval::search::SearchExplainDetails>,
54}
55
56#[derive(Serialize)]
57pub(super) struct RawHitItem {
58 pub id: i64,
59 pub session_id: String,
60 pub project: String,
61 pub role: String,
62 pub preview: String,
63 pub source: String,
64 #[serde(skip_serializing_if = "Option::is_none")]
65 pub branch: Option<String>,
66 pub created_at_epoch: i64,
67}
68
69#[derive(Serialize)]
70pub(super) struct MultiHopInfo {
71 pub hops: u8,
72 pub entities_discovered: Vec<String>,
73}
74
75#[derive(Serialize)]
76pub(super) struct MemoryItem {
77 pub id: i64,
78 #[serde(skip_serializing_if = "Option::is_none")]
79 pub version: Option<i64>,
80 pub title: String,
81 pub content: String,
82 pub memory_type: String,
83 pub project: String,
84 pub scope: String,
85 pub status: String,
86 pub classification: crate::truth::MemoryVisibilityClass,
87 pub current_context_eligible: bool,
88 pub classification_reason: String,
89 pub staleness: crate::memory::MemoryStalenessLabel,
90 #[serde(skip_serializing_if = "Option::is_none")]
91 pub topic_key: Option<String>,
92 #[serde(skip_serializing_if = "Option::is_none")]
93 pub branch: Option<String>,
94 pub created_at_epoch: i64,
95 pub updated_at_epoch: i64,
96}
97
98#[derive(Serialize)]
99pub(super) struct Meta {
100 pub count: usize,
101 pub has_more: bool,
102 pub limit: i64,
103 pub offset: i64,
104}
105
106#[derive(Serialize)]
107pub(super) struct ErrorResponse {
108 pub error: ErrorDetail,
109}
110
111#[derive(Serialize)]
112pub(super) struct ErrorDetail {
113 pub code: String,
114 pub message: String,
115}
116
117#[derive(Serialize)]
118pub(super) struct CapabilitiesResponse {
119 pub version: &'static str,
120 pub schema_version: i64,
121 pub api_version: u16,
122 pub features: CapabilitiesFeatures,
123 pub endpoints: BTreeMap<&'static str, &'static str>,
124}
125
126#[derive(Serialize)]
127pub(super) struct CapabilitiesFeatures {
128 pub health: bool,
129 pub status: bool,
130 pub stats: bool,
131 pub search: bool,
132 pub search_explain: bool,
133 pub memory_list: bool,
134 pub memory_detail: bool,
135 pub save_memory: bool,
136 pub memory_archive: bool,
137 pub memory_restore: bool,
138 pub memory_delete: bool,
139 pub candidate_rows: bool,
140 pub candidate_filters: bool,
141 pub candidate_review: bool,
142 pub candidate_detail: bool,
143 pub candidate_evidence: bool,
144 pub candidate_review_safe: bool,
145 pub observations: bool,
146 pub sessions: bool,
147 pub session_activity: bool,
148 pub workstreams: bool,
149 pub events: bool,
150 pub tasks: bool,
151 pub graph: bool,
152 pub user_recall: bool,
153 pub user_recall_usage_policy: bool,
154}
155
156#[derive(Serialize)]
157pub(super) struct HealthResponse {
158 pub ok: bool,
159 pub version: &'static str,
160 pub api_version: u16,
161 pub schema_version: i64,
162}
163
164#[derive(Deserialize)]
165#[serde(deny_unknown_fields)]
166pub(super) struct SaveMemoryRequest {
167 pub text: String,
168 #[serde(default)]
169 pub title: Option<String>,
170 #[serde(default)]
171 pub project: Option<String>,
172 #[serde(default)]
173 pub session_id: Option<String>,
174 #[serde(default)]
175 pub host: Option<String>,
176 #[serde(default)]
177 pub topic_key: Option<String>,
178 #[serde(default)]
179 pub memory_type: Option<String>,
180 #[serde(default)]
181 pub files: Option<Vec<String>>,
182 #[serde(default)]
183 pub scope: Option<String>,
184 #[serde(default)]
185 pub reference_time_epoch: Option<i64>,
186 #[serde(default)]
187 pub created_at_epoch: Option<i64>,
188 #[serde(default)]
189 pub branch: Option<String>,
190 #[serde(default)]
191 pub local_path: Option<String>,
192 #[serde(default)]
193 pub local_copy_enabled: Option<bool>,
194 #[serde(default)]
195 pub claim_enabled: Option<bool>,
196 #[serde(default)]
197 pub claim_source: Option<String>,
198 #[serde(default)]
199 pub idempotency_key: Option<String>,
200}
201
202#[derive(Deserialize)]
203pub(super) struct UserRecallRequest {
204 pub query: String,
205 #[serde(default)]
206 pub project: Option<String>,
207 #[serde(default)]
208 pub cwd: Option<String>,
209 #[serde(default)]
210 pub task_intent: Option<String>,
211 #[serde(default)]
212 pub current_files: Vec<String>,
213 #[serde(default)]
214 pub host: Option<String>,
215 #[serde(default)]
216 pub owner_scope: Option<String>,
217 #[serde(default)]
218 pub owner_key: Option<String>,
219 #[serde(default)]
220 pub state_keys: Vec<String>,
221 #[serde(default)]
222 pub include_sensitive: bool,
223 #[serde(default)]
224 pub include_suppressed: bool,
225 #[serde(default)]
226 pub limit: Option<i64>,
227 #[serde(default)]
228 pub budget_chars: Option<usize>,
229}
230
231#[derive(Serialize)]
232pub(super) struct SaveMemoryResponse {
233 pub id: i64,
234 pub status: String,
235 pub memory_type: String,
236 pub project: String,
237 pub scope: String,
238 pub topic_key: Option<String>,
239 pub branch: Option<String>,
240 pub operation: String,
241 pub created_at_epoch: i64,
242 pub reference_time_epoch: i64,
243 pub updated_at_epoch: i64,
244 pub upserted: bool,
245 pub local_copy: LocalCopyResponse,
246 pub local_status: String,
247 #[serde(skip_serializing_if = "Option::is_none")]
248 pub local_path: Option<String>,
249 pub claim_status: String,
250 pub claim_id: Option<i64>,
251 pub claim_error: Option<String>,
252 pub next_step: SaveMemoryNextStepResponse,
253}
254
255#[derive(Serialize)]
256pub(super) struct LocalCopyResponse {
257 pub status: String,
258 pub path: Option<String>,
259 #[serde(skip_serializing_if = "Option::is_none")]
260 pub reason: Option<String>,
261}
262
263#[derive(Serialize)]
264pub(super) struct SaveMemoryNextStepResponse {
265 pub tool: String,
266 pub ids: Vec<i64>,
267 pub source: String,
268 pub reason: String,
269}
270
271#[derive(Deserialize)]
272pub(super) struct ShowParams {
273 pub id: i64,
274 pub include_suppressed: Option<bool>,
275}
276
277#[derive(Deserialize)]
278pub(super) struct MemoryDetailParams {
279 pub include_suppressed: Option<bool>,
280}
281#[derive(Deserialize)]
284pub(super) struct ListParams {
285 pub project: Option<String>,
286 #[serde(rename = "type")]
287 pub memory_type: Option<String>,
288 pub scope: Option<String>,
289 pub status: Option<String>,
290 pub branch: Option<String>,
291 pub q: Option<String>,
292 pub include_suppressed: Option<bool>,
293 pub limit: Option<i64>,
294 pub offset: Option<i64>,
295}
296
297pub(super) struct ListMeta {
298 pub count: usize,
299 pub total: i64,
300 pub limit: i64,
301 pub offset: i64,
302}
303
304impl Serialize for ListMeta {
305 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
306 where
307 S: serde::Serializer,
308 {
309 let count = i64::try_from(self.count).unwrap_or(i64::MAX);
310 let consumed = self.offset.saturating_add(count);
311 let has_more = consumed < self.total;
312 let next_offset = has_more.then_some(consumed);
313
314 let mut state = serializer.serialize_struct("ListMeta", 6)?;
315 state.serialize_field("count", &self.count)?;
316 state.serialize_field("total", &self.total)?;
317 state.serialize_field("limit", &self.limit)?;
318 state.serialize_field("offset", &self.offset)?;
319 state.serialize_field("has_more", &has_more)?;
320 state.serialize_field("next_offset", &next_offset)?;
321 state.end()
322 }
323}
324
325#[derive(Serialize)]
326pub(super) struct ListResponse<T: Serialize> {
327 pub data: Vec<T>,
328 pub meta: ListMeta,
329}
330
331#[derive(Deserialize)]
332pub(super) struct CandidateParams {
333 pub project: Option<String>,
334 pub status: Option<String>,
335 #[serde(rename = "type")]
336 pub memory_type: Option<String>,
337 pub block_reason: Option<String>,
338 pub topic_key: Option<String>,
339 pub contains: Option<String>,
340 pub min_confidence: Option<f64>,
341 pub older_than_days: Option<i64>,
342 pub limit: Option<i64>,
343 pub offset: Option<i64>,
344}
345
346#[derive(Deserialize)]
347pub(super) struct BlockedParams {
348 pub project: Option<String>,
349}
350
351#[derive(Serialize)]
352pub(super) struct BlockedReasonItem {
353 #[serde(skip_serializing_if = "Option::is_none")]
354 pub reason: Option<String>,
355 pub pending: i64,
356 pub example_ids: Vec<i64>,
357}
358
359#[derive(Serialize)]
360pub(super) struct CandidateItem {
361 pub id: i64,
362 #[serde(skip_serializing_if = "Option::is_none")]
363 pub project: Option<String>,
364 pub memory_type: String,
365 pub text: String,
366 pub scope: String,
367 pub confidence: f64,
368 pub risk_class: String,
369 pub review_status: String,
370 pub evidence_count: i64,
371 pub created_at_epoch: i64,
372}
373
374#[derive(Deserialize)]
375pub(super) struct CandidateEditRequest {
376 #[serde(default)]
377 pub scope: Option<String>,
378 #[serde(default, rename = "memory_type")]
379 pub memory_type: Option<String>,
380 #[serde(default)]
381 pub topic_key: Option<String>,
382 #[serde(default)]
383 pub text: Option<String>,
384}
385
386#[derive(Deserialize, Default)]
387pub(super) struct CandidateApproveRequest {
388 #[serde(default)]
389 pub acknowledge_pattern: Option<String>,
390}
391
392#[derive(Serialize)]
393pub(super) struct CandidateReviewResponse {
394 pub candidate_id: i64,
395 pub status: String,
396 #[serde(skip_serializing_if = "Option::is_none")]
397 pub memory_id: Option<i64>,
398}
399
400#[derive(Serialize)]
401pub(super) struct CandidateDetailResponse {
402 pub data: CandidateDetailItem,
403 pub evidence: Vec<CandidateEvidenceItem>,
404 #[serde(skip_serializing_if = "Option::is_none")]
405 pub provenance: Option<CandidateDreamProvenance>,
406 pub decision: CandidateReviewDecision,
407}
408
409#[derive(Serialize)]
410pub(super) struct CandidateDreamProvenance {
411 pub kind: &'static str,
412 pub review_token: Option<String>,
413 pub authorized_supersede_ids: Vec<i64>,
414 pub artifacts: Vec<CandidateDreamArtifact>,
415}
416
417#[derive(Serialize)]
418pub(super) struct CandidateDreamArtifact {
419 pub artifact_id: i64,
420 pub version: i64,
421 pub project: String,
422 pub cluster_signature: String,
423 pub member_ids: Vec<i64>,
424 pub decision_kind: String,
425 pub decision_ids: Vec<i64>,
426 pub decision_payload_sha256: String,
427 pub intended_superseded_ids: Vec<i64>,
428 pub generated_topic_key: Option<String>,
429 pub generated_memory_type: Option<String>,
430 pub generated_title: Option<String>,
431 pub generated_content: Option<String>,
432 pub generated_field: String,
433 pub pattern_id: String,
434 pub pattern_version: i64,
435 pub source_operation: String,
436 pub source_trust_class: String,
437 pub occurrence_count: i64,
438 pub created_at_epoch: i64,
439 pub updated_at_epoch: i64,
440}
441
442#[derive(Serialize)]
443pub(super) struct CandidateDetailItem {
444 pub id: i64,
445 pub project: Option<String>,
446 pub scope: String,
447 pub memory_type: String,
448 pub topic_key: String,
449 pub text: String,
450 pub source_kind: Option<String>,
451 pub source_project: Option<String>,
452 pub target_project: Option<String>,
453 pub owner_scope: Option<String>,
454 pub owner_key: Option<String>,
455 pub topic_domain: Option<String>,
456 pub routing_confidence: Option<f64>,
457 pub routing_reason: Option<String>,
458 pub context_class: Option<String>,
459 pub confidence: f64,
460 pub risk_class: String,
461 pub review_status: String,
462 pub auto_promote_block_reason: Option<String>,
463 pub source_trust_class: String,
464 pub quarantine_pattern_id: Option<String>,
465 pub quarantine_pattern_version: Option<i64>,
466 pub version: i64,
467 pub created_at_epoch: i64,
468 pub updated_at_epoch: i64,
469}
470
471#[derive(Serialize)]
472pub(super) struct CandidateEvidenceItem {
473 pub source_kind: &'static str,
474 pub source_id: i64,
475 pub event_type: Option<String>,
476 pub role: Option<String>,
477 pub tool_name: Option<String>,
478 pub created_at_epoch: Option<i64>,
479 pub summary: String,
480 pub preview: String,
481 pub provenance_status: String,
482 pub redacted: bool,
483}
484
485#[derive(Serialize)]
486pub(super) struct CandidateReviewDecision {
487 pub can_review: bool,
488 pub blocked_reasons: Vec<String>,
489 pub actions: CandidateReviewActionDecisions,
490}
491
492#[derive(Serialize)]
493pub(super) struct CandidateReviewActionDecisions {
494 pub approve: CandidateReviewActionDecision,
495 pub reject: CandidateReviewActionDecision,
496 pub edit: CandidateReviewActionDecision,
497}
498
499#[derive(Serialize)]
500pub(super) struct CandidateReviewActionDecision {
501 pub allowed: bool,
502 pub blocked_reasons: Vec<String>,
503}
504
505#[derive(Debug, Clone, Deserialize)]
506#[serde(deny_unknown_fields)]
507pub(super) struct CandidateSafeApproveRequest {
508 pub reason: String,
509 pub expected_version: i64,
510 pub idempotency_key: String,
511 #[serde(default)]
512 pub acknowledge_pattern: Option<String>,
513 #[serde(default)]
514 pub acknowledge_dream_review_token: Option<String>,
515}
516
517#[derive(Debug, Clone, Deserialize)]
518#[serde(deny_unknown_fields)]
519pub(super) struct CandidateSafeRejectRequest {
520 pub reason: String,
521 pub expected_version: i64,
522 pub idempotency_key: String,
523}
524
525#[derive(Debug, Clone, Deserialize)]
526#[serde(deny_unknown_fields)]
527pub(super) struct CandidateSafeEditRequest {
528 pub reason: String,
529 pub expected_version: i64,
530 pub idempotency_key: String,
531 #[serde(default)]
532 pub scope: Option<String>,
533 #[serde(default, rename = "memory_type")]
534 pub memory_type: Option<String>,
535 #[serde(default)]
536 pub topic_key: Option<String>,
537 #[serde(default)]
538 pub text: Option<String>,
539}
540
541#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
542pub(super) struct CandidateSafeReviewResponse {
543 pub response_schema_version: i64,
544 pub operation_id: String,
545 pub audit_id: i64,
546 pub candidate_id: i64,
547 #[serde(skip_serializing_if = "Option::is_none")]
548 pub memory_id: Option<i64>,
549 pub action: String,
550 pub before_status: String,
551 pub after_status: String,
552 pub version: i64,
553 pub occurred_at_epoch: i64,
554 pub replayed: bool,
555}
556
557#[derive(Debug, Clone, Deserialize)]
558#[serde(deny_unknown_fields)]
559pub(super) struct MemorySafeGovernanceRequest {
560 pub reason: String,
561 pub expected_version: i64,
562 pub idempotency_key: String,
563}
564
565#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
566pub(super) struct MemorySafeGovernanceResponse {
567 pub response_schema_version: i64,
568 pub operation_id: String,
569 pub audit_id: i64,
570 pub memory_id: i64,
571 pub action: String,
572 pub before_status: String,
573 pub after_status: String,
574 pub version: i64,
575 pub occurred_at_epoch: i64,
576 pub replayed: bool,
577}
578
579#[derive(Serialize)]
580pub(super) struct SafeMutationErrorResponse {
581 pub error: SafeMutationErrorDetail,
582}
583
584#[derive(Serialize)]
585pub(super) struct SafeMutationErrorDetail {
586 pub code: String,
587 pub message: String,
588 #[serde(skip_serializing_if = "Option::is_none")]
589 pub operation_id: Option<String>,
590}
591
592#[derive(Deserialize)]
593pub(super) struct GraphParams {
594 pub project: Option<String>,
595 pub include_suppressed: Option<bool>,
596 pub limit: Option<i64>,
597}
598
599#[derive(Serialize)]
600pub(super) struct GraphNodeItem {
601 pub id: i64,
602 pub name: String,
603 pub entity_type: Option<String>,
604 pub mention_count: i64,
605 pub mems: Vec<i64>,
606}
607
608#[derive(Serialize)]
609pub(super) struct GraphEdgeItem {
610 pub a: i64,
611 pub b: i64,
612 pub w: i64,
613}
614
615#[derive(Serialize)]
616pub(super) struct GraphResponse {
617 pub nodes: Vec<GraphNodeItem>,
618 pub edges: Vec<GraphEdgeItem>,
619}
620
621#[derive(Serialize)]
622pub(super) struct MemoryEdgeItem {
623 pub id: i64,
624 pub edge_type: String,
625 pub from_memory_id: Option<i64>,
626 pub to_memory_id: Option<i64>,
627 pub confidence: Option<f64>,
628}
629
630#[derive(Serialize)]
631pub(super) struct MemoryDetailResponse {
632 #[serde(flatten)]
633 pub memory: MemoryItem,
634 pub entities: Vec<String>,
635 pub edges: Vec<MemoryEdgeItem>,
636}
637
638#[derive(Serialize)]
639pub(super) struct TypeCount {
640 pub memory_type: String,
641 pub count: i64,
642}
643
644#[derive(Serialize)]
645pub(super) struct StatsResponse {
646 pub active_memories: i64,
647 pub total_memories: i64,
648 pub pending_candidates: i64,
649 pub captured_events: i64,
650 pub pending_extraction_tasks: i64,
651 pub ai_calls: i64,
652 pub ai_cost_usd: f64,
653 pub ai_total_tokens: i64,
654 pub type_distribution: Vec<TypeCount>,
655}