digest: native Rust implementation replacing Python scripts

Replace daily-digest.py, weekly-digest.py, monthly-digest.py with a
single digest.rs module. All three digest types now:
- Gather input directly from the Store (no subprocess calls)
- Build prompts in Rust (same templates as the Python versions)
- Call Sonnet via `claude -p --model sonnet`
- Import results back into the store automatically
- Extract links and save agent results

606 lines of Rust replaces 729 lines of Python + store_helpers.py
overhead. More importantly: this is now callable as a library from
poc-agent, and shares types/code with the rest of poc-memory.

Also adds `digest monthly [YYYY-MM]` subcommand (was Python-only).
This commit is contained in:
ProofOfConcept 2026-02-28 23:58:05 -05:00
parent 1ca6e55b7d
commit 91122fe1d1
3 changed files with 629 additions and 107 deletions

View file

@ -14,6 +14,7 @@
// interference detection, schema assimilation, reconsolidation.
mod capnp_store;
mod digest;
mod graph;
mod search;
mod similarity;
@ -144,6 +145,7 @@ Commands:
apply-agent [--all] Import pending agent results into the graph
digest daily [DATE] Generate daily episodic digest (default: today)
digest weekly [DATE] Generate weekly digest (any date in target week)
digest monthly [YYYY-MM] Generate monthly digest (default: current month)
trace KEY Walk temporal links: semantic episodic conversation
list-keys List all node keys (one per line)
list-edges List all edges (tsv: source target strength type)
@ -622,43 +624,34 @@ fn cmd_apply_agent(args: &[String]) -> Result<(), String> {
fn cmd_digest(args: &[String]) -> Result<(), String> {
if args.is_empty() {
return Err("Usage: poc-memory digest daily [DATE] | weekly [DATE]".into());
return Err("Usage: poc-memory digest daily|weekly|monthly [DATE]".into());
}
let home = env::var("HOME").unwrap_or_default();
let scripts_dir = std::path::PathBuf::from(&home).join("poc/memory/scripts");
let mut store = capnp_store::Store::load()?;
let date_arg = args.get(1).map(|s| s.as_str()).unwrap_or("");
match args[0].as_str() {
"daily" => {
let mut cmd = std::process::Command::new("python3");
cmd.arg(scripts_dir.join("daily-digest.py"));
if args.len() > 1 {
cmd.arg(&args[1]);
}
// Unset CLAUDECODE for nested claude calls
cmd.env_remove("CLAUDECODE");
let status = cmd.status()
.map_err(|e| format!("run daily-digest.py: {}", e))?;
if !status.success() {
return Err("daily-digest.py failed".into());
}
Ok(())
let date = if date_arg.is_empty() {
capnp_store::format_date(capnp_store::now_epoch())
} else {
date_arg.to_string()
};
digest::generate_daily(&mut store, &date)
}
"weekly" => {
let mut cmd = std::process::Command::new("python3");
cmd.arg(scripts_dir.join("weekly-digest.py"));
if args.len() > 1 {
cmd.arg(&args[1]);
}
cmd.env_remove("CLAUDECODE");
let status = cmd.status()
.map_err(|e| format!("run weekly-digest.py: {}", e))?;
if !status.success() {
return Err("weekly-digest.py failed".into());
}
Ok(())
let date = if date_arg.is_empty() {
capnp_store::format_date(capnp_store::now_epoch())
} else {
date_arg.to_string()
};
digest::generate_weekly(&mut store, &date)
}
_ => Err(format!("Unknown digest type: {}. Use: daily, weekly", args[0])),
"monthly" => {
let month = if date_arg.is_empty() { "" } else { date_arg };
digest::generate_monthly(&mut store, month)
}
_ => Err(format!("Unknown digest type: {}. Use: daily, weekly, monthly", args[0])),
}
}