config: hot-reload via RPC, Arc<Config> for cheap sharing
Config is now stored in RwLock<Arc<Config>> instead of OnceLock<Config>. get() returns Arc<Config> (cheap clone), and reload() re-reads from disk. New RPC: "reload-config" — reloads config.jsonl without restarting the daemon. Logs the change to daemon.log. Useful for switching between API backends and claude accounts without losing in-flight tasks. New CLI: poc-memory agent daemon reload-config Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
0944ecc43f
commit
af3171d6ec
4 changed files with 43 additions and 7 deletions
|
|
@ -1074,6 +1074,18 @@ pub fn run_daemon() -> Result<(), String> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
daemon.add_rpc_handler(|cmd, _ctx| {
|
||||||
|
if cmd != "reload-config" { return None; }
|
||||||
|
let changed = crate::config::reload();
|
||||||
|
let config = crate::config::get();
|
||||||
|
let api = config.api_base_url.as_deref().unwrap_or("(none)");
|
||||||
|
let model = config.api_model.as_deref().unwrap_or("(default)");
|
||||||
|
log_event("daemon", "config-reload",
|
||||||
|
&format!("changed={}, api={}, model={}", changed, api, model));
|
||||||
|
Some(format!("{{\"ok\":true,\"changed\":{},\"api_base_url\":\"{}\",\"api_model\":\"{}\"}}\n",
|
||||||
|
changed, api, model))
|
||||||
|
});
|
||||||
|
|
||||||
daemon.add_rpc_handler(|cmd, _ctx| {
|
daemon.add_rpc_handler(|cmd, _ctx| {
|
||||||
if !cmd.starts_with("record-hits ") { return None; }
|
if !cmd.starts_with("record-hits ") { return None; }
|
||||||
let keys: Vec<&str> = cmd.strip_prefix("record-hits ")
|
let keys: Vec<&str> = cmd.strip_prefix("record-hits ")
|
||||||
|
|
|
||||||
|
|
@ -268,7 +268,7 @@ pub fn cmd_load_context(stats: bool) -> Result<(), String> {
|
||||||
println!("{}", "-".repeat(42));
|
println!("{}", "-".repeat(42));
|
||||||
|
|
||||||
for group in &cfg.context_groups {
|
for group in &cfg.context_groups {
|
||||||
let entries = get_group_content(group, &store, cfg);
|
let entries = get_group_content(group, &store, &cfg);
|
||||||
let words: usize = entries.iter()
|
let words: usize = entries.iter()
|
||||||
.map(|(_, c)| c.split_whitespace().count())
|
.map(|(_, c)| c.split_whitespace().count())
|
||||||
.sum();
|
.sum();
|
||||||
|
|
@ -287,7 +287,7 @@ pub fn cmd_load_context(stats: bool) -> Result<(), String> {
|
||||||
println!();
|
println!();
|
||||||
|
|
||||||
for group in &cfg.context_groups {
|
for group in &cfg.context_groups {
|
||||||
let entries = get_group_content(group, &store, cfg);
|
let entries = get_group_content(group, &store, &cfg);
|
||||||
if !entries.is_empty() && group.source == crate::config::ContextSource::Journal {
|
if !entries.is_empty() && group.source == crate::config::ContextSource::Journal {
|
||||||
println!("--- recent journal entries ({}/{}) ---",
|
println!("--- recent journal entries ({}/{}) ---",
|
||||||
entries.len(), cfg.journal_max);
|
entries.len(), cfg.journal_max);
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,9 @@
|
||||||
// {"group": "orientation", "keys": ["where-am-i.md"], "source": "file"}
|
// {"group": "orientation", "keys": ["where-am-i.md"], "source": "file"}
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::OnceLock;
|
use std::sync::{Arc, OnceLock, RwLock};
|
||||||
|
|
||||||
static CONFIG: OnceLock<Config> = OnceLock::new();
|
static CONFIG: OnceLock<RwLock<Arc<Config>>> = OnceLock::new();
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum ContextSource {
|
pub enum ContextSource {
|
||||||
|
|
@ -210,7 +210,23 @@ fn expand_home(path: &str) -> PathBuf {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the global config (loaded once on first access).
|
/// Get the global config (cheap Arc clone).
|
||||||
pub fn get() -> &'static Config {
|
pub fn get() -> Arc<Config> {
|
||||||
CONFIG.get_or_init(Config::load_from_file)
|
CONFIG
|
||||||
|
.get_or_init(|| RwLock::new(Arc::new(Config::load_from_file())))
|
||||||
|
.read()
|
||||||
|
.unwrap()
|
||||||
|
.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reload the config from disk. Returns true if changed.
|
||||||
|
pub fn reload() -> bool {
|
||||||
|
let lock = CONFIG.get_or_init(|| RwLock::new(Arc::new(Config::load_from_file())));
|
||||||
|
let new = Config::load_from_file();
|
||||||
|
let mut current = lock.write().unwrap();
|
||||||
|
let changed = format!("{:?}", **current) != format!("{:?}", new);
|
||||||
|
if changed {
|
||||||
|
*current = Arc::new(new);
|
||||||
|
}
|
||||||
|
changed
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -469,6 +469,8 @@ enum DaemonCmd {
|
||||||
},
|
},
|
||||||
/// Interactive TUI
|
/// Interactive TUI
|
||||||
Tui,
|
Tui,
|
||||||
|
/// Reload config file without restarting
|
||||||
|
ReloadConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
#[derive(Subcommand)]
|
||||||
|
|
@ -1057,6 +1059,12 @@ fn cmd_daemon(sub: DaemonCmd) -> Result<(), String> {
|
||||||
DaemonCmd::Consolidate => daemon::rpc_consolidate(),
|
DaemonCmd::Consolidate => daemon::rpc_consolidate(),
|
||||||
DaemonCmd::Run { agent, count } => daemon::rpc_run_agent(&agent, count),
|
DaemonCmd::Run { agent, count } => daemon::rpc_run_agent(&agent, count),
|
||||||
DaemonCmd::Tui => tui::run_tui(),
|
DaemonCmd::Tui => tui::run_tui(),
|
||||||
|
DaemonCmd::ReloadConfig => {
|
||||||
|
match daemon::send_rpc_pub("reload-config") {
|
||||||
|
Some(resp) => { eprintln!("{}", resp.trim()); Ok(()) }
|
||||||
|
None => Err("daemon not running".into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue