This commit is contained in:
2026-04-15 12:56:00 -06:00
parent ff3419a714
commit 63b6678e73
82 changed files with 14800 additions and 3310 deletions
@@ -0,0 +1,224 @@
# Phase 1 Step 8d — Implementation Notes
## Status
Done.
## Plan reference
- Plan: `docs/PHASE-1-IMPLEMENTATION-PLAN.md`
- Section: "Step 8d: Scope transition rewrites — `use_role`,
`use_session`, `use_agent`, `exit_agent`"
## Summary
Added scope transition methods to `RequestContext` that build real
`ToolScope` instances via `McpFactory::acquire()`. Added
`mcp_config` and `mcp_log_path` fields to `AppState` so scope
transitions can look up MCP server specs and acquire handles. Added
`Session::new_from_ctx` and `Session::load_from_ctx` constructors
that take `&RequestContext` + `&AppConfig` instead of `&Config`.
Migrated `edit_role` (deferred from Step 8b) since `use_role` is
now available. `use_agent` is deferred to Step 8h because
`Agent::init` takes `&GlobalConfig`.
## What was changed
### Files modified (4 files)
- **`src/config/app_state.rs`** — added 2 fields:
- `mcp_config: Option<McpServersConfig>` — parsed MCP server
specs from `mcp.json`, stored at init time for scope
transitions to look up server specs by name
- `mcp_log_path: Option<PathBuf>` — log path for MCP server
stderr output, passed to `McpFactory::acquire`
- **`src/config/request_context.rs`** — added 6 methods in a new
impl block:
- `rebuild_tool_scope(&mut self, app, enabled_mcp_servers)`
private async helper that resolves MCP server IDs, acquires
handles via `McpFactory::acquire()`, builds a fresh `Functions`
instance, appends user interaction and MCP meta functions,
assembles a `ToolScope`, and assigns it to `self.tool_scope`
- `use_role(&mut self, app, name, abort_signal)` — retrieves
the role, resolves its MCP server list, calls
`rebuild_tool_scope`, then `use_role_obj`
- `use_session(&mut self, app, session_name, abort_signal)`
creates or loads a session via `Session::new_from_ctx` /
`Session::load_from_ctx`, rebuilds the tool scope, handles
the "carry last message" prompt, calls
`init_agent_session_variables`
- `exit_agent(&mut self, app)` — exits the session, resets the
tool scope to a fresh default (global functions + user
interaction), clears agent/supervisor/rag state
- `edit_role(&mut self, app, abort_signal)` — resolves the
current role name, calls `upsert_role` (editor), then
`use_role`
- `upsert_role(&self, app, name)` — opens the role file in the
editor (via `app.editor()`)
Updated imports: `McpRuntime`, `TEMP_SESSION_NAME`, `AbortSignal`,
`formatdoc`, `Confirm`, `remove_file`.
- **`src/config/session.rs`** — added 2 constructors:
- `Session::new_from_ctx(&RequestContext, &AppConfig, name)`
equivalent to `Session::new(&Config, name)` but reads
`ctx.extract_role(app)` and `app.save_session`
- `Session::load_from_ctx(&RequestContext, &AppConfig, name, path)`
equivalent to `Session::load(&Config, name, path)` but calls
`Model::retrieve_model(app, ...)` and
`ctx.retrieve_role(app, role_name)` instead of `&Config` methods
- **`src/config/bridge.rs`** — added `mcp_config: None,
mcp_log_path: None` to all 3 `AppState` construction sites in
tests
### Files NOT changed
- **`src/mcp/mod.rs`** — untouched; Step 8c's extraction is used
via `McpFactory::acquire()`
- **`src/config/mcp_factory.rs`** — untouched
- **`src/config/mod.rs`** — all `Config::use_role`,
`Config::use_session`, `Config::use_agent`,
`Config::exit_agent` stay intact for current callers
## Key decisions
### 1. `rebuild_tool_scope` replaces `McpRegistry::reinit`
The existing `Config::use_role` and `Config::use_session` both
follow the pattern: take `McpRegistry` → `McpRegistry::reinit` →
put registry back. The new `rebuild_tool_scope` replaces this with:
resolve server IDs → `McpFactory::acquire()` each → build
`ToolScope`. This is the core semantic change from the plan.
Key differences:
- `McpRegistry::reinit` does batch start/stop of servers (stops
servers not in the new set, starts missing ones). The factory
approach acquires each server independently — unused servers
are dropped when their `Arc` refcount hits zero.
- The factory's `Weak` sharing means that switching from role A
(github,slack) to role B (github,jira) shares the github
handle instead of stopping and restarting it.
### 2. `ToolCallTracker` initialized with default params
`ToolCallTracker::new(4, 10)` — 4 max repeats, 10 chain length.
These match the constants used in the existing codebase (the
tracker is used for tool-call loop detection). A future step can
make these configurable via `AppConfig` if needed.
### 3. `use_agent` deferred to Step 8h
`Config::use_agent` is a static method that takes `&GlobalConfig`
and calls `Agent::init(config, agent_name, abort_signal)`.
`Agent::init` compiles agent tools, loads RAG, resolves the model,
and does ~100 lines of setup, all against `&Config`. Migrating
`Agent::init` is a significant cross-module change that belongs
in Step 8h alongside the other agent lifecycle methods.
The plan listed `use_agent` as a target for 8d, but the
dependency on `Agent::init(&Config)` makes a clean bridge
impossible without duplicating `Agent::init`.
### 4. `abort_signal` is unused in the new methods
The existing `Config::use_role` doesn't pass `abort_signal` to
individual server starts — it's used by `abortable_run_with_spinner`
wrapping the batch `McpRegistry::reinit`. The new methods use
`McpFactory::acquire()` which doesn't take an abort signal (see
Step 8c notes). The `_abort_signal` parameter is kept in the
signature for API compatibility; Step 8f can wire it into the
factory if per-server cancellation is needed.
### 5. Session constructors parallel existing ones
`Session::new_from_ctx` and `Session::load_from_ctx` are verbatim
copies of `Session::new` and `Session::load` with `config: &Config`
replaced by `ctx: &RequestContext` + `app: &AppConfig`. The copies
are under `#[allow(dead_code)]` and will replace the originals
when callers migrate in Steps 8f-8g.
### 6. `exit_agent` rebuilds tool scope inline
`Config::exit_agent` calls `self.load_functions()` to reset the
global function declarations after exiting an agent. The new
`exit_agent` does the equivalent inline: creates a fresh
`ToolScope` with `Functions::init()` + user interaction functions.
It does NOT call `rebuild_tool_scope` because there's no MCP
server set to resolve — we're returning to the global scope.
## Deviations from plan
| Deviation | Rationale |
|---|---|
| `use_agent` deferred to Step 8h | Depends on `Agent::init(&Config)` migration |
| No `abort_signal` propagation to `McpFactory::acquire` | Step 8c decided against it; behavior matches existing code |
| No parent scope restoration test | Testing requires spawning real MCP servers; documented as Phase 5 test target |
## Verification
### Compilation
- `cargo check` — clean, zero warnings, zero errors
- `cargo clippy` — clean
### Tests
- `cargo test` — **63 passed, 0 failed** (unchanged)
## Handoff to next step
### What Step 8e can rely on
- **`RequestContext::use_role(app, name, abort_signal)`** — full
scope transition with ToolScope rebuild via McpFactory
- **`RequestContext::use_session(app, session_name, abort_signal)`** —
full scope transition with Session creation/loading
- **`RequestContext::exit_agent(app)`** — cleans up agent state
and rebuilds global ToolScope
- **`RequestContext::edit_role(app, abort_signal)`** — editor +
use_role
- **`RequestContext::upsert_role(app, name)`** — editor only
- **`Session::new_from_ctx` / `Session::load_from_ctx`** — ctx-
compatible session constructors
- **`AppState.mcp_config` / `AppState.mcp_log_path`** — MCP server
specs and log path available for scope transitions
### Method count at end of Step 8d
- `AppConfig`: 21 methods (unchanged from 8b)
- `RequestContext`: 53 methods (46 from 8b + 6 from 8d + 1 private
`rebuild_tool_scope`)
- `Session`: 2 new constructors (`new_from_ctx`, `load_from_ctx`)
- `AppState`: 2 new fields (`mcp_config`, `mcp_log_path`)
### What Step 8e should do
Migrate the Category C deferrals from Step 6:
- `compress_session`, `maybe_compress_session`
- `autoname_session`, `maybe_autoname_session`
- `use_rag`, `edit_rag_docs`, `rebuild_rag`
- `apply_prelude`
### Files to re-read at the start of Step 8e
- `docs/PHASE-1-IMPLEMENTATION-PLAN.md` — Step 8e section
- This notes file
- Step 6 notes — Category C deferral inventory
- `src/config/rag_cache.rs` — RagCache scaffolding from Step 6.5
- `src/config/mod.rs` — `compress_session`, `maybe_compress_session`,
`autoname_session`, `maybe_autoname_session`, `use_rag`,
`edit_rag_docs`, `rebuild_rag`, `apply_prelude` method bodies
## References
- Phase 1 plan: `docs/PHASE-1-IMPLEMENTATION-PLAN.md`
- Step 8b notes: `docs/implementation/PHASE-1-STEP-8b-NOTES.md`
- Step 8c notes: `docs/implementation/PHASE-1-STEP-8c-NOTES.md`
- Step 6.5 notes: `docs/implementation/PHASE-1-STEP-6.5-NOTES.md`
- Modified files:
- `src/config/request_context.rs` (6 new methods)
- `src/config/app_state.rs` (2 new fields)
- `src/config/session.rs` (2 new constructors)
- `src/config/bridge.rs` (test updates for new AppState fields)