8.3 KiB
Phase 1 Step 16e — Implementation Notes
Status
Done.
Plan reference
- Parent plan:
docs/implementation/PHASE-1-STEP-16-NOTES.md - Sub-phase goal: "Switch main.rs and all Config::init() callers to the new flow"
Summary
main.rs and cli/completer.rs no longer call Config::init or
Config::init_bare — they use the new flow:
Config::load_with_interpolation → AppConfig::from_config →
AppState::init → RequestContext::bootstrap.
The bridge Config::to_request_context and the old Config::init
are now dead code, gated with #[allow(dead_code)] pending deletion
in 16f.
What was changed
New helpers
Config::load_with_interpolation(info_flag: bool) -> Result<Self> in
src/config/mod.rs — absorbs the two-pass YAML parse with secret
interpolation. Handles:
- Missing config file (creates via
create_config_fileif TTY, orload_dynamicfrom env vars) - Reading the raw YAML content
- Bootstrapping a Vault from the freshly-parsed Config
- Interpolating secrets
- Re-parsing Config if interpolation changed anything
- Sets
config.vault(legacy field — deleted in 16f)
config::default_sessions_dir() -> PathBuf and
config::list_sessions() -> Vec<String> free functions —
provide session listing without needing a Config instance. Used by
the session completer.
RequestContext::bootstrap(app: Arc<AppState>, working_mode, info_flag) -> Result<Self> in src/config/request_context.rs —
the new entry point for creating the initial RequestContext. Builds:
- Resolved
Modelfromapp.config.model_id ToolScope.functionscloned fromapp.functionswithappend_user_interaction_functionsadded in REPL modeToolScope.mcp_runtimesynced fromapp.mcp_registry
Made public in Config for new flow
Config::load_from_file(wasfn)Config::load_from_str(wasfn)Config::load_dynamic(wasfn)config::create_config_file(wasasync fn)
src/main.rs
Three startup paths rewired:
// Path 1: --authenticate
let cfg = Config::load_with_interpolation(true).await?;
let app_config = AppConfig::from_config(cfg)?;
let (client_name, provider) =
resolve_oauth_client(client_arg.as_deref(), &app_config.clients)?;
oauth::run_oauth_flow(&*provider, &client_name).await?;
// Path 2: vault flags
let cfg = Config::load_with_interpolation(true).await?;
let app_config = AppConfig::from_config(cfg)?;
let vault = Vault::init(&app_config);
return Vault::handle_vault_flags(cli, &vault);
// Path 3: main
let cfg = Config::load_with_interpolation(info_flag).await?;
let app_config: Arc<AppConfig> = Arc::new(AppConfig::from_config(cfg)?);
let app_state: Arc<AppState> = Arc::new(
AppState::init(app_config, log_path, start_mcp_servers, abort_signal.clone()).await?
);
let ctx = RequestContext::bootstrap(app_state, working_mode, info_flag)?;
No more Config::init, Config::to_app_config, cfg.mcp_registry,
or cfg.to_request_context references in main.rs.
src/cli/completer.rs
Three completers that needed config access updated:
model_completer→ uses newload_app_config_for_completion()helper (runsConfig::load_with_interpolationsynchronously from the completion context; async viaHandle::try_currentor a fresh runtime)session_completer→ uses the new free functionlist_sessions()(no Config needed)secrets_completer→ usesVault::init(&app_config)directly
#[allow(dead_code)] removed
AppConfig::from_configAppConfig::resolve_modelAppState::initAppState.rag_cache(was flagged dead; now wired in)
#[allow(dead_code)] added (temporary, deleted in 16f)
Config::init_bare— no longer calledConfig::sessions_dir— replaced by free functionConfig::list_sessions— replaced by free functionConfig::to_request_context— replaced byRequestContext::bootstrap
Behavior parity
-
main.rsstartup now invokes:install_builtins()(installs builtin global tools, agents, macros — same files get copied as before, Step 16b)Config::load_with_interpolation(same YAML loading + secret interpolation as oldConfig::init)AppConfig::from_config(same env/wrap/docs/user-agent/model resolution as old Config mutations)AppState::init(same vault init + MCP registry startup + global Functions loading as old Config methods, now owned by AppState; also pre-registers initial servers with McpFactory — new behavior that fixes a latent cache miss bug)RequestContext::bootstrap(same initial state as old bridgeto_request_context: resolved Model, Functions with REPL extensions, MCP runtime from registry)
-
Completer paths now use a lighter-weight config load (no MCP startup) which is appropriate since shell completion isn't supposed to start subprocesses.
Files modified
src/config/mod.rs— addedload_with_interpolation,default_sessions_dir,list_sessions; made 3 methods public; added#[allow(dead_code)]toConfig::init_bare,sessions_dir,list_sessions.src/config/request_context.rs— addedbootstrap.src/config/app_config.rs— removed 2#[allow(dead_code)]gates.src/config/app_state.rs— removed 2#[allow(dead_code)]gates.src/config/bridge.rs— added#[allow(dead_code)]toto_request_context.src/main.rs— rewired three startup paths.src/cli/completer.rs— rewired three completers.
Assumptions made
-
Completer helper runtime handling: The three completers run in a sync context (clap completion). The new
load_app_config_for_completionusesHandle::try_current().ok()to detect if a tokio runtime exists; if so, usesblock_in_place; otherwise creates a fresh runtime. This matches the oldConfig::init_barepattern (which also usedblock_in_place+block_on). -
Config::to_request_contextkept with#[allow(dead_code)]: It's unused now but 16f deletes it cleanly. Leaving it in place keeps 16e a non-destructive switchover. -
RequestContext::bootstrapreturnsResult<Self>notArc<Self>: caller decides wrapping. main.rs doesn't wrap; the REPL wrapsArc<RwLock<RequestContext>>a few lines later. -
install_builtin_global_toolsadded toinstall_builtins: A function added in user's 16b commit extracted builtin tool installation out ofFunctions::initinto a standalone function. My Step 16b commit that extractedinstall_builtinsmissed including this function — fixed in this step.
Verification
cargo check— clean, zero warningscargo clippy— clean, zero warningscargo test— 122 passing, zero failures- Grep confirmation:
Config::init(— only called fromConfig::init_bare(which is now dead)Config::init_bare— no external callers (test helper uses#[allow(dead_code)])to_request_context— zero callers outside bridge.rscfg.mcp_registry/cfg.functions/cfg.vaultreferences in main.rs — zero
Remaining work for Step 16
- 16f: Delete all
#[allow(dead_code)]scaffolding:Config::init,Config::init_bareConfig::sessions_dir,Config::list_sessionsConfig::set_wrap,Config::setup_document_loaders,Config::setup_user_agent,Config::load_envs,Config::load_functions,Config::load_mcp_servers,Config::setup_model,Config::set_model,Config::role_like_mut,Config::vault_password_filebridge.rs— delete entirely- All
#[serde(skip)]runtime fields onConfig mod bridge;declaration
After 16f, Config will be a pure serde POJO with only serialized
fields and load_from_file / load_from_str / load_dynamic /
load_with_interpolation methods.
Migration direction achieved
Before 16e:
main.rs: Config::init → to_app_config → AppState {...} → to_request_context
After 16e:
main.rs:
install_builtins()
Config::load_with_interpolation → AppConfig::from_config
AppState::init(app_config, ...).await
RequestContext::bootstrap(app_state, working_mode, info_flag)
No more god-init. Each struct owns its initialization. The REST
API path is now trivial: skip install_builtins() if not desired,
call AppConfig::from_config(yaml_string), call
AppState::init(...), create per-request RequestContext as
needed.