# Phase 1 Step 6 — Implementation Notes ## Status Done. ## Plan reference - Plan: `docs/PHASE-1-IMPLEMENTATION-PLAN.md` - Section: "Step 6: Migrate request-write methods to RequestContext" ## Summary Added 12 of 27 planned request-write methods to `RequestContext` as inherent methods, duplicating the bodies that still exist on `Config`. The other 15 methods were deferred: some to Step 6.5 (because they touch `self.functions` and `self.mcp_registry` — runtime fields being restructured by the `ToolScope` / `McpFactory` rework), some to Step 7 (because they cross the `AppConfig` / `RequestContext` boundary or call into `set_*` mixed methods), and some because their `GlobalConfig`-based static signatures don't fit the `&mut RequestContext` pattern at all. This step has the highest deferral ratio of the bridge phases so far (12/27 ≈ 44% migrated). That's by design — Step 6 is where the plan hits the bulk of the interesting refactoring territory, and it's where the `ToolScope` / `AgentRuntime` unification in Step 6.5 makes a big difference in what's migrateable. ## What was changed ### Modified files - **`src/config/request_context.rs`** — added 1 new import (`Input` from `super::`) and a new `impl RequestContext` block with 12 methods under `#[allow(dead_code)]`: **Role lifecycle (2):** - `use_role_obj(&mut self, role) -> Result<()>` — sets the role on the current session, or on `self.role` if no session is active; errors if an agent is active - `exit_role(&mut self) -> Result<()>` — clears the role from session or from `self.role` **Session lifecycle (5):** - `exit_session(&mut self) -> Result<()>` — saves session on exit and clears `self.session` - `save_session(&mut self, name) -> Result<()>` — persists the current session, optionally renaming - `empty_session(&mut self) -> Result<()>` — clears messages in the active session - `set_save_session_this_time(&mut self) -> Result<()>` — sets the session's one-shot save flag - `exit_agent_session(&mut self) -> Result<()>` — exits the agent's session without exiting the agent **RAG lifecycle (1):** - `exit_rag(&mut self) -> Result<()>` — drops `self.rag` **Chat lifecycle (2):** - `before_chat_completion(&mut self, input) -> Result<()>` — stores the input as `last_message` with empty output - `discontinuous_last_message(&mut self)` — clears the continuous flag on the last message **Agent variable init (2):** - `init_agent_shared_variables(&mut self) -> Result<()>` — prompts for agent variables on first activation - `init_agent_session_variables(&mut self, new_session) -> Result<()>` — syncs agent variables into/from session on new or resumed session All bodies are copy-pasted verbatim from `Config` with no modifications — every one of these methods only touches fields that already exist on `RequestContext` with the same names and types. ### Unchanged files - **`src/config/mod.rs`** — all 27 original `Config` methods (including the 15 deferred ones) are deliberately left intact. They continue to work for every existing caller. ## Key decisions ### 1. Only 12 of 27 methods migrated The plan's Step 6 table listed ~20 methods, but when I scanned for `fn (use_prompt|use_role|use_role_obj|...)` I found 27 (several methods have paired variants: `compress_session` + `maybe_compress_session`, `autoname_session` + `maybe_autoname_session`, `use_role_safely` vs `use_role`). Of those 27, **12 are pure runtime-writes that migrated cleanly** and **15 are deferred** to later steps. Full breakdown below. ### 2. Same duplication pattern as Steps 3-5 Callers hold `Config`, not `RequestContext`. Duplication is strictly additive during the bridge window and auto-deletes in Step 10. ### 3. Identified three distinct deferral categories The 15 deferred methods fall into three categories, each with a different resolution step: **Category A: Touch `self.functions` or `self.mcp_registry`** (resolved in Step 6.5 when `ToolScope` / `McpFactory` replace those fields): - `use_role` (async, reinits MCP registry for role's servers) - `use_session` (async, reinits MCP registry for session's servers) **Category B: Call into Step 7 mixed methods** (resolved in Step 7): - `use_prompt` (calls `self.current_model()`) - `edit_role` (calls `self.editor()` + `self.use_role()`) - `after_chat_completion` (calls private `save_message` which touches `self.save`, `self.session`, `self.agent`, etc.) **Category C: Static async methods taking `&GlobalConfig` that don't fit the `&mut RequestContext` pattern at all** (resolved in Step 8 or a dedicated lifecycle-refactor step): - `maybe_compress_session` — takes owned `GlobalConfig`, spawns tokio task - `compress_session` — async, takes `&GlobalConfig` - `maybe_autoname_session` — takes owned `GlobalConfig`, spawns tokio task - `autoname_session` — async, takes `&GlobalConfig` - `use_rag` — async, takes `&GlobalConfig`, calls `Rag::init` / `Rag::load` which expect `&GlobalConfig` - `edit_rag_docs` — async, takes `&GlobalConfig`, calls into `Rag::refresh_document_paths` which expects `&GlobalConfig` - `rebuild_rag` — same as `edit_rag_docs` - `use_agent` — async, takes `&GlobalConfig`, mutates multiple fields under the same write lock, calls `Config::use_session_safely` - `apply_prelude` — async, calls `self.use_role()` / `self.use_session()` which are Category A - `exit_agent` — calls `self.load_functions()` which writes `self.functions` (runtime, restructured in Step 6.5) ### 4. `exit_agent_session` migrated despite calling other methods `exit_agent_session` calls `self.exit_session()` and `self.init_agent_shared_variables()`. Since both of those are also being migrated in Step 6, `exit_agent_session` can migrate cleanly and call the new `RequestContext::exit_session` and `RequestContext::init_agent_shared_variables` on its own struct. ### 5. `exit_session` works because Step 5 migrated `sessions_dir` `exit_session` calls `self.sessions_dir()` which is now a `RequestContext` method (Step 5). Similarly, `save_session` calls `self.session_file()` (Step 5) and reads `self.working_mode` (a `RequestContext` field). This demonstrates how Steps 5 and 6 layer correctly — Step 5's reads enable Step 6's writes. ### 6. Agent variable init is pure runtime `init_agent_shared_variables` and `init_agent_session_variables` look complex (they call `Agent::init_agent_variables` which can prompt interactively) but they only touch `self.agent`, `self.agent_variables`, `self.info_flag`, and `self.session` — all runtime fields that exist on `RequestContext`. `Agent::init_agent_variables` itself is a static associated function on `Agent` that takes `defined_variables`, `existing_variables`, and `info_flag` as parameters — no `&Config` dependency. Clean migration. ## Deviations from plan ### 15 methods deferred Summary table of every method in the Step 6 target list: | Method | Status | Reason | |---|---|---| | `use_prompt` | **Step 7** | Calls `current_model()` (mixed) | | `use_role` | **Step 6.5** | Touches `functions`, `mcp_registry` | | `use_role_obj` | ✅ Migrated | Pure runtime-write | | `exit_role` | ✅ Migrated | Pure runtime-write | | `edit_role` | **Step 7** | Calls `editor()` + `use_role()` | | `use_session` | **Step 6.5** | Touches `functions`, `mcp_registry` | | `exit_session` | ✅ Migrated | Pure runtime-write (uses Step 5 `sessions_dir`) | | `save_session` | ✅ Migrated | Pure runtime-write (uses Step 5 `session_file`) | | `empty_session` | ✅ Migrated | Pure runtime-write | | `set_save_session_this_time` | ✅ Migrated | Pure runtime-write | | `maybe_compress_session` | **Step 7/8** | `GlobalConfig` + spawns task + `light_theme()` | | `compress_session` | **Step 7/8** | `&GlobalConfig`, complex LLM workflow | | `maybe_autoname_session` | **Step 7/8** | `GlobalConfig` + spawns task + `light_theme()` | | `autoname_session` | **Step 7/8** | `&GlobalConfig`, calls `retrieve_role` + LLM | | `use_rag` | **Step 7/8** | `&GlobalConfig`, calls `Rag::init`/`Rag::load` | | `edit_rag_docs` | **Step 7/8** | `&GlobalConfig`, calls `editor()` + Rag refresh | | `rebuild_rag` | **Step 7/8** | `&GlobalConfig`, Rag refresh | | `exit_rag` | ✅ Migrated | Trivial (drops `self.rag`) | | `use_agent` | **Step 7/8** | `&GlobalConfig`, complex multi-field mutation | | `exit_agent` | **Step 6.5** | Calls `load_functions()` which writes `functions` | | `exit_agent_session` | ✅ Migrated | Composes migrated methods | | `apply_prelude` | **Step 7/8** | Calls `use_role` / `use_session` (deferred) | | `before_chat_completion` | ✅ Migrated | Pure runtime-write | | `after_chat_completion` | **Step 7** | Calls `save_message` (mixed) | | `discontinuous_last_message` | ✅ Migrated | Pure runtime-write | | `init_agent_shared_variables` | ✅ Migrated | Pure runtime-write | | `init_agent_session_variables` | ✅ Migrated | Pure runtime-write | **Step 6 total: 12 migrated, 15 deferred.** ### Step 6's deferral load redistributes to later steps Running tally of deferrals after Step 6: - **Step 6.5 targets:** `use_role`, `use_session`, `exit_agent` (3 methods). These must be migrated alongside the `ToolScope` / `McpFactory` rework because they reinit or inspect the MCP registry. - **Step 7 targets:** `use_prompt`, `edit_role`, `after_chat_completion`, `select_functions`, `select_enabled_functions`, `select_enabled_mcp_servers` (from Step 3), `setup_model`, `update` (from Step 4), `info`, `session_info`, `sysinfo` (from Step 5), **plus** the original Step 7 mixed-method list: `current_model`, `extract_role`, `set_temperature`, `set_top_p`, `set_enabled_tools`, `set_enabled_mcp_servers`, `set_save_session`, `set_compression_threshold`, `set_rag_reranker_model`, `set_rag_top_k`, `set_max_output_tokens`, `set_model`, `retrieve_role`, `use_role_safely`, `use_session_safely`, `save_message`, `render_prompt_left`, `render_prompt_right`, `generate_prompt_context`, `repl_complete`. This is a big step. - **Step 7/8 targets (lifecycle refactor):** Session compression and autonaming tasks, RAG lifecycle methods, `use_agent`, `apply_prelude`. These may want their own dedicated step if the Step 7 list gets too long. ## Verification ### Compilation - `cargo check` — clean, **zero warnings, zero errors** - `cargo clippy` — clean ### Tests - `cargo test` — **63 passed, 0 failed** (unchanged from Steps 1–5) Step 6 added no new tests — duplication pattern. Existing tests confirm nothing regressed. ### Manual smoke test Not applicable — no runtime behavior changed. CLI and REPL still call `Config::use_role_obj()`, `exit_session()`, etc. as before. ## Handoff to next step ### What Step 6.5 can rely on Step 6.5 (unify `ToolScope` / `AgentRuntime` / `McpFactory` / `RagCache`) can rely on: - `RequestContext` now has **25 inherent methods** across all impl blocks (1 constructor + 13 reads from Step 5 + 12 writes from Step 6) - `role_like_mut` is available (Step 5) — foundation for Step 7's `set_*` methods - `exit_session`, `save_session`, `empty_session`, `exit_agent_session`, `init_agent_shared_variables`, `init_agent_session_variables` are all on `RequestContext` — the `use_role`, `use_session`, and `exit_agent` migrations in Step 6.5 can call these directly on the new context type - `before_chat_completion`, `discontinuous_last_message`, etc. are also on `RequestContext` — available for the new `RequestContext` versions of deferred methods - `Config::use_role`, `Config::use_session`, `Config::exit_agent` are **still on `Config`** and must be handled by Step 6.5's `ToolScope` refactoring because they touch `self.functions` and `self.mcp_registry` - The bridge from Step 1, `paths` module from Step 2, Steps 3-5 new methods, and all previous deferrals are unchanged ### What Step 6.5 should watch for - **Step 6.5 is the big architecture step.** It replaces: - `Config.functions: Functions` with `RequestContext.tool_scope: ToolScope` (containing `functions`, `mcp_runtime`, `tool_tracker`) - `Config.mcp_registry: Option` with `AppState.mcp_factory: Arc` (pool) + `ToolScope.mcp_runtime: McpRuntime` (per-scope handles) - Agent-scoped supervisor/inbox/todo into `RequestContext.agent_runtime: Option` - Agent RAG into a shared `AppState.rag_cache: Arc` - **Once `ToolScope` exists**, Step 6.5 can migrate `use_role` and `use_session` by replacing the `self.functions.clear_*` / `McpRegistry::reinit` dance with `self.tool_scope = app.mcp_factory.build_tool_scope(...)`. - **`exit_agent` calls `self.load_functions()`** which reloads the global tools. In the new design, exiting an agent should rebuild the `tool_scope` for the now-topmost `RoleLike`. The plan's Step 6.5 describes this exact transition. - **Phase 5 adds the idle pool to `McpFactory`.** Step 6.5 ships the no-pool version: `acquire()` always spawns fresh, `Drop` always tears down. Correct but not optimized. - **`RagCache` serves both standalone and agent RAGs.** Step 6.5 needs to route `use_rag` (deferred) and agent activation through the cache. Since `use_rag` is a Category C deferral (takes `&GlobalConfig`), Step 6.5 may not touch it — it may need to wait for Step 8. ### What Step 6.5 should NOT do - Don't touch the 25 methods already on `RequestContext` — they stay until Steps 8+ caller migration. - Don't touch the `AppConfig` methods from Steps 3-4. - Don't migrate the Step 7 targets unless they become unblocked by the `ToolScope` / `AgentRuntime` refactor. - Don't try to build the `McpFactory` idle pool — that's Phase 5. ### Files to re-read at the start of Step 6.5 - `docs/PHASE-1-IMPLEMENTATION-PLAN.md` — Step 6.5 section (the biggest single section, ~90 lines) - `docs/REST-API-ARCHITECTURE.md` — section 5 (Tool Scope Isolation) has the full design for `ToolScope`, `McpRuntime`, `McpFactory`, `RagCache`, `AgentRuntime` - This notes file — specifically "Category A" deferrals (`use_role`, `use_session`, `exit_agent`) - `src/config/mod.rs` — current `Config::use_role`, `Config::use_session`, `Config::exit_agent` bodies to see the MCP/functions handling that needs replacing ## Follow-up (not blocking Step 6.5) ### 1. `save_message` is private and heavy `after_chat_completion` was deferred because it calls the private `save_message` method, which is ~50 lines of logic touching `self.save` (serialized), `self.session` (runtime), `self.agent` (runtime), and the messages file (via `self.messages_file()` which is on `RequestContext`). Step 7 should migrate `save_message` first, then `after_chat_completion` can follow. ### 2. `Config::use_session_safely` and `use_role_safely` are a pattern to replace Both methods do `take(&mut *guard)` on the `GlobalConfig` then call the instance method on the taken `Config`, then put it back. This pattern exists because `use_role` and `use_session` are `&mut self` methods that need to await across the call, and the `RwLock` can't be held across `.await`. When `use_role` and `use_session` move to `RequestContext` in Step 6.5, the `_safely` wrappers can be eliminated entirely — the caller just takes `&mut RequestContext` directly. Flag this as a cleanup opportunity for Step 8. ### 3. `RequestContext` is now ~400 lines Counting imports, struct definition, and 3 `impl` blocks: ``` use statements: ~20 lines struct definition: ~30 lines impl 1 (new): ~25 lines impl 2 (reads, Step 5): ~155 lines impl 3 (writes, Step 6): ~160 lines Total: ~390 lines ``` Still manageable. Step 6.5 will add `tool_scope` and `agent_runtime` fields plus their methods, pushing toward ~500 lines. Post-Phase 1 cleanup should probably split into separate files (`reads.rs`, `writes.rs`, `tool_scope.rs`, `agent_runtime.rs`) but that's optional. ### 4. Bridge-window duplication count at end of Step 6 Running tally: - `AppConfig` (Steps 3+4): 11 methods - `RequestContext` (Steps 5+6): 25 methods - `paths` module (Step 2): 33 free functions (not duplicated) **Total bridge-window duplication: 36 methods / ~550 lines.** All auto-delete in Step 10. ## References - Phase 1 plan: `docs/PHASE-1-IMPLEMENTATION-PLAN.md` - Architecture doc: `docs/REST-API-ARCHITECTURE.md` - Step 5 notes: `docs/implementation/PHASE-1-STEP-5-NOTES.md` - Modified file: `src/config/request_context.rs` (new `impl RequestContext` block with 12 write methods, plus `Input` import) - Unchanged but referenced: `src/config/mod.rs` (original `Config` methods still exist for all 27 targets)