131 lines
5.1 KiB
Markdown
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
|