refactor: Fully ripped out the god Config struct

This commit is contained in:
2026-04-19 19:14:25 -06:00
parent 1259c6865f
commit 715807645a
8 changed files with 431 additions and 682 deletions
+20 -57
View File
@@ -1,9 +1,11 @@
//! Per-request mutable state for a single Loki interaction.
//!
//! `RequestContext` holds everything that was formerly the runtime
//! (`#[serde(skip)]`) half of [`Config`](super::Config): the active role,
//! session, agent, RAG, supervisor state, inbox/escalation queues, and
//! the conversation's "last message" cursor.
//! `RequestContext` owns the runtime state that was previously stored
//! on `Config` as `#[serde(skip)]` fields: the active role, session,
//! agent, RAG, supervisor state, inbox/escalation queues, the
//! conversation's "last message" cursor, and the per-scope
//! [`ToolScope`](super::tool_scope::ToolScope) carrying functions and
//! live MCP handles.
//!
//! Each frontend constructs and owns a `RequestContext`:
//!
@@ -12,43 +14,11 @@
//! * **API** — one `RequestContext` per HTTP request, hydrated from a
//! persisted session and written back at the end.
//!
//! # Tool scope and agent runtime (planned)
//!
//! # Flat fields and sub-struct fields coexist during the bridge
//!
//! The flat fields (`functions`, `tool_call_tracker`, `supervisor`,
//! `inbox`, `root_escalation_queue`, `self_agent_id`, `current_depth`,
//! `parent_supervisor`) mirror the runtime half of today's `Config`
//! and are populated by
//! [`Config::to_request_context`](super::Config::to_request_context)
//! during the bridge.
//!
//! Step 6.5 added two **sub-struct fields** alongside the flat ones:
//!
//! * [`tool_scope: ToolScope`](super::tool_scope::ToolScope) — the
//! planned home for `functions`, `mcp_runtime`, and `tool_tracker`
//! * [`agent_runtime: Option<AgentRuntime>`](super::agent_runtime::AgentRuntime) —
//! the planned home for `supervisor`, `inbox`, `escalation_queue`,
//! `todo_list`, `self_agent_id`, `parent_supervisor`, and `depth`
//!
//! During the bridge window the sub-struct fields are **additive
//! scaffolding**: they're initialized to defaults in
//! [`RequestContext::new`] and stay empty until Step 8 rewrites the
//! entry points to build them explicitly. The flat fields continue
//! to carry the actual state from `Config` during the bridge.
//!
//! # Phase 1 refactor history
//!
//! * **Step 0** — struct introduced alongside `Config`
//! * **Step 5** — added 13 request-read methods
//! * **Step 6** — added 12 request-write methods
//! * **Step 6.5** — added `tool_scope` and `agent_runtime` sub-struct
//! fields as additive scaffolding
//! * **Step 7** — added 14 mixed-method splits that take `&AppConfig`
//! as a parameter for the serialized half
//!
//! See `docs/PHASE-1-IMPLEMENTATION-PLAN.md` for the full migration
//! plan.
//! `RequestContext` is built via [`RequestContext::bootstrap`] (CLI/REPL
//! entry point) or [`RequestContext::new`] (test/child-agent helper).
//! It holds an `Arc<AppState>` for shared, immutable services
//! (config, vault, MCP factory, RAG cache, MCP registry, base
//! functions).
use super::MessageContentToolCalls;
use super::rag_cache::{RagCache, RagKey};
@@ -2396,7 +2366,7 @@ impl RequestContext {
#[cfg(test)]
mod tests {
use super::*;
use crate::config::{AppState, Config};
use crate::config::AppState;
use crate::utils::get_env_name;
use std::env;
use std::fs::{create_dir_all, remove_dir_all, write};
@@ -2446,31 +2416,26 @@ mod tests {
}
}
fn app_state_from_config(cfg: &Config) -> Arc<AppState> {
fn default_app_state() -> Arc<AppState> {
Arc::new(AppState {
config: Arc::new(cfg.to_app_config()),
vault: cfg.vault.clone(),
config: Arc::new(AppConfig::default()),
vault: Arc::new(crate::vault::Vault::default()),
mcp_factory: Arc::new(super::super::mcp_factory::McpFactory::default()),
rag_cache: Arc::new(RagCache::default()),
mcp_config: None,
mcp_log_path: None,
mcp_registry: None,
functions: cfg.functions.clone(),
functions: Functions::default(),
})
}
fn create_test_ctx() -> RequestContext {
let cfg = Config::default();
let app_state = app_state_from_config(&cfg);
cfg.to_request_context(app_state)
RequestContext::new(default_app_state(), WorkingMode::Cmd)
}
#[test]
fn to_request_context_creates_clean_state() {
let cfg = Config::default();
let app_state = app_state_from_config(&cfg);
let ctx = cfg.to_request_context(app_state);
fn new_creates_clean_state() {
let ctx = RequestContext::new(default_app_state(), WorkingMode::Cmd);
assert!(ctx.role.is_none());
assert!(ctx.session.is_none());
@@ -2483,9 +2448,7 @@ mod tests {
#[test]
fn update_app_config_persists_changes() {
let cfg = Config::default();
let app_state = app_state_from_config(&cfg);
let mut ctx = RequestContext::new(app_state, WorkingMode::Cmd);
let mut ctx = RequestContext::new(default_app_state(), WorkingMode::Cmd);
let previous = Arc::clone(&ctx.app.config);
ctx.update_app_config(|app| {