use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct MemoryChunk {
pub id: String,
pub created_at: DateTime<Utc>,
pub kind: ChunkKind,
pub tier: MemoryTier,
pub source: ChunkSource,
pub session_id: Option<String>,
pub project_root: Option<String>,
pub caller_id: String,
pub content: String,
pub metadata: Value,
pub importance: f32,
pub pinned: bool,
#[serde(default)]
pub shareable: bool,
pub superseded_by: Option<String>,
pub embedding_model: String,
pub embedding_dim: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct NewMemoryChunk {
pub kind: ChunkKind,
pub source: ChunkSource,
pub session_id: Option<String>,
pub project_root: Option<String>,
pub caller_id: String,
pub content: String,
#[serde(default)]
pub metadata: Value,
#[serde(default)]
pub importance: Option<f32>,
#[serde(default)]
pub shareable: bool,
#[serde(default)]
pub pinned: bool,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "snake_case")]
pub enum ChunkKind {
Chat,
Action,
Fire,
Observation,
Correction,
JobSummary,
Context,
Rollup,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "snake_case")]
pub enum ChunkSource {
Embedded,
Mcp,
Gateway,
Matcher,
Perception,
System,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "snake_case")]
pub enum MemoryTier {
Session,
LongTerm,
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn chunk_kind_serializes_snake_case() {
assert_eq!(
serde_json::to_value(ChunkKind::JobSummary).unwrap(),
json!("job_summary")
);
let back: ChunkKind = serde_json::from_value(json!("rollup")).unwrap();
assert_eq!(back, ChunkKind::Rollup);
}
#[test]
fn chunk_source_serializes_snake_case() {
assert_eq!(
serde_json::to_value(ChunkSource::Gateway).unwrap(),
json!("gateway")
);
}
#[test]
fn new_chunk_defaults() {
let raw = r#"{"kind":"chat","source":"embedded","caller_id":"embedded","content":"hi","session_id":null,"project_root":null}"#;
let n: NewMemoryChunk = serde_json::from_str(raw).unwrap();
assert_eq!(n.kind, ChunkKind::Chat);
assert_eq!(n.importance, None);
assert!(!n.shareable);
assert!(!n.pinned);
assert_eq!(n.metadata, Value::Null);
}
#[test]
fn chunk_round_trip() {
let c = MemoryChunk {
id: "01950000-0000-7000-8000-000000000001".into(),
created_at: Utc::now(),
kind: ChunkKind::Action,
tier: MemoryTier::Session,
source: ChunkSource::Gateway,
session_id: Some("sess_abc".into()),
project_root: Some("/Users/dim/Workspace".into()),
caller_id: "embedded".into(),
content: "Agent attempted fs.copy".into(),
metadata: json!({"action_type":"fs.copy"}),
importance: 0.7,
pinned: false,
shareable: false,
superseded_by: None,
embedding_model: "none".into(),
embedding_dim: 0,
};
let s = serde_json::to_string(&c).unwrap();
let back: MemoryChunk = serde_json::from_str(&s).unwrap();
assert_eq!(c, back);
}
#[test]
fn chunk_shareable_default_false_on_deserialize() {
let raw = r#"{"id":"x","created_at":"2026-01-01T00:00:00Z","kind":"chat",
"tier":"session","source":"embedded","session_id":null,
"project_root":null,"caller_id":"embedded",
"content":"hi","metadata":null,"importance":0.5,"pinned":false,
"superseded_by":null,"embedding_model":"none","embedding_dim":0}"#;
let c: MemoryChunk = serde_json::from_str(raw).unwrap();
assert!(!c.shareable);
}
}