Files
loki/docs/implementation/PHASE-1-STEP-8j-NOTES.md
2026-04-15 12:56:00 -06:00

131 lines
5.1 KiB
Markdown

# Phase 1 Step 8j — Implementation Notes
## Status
Done (partial — hot-path methods migrated, `config` field kept for
client creation and embeddings).
## Plan reference
- Plan: `docs/PHASE-1-IMPLEMENTATION-PLAN.md`
- Section: "Step 8j: Migrate `Input` and chat completion chain away
from `GlobalConfig`"
## Summary
Added 3 captured fields to the `Input` struct: `stream_enabled`,
`session`, `functions`. These are populated at construction time
from the `GlobalConfig`, eliminating 5 of 7 `self.config.read()`
calls. The remaining 2 calls (`set_regenerate`, `use_embeddings`)
still need the `GlobalConfig` and are low-frequency.
The `config: GlobalConfig` field is KEPT on `Input` because:
1. `create_client()` calls `init_client(&self.config, ...)` — the
client holds the `GlobalConfig` and passes it to `eval_tool_calls`
2. `use_embeddings()` calls `Config::search_rag(&self.config, ...)`
3. `set_regenerate()` calls `self.config.read().extract_role()`
Full elimination of `config` from `Input` requires migrating
`init_client`, every client struct, and `eval_tool_calls` — which
is a cross-cutting change across the entire client module.
## What was changed
### Files modified (1 file)
- **`src/config/input.rs`**:
- Added fields: `stream_enabled: bool`, `session: Option<Session>`,
`functions: Option<Vec<FunctionDeclaration>>`
- `from_str`: captures `stream_enabled`, `session`, `functions`
from `config.read()` at construction time
- `from_files`: same captures
- `stream()`: reads `self.stream_enabled` instead of
`self.config.read().stream`
- `prepare_completion_data()`: uses `self.functions.clone()`
instead of `self.config.read().select_functions(...)`
- `build_messages()`: uses `self.session(...)` with
`&self.session` instead of `&self.config.read().session`
- `echo_messages()`: same
### config.read() call reduction
| Method | Before | After |
|---|---|---|
| `stream()` | `self.config.read().stream` | `self.stream_enabled` |
| `prepare_completion_data()` | `self.config.read().select_functions(...)` | `self.functions.clone()` |
| `build_messages()` | `self.config.read().session` | `self.session` |
| `echo_messages()` | `self.config.read().session` | `self.session` |
| `set_regenerate()` | `self.config.read().extract_role()` | unchanged |
| `use_embeddings()` | `self.config.read().rag.clone()` | unchanged |
| `from_files()` (last_message) | `config.read().last_message` | unchanged |
**Total: 7 → 2 config.read() calls** (71% reduction).
## Key decisions
### 1. Kept `config: GlobalConfig` on Input
The `GlobalConfig` that `Input` passes to `init_client` ends up on
the `Client` struct, which passes it to `eval_tool_calls`. The
`eval_tool_calls` function reads `tool_call_tracker`,
`current_depth`, and `root_escalation_queue` from this GlobalConfig.
These are runtime fields that MUST reflect the current state.
If we replaced `config` with a temp GlobalConfig (like Rag's
`build_temp_global_config`), the tool call tracker and escalation
queue would be missing, breaking tool-call loop detection and
sub-agent escalation.
### 2. `eval_tool_calls` migration deferred
The plan listed `eval_tool_calls` migration as part of 8j. This
was deferred because `eval_tool_calls` is called from
`client/common.rs` via `client.global_config()`, and every client
struct holds `global_config: GlobalConfig`. Migrating eval_tool_calls
requires migrating init_client and every client struct — a separate
effort.
### 3. Functions pre-computed at construction time
`select_functions` involves reading `self.functions.declarations()`,
`self.mapping_tools`, `self.mapping_mcp_servers`, and the agent's
functions. Pre-computing this at Input construction time means the
function list is fixed for the duration of the chat turn. This is
correct behavior — tool availability shouldn't change mid-turn.
## Deviations from plan
| Deviation | Rationale |
|---|---|
| `eval_tool_calls` not migrated | Requires client module migration |
| `client/common.rs` not changed | Depends on eval_tool_calls migration |
| `config` field kept on Input | Client → eval_tool_calls needs real GlobalConfig |
| `_ctx` bridge constructors kept | Still useful for main.rs callers |
## Verification
- `cargo check` — clean, zero warnings
- `cargo clippy` — clean
- `cargo test` — 63 passed, 0 failed
## Handoff to next step
Step 8k (Agent::init migration) can proceed. The Input struct
changes don't affect Agent::init directly — agents create Input
internally via `Input::from_str` which still takes `&GlobalConfig`.
The full `Input` migration (eliminating the `config` field entirely)
is blocked on:
1. Migrating `init_client` to take `&AppConfig` + `&[ClientConfig]`
2. Migrating every client struct to not hold `GlobalConfig`
3. Migrating `eval_tool_calls` to take `&AppConfig` + `&mut RequestContext`
These form a single atomic change that should be its own dedicated
step (possibly Step 8n if needed, or as part of Phase 2).
## References
- Phase 1 plan: `docs/PHASE-1-IMPLEMENTATION-PLAN.md` — Step 8j
- Step 8i notes: `docs/implementation/PHASE-1-STEP-8i-NOTES.md`
- QA checklist: `docs/QA-CHECKLIST.md` — items 2-6, 8, 12, 22