15 KiB
Phase 1 Step 16f — Implementation Notes
Status
Done. Phase 1 Step 16 (Config → AppConfig migration) complete.
Plan reference
- Parent plan:
docs/implementation/PHASE-1-STEP-16-NOTES.md - Predecessor:
docs/implementation/PHASE-1-STEP-16e-NOTES.md - Sub-phase goal: "Delete all #[allow(dead_code)] scaffolding from Config and bridge.rs, delete runtime fields from Config, delete bridge.rs entirely."
Summary
Config is now a pure serde POJO. bridge.rs is gone. Every
runtime field, every Config::init* flavor, and every Config method
that was scaffolding for the old god-init has been deleted. The
project compiles clean, clippy clean, and all 122 tests pass.
What was changed
Deleted: src/config/bridge.rs
Whole file removed. mod bridge; declaration in config/mod.rs
removed. The two methods (Config::to_app_config and
Config::to_request_context) had no remaining callers after 16e.
src/config/mod.rs — Config slimmed to a POJO
Deleted runtime (#[serde(skip)]) fields from Config:
vault,macro_flag,info_flag,agent_variablesmodel,functions,mcp_registry,working_mode,last_messagerole,session,rag,agent,tool_call_trackersupervisor,parent_supervisor,self_agent_id,current_depth,inbox,root_escalation_queue
Deleted methods on Config:
init,init_bare(god-init replaced byload_with_interpolation+AppConfig::from_config+AppState::init+RequestContext::bootstrap)sessions_dir,list_sessions(replaced byconfig::default_sessions_dir/config::list_sessionsfree functions for use without a Config; per-context paths live onRequestContext::sessions_dir/RequestContext::list_sessions)role_like_mut(lives onRequestContextpost-migration)set_wrap,setup_document_loaders,setup_user_agent,load_envs(lives onAppConfigpost-migration)set_model,setup_model(model resolution now inAppConfig::resolve_model; per-scope model selection lives onRequestContext)load_functions,load_mcp_servers(absorbed byAppState::init)
Default impl entries for the deleted runtime fields removed.
Imports cleaned up: removed unused ToolCallTracker,
McpRegistry, Supervisor, EscalationQueue, Inbox, RwLock,
ColorScheme, QueryOptions, color_scheme, Handle. Kept
Model, ModelType, GlobalVault because sibling modules
(role.rs, input.rs, agent.rs, session.rs) use
use super::*; and depend on those re-exports.
Removed assertions for the deleted runtime fields from
config_defaults_match_expected test.
src/config/mod.rs — load_with_interpolation no longer touches AppConfig::to_app_config
Previously called config.to_app_config() to build a Vault for
secret interpolation. Now constructs a minimal AppConfig inline
with only vault_password_file populated, since that's all
Vault::init reads. Also removed the config.vault = Arc::new(vault)
assignment that was the last write to the deleted runtime field.
src/config/mod.rs — vault_password_file made pub(super)
Previously private. Now pub(super) so AppConfig::from_config (a
sibling module under config/) can read it during the field-copy.
src/config/app_config.rs — AppConfig::from_config self-contained
Previously delegated to Config::to_app_config() (lived on bridge)
for the field-copy. Now inlines the field-copy directly in
from_config, then runs load_envs, set_wrap,
setup_document_loaders, setup_user_agent, and resolve_model
as before.
Removed #[allow(dead_code)] from AppConfig.model_id — it's
read from app.config.model_id in RequestContext::bootstrap so
the lint exemption was stale.
Test refactor: the three to_app_config_* tests rewritten as
from_config_* tests using AppConfig::from_config(cfg).unwrap().
A ClientConfig::default() and non-empty model_id: "test-model"
were added so resolve_model() doesn't bail with "No available
model" during the runtime initialization.
src/config/session.rs — Test helper rewired
session_new_from_ctx_captures_save_session rewritten to build the
test AppState directly with AppConfig::default(),
Vault::default(), Functions::default() instead of going through
cfg.to_app_config() / cfg.vault / cfg.functions. Then uses
RequestContext::new(app_state, WorkingMode::Cmd) instead of the
deleted cfg.to_request_context(app_state).
src/config/request_context.rs — Test helpers rewired
The app_state_from_config(&Config) helper rewritten as
default_app_state() — no longer takes a Config, builds AppState
from AppConfig::default() + Vault::default() + Functions::default()
directly. The two callers (create_test_ctx,
update_app_config_persists_changes) updated.
The to_request_context_creates_clean_state test renamed to
new_creates_clean_state and rewritten to use RequestContext::new
directly.
Doc comment refresh
Three module docstrings rewritten to reflect the post-16f world:
app_config.rs— was "Phase 1 Step 0 ... not yet wired into the runtime." Now describesAppConfigas the runtime-resolved view of YAML, built viaAppConfig::from_config.app_state.rs— was "Step 6.5 added mcp_factory and rag_cache ... neither wired in yet ... Step 8+ will connect." Now describesAppState::initas the wiring point.request_context.rs— was an extensive description of the bridge window with flat fields vs sub-struct fields, citingConfig::to_request_context. Now describes the type's actual ownership/lifecycle without referring to deleted entry points.tool_scope.rs— was "Step 6.5 scope ... unused parallel structure ... Step 8 will rewrite." Now describesToolScopeas the live per-scope tool runtime.
(Other phase-era comments in paths.rs, mcp_factory.rs,
rag_cache.rs not touched. They reference Step 2 / Step 6.5 /
Step 8 but the affected types still exist and the descriptions
aren't actively misleading — those files weren't part of 16f
scope. Future cleanup if desired.)
What Config looks like now
#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct Config {
pub model_id: String,
pub temperature: Option<f64>,
pub top_p: Option<f64>,
pub dry_run: bool,
pub stream: bool,
pub save: bool,
pub keybindings: String,
pub editor: Option<String>,
pub wrap: Option<String>,
pub wrap_code: bool,
pub(super) vault_password_file: Option<PathBuf>,
pub function_calling_support: bool,
pub mapping_tools: IndexMap<String, String>,
pub enabled_tools: Option<String>,
pub visible_tools: Option<Vec<String>>,
pub mcp_server_support: bool,
pub mapping_mcp_servers: IndexMap<String, String>,
pub enabled_mcp_servers: Option<String>,
pub repl_prelude: Option<String>,
pub cmd_prelude: Option<String>,
pub agent_session: Option<String>,
pub save_session: Option<bool>,
pub compression_threshold: usize,
pub summarization_prompt: Option<String>,
pub summary_context_prompt: Option<String>,
pub rag_embedding_model: Option<String>,
pub rag_reranker_model: Option<String>,
pub rag_top_k: usize,
pub rag_chunk_size: Option<usize>,
pub rag_chunk_overlap: Option<usize>,
pub rag_template: Option<String>,
pub document_loaders: HashMap<String, String>,
pub highlight: bool,
pub theme: Option<String>,
pub left_prompt: Option<String>,
pub right_prompt: Option<String>,
pub user_agent: Option<String>,
pub save_shell_history: bool,
pub sync_models_url: Option<String>,
pub clients: Vec<ClientConfig>,
}
impl Config {
pub async fn load_with_interpolation(info_flag: bool) -> Result<Self> { ... }
pub fn load_from_file(config_path: &Path) -> Result<(Self, String)> { ... }
pub fn load_from_str(content: &str) -> Result<Self> { ... }
pub fn load_dynamic(model_id: &str) -> Result<Self> { ... }
}
Just shape + four loaders. The three associated functions that
used to live here (search_rag, load_macro, sync_models)
were relocated in the 16f cleanup pass below — none of them
touched Config state, they were squatters from the god-object
era.
Assumptions made
-
Doc cleanup scope: The user asked to "delete the dead-code scaffolding from Config and bridge.rs." Doc comments in
paths.rs,mcp_factory.rs,rag_cache.rsstill reference "Phase 1 Step 6.5 / Step 8" but the types they describe are still real and the descriptions aren't actively wrong (just historically dated). Left them alone. Updated only the docs inapp_config.rs,app_state.rs,request_context.rs, andtool_scope.rsbecause those were either pointing at deleted types (Config::to_request_context) or making explicitly false claims ("not wired into the runtime yet"). -
set_*_defaulthelpers on AppConfig: Lines 485–528 ofapp_config.rsdefine nine#[allow(dead_code)]set_*_defaultmethods. These were added in earlier sub-phases as planned setters for runtime overrides. They're still unused. The 16-NOTES plan flagged them ("set_*_default ... become reachable") but reachability never happened. Since the user's directive was specifically "Config and bridge.rs scaffolding," I left these untouched. Removing them is independent cleanup that doesn't block 16f. -
reload_current_modelon RequestContext: Same situation — one#[allow(dead_code)]left on a RequestContext method. Belongs to a different cleanup task; not Config or bridge scaffolding. -
vault_password_filevisibility:Config.vault_password_filewas a private field. Made itpub(super)soAppConfig::from_config(sibling underconfig/) can read it for the field-copy. This is the minimum viable visibility — no code outsideconfig/can touch it, matching the previous intent. -
Bootstrap vault construction in
load_with_interpolation: UsedAppConfig { vault_password_file: ..., ..AppConfig::default() }instead of e.g. a dedicated helper. The vault only readsvault_password_fileso this is sufficient. A comment explains the dual-vault pattern (bootstrap for secret interpolation vs canonical fromAppState::init).
Verification
cargo check— clean, zero warningscargo clippy --all-targets— clean, zero warningscargo test— 122 passing, zero failures (same count as 16e)- Grep confirmation:
to_app_config— zero hits insrc/to_request_context— zero hits insrc/Config::init/Config::init_bare— zero hits insrc/bridge::/config::bridge/mod bridge— zero hits insrc/src/config/bridge.rs— file deleted
- Config now contains only serde fields and load/helper functions; no runtime state.
Phase 1 Step 16 — overall outcome
The full migration is complete:
| Sub-phase | Outcome |
|---|---|
| 16a | AppConfig::from_config built |
| 16b | install_builtins() extracted |
| 16c | Vault on AppState (already-existing field, Vault::init rewired to &AppConfig) |
| 16d | AppState::init built |
| 16e | main.rs + completers + RequestContext::bootstrap switched to new flow |
| 16f | Bridge + Config runtime fields + dead methods deleted |
Config is a serde POJO. AppConfig is the runtime-resolved
process-wide settings. AppState owns process-wide services
(vault, MCP registry, base functions, MCP factory, RAG cache).
RequestContext owns per-request mutable state. Each struct
owns its initialization. The REST API surface is now trivial:
parse YAML → AppConfig::from_config → AppState::init →
per-request RequestContext.
Files modified (16f)
src/config/mod.rs— runtime fields/methods/Default entries deleted, imports cleaned up,vault_password_filemadepub(super),load_with_interpolationdecoupled fromto_app_config, default-test simplifiedsrc/config/app_config.rs—from_configinlines field-copy,#[allow(dead_code)]onmodel_idremoved, three tests rewritten, module docstring refreshedsrc/config/session.rs— test helper rewired, imports updatedsrc/config/request_context.rs— test helpers rewired, imports updated, module docstring refreshedsrc/config/app_state.rs— module docstring refreshedsrc/config/tool_scope.rs— module docstring refreshed
Files deleted (16f)
src/config/bridge.rs
16f cleanup pass — Config straggler relocation
After the main 16f deletions landed, three associated functions
remained on impl Config that took no &self and didn't touch
any Config field — they were holdovers from the god-object era,
attached to Config only because Config used to be the
namespace for everything. Relocated each to its rightful owner:
| Method | New home | Why |
|---|---|---|
Config::load_macro(name) |
Macro::load(name) in src/config/macros.rs |
Sibling of Macro::install_macros already there. The function loads a macro from disk and parses it into a Macro — pure macro concern. |
Config::search_rag(app, rag, text, signal) |
Rag::search_with_template(&self, app, text, signal) in src/rag/mod.rs |
Operates on a Rag instance and one field of AppConfig. Pulled RAG_TEMPLATE constant along with it. |
Config::sync_models(url, signal) |
Free function config::sync_models(url, signal) in src/config/mod.rs |
Fetches a URL, parses YAML, writes to paths::models_override_file(). No Config state involved. Sibling pattern to install_builtins, default_sessions_dir, list_sessions. |
Caller updates
src/config/macros.rs:23—Config::load_macro(name)→Macro::load(name)src/config/input.rs:214—Config::search_rag(&self.app_config, rag, &self.text, abort_signal)→rag.search_with_template(&self.app_config, &self.text, abort_signal)src/main.rs:149—Config::sync_models(&url, abort_signal.clone())→sync_models(&url, abort_signal.clone())(addedsync_modelsto thecrate::config::{...}import list)
Constants relocated
RAG_TEMPLATEmoved fromsrc/config/mod.rstosrc/rag/mod.rsalongside the newsearch_with_templatemethod that uses it.
Final shape of impl Config
impl Config {
pub async fn load_with_interpolation(info_flag: bool) -> Result<Self> { ... }
pub fn load_from_file(config_path: &Path) -> Result<(Self, String)> { ... }
pub fn load_from_str(content: &str) -> Result<Self> { ... }
pub fn load_dynamic(model_id: &str) -> Result<Self> { ... }
}
Four loaders, all returning Self or (Self, String). Nothing
else. The Config type is now genuinely what its docstring
claims: a serde POJO with constructors. No squatters.
Verification (cleanup pass)
cargo check— cleancargo clippy --all-targets— cleancargo test— 122 passing, zero failuresConfig::sync_models/Config::load_macro/Config::search_rag— zero hits insrc/
Files modified (cleanup pass)
src/config/mod.rs— deletedConfig::load_macro,Config::search_rag,Config::sync_models, andRAG_TEMPLATEconst; added freesync_modelsfunctionsrc/config/macros.rs— addedMacro::load, updated import (addedContext,read_to_string; removedConfig)src/rag/mod.rs— addedRAG_TEMPLATEconst andRag::search_with_templatemethodsrc/config/input.rs— updated caller torag.search_with_templatesrc/main.rs— addedsync_modelsto import list, updated caller