16 KiB
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 (Inputfromsuper::) and a newimpl RequestContextblock 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 onself.roleif no session is active; errors if an agent is activeexit_role(&mut self) -> Result<()>— clears the role from session or fromself.role
Session lifecycle (5):
exit_session(&mut self) -> Result<()>— saves session on exit and clearsself.sessionsave_session(&mut self, name) -> Result<()>— persists the current session, optionally renamingempty_session(&mut self) -> Result<()>— clears messages in the active sessionset_save_session_this_time(&mut self) -> Result<()>— sets the session's one-shot save flagexit_agent_session(&mut self) -> Result<()>— exits the agent's session without exiting the agent
RAG lifecycle (1):
exit_rag(&mut self) -> Result<()>— dropsself.rag
Chat lifecycle (2):
before_chat_completion(&mut self, input) -> Result<()>— stores the input aslast_messagewith empty outputdiscontinuous_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 activationinit_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
Configwith no modifications — every one of these methods only touches fields that already exist onRequestContextwith the same names and types.
Unchanged files
src/config/mod.rs— all 27 originalConfigmethods (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(callsself.current_model())edit_role(callsself.editor()+self.use_role())after_chat_completion(calls privatesave_messagewhich touchesself.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 ownedGlobalConfig, spawns tokio taskcompress_session— async, takes&GlobalConfigmaybe_autoname_session— takes ownedGlobalConfig, spawns tokio taskautoname_session— async, takes&GlobalConfiguse_rag— async, takes&GlobalConfig, callsRag::init/Rag::loadwhich expect&GlobalConfigedit_rag_docs— async, takes&GlobalConfig, calls intoRag::refresh_document_pathswhich expects&GlobalConfigrebuild_rag— same asedit_rag_docsuse_agent— async, takes&GlobalConfig, mutates multiple fields under the same write lock, callsConfig::use_session_safelyapply_prelude— async, callsself.use_role()/self.use_session()which are Category Aexit_agent— callsself.load_functions()which writesself.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 theToolScope/McpFactoryrework 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 errorscargo 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:
RequestContextnow has 25 inherent methods across all impl blocks (1 constructor + 13 reads from Step 5 + 12 writes from Step 6)role_like_mutis available (Step 5) — foundation for Step 7'sset_*methodsexit_session,save_session,empty_session,exit_agent_session,init_agent_shared_variables,init_agent_session_variablesare all onRequestContext— theuse_role,use_session, andexit_agentmigrations in Step 6.5 can call these directly on the new context typebefore_chat_completion,discontinuous_last_message, etc. are also onRequestContext— available for the newRequestContextversions of deferred methodsConfig::use_role,Config::use_session,Config::exit_agentare still onConfigand must be handled by Step 6.5'sToolScoperefactoring because they touchself.functionsandself.mcp_registry- The bridge from Step 1,
pathsmodule 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: FunctionswithRequestContext.tool_scope: ToolScope(containingfunctions,mcp_runtime,tool_tracker)Config.mcp_registry: Option<McpRegistry>withAppState.mcp_factory: Arc<McpFactory>(pool) +ToolScope.mcp_runtime: McpRuntime(per-scope handles)- Agent-scoped supervisor/inbox/todo into
RequestContext.agent_runtime: Option<AgentRuntime> - Agent RAG into a shared
AppState.rag_cache: Arc<RagCache>
- Once
ToolScopeexists, Step 6.5 can migrateuse_roleanduse_sessionby replacing theself.functions.clear_*/McpRegistry::reinitdance withself.tool_scope = app.mcp_factory.build_tool_scope(...). exit_agentcallsself.load_functions()which reloads the global tools. In the new design, exiting an agent should rebuild thetool_scopefor the now-topmostRoleLike. 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,Dropalways tears down. Correct but not optimized. RagCacheserves both standalone and agent RAGs. Step 6.5 needs to routeuse_rag(deferred) and agent activation through the cache. Sinceuse_ragis 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
AppConfigmethods from Steps 3-4. - Don't migrate the Step 7 targets unless they become
unblocked by the
ToolScope/AgentRuntimerefactor. - Don't try to build the
McpFactoryidle 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 forToolScope,McpRuntime,McpFactory,RagCache,AgentRuntime- This notes file — specifically "Category A" deferrals
(
use_role,use_session,exit_agent) src/config/mod.rs— currentConfig::use_role,Config::use_session,Config::exit_agentbodies 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 methodsRequestContext(Steps 5+6): 25 methodspathsmodule (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(newimpl RequestContextblock with 12 write methods, plusInputimport) - Unchanged but referenced:
src/config/mod.rs(originalConfigmethods still exist for all 27 targets)