// models.mbt — data models (mirrors Python `models.py`)
//
// Translates MemoryType / MemoryStatus enums and the MemoryEntry dataclass
// into MoonBit. Timestamps are kept as epoch milliseconds (Int64) rather than
// ISO-8601 strings so the system stays dependency-free; the on-disk JSON
// shape (same field names, same semantics) is otherwise identical.
///|
/// Memory categories. Mirrors Python `MemoryType(str, Enum)`.
pub enum MemoryType {
CommonSense
Instruction
Preference
Fact
Other
} derive(Debug, Eq)
///|
pub fn MemoryType::value(self : MemoryType) -> String {
match self {
CommonSense => "common_sense"
Instruction => "instruction"
Preference => "preference"
Fact => "fact"
Other => "other"
}
}
///|
pub fn MemoryType::from_str(s : String) -> MemoryType {
match s {
"common_sense" => CommonSense
"instruction" => Instruction
"preference" => Preference
"fact" => Fact
_ => Other
}
}
///|
/// Memory lifecycle status. Mirrors Python `MemoryStatus(str, Enum)`.
pub enum MemoryStatus {
Active
Deprecated
Archived
} derive(Debug, Eq)
///|
pub fn MemoryStatus::value(self : MemoryStatus) -> String {
match self {
Active => "active"
Deprecated => "deprecated"
Archived => "archived"
}
}
///|
pub fn MemoryStatus::from_str(s : String) -> MemoryStatus {
match s {
"deprecated" => Deprecated
"archived" => Archived
_ => Active
}
}
///|
/// All memory types, used for storage directory layout and index categories.
pub fn all_types() -> Array[MemoryType] {
[CommonSense, Instruction, Preference, Fact, Other]
}
///|
/// A single memory entry. Mirrors Python `MemoryEntry`.
///
/// Field `mtype` maps to JSON key `"type"` (the word `type` is a MoonBit
/// keyword, so the struct field is named `mtype` and serialization renames it).
pub struct MemoryEntry {
id : String
mtype : String
content : String
confidence : Double
source : String
timestamp : Int64
access_count : Int
priority : Double
status : String
last_accessed : Int64
feedback_score : Double
tags : Array[String]
} derive(Debug)
///|
let _id_counter : @ref.Ref[Int64] = @ref.Ref(0L)
///|
/// Current time in epoch milliseconds (UTC).
pub fn now_ms() -> Int64 {
@env.now().reinterpret_as_int64()
}
///|
/// Generate a unique entry id.
///
/// Python's original uses `"mem_" + uuid4().hex[:12]` (random). We use a
/// time + monotonic-counter scheme that is equally unique and keeps the port
/// dependency-free (no UUID package needed). Tests only rely on uniqueness
/// and the `"mem_"` prefix, both of which this satisfies.
fn gen_id() -> String {
let c = _id_counter.val
_id_counter.val = c + 1L
"mem_" + now_ms().to_string() + "_" + c.to_string()
}
///|
/// Create a new entry (mirrors `MemoryEntry.create`).
pub fn MemoryEntry::create(
content : String,
mtype : MemoryType,
source? : String = "user_input",
confidence? : Double = 0.8,
tags? : Array[String] = [],
) -> MemoryEntry {
let now = now_ms()
{
id: gen_id(),
mtype: mtype.value(),
content,
confidence,
source,
timestamp: now,
access_count: 0,
priority: 0.5,
status: MemoryStatus::Active.value(),
last_accessed: now,
feedback_score: 0.0,
tags,
}
}
///|
/// Serialize an entry to a JSON value (key `"type"` is emitted for `mtype`).
pub fn MemoryEntry::to_json(self : MemoryEntry) -> Json {
let m : Map[String, Json] = Map([], capacity=16)
m.set("id", Json::string(self.id))
m.set("type", Json::string(self.mtype))
m.set("content", Json::string(self.content))
m.set("confidence", Json::number(self.confidence))
m.set("source", Json::string(self.source))
m.set("timestamp", Json::number(self.timestamp.to_double()))
m.set("access_count", Json::number(self.access_count.to_double()))
m.set("priority", Json::number(self.priority))
m.set("status", Json::string(self.status))
m.set("last_accessed", Json::number(self.last_accessed.to_double()))
m.set("feedback_score", Json::number(self.feedback_score))
let tags_json : Array[Json] = self.tags.map(fn(t) { Json::string(t) })
m.set("tags", Json::array(tags_json))
Json::object(m)
}
///|
/// Parse an entry from a JSON value. Returns `None` on malformed input.
pub fn MemoryEntry::from_json(j : Json) -> MemoryEntry? {
match j {
Object(m) => {
let id = match m.get("id") {
Some(String(s)) => s
_ => return None
}
let mtype = match m.get("type") {
Some(String(s)) => s
_ => return None
}
let content = match m.get("content") {
Some(String(s)) => s
_ => return None
}
let source = match m.get("source") {
Some(String(s)) => s
_ => "user_input"
}
let confidence = match m.get("confidence") {
Some(Number(n, ..)) => n
_ => 0.8
}
let timestamp = match m.get("timestamp") {
Some(Number(n, ..)) => n.to_int64()
_ => 0L
}
let access_count = match m.get("access_count") {
Some(Number(n, ..)) => n.to_int64().to_int()
_ => 0
}
let priority = match m.get("priority") {
Some(Number(n, ..)) => n
_ => 0.5
}
let status = match m.get("status") {
Some(String(s)) => s
_ => "active"
}
let last_accessed = match m.get("last_accessed") {
Some(Number(n, ..)) => n.to_int64()
_ => 0L
}
let feedback_score = match m.get("feedback_score") {
Some(Number(n, ..)) => n
_ => 0.0
}
let tags : Array[String] = match m.get("tags") {
Some(Array(a)) =>
a.map(fn(x) {
match x {
String(s) => s
_ => ""
}
})
_ => []
}
Some({
id,
mtype,
content,
confidence,
source,
timestamp,
access_count,
priority,
status,
last_accessed,
feedback_score,
tags,
})
}
_ => None
}
}