use clap::Parser;
use ix::builder::Builder;
use ix::format::Beacon;
use ix::idle::IdleTracker;
use ix::watcher::Watcher;
use llmosafe::llmosafe_body::EnvironmentalVitals;
use llmosafe::{EscalationPolicy, ResourceGuard, SafetyDecision};
use std::fs;
use std::path::PathBuf;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::Duration;
const ENTROPY_SAFE_THRESHOLD: u16 = 800; const ENTROPY_CRITICAL: u16 = 1000; const PRE_BUILD_WAIT_SECS: u64 = 5; const METABOLIC_BACKOFF_MS: u64 = 1000; const WARN_COOLDOWN_MS: u64 = 300;
#[derive(Parser)]
#[command(
name = "ixd",
version = env!("CARGO_PKG_VERSION"),
about = "CRITICAL: REASON BEFORE ANY ACTIONS. Background daemon for automatic indexing. Proactively monitors metabolic state.",
after_help = "LLM AGENT MANDATE:
1. METABOLIC PACING: Auto-throttling (sleep 500ms) occurs if load > 8.0 or iowait > 15.0.
2. BACK-PRESSURE: Clears current batch and prunes the task branch on Error -7.
3. SIGNAL: Do not restart ixd if it throttles; it is protecting the host."
)]
struct Cli {
#[arg(default_value = ".", value_name = "PATH")]
path: PathBuf,
}
fn main() -> ix::error::Result<()> {
let cli = Cli::parse();
let root = cli.path.canonicalize().map_err(ix::error::Error::Io)?;
println!("ixd: watching {}...", root.display());
let running = Arc::new(AtomicBool::new(true));
{
use nix::sys::signal::{SaFlags, SigAction, SigHandler, SigSet, Signal, sigaction};
let action = SigAction::new(
SigHandler::Handler(handle_signal),
SaFlags::empty(),
SigSet::empty(),
);
unsafe {
sigaction(Signal::SIGTERM, &action).expect("failed to install SIGTERM handler");
sigaction(Signal::SIGINT, &action).expect("failed to install SIGINT handler");
}
}
let r = running.clone();
let _ = ctrlc::set_handler(move || {
SHUTDOWN.store(true, Ordering::SeqCst);
r.store(false, Ordering::SeqCst);
});
let ix_dir_early = root.join(".ix");
let beacon_path = ix_dir_early.join("beacon.json");
if beacon_path.exists()
&& let Ok(existing) = ix::format::Beacon::read_from(&ix_dir_early)
{
let pid = nix::unistd::Pid::from_raw(existing.pid);
if nix::sys::signal::kill(pid, None).is_ok() {
eprintln!(
"ixd: another instance is already watching {} (PID {}). \
Stop it first or remove {}/beacon.json.",
root.display(),
existing.pid,
ix_dir_early.display()
);
std::process::exit(1);
}
eprintln!("ixd: removing stale beacon from PID {}", existing.pid);
let _ = std::fs::remove_file(&beacon_path);
}
let guard = ResourceGuard::auto(0.6);
let mut builder = match Builder::new(&root) {
Ok(b) => b.with_resource_guard(guard.clone()),
Err(e) => {
eprintln!("ixd: cannot create index in {}: {}", root.display(), e);
return Err(e);
}
};
if let Err(e) = guard.check() {
eprintln!(
"ixd: memory pressure before initial build: {:?} — waiting for safe state",
e
);
std::thread::sleep(Duration::from_secs(PRE_BUILD_WAIT_SECS));
}
if let Err(e) = builder.build() {
eprintln!(
"ixd: initial build failed: {} — will watch for changes anyway",
e
);
} else {
println!(
"ixd: initial build complete ({} files, {} trigrams)",
builder.files_len(),
builder.trigrams_len()
);
}
let mut watcher = Watcher::new(&root)?;
let rx = watcher.start()?;
let ix_dir = root.join(".ix");
if !ix_dir.exists() {
fs::create_dir_all(&ix_dir)?;
}
let mut beacon = Beacon::new(&root);
beacon.write_to(&ix_dir)?;
let mut idle = IdleTracker::new();
loop {
if SHUTDOWN.load(Ordering::SeqCst) {
running.store(false, Ordering::SeqCst);
break;
}
match rx.recv_timeout(Duration::from_millis(500)) {
Ok(changed_files) => {
let vitals = EnvironmentalVitals::capture();
if vitals.load_avg > 8.0 {
eprintln!(
"ixd: metabolic pressure detected (load: {:.2}) — entering back-pressure mode",
vitals.load_avg
);
beacon.status = format!("metabolic backoff (load: {:.2})", vitals.load_avg);
let _ = beacon.write_to(&ix_dir);
std::thread::sleep(Duration::from_millis(METABOLIC_BACKOFF_MS));
continue;
}
let check_result = guard.check();
let (entropy, safety_decision) = match check_result {
Ok(synapse) => {
let raw = synapse.raw_entropy();
let entropy_val = raw;
let policy = EscalationPolicy::default();
let decision = policy.decide(entropy_val, 0, false);
(entropy_val, decision)
}
Err(e) => {
eprintln!(
"ixd: resource check error: {:?} — proceeding with elevated caution",
e
);
(ENTROPY_CRITICAL, SafetyDecision::Escalate {
entropy: ENTROPY_CRITICAL,
reason: llmosafe::llmosafe_integration::EscalationReason::ResourcePressure,
cooldown_ms: ENTROPY_CRITICAL as u32,
})
}
};
match &safety_decision {
SafetyDecision::Halt(err, cooldown) => {
eprintln!(
"ixd: critical safety decision (Halt: {:?}) — pausing operations",
err
);
beacon.status = "safety halt".to_string();
let _ = beacon.write_to(&ix_dir);
std::thread::sleep(Duration::from_millis(*cooldown as u64));
continue;
}
SafetyDecision::Exit(err) => {
eprintln!("ixd: SAFETY EXIT (unrecoverable: {:?}) — terminating", err);
beacon.status = "safety exit".to_string();
let _ = beacon.write_to(&ix_dir);
std::process::exit(1);
}
SafetyDecision::Escalate {
entropy,
reason,
cooldown_ms,
} => {
eprintln!(
"ixd: safety escalation (entropy: {}, reason: {:?}) — throttling",
entropy, reason
);
beacon.status = format!("escalated (entropy: {})", entropy);
let _ = beacon.write_to(&ix_dir);
std::thread::sleep(Duration::from_millis(*cooldown_ms as u64));
continue; }
SafetyDecision::Warn(reason) => {
if safety_decision.severity() >= 2 {
eprintln!(
"ixd: safety warning (severity {}): {}",
safety_decision.severity(),
reason
);
beacon.status = format!("warned: {}", reason);
let _ = beacon.write_to(&ix_dir);
std::thread::sleep(Duration::from_millis(WARN_COOLDOWN_MS));
}
}
SafetyDecision::Proceed => {
}
}
println!(
"ixd: {} files changed, updating index... (Entropy: {}, Decision: {:?})",
changed_files.len(),
entropy,
safety_decision
);
beacon.status = format!("indexing (entropy: {})", entropy);
beacon.last_event_at = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
let _ = beacon.write_to(&ix_dir);
idle.record_change();
if entropy > ENTROPY_SAFE_THRESHOLD {
eprintln!("ixd: entropy too high ({}), deferring update", entropy);
beacon.status = format!("deferred (entropy: {})", entropy);
let _ = beacon.write_to(&ix_dir);
continue;
}
if let Err(e) = builder.update(&changed_files) {
eprintln!("ixd: update failed: {} — retrying on next change", e);
}
beacon.status = "idle".to_string();
let _ = beacon.write_to(&ix_dir);
}
Err(crossbeam_channel::RecvTimeoutError::Timeout) => continue,
Err(crossbeam_channel::RecvTimeoutError::Disconnected) => break,
}
}
eprintln!("ixd: shutting down...");
watcher.stop();
let _ = fs::remove_file(ix_dir.join("beacon.json"));
loop {
use nix::sys::wait::{WaitPidFlag, waitpid};
use nix::unistd::Pid;
match waitpid(Pid::from_raw(-1), Some(WaitPidFlag::WNOHANG)) {
Ok(nix::sys::wait::WaitStatus::StillAlive) | Err(_) => break,
Ok(_) => continue, }
}
Ok(())
}
static SHUTDOWN: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
extern "C" fn handle_signal(_: libc::c_int) {
SHUTDOWN.store(true, Ordering::SeqCst);
}