atman_runtime/memory/
mod.rs1use 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, 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}