daemon: use CLAUDE_CONFIG_DIR for OAuth credential separation, fix shutdown

Replace agent_api_key (which didn't work — claude CLI uses OAuth, not
API keys) with agent_config_dir. When configured, sets CLAUDE_CONFIG_DIR
on claude subprocesses so daemon agent work authenticates with separate
OAuth credentials from the interactive session.

Fix daemon not shutting down on SIGTERM: use process::exit(0) after
cleanup so PR_SET_PDEATHSIG kills child claude processes immediately.
Previously the daemon hung waiting for choir threads/subprocesses to
finish. Restart now takes ~20ms instead of timing out.

Also: main.rs now uses `use poc_memory::*` since lib.rs exists.
This commit is contained in:
ProofOfConcept 2026-03-05 22:43:50 -05:00
parent 2f3ac1ecb6
commit e33fd4ffbc
4 changed files with 15 additions and 40 deletions

View file

@ -49,9 +49,10 @@ pub struct Config {
pub journal_max: usize,
/// Ordered context groups for session-start loading.
pub context_groups: Vec<ContextGroup>,
/// Separate API key for background agent work (daemon jobs).
/// If set, passed as ANTHROPIC_API_KEY to model calls.
pub agent_api_key: Option<String>,
/// Separate Claude config dir for background agent work (daemon jobs).
/// If set, passed as CLAUDE_CONFIG_DIR so the daemon authenticates
/// with different OAuth credentials than the interactive session.
pub agent_config_dir: Option<PathBuf>,
}
impl Default for Config {
@ -72,7 +73,7 @@ impl Default for Config {
source: ContextSource::Store,
},
],
agent_api_key: None,
agent_config_dir: None,
}
}
}
@ -126,8 +127,8 @@ impl Config {
if let Some(m) = cfg.get("journal_max").and_then(|v| v.as_u64()) {
config.journal_max = m as usize;
}
if let Some(s) = cfg.get("agent_api_key").and_then(|v| v.as_str()) {
config.agent_api_key = Some(s.to_string());
if let Some(s) = cfg.get("agent_config_dir").and_then(|v| v.as_str()) {
config.agent_config_dir = Some(expand_home(s));
}
continue;
}

View file

@ -487,18 +487,14 @@ pub fn run_daemon() -> Result<(), String> {
log_event("daemon", "stopping", "");
eprintln!("Shutting down...");
// Cancel all tasks
for info in choir_main.task_statuses() {
if !info.status.is_finished() {
log_event(&info.name, "cancelling", "");
}
}
// Clean up socket
let _ = fs::remove_file(status_sock_path());
log_event("daemon", "stopped", "");
Ok(())
// Exit immediately — PR_SET_PDEATHSIG on child processes ensures
// claude subprocesses get SIGTERM when we die.
std::process::exit(0)
}
fn read_status_socket() -> Option<DaemonStatus> {

View file

@ -26,9 +26,9 @@ fn call_model(model: &str, prompt: &str) -> Result<String, String> {
.stdin(fs::File::open(&tmp).map_err(|e| format!("open temp: {}", e))?)
.env_remove("CLAUDECODE");
// Use separate API key for agent work if configured
if let Some(ref key) = crate::config::get().agent_api_key {
cmd.env("ANTHROPIC_API_KEY", key);
// Use separate OAuth credentials for agent work if configured
if let Some(ref dir) = crate::config::get().agent_config_dir {
cmd.env("CLAUDE_CONFIG_DIR", dir);
}
let result = unsafe {

View file

@ -16,29 +16,7 @@
// Neuroscience-inspired: spaced repetition replay, emotional gating,
// interference detection, schema assimilation, reconsolidation.
mod config;
mod store;
mod util;
mod llm;
mod digest;
mod audit;
mod enrich;
mod consolidate;
mod graph;
mod search;
mod similarity;
mod migrate;
mod neuro;
mod query;
mod spectral;
mod lookups;
mod daemon;
mod fact_mine;
mod knowledge;
pub mod memory_capnp {
include!(concat!(env!("OUT_DIR"), "/schema/memory_capnp.rs"));
}
use poc_memory::*;
use std::env;
use std::process;