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