config: unify subconscious API resolution with the main chat path
Two parallel backend-resolution paths had drifted apart:
- Main chat: AppConfig::resolve_model() → a named BackendConfig in
AppConfig.backends
- Subconscious / oneshot / context_window(): four skip-serde
"cache" fields on Config (memory section) — api_base_url, api_key,
api_model, api_context_window — that used to be populated at
Config::try_load_shared time by walking memory.agent_model →
root.models[name] → root[backend_name]
When we renamed `models` to `backends` and collapsed ModelConfig into
BackendConfig, the latter chain started silently dereferencing
`root.get("models")` → None → no population. Subconscious agents fell
through the "API not configured" guard; context_window() started
returning 0 (since api_context_window default is u64's 0 now that we
don't populate it). It was only visibly working for the main chat.
Collapse to one path:
- Drop Config.agent_model (duplicate of AppConfig.default_backend)
- Drop Config.{api_base_url, api_key, api_model, api_context_window}
— no longer populated, no longer needed
- Drop default_context_window() — nobody reads the field anymore
- Drop the memory-side resolution block in try_load_shared()
- Subconscious (mind/unconscious.rs) and oneshot (agent/oneshot.rs)
now call load_app() + resolve_model(&app.default_backend) just like
the main chat does
- context_window() reads from config::app().backends[default_backend]
.context_window, defaulting to 128k only if the backend doesn't
specify one
Side effect: Kent's config file drops agent_model, api_reasoning,
journal_days, journal_max — all fields whose Rust counterparts are
now gone. (Figment tolerates unknown fields, so leaving them wouldn't
have broken anything, but they were lying about what's configurable.)
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
This commit is contained in:
parent
28484a385b
commit
60de579305
4 changed files with 21 additions and 60 deletions
|
|
@ -992,7 +992,10 @@ impl ContextState {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn context_window() -> usize {
|
pub fn context_window() -> usize {
|
||||||
crate::config::get().api_context_window
|
let app = crate::config::app();
|
||||||
|
app.backends.get(&app.default_backend)
|
||||||
|
.and_then(|b| b.context_window)
|
||||||
|
.unwrap_or(128_000)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn context_budget_tokens() -> usize {
|
pub fn context_budget_tokens() -> usize {
|
||||||
|
|
|
||||||
|
|
@ -247,19 +247,14 @@ impl AutoAgent {
|
||||||
&mut self,
|
&mut self,
|
||||||
bail_fn: Option<&(dyn Fn(usize) -> Result<(), String> + Sync)>,
|
bail_fn: Option<&(dyn Fn(usize) -> Result<(), String> + Sync)>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let config = crate::config::get();
|
// Load system prompt + identity from config.
|
||||||
let base_url = config.api_base_url.as_deref().unwrap_or("");
|
|
||||||
let api_key = config.api_key.as_deref().unwrap_or("");
|
|
||||||
let model = config.api_model.as_deref().unwrap_or("");
|
|
||||||
if base_url.is_empty() || model.is_empty() {
|
|
||||||
return Err("API not configured (no base_url or model)".to_string());
|
|
||||||
}
|
|
||||||
let client = super::api::ApiClient::new(base_url, api_key, model);
|
|
||||||
|
|
||||||
// Load system prompt + identity from config
|
|
||||||
let cli = crate::user::CliArgs::default();
|
let cli = crate::user::CliArgs::default();
|
||||||
let (app, _) = crate::config::load_app(&cli)
|
let (app, _) = crate::config::load_app(&cli)
|
||||||
.map_err(|e| format!("config: {}", e))?;
|
.map_err(|e| format!("config: {}", e))?;
|
||||||
|
let resolved = app.resolve_model(&app.default_backend)
|
||||||
|
.map_err(|e| format!("API not configured: {}", e))?;
|
||||||
|
let client = super::api::ApiClient::new(
|
||||||
|
&resolved.api_base, &resolved.api_key, &resolved.model_id);
|
||||||
let personality = crate::config::reload_context()
|
let personality = crate::config::reload_context()
|
||||||
.await.map_err(|e| format!("config: {}", e))?;
|
.await.map_err(|e| format!("config: {}", e))?;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,6 @@ pub fn config_path() -> PathBuf {
|
||||||
|
|
||||||
static CONFIG: OnceLock<RwLock<Arc<Config>>> = OnceLock::new();
|
static CONFIG: OnceLock<RwLock<Arc<Config>>> = OnceLock::new();
|
||||||
|
|
||||||
fn default_context_window() -> usize { 128_000 }
|
|
||||||
fn default_stream_timeout() -> u64 { 60 }
|
fn default_stream_timeout() -> u64 { 60 }
|
||||||
fn default_scoring_interval_secs() -> u64 { 3600 } // 1 hour
|
fn default_scoring_interval_secs() -> u64 { 3600 } // 1 hour
|
||||||
fn default_scoring_response_window() -> usize { 100 }
|
fn default_scoring_response_window() -> usize { 100 }
|
||||||
|
|
@ -60,18 +59,6 @@ pub struct Config {
|
||||||
pub agent_nodes: Vec<String>,
|
pub agent_nodes: Vec<String>,
|
||||||
pub llm_concurrency: usize,
|
pub llm_concurrency: usize,
|
||||||
pub agent_budget: usize,
|
pub agent_budget: usize,
|
||||||
/// Resolved from agent_model → models → backend (not in config directly)
|
|
||||||
#[serde(skip)]
|
|
||||||
pub api_base_url: Option<String>,
|
|
||||||
#[serde(skip)]
|
|
||||||
pub api_key: Option<String>,
|
|
||||||
#[serde(skip)]
|
|
||||||
pub api_model: Option<String>,
|
|
||||||
#[serde(skip, default = "default_context_window")]
|
|
||||||
pub api_context_window: usize,
|
|
||||||
/// Used to resolve API settings, not stored on Config
|
|
||||||
#[serde(default)]
|
|
||||||
agent_model: Option<String>,
|
|
||||||
/// Stream chunk timeout in seconds (no data = timeout).
|
/// Stream chunk timeout in seconds (no data = timeout).
|
||||||
#[serde(default = "default_stream_timeout")]
|
#[serde(default = "default_stream_timeout")]
|
||||||
pub api_stream_timeout_secs: u64,
|
pub api_stream_timeout_secs: u64,
|
||||||
|
|
@ -115,14 +102,9 @@ impl Default for Config {
|
||||||
agent_nodes: vec!["identity".into(), "core-practices".into()],
|
agent_nodes: vec!["identity".into(), "core-practices".into()],
|
||||||
llm_concurrency: 1,
|
llm_concurrency: 1,
|
||||||
agent_budget: 1000,
|
agent_budget: 1000,
|
||||||
api_base_url: None,
|
|
||||||
api_key: None,
|
|
||||||
api_model: None,
|
|
||||||
api_context_window: default_context_window(),
|
|
||||||
api_stream_timeout_secs: default_stream_timeout(),
|
api_stream_timeout_secs: default_stream_timeout(),
|
||||||
scoring_interval_secs: default_scoring_interval_secs(),
|
scoring_interval_secs: default_scoring_interval_secs(),
|
||||||
scoring_response_window: default_scoring_response_window(),
|
scoring_response_window: default_scoring_response_window(),
|
||||||
agent_model: None,
|
|
||||||
agent_types: vec![
|
agent_types: vec![
|
||||||
"linker".into(), "organize".into(), "distill".into(),
|
"linker".into(), "organize".into(), "distill".into(),
|
||||||
"separator".into(), "split".into(),
|
"separator".into(), "split".into(),
|
||||||
|
|
@ -153,25 +135,7 @@ impl Config {
|
||||||
let mut config: Config = serde_json::from_value(mem_value.clone()).ok()?;
|
let mut config: Config = serde_json::from_value(mem_value.clone()).ok()?;
|
||||||
config.llm_concurrency = config.llm_concurrency.max(1);
|
config.llm_concurrency = config.llm_concurrency.max(1);
|
||||||
|
|
||||||
// Resolve API settings: agent_model → models → backend
|
// Top-level sections (not inside "memory").
|
||||||
if let Some(model_name) = &config.agent_model
|
|
||||||
&& let Some(model_cfg) = root.get("models").and_then(|m| m.get(model_name.as_str())) {
|
|
||||||
let backend_name = model_cfg.get("backend").and_then(|v| v.as_str()).unwrap_or("");
|
|
||||||
let model_id = model_cfg.get("model_id").and_then(|v| v.as_str()).unwrap_or("");
|
|
||||||
|
|
||||||
if let Some(backend) = root.get(backend_name) {
|
|
||||||
config.api_base_url = backend.get("base_url")
|
|
||||||
.and_then(|v| v.as_str()).map(String::from);
|
|
||||||
config.api_key = backend.get("api_key")
|
|
||||||
.and_then(|v| v.as_str()).map(String::from);
|
|
||||||
}
|
|
||||||
config.api_model = Some(model_id.to_string());
|
|
||||||
if let Some(cw) = model_cfg.get("context_window").and_then(|v| v.as_u64()) {
|
|
||||||
config.api_context_window = cw as usize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Top-level config sections (not inside "memory")
|
|
||||||
if let Some(servers) = root.get("lsp_servers") {
|
if let Some(servers) = root.get("lsp_servers") {
|
||||||
config.lsp_servers = serde_json::from_value(servers.clone()).unwrap_or_default();
|
config.lsp_servers = serde_json::from_value(servers.clone()).unwrap_or_default();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -275,17 +275,7 @@ pub async fn prepare_spawn(name: &str, mut auto: AutoAgent, wake: std::sync::Arc
|
||||||
phase: s.phase.clone(),
|
phase: s.phase.clone(),
|
||||||
}).collect());
|
}).collect());
|
||||||
|
|
||||||
// Create standalone Agent — stored so UI can read context
|
// Create standalone Agent — stored so UI can read context.
|
||||||
let config = crate::config::get();
|
|
||||||
let base_url = config.api_base_url.as_deref().unwrap_or("");
|
|
||||||
let api_key = config.api_key.as_deref().unwrap_or("");
|
|
||||||
let model = config.api_model.as_deref().unwrap_or("");
|
|
||||||
if base_url.is_empty() || model.is_empty() {
|
|
||||||
dbglog!("[unconscious] API not configured");
|
|
||||||
auto.steps = orig_steps;
|
|
||||||
return Err(auto);
|
|
||||||
}
|
|
||||||
|
|
||||||
let cli = crate::user::CliArgs::default();
|
let cli = crate::user::CliArgs::default();
|
||||||
let (app, _) = match crate::config::load_app(&cli) {
|
let (app, _) = match crate::config::load_app(&cli) {
|
||||||
Ok(r) => r,
|
Ok(r) => r,
|
||||||
|
|
@ -295,9 +285,18 @@ pub async fn prepare_spawn(name: &str, mut auto: AutoAgent, wake: std::sync::Arc
|
||||||
return Err(auto);
|
return Err(auto);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let resolved = match app.resolve_model(&app.default_backend) {
|
||||||
|
Ok(r) => r,
|
||||||
|
Err(e) => {
|
||||||
|
dbglog!("[unconscious] API not configured: {}", e);
|
||||||
|
auto.steps = orig_steps;
|
||||||
|
return Err(auto);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Unconscious agents have self-contained prompts — no standard context.
|
// Unconscious agents have self-contained prompts — no standard context.
|
||||||
let client = crate::agent::api::ApiClient::new(base_url, api_key, model);
|
let client = crate::agent::api::ApiClient::new(
|
||||||
|
&resolved.api_base, &resolved.api_key, &resolved.model_id);
|
||||||
let agent = crate::agent::Agent::new(
|
let agent = crate::agent::Agent::new(
|
||||||
client, Vec::new(),
|
client, Vec::new(),
|
||||||
app, None,
|
app, None,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue