Deploy Poimen Memory K8s cluster with ArgoCD tracking (M2.2, M3.5-M3.7)
ci / markdown (push) Waiting to run

This commit is contained in:
Story Crater Bot
2026-08-22 23:13:42 -07:00
parent 9c723fe66f
commit d3be7f6fd4
105 changed files with 10973 additions and 113 deletions
+223
View File
@@ -0,0 +1,223 @@
//! `mem capture | resolve | lookup | materialize`
//!
//! Storage layout under `$MEM_HOME` (default `~/.mem`):
//!
//! ```text
//! events.jsonl authoritative, append-only
//! lessons.json projection, rebuilt by `mem resolve`
//! skills/<tool>-failures/SKILL.md projection, Claude Code convention
//! MEMORY.md projection, CLAUDE.md-style @import target
//! ```
//!
//! Only `events.jsonl` is authoritative. Everything else is regenerable, which
//! is the same invariant the full design applies to pgvector.
use anyhow::{Context, Result};
use mem_core::lesson::{
derive_lessons, extract, lookup as lookup_lesson, render_injection, render_skill, tool_of_cmd,
Confidence, Event, Lesson,
};
use std::collections::BTreeMap;
use std::fs;
use std::io::Write;
use std::path::{Path, PathBuf};
use time::format_description::well_known::Rfc3339;
use time::OffsetDateTime;
pub fn mem_home() -> PathBuf {
std::env::var("MEM_HOME")
.map(PathBuf::from)
.unwrap_or_else(|_| {
let home = std::env::var("HOME").unwrap_or_else(|_| ".".into());
PathBuf::from(home).join(".mem")
})
}
fn events_path() -> PathBuf {
mem_home().join("events.jsonl")
}
fn lessons_path() -> PathBuf {
mem_home().join("lessons.json")
}
/// Cap stored output. A 50KB log adds nothing a signature does not already
/// carry, and the log is append-only so it never shrinks.
const OUTPUT_CAP: usize = 4096;
pub fn cmd_capture(cmd: &str, exit: i32, output_file: Option<&Path>, cwd: Option<&str>) -> Result<()> {
// Successes are recorded too: without them there is no fail -> success pair
// to learn from.
let raw = match output_file {
Some(p) => fs::read_to_string(p).unwrap_or_default(),
None => {
use std::io::Read;
let mut s = String::new();
let _ = std::io::stdin().read_to_string(&mut s);
s
}
};
let tail: String = if raw.len() > OUTPUT_CAP {
raw[raw.len() - OUTPUT_CAP..].to_string()
} else {
raw
};
let cwd = cwd
.map(str::to_string)
.unwrap_or_else(|| std::env::current_dir().map(|p| p.display().to_string()).unwrap_or_default());
let ev = Event {
ts: OffsetDateTime::now_utc().format(&Rfc3339)?,
cwd,
cmd: cmd.to_string(),
exit,
output: tail,
};
let p = events_path();
fs::create_dir_all(p.parent().unwrap())?;
let mut f = fs::OpenOptions::new().create(true).append(true).open(&p)?;
writeln!(f, "{}", serde_json::to_string(&ev)?)?;
Ok(())
}
fn load_events() -> Result<Vec<Event>> {
let p = events_path();
if !p.exists() {
return Ok(vec![]);
}
let s = fs::read_to_string(&p)?;
Ok(s.lines()
.filter(|l| !l.trim().is_empty())
.filter_map(|l| serde_json::from_str::<Event>(l).ok())
.collect())
}
pub fn load_lessons() -> Result<Vec<Lesson>> {
let p = lessons_path();
if !p.exists() {
return Ok(vec![]);
}
Ok(serde_json::from_str(&fs::read_to_string(&p)?)?)
}
pub fn cmd_resolve(json: bool) -> Result<()> {
let events = load_events()?;
let mut derived = derive_lessons(&events, tool_of_cmd);
// Preserve human confirmation across rebuilds. The projection is
// regenerable, but the human's judgement about it is not.
let previous = load_lessons().unwrap_or_default();
for l in derived.iter_mut() {
if let Some(old) = previous.iter().find(|o| o.sig_sha == l.sig_sha) {
if old.confidence == Confidence::Confirmed {
l.confidence = Confidence::Confirmed;
}
}
}
fs::create_dir_all(mem_home())?;
fs::write(lessons_path(), serde_json::to_string_pretty(&derived)?)?;
if json {
println!("{}", serde_json::to_string_pretty(&derived)?);
} else {
println!(
"{} events -> {} lessons ({} recurring)",
events.len(),
derived.len(),
derived.iter().filter(|l| l.seen >= 3).count()
);
for l in &derived {
println!(" [{}] seen {}x {}", l.tool, l.seen, l.raw.trim());
}
}
Ok(())
}
/// Default similarity floor. Below this we abstain: an agent acts on the top
/// result, so a weak match is worse than nothing.
pub const DEFAULT_FLOOR: f32 = 0.55;
pub fn cmd_lookup(tool: Option<&str>, cmd: Option<&str>, file: Option<&Path>, floor: f32) -> Result<()> {
let raw = match file {
Some(p) => fs::read_to_string(p).with_context(|| format!("reading {}", p.display()))?,
None => {
use std::io::Read;
let mut s = String::new();
std::io::stdin().read_to_string(&mut s)?;
s
}
};
let tool = tool
.map(str::to_string)
.or_else(|| cmd.map(tool_of_cmd))
.unwrap_or_else(|| "unknown".into());
let Some(sig) = extract(&tool, &raw) else {
return Ok(()); // nothing extractable: stay silent
};
let lessons = load_lessons()?;
match lookup_lesson(&sig, &lessons, floor) {
// Silence is the correct and common answer.
None => Ok(()),
Some(hit) => {
print!("{}", render_injection(&hit, 600));
Ok(())
}
}
}
pub fn cmd_materialize() -> Result<()> {
let lessons = load_lessons()?;
if lessons.is_empty() {
println!("no lessons yet - run `mem resolve` after capturing some failures");
return Ok(());
}
let mut by_tool: BTreeMap<String, Vec<Lesson>> = BTreeMap::new();
for l in lessons {
by_tool.entry(l.tool.clone()).or_default().push(l);
}
let skills_dir = mem_home().join("skills");
let mut written = vec![];
for (tool, ls) in &by_tool {
let dir = skills_dir.join(format!("{tool}-failures"));
fs::create_dir_all(&dir)?;
let path = dir.join("SKILL.md");
fs::write(&path, render_skill(tool, ls))?;
written.push(path);
}
// A CLAUDE.md-style digest: only the recurring lessons, because this file
// is loaded eagerly and every byte competes with the task.
let mut digest = String::from("# Learned failures\n\nGenerated by `mem materialize`. Recurring failures only.\n\n");
for (tool, ls) in &by_tool {
let recurring: Vec<&Lesson> = ls.iter().filter(|l| l.seen >= 3).collect();
if recurring.is_empty() {
continue;
}
digest.push_str(&format!("## {tool}\n\n"));
for l in recurring {
digest.push_str(&format!(
"- `{}` (seen {}x) -> {}\n",
l.raw.trim(),
l.seen,
l.resolution.join(" && ")
));
}
digest.push('\n');
}
let digest_path = mem_home().join("MEMORY.md");
fs::write(&digest_path, digest)?;
println!("wrote {} skill(s):", written.len());
for p in written {
println!(" {}", p.display());
}
println!(" {}", digest_path.display());
println!("\nWire into Claude Code / pi:");
println!(" ln -s {} ~/.claude/skills/", skills_dir.display());
println!(" echo '@{}' >> ~/.claude/CLAUDE.md", digest_path.display());
Ok(())
}