Skip to main content

nexus_memory_agent/
pulse.rs

1//! Agent pulse file for cross-process status communication
2//!
3//! Writes a lightweight JSON file that the Claude Code hook shim can read
4//! to display a visual indicator when the always-on agent is actively
5//! dreaming (consolidating memories).
6
7use chrono::Utc;
8use serde::Serialize;
9use std::path::PathBuf;
10use tracing::debug;
11
12/// State directory for nexus agent runtime files
13fn state_dir() -> PathBuf {
14    if let Ok(dir) = std::env::var("NEXUS_STATE_DIR") {
15        PathBuf::from(dir)
16    } else if let Some(dir) = dirs::state_dir() {
17        dir.join("nexus-memory-system")
18    } else {
19        std::env::var("HOME")
20            .map(|h| PathBuf::from(h).join(".local/state/nexus-memory-system"))
21            .unwrap_or_else(|_| std::env::temp_dir().join("nexus-memory-system"))
22    }
23}
24
25fn pulse_path() -> PathBuf {
26    state_dir().join("agent-pulse.json")
27}
28
29/// Pulse data written to disk for the hook shim to read.
30#[derive(Debug, Serialize)]
31struct AgentPulse {
32    /// ISO 8601 timestamp of the last agent activity
33    last_activity: String,
34    /// What the agent last did
35    last_action: String,
36    /// Total memories consolidated so far
37    memories_consolidated: u64,
38    /// Total files ingested so far
39    files_processed: u64,
40}
41
42/// Write a pulse update to the state directory.
43///
44/// Called after consolidation, ingestion, or other agent activities.
45/// The hook shim reads this file to decide whether to show a visual indicator.
46pub fn write_pulse(action: &str, memories_consolidated: u64, files_processed: u64) {
47    let path = pulse_path();
48
49    if let Some(parent) = path.parent() {
50        if let Err(e) = std::fs::create_dir_all(parent) {
51            debug!(error = %e, path = %parent.display(), "Failed to create state directory for pulse file");
52            return;
53        }
54    }
55
56    let pulse = AgentPulse {
57        last_activity: Utc::now().to_rfc3339(),
58        last_action: action.to_string(),
59        memories_consolidated,
60        files_processed,
61    };
62
63    match serde_json::to_string_pretty(&pulse) {
64        Ok(json) => {
65            if let Err(e) = std::fs::write(&path, json) {
66                debug!(error = %e, path = %path.display(), "Failed to write pulse file");
67            }
68        }
69        Err(e) => {
70            debug!(error = %e, "Failed to serialize pulse data");
71        }
72    }
73}