daemon: background job orchestration for memory maintenance

Replace fragile cron+shell approach with `poc-memory daemon` — a single
long-running process using jobkit for worker pool, status tracking,
retry, cancellation, and resource pools.

Jobs:
  - session-watcher: detects ended Claude sessions, triggers extraction
  - scheduler: runs daily decay, consolidation, knowledge loop, digests
  - health: periodic graph metrics check
  - All Sonnet API calls serialized through a ResourcePool(1)

Status queryable via `poc-memory daemon status`, structured log via
`poc-memory daemon log`. Phase 1: shells out to existing subcommands.

Co-Authored-By: ProofOfConcept <poc@bcachefs.org>
This commit is contained in:
ProofOfConcept 2026-03-05 13:18:00 -05:00
parent c085679a0f
commit e37f819dd2
4 changed files with 499 additions and 1 deletions

View file

@ -28,6 +28,7 @@ mod neuro;
mod query;
mod spectral;
mod lookups;
mod daemon;
pub mod memory_capnp {
include!(concat!(env!("OUT_DIR"), "/schema/memory_capnp.rs"));
@ -130,6 +131,7 @@ fn main() {
"query" => cmd_query(&args[2..]),
"lookup-bump" => cmd_lookup_bump(&args[2..]),
"lookups" => cmd_lookups(&args[2..]),
"daemon" => cmd_daemon(&args[2..]),
_ => {
eprintln!("Unknown command: {}", args[1]);
usage();
@ -211,7 +213,10 @@ Commands:
Stages: sort F [asc], limit N, select F,F, count
Ex: \"degree > 15 | sort degree | limit 10\"
lookup-bump KEY [KEY...] Bump daily lookup counter for keys (fast, no store)
lookups [DATE] Show daily lookup counts (default: today)");
lookups [DATE] Show daily lookup counts (default: today)
daemon Start background job daemon
daemon status Show daemon status
daemon log [JOB] [N] Show last N log lines (default 50, optional job filter)");
}
fn cmd_search(args: &[String]) -> Result<(), String> {
@ -1808,3 +1813,23 @@ fn cmd_lookups(args: &[String]) -> Result<(), String> {
resolved.iter().map(|(_, c)| *c as u64).sum::<u64>());
Ok(())
}
fn cmd_daemon(args: &[String]) -> Result<(), String> {
if args.is_empty() {
return daemon::run_daemon();
}
match args[0].as_str() {
"status" => daemon::show_status(),
"log" => {
let job = args.get(1).map(|s| s.as_str());
let lines = args.get(2)
.and_then(|s| s.parse().ok())
.unwrap_or(50);
daemon::show_log(job, lines)
}
_ => {
eprintln!("Usage: poc-memory daemon [status|log [JOB] [LINES]]");
Err("unknown daemon subcommand".into())
}
}
}