Skip to main content

atman_runtime/memory/
mod.rs

1use std::path::{Path, PathBuf};
2
3use serde::{Deserialize, Serialize};
4use tokio::io::AsyncWriteExt;
5use uuid::Uuid;
6
7use crate::error::RuntimeError;
8
9pub mod confession;
10pub mod goal;
11pub mod plan;
12pub mod spec;
13pub mod todo;
14
15pub use confession::{Confession, ConfessionStore};
16pub use goal::GoalStore;
17pub use plan::{Plan, PlanStep, PlanStore};
18pub use spec::{
19    SpecDeviation, SpecEntry, SpecMaterializeResult, SpecReview, SpecStatus, SpecStore,
20};
21pub use todo::{Todo, TodoStatus, TodoStore};
22
23#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
24#[serde(transparent)]
25pub struct MemoryId(pub Uuid);
26
27impl MemoryId {
28    pub fn now() -> Self {
29        Self(Uuid::now_v7())
30    }
31
32    pub fn parse(s: &str) -> Result<Self, uuid::Error> {
33        Uuid::parse_str(s).map(Self)
34    }
35}
36
37impl std::fmt::Display for MemoryId {
38    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39        self.0.fmt(f)
40    }
41}
42
43pub(crate) async fn append_jsonl(path: &Path, value: &impl Serialize) -> Result<(), RuntimeError> {
44    if let Some(parent) = path.parent() {
45        tokio::fs::create_dir_all(parent)
46            .await
47            .map_err(|e| RuntimeError::ToolFailed(format!("mkdir {}: {e}", parent.display())))?;
48    }
49    let mut file = tokio::fs::OpenOptions::new()
50        .create(true)
51        .append(true)
52        .open(path)
53        .await
54        .map_err(|e| RuntimeError::ToolFailed(format!("open {}: {e}", path.display())))?;
55    let line = serde_json::to_string(value)
56        .map_err(|e| RuntimeError::ToolFailed(format!("encode: {e}")))?;
57    file.write_all(line.as_bytes())
58        .await
59        .map_err(|e| RuntimeError::ToolFailed(format!("write: {e}")))?;
60    file.write_all(b"\n")
61        .await
62        .map_err(|e| RuntimeError::ToolFailed(format!("write: {e}")))?;
63    file.flush()
64        .await
65        .map_err(|e| RuntimeError::ToolFailed(format!("flush: {e}")))?;
66    Ok(())
67}
68
69pub(crate) async fn read_jsonl<T: for<'de> Deserialize<'de>>(
70    path: &PathBuf,
71) -> Result<Vec<T>, RuntimeError> {
72    let contents = match tokio::fs::read_to_string(path).await {
73        Ok(c) => c,
74        Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(Vec::new()),
75        Err(e) => {
76            return Err(RuntimeError::ToolFailed(format!(
77                "read {}: {e}",
78                path.display()
79            )));
80        }
81    };
82    let mut out = Vec::new();
83    for (i, line) in contents.lines().enumerate() {
84        if line.trim().is_empty() {
85            continue;
86        }
87        match serde_json::from_str::<T>(line) {
88            Ok(v) => out.push(v),
89            Err(e) => {
90                let key = format!("jsonl.malformed:{}", path.display());
91                crate::notify!(
92                    warn,
93                    location = Log,
94                    stack = merge_count(key, 60_000),
95                    "skipping malformed jsonl line {}:{}: {e}",
96                    path.display(),
97                    i + 1
98                );
99            }
100        }
101    }
102    Ok(out)
103}