oneshot: remove PID tracking from run_one_agent

run_one_agent is meant to run within a long-running process (daemon,
CLI) — PID tracking is the caller's concern. Remove PidGuard, signal
handlers, setup_agent_state. Process management (scan_pid_files,
spawn_agent) stays for callers that need it.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
This commit is contained in:
ProofOfConcept 2026-04-04 17:47:49 -04:00 committed by Kent Overstreet
parent a1fb3fe557
commit 618121067b

View file

@ -13,48 +13,6 @@ use crate::subconscious::{defs, prompts};
use std::fs; use std::fs;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::atomic::{AtomicPtr, Ordering};
// Global pid path for signal handler cleanup — stored as a leaked CString
// so the signal handler can unlink it without allocation.
static PID_CPATH: AtomicPtr<libc::c_char> = AtomicPtr::new(std::ptr::null_mut());
/// RAII guard that removes the pid file on drop (normal exit, panic).
struct PidGuard;
impl Drop for PidGuard {
fn drop(&mut self) {
let ptr = PID_CPATH.swap(std::ptr::null_mut(), Ordering::SeqCst);
if !ptr.is_null() {
unsafe { libc::unlink(ptr); }
unsafe { drop(std::ffi::CString::from_raw(ptr)); }
}
}
}
fn register_pid_cleanup(pid_path: &std::path::Path) {
let c_path = std::ffi::CString::new(pid_path.to_string_lossy().as_bytes())
.expect("pid path contains null");
let old = PID_CPATH.swap(c_path.into_raw(), Ordering::SeqCst);
if !old.is_null() {
unsafe { drop(std::ffi::CString::from_raw(old)); }
}
unsafe {
libc::signal(libc::SIGTERM, pid_cleanup_handler as *const () as libc::sighandler_t);
libc::signal(libc::SIGINT, pid_cleanup_handler as *const () as libc::sighandler_t);
}
}
extern "C" fn pid_cleanup_handler(sig: libc::c_int) {
let ptr = PID_CPATH.swap(std::ptr::null_mut(), Ordering::SeqCst);
if !ptr.is_null() {
unsafe { libc::unlink(ptr); }
}
unsafe {
libc::signal(sig, libc::SIG_DFL);
libc::raise(sig);
}
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Agent execution // Agent execution
@ -80,7 +38,13 @@ pub fn run_one_agent(
let def = defs::get_def(agent_name) let def = defs::get_def(agent_name)
.ok_or_else(|| format!("no .agent file for {}", agent_name))?; .ok_or_else(|| format!("no .agent file for {}", agent_name))?;
let (state_dir, pid_path, _guard) = setup_agent_state(agent_name, &def)?; // State dir for agent output files
let state_dir = std::env::var("POC_AGENT_OUTPUT_DIR")
.map(PathBuf::from)
.unwrap_or_else(|_| store::memory_dir().join("agent-output").join(agent_name));
fs::create_dir_all(&state_dir)
.map_err(|e| format!("create state dir: {}", e))?;
unsafe { std::env::set_var("POC_AGENT_OUTPUT_DIR", &state_dir); }
// Build prompt batch — either from explicit keys or the agent's query // Build prompt batch — either from explicit keys or the agent's query
let agent_batch = if let Some(keys) = keys { let agent_batch = if let Some(keys) = keys {
@ -153,7 +117,6 @@ pub fn run_one_agent(
.map(|s| s.prompt.clone()).collect(); .map(|s| s.prompt.clone()).collect();
let step_phases: Vec<String> = agent_batch.steps.iter() let step_phases: Vec<String> = agent_batch.steps.iter()
.map(|s| s.phase.clone()).collect(); .map(|s| s.phase.clone()).collect();
let step_phases_for_bail = step_phases.clone();
if std::env::var("POC_AGENT_VERBOSE").is_ok() { if std::env::var("POC_AGENT_VERBOSE").is_ok() {
for (i, s) in agent_batch.steps.iter().enumerate() { for (i, s) in agent_batch.steps.iter().enumerate() {
@ -165,14 +128,9 @@ pub fn run_one_agent(
// Bail check: if the agent defines a bail script, run it between steps. // Bail check: if the agent defines a bail script, run it between steps.
let bail_script = def.bail.as_ref().map(|name| defs::agents_dir().join(name)); let bail_script = def.bail.as_ref().map(|name| defs::agents_dir().join(name));
let state_dir_for_bail = state_dir.clone(); let state_dir_for_bail = state_dir.clone();
let pid_path_for_bail = pid_path.clone();
let bail_fn = move |step_idx: usize| -> Result<(), String> { let bail_fn = move |step_idx: usize| -> Result<(), String> {
if step_idx < step_phases_for_bail.len() {
fs::write(&pid_path_for_bail, &step_phases_for_bail[step_idx]).ok();
}
if let Some(ref script) = bail_script { if let Some(ref script) = bail_script {
let status = std::process::Command::new(script) let status = std::process::Command::new(script)
.arg(&pid_path_for_bail)
.current_dir(&state_dir_for_bail) .current_dir(&state_dir_for_bail)
.status() .status()
.map_err(|e| format!("bail script {:?} failed: {}", script, e))?; .map_err(|e| format!("bail script {:?} failed: {}", script, e))?;
@ -197,33 +155,9 @@ pub fn run_one_agent(
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// PID file management // Process management — PID tracking and subprocess spawning
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
fn setup_agent_state(
agent_name: &str,
def: &defs::AgentDef,
) -> Result<(PathBuf, PathBuf, PidGuard), String> {
let state_dir = std::env::var("POC_AGENT_OUTPUT_DIR")
.map(PathBuf::from)
.unwrap_or_else(|_| store::memory_dir().join("agent-output").join(agent_name));
fs::create_dir_all(&state_dir)
.map_err(|e| format!("create state dir: {}", e))?;
unsafe { std::env::set_var("POC_AGENT_OUTPUT_DIR", &state_dir); }
scan_pid_files(&state_dir, 0);
let pid = std::process::id();
let pid_path = state_dir.join(format!("pid-{}", pid));
let first_phase = def.steps.first()
.map(|s| s.phase.as_str())
.unwrap_or("step-0");
fs::write(&pid_path, first_phase).ok();
register_pid_cleanup(&pid_path);
Ok((state_dir, pid_path, PidGuard))
}
/// Check for live agent processes in a state dir. Returns (phase, pid) pairs. /// Check for live agent processes in a state dir. Returns (phase, pid) pairs.
/// Cleans up stale pid files and kills timed-out processes. /// Cleans up stale pid files and kills timed-out processes.
pub fn scan_pid_files(state_dir: &std::path::Path, timeout_secs: u64) -> Vec<(String, u32)> { pub fn scan_pid_files(state_dir: &std::path::Path, timeout_secs: u64) -> Vec<(String, u32)> {
@ -260,10 +194,6 @@ pub fn scan_pid_files(state_dir: &std::path::Path, timeout_secs: u64) -> Vec<(St
live live
} }
// ---------------------------------------------------------------------------
// Subprocess spawning
// ---------------------------------------------------------------------------
pub struct SpawnResult { pub struct SpawnResult {
pub child: std::process::Child, pub child: std::process::Child,
pub log_path: PathBuf, pub log_path: PathBuf,