From 1a739ecef51ea408a8c88ced78c949b3f642f211 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sun, 21 Jun 2026 18:32:02 -0400 Subject: [PATCH 1/2] conductor(spec+plan): phase2_4_5_call_site_completion_20260621 + code_path_audit pre-flight adjustments + Phase 3 analysis PHASE 2/4/5 FOLLOW-UP TRACK (Tier 1 decided SHINK to 6a + 6b + 6d): - Phase 6a: Fix HookServer.broadcast() callers (app_controller.py + events.py + gui_2.py) Adds tests/test_websocket_broadcast_regression.py with no-TypeError assertion - Phase 6b: Complete _send_grok/_send_minimax/_send_llama OpenAICompatibleRequest migration - Phase 6d: Update those 3 senders' NormalizedResponse to use UsageStats Total: ~16 atomic commits, ~3 hours Tier 2 work. Unblocks code_path_audit_20260607. CODE_PATH_AUDIT_20260607 PRE-FLIGHT ADJUSTMENTS (per handoffs): - Add 2 new actions: provider_history_append + websocket_broadcast - Add 5 micro-benchmarks: NormalizedResponse.__init__, WebSocketMessage.__init__, UsageStats.__init__, ProviderHistory.lock, ToolSpec.__init__ - Add no-TypeError-errors-on-any-thread assertion (backs test_websocket_broadcast_regression.py) - Add 89 fat-struct sites from ANY_TYPE_AUDIT_20260621.md as instrumented targets - BLOCKER: phase2_4_5_call_site_completion_20260621 (broadcast() TypeError) PHASE 3 HYPOTHETICAL ANALYSIS (separate doc): docs/reports/PHASE3_HYPOTHETICAL_PROMOTION.md - dataclass definitions (already on tier2 branch), per-provider codepath catalog (112 sites), qualitative cost estimation (~+1-2ms per session, ~+8-15us per _send_anthropic turn). Input for the audit; the audit quantifies the cost. REGISTRATION: conductor/tracks.md updated: new row 27 (follow-up), new row 28 (parent any_type_componentization), row 17 (code_path_audit) updated with pre-flight adjustments note. Files: - conductor/tracks/phase2_4_5_call_site_completion_20260621/spec.md (NEW; 633 lines) - conductor/tracks/phase2_4_5_call_site_completion_20260621/plan.md (NEW; 7 phases, 23 tasks) - conductor/tracks/phase2_4_5_call_site_completion_20260621/metadata.json (NEW; 8.8KB) - conductor/tracks/phase2_4_5_call_site_completion_20260621/state.toml (NEW; 11.8KB) - docs/reports/PHASE3_HYPOTHETICAL_PROMOTION.md (NEW; 380 lines; qualitative cost analysis) - conductor/tracks/code_path_audit_20260607/spec.md (MODIFIED; +93 lines Pre-Flight Adjustments) - conductor/tracks.md (MODIFIED; +35 lines: 3 new entries + 1 stale row fix) --- conductor/tracks.md | 36 +- .../tracks/code_path_audit_20260607/spec.md | 73 +++ .../metadata.json | 111 ++++ .../plan.md | 508 ++++++++++++++++++ .../spec.md | 206 +++++++ .../state.toml | 77 +++ docs/reports/PHASE3_HYPOTHETICAL_PROMOTION.md | 243 +++++++++ 7 files changed, 1253 insertions(+), 1 deletion(-) create mode 100644 conductor/tracks/phase2_4_5_call_site_completion_20260621/metadata.json create mode 100644 conductor/tracks/phase2_4_5_call_site_completion_20260621/plan.md create mode 100644 conductor/tracks/phase2_4_5_call_site_completion_20260621/spec.md create mode 100644 conductor/tracks/phase2_4_5_call_site_completion_20260621/state.toml create mode 100644 docs/reports/PHASE3_HYPOTHETICAL_PROMOTION.md diff --git a/conductor/tracks.md b/conductor/tracks.md index a90aaf78..80f238b4 100644 --- a/conductor/tracks.md +++ b/conductor/tracks.md @@ -49,7 +49,7 @@ Tracks that are unblocked and ready to start. Ordered by **dependency** (blocked | 15a | — | [Manual UX Validation — ASCII-Sketch Workflow](#track-manual-ux-validation--ascii-sketch-workflow-new-2026-06-08) | spec ✓, plan ✓, ready to start | (none — independent; NEW 2026-06-08) | | 15b | — | [Chunkification Optimization (Contingency)](#track-chunkification-optimization-new-2026-06-08-contingency) | spec ✓ (contingency), no plan | hard constraint surface (deferred) | | 16 | — | [GenCpp Dogfood Feedback Loop](#track-gencpp-dogfood-feedback-loop) | spec TBD | (none — independent; oldest pending track) | -| 17 | — | [Code Path Audit](#track-code-path-audit) | spec TBD | test_infrastructure_hardening_20260609 (merged) | +| 17 | A | [Code Path Audit](#track-code-path-audit) | spec ✓ + plan ✓ (revised 2026-06-08 post-4-tracks; **pre-flight adjusted 2026-06-21** with 2 new actions + 5 micro-benchmarks + no-TypeError assertion per `docs/handoffs/PROMPT_FOR_TIER_1.md`) | test_infrastructure_hardening_20260609 (merged), any_type_componentization_20260621 (shipped 2026-06-21), phase2_4_5_call_site_completion_20260621 (BLOCKER for the broadcast() TypeError fix; unblocks audit instrumentation) | | 23 | A (research) | [Intent-Based Scripting Languages Survey](#track-intent-based-scripting-languages-survey-new-2026-06-12) | spec ✓, plan pending | (none — independent; NEW 2026-06-12; **non-impl research track**, **time-sensitive: report must complete before nagent v2.2**) | | 24 | A (bugfix) | [AI Loop Regressions (MiniMax, Gemini, Gemini CLI, DeepSeek)](#track-ai-loop-regressions-minimax-gemini-gemini-cli-deepseek-new-2026-06-14) | spec ✓, plan ✓, shipped 2026-06-15 (with 1 critical `_api_generate` regression + 2 deferred bugs — see `doeh_test_thinking_cleanup_20260615`) | (none — independent; **NEW 2026-06-14**; user-blocking; 3 bugs from `data_oriented_error_handling_20260606`) | | 25 | B (research) | [Fable System Prompt Review (Critical Analysis)](#track-fable-system-prompt-review-critical-analysis-new-2026-06-17) | spec ✓, plan pending | (none — independent; **NEW 2026-06-17**; **non-impl research track**, **informs the deferred nagent-rebuild**; 10 cluster sub-reports + 17-section synthesis report >3500 LOC + 3 side artifacts; Fable artifact at `docs/artifacts/Fable System Prompt.txt` is local-only and **NEVER committed**) | @@ -63,6 +63,8 @@ Tracks that are unblocked and ready to start. Ordered by **dependency** (blocked | 21 | A | [Conductor Chronology (chronology.md canonical index)](#track-conductor-chronology) | spec ✓, plan ✓, 10/10 phases implemented; Phase 10 (user sign-off) pending; end-of-track report at `docs/reports/TRACK_COMPLETION_chronology_20260619.md` | (none — independent; **NEW 2026-06-19**; canonical-track infrastructure; the `superpowers_review_20260619` track is `blocked_by` this one) | | 22b | A (meta-tooling) | [Meta-Tooling Workflow Review — Past-Month LLM Behavior Analysis](#track-meta-tooling-workflow-review-past-month-llm-behavior-analysis) | spec ✓, plan ✓, metadata ✓, state ✓, **parked 2026-06-20** (current_phase=0); 11-phase plan; ≥4,000-LOC 4-part report; 13-15 atomic commits; Tier 1 anchor + 3 Tier 3 parallel sweeps | (none — independent; **NEW 2026-06-20**; sibling to nagent_review + fable_review + superpowers_review + intent_dsl_survey; produces workflow_improvements.md + implementation_sequencing.md as standalone inputs for a near-future "workflow improvements rebuild" track; research-only; no src/, tests/, AGENTS.md, conductor/*.md, .opencode/, or scripts/audit_*.py changes; **anti-sliming guard**: Phase 9 self-review + Phase 10 user review gate are literal hard gates per the chronology_20260619 handover) | | 26 | A (research) | [Video Analysis Campaign (12 videos, 5 clusters, Pass 1 of 3)](#track-video-analysis-campaign-20260621) | spec ✓, plan ✓, **14 folders scaffolded (1 umbrella + 12 children + 1 synthesis); Pass 1 of 3 (information extraction); awaiting Phase 0 tooling prerequisites (yt-dlp, cv2, imagehash install in repo venv)**; 12 children in execution order: CS229 → math foundations → Platonic/geometric → biological → CS336 → applied capstone; per-video target: 1000-10000 LOC markdown deep-dive report | (none — independent; **NEW 2026-06-21**; multi-track research campaign; 12 videos across 5 clusters (E: Stanford >1hr; A: math foundations; B: Platonic AI; C: biological/cognitive; D: applied); multi-pass handoff to Pass 2 (de-obfuscation via user's math encoding — USER must rediscover notation before Pass 2 starts) + Pass 3 (projection to applied domain — USER must articulate "own caveats" before Pass 3 starts); **lossless preservation directive**: Pass 1 artifacts must NOT be over-summarized (data cascades to Pass 2/3); **2 E-cluster videos failed oEmbed 401** (yt-dlp may still work; verify in Phase 1); reusable tooling: 5 TDD scripts in `scripts/video_analysis/` (download_video, extract_transcript, extract_keyframes, ocr_frames, synthesize_report) | +| 27 | A | [Phase 2/4/5 Call-Site Completion (post any_type_componentization)](#track-phase2-4-5-call-site-completion-20260621) | spec ✓, plan pending; **Tier 1 decided SHINK scope** to Phase 6a + 6b + 6d (~16 commits, ~3 hours Tier 2); **BLOCKER for `code_path_audit_20260607`** (the broadcast() TypeError contaminates audit instrumentation); see `docs/handoffs/PROMPT_FOR_TIER_1.md` | any_type_componentization_20260621 (parent; shipped 2026-06-21 with 48/89 sites + 1 runtime bug) | (**NEW 2026-06-21**; bugfix + refactor + test-infrastructure; Phase 6a: fix `HookServer.broadcast()` callers in `src/app_controller.py` + `src/events.py` + `src/gui_2.py` (5-10 sites) — migrate to `WebSocketMessage` signature; Phase 6b: complete `_send_grok` + `_send_minimax` + `_send_llama` `OpenAICompatibleRequest` migration (3 sites); Phase 6d: update those 3 senders' `NormalizedResponse` to use `UsageStats` (3 sites); adds `tests/test_websocket_broadcast_regression.py` with "no-TypeError" assertion that the audit will reuse; **deferred**: Phase 3 (`provider_state.ProviderHistory` call-site migration in `ai_client.py` — 112 sites) → separate track post-audit; cross-phase coupling → separate track; `audit_tier2_leaks.py` sandbox-pollution fixes → infra track; pre-existing `test_gui2_custom_callback_hook_works` flake → separate investigation; **does NOT merge `tier2/any_type_componentization_20260621` branch** per Tier 2's reconnaissance framing; analysis input for Phase 3 cost estimation: `docs/reports/PHASE3_HYPOTHETICAL_PROMOTION.md`) | +| 28 | A | [Any-Type Componentization (Promote dict[str, Any] to dataclass(frozen=True))](#track-any-type-componentization-promote-dictstr-any-to-dataclassfrozentrue) | spec ✓, plan ✓, metadata ✓, state ✓, **shipped 2026-06-21** with 48/89 fat-struct sites promoted (Phases 1, 2, 4, 5 complete); Phase 3 (`provider_state` call-site migration in `ai_client.py`) DEFERRED to a separate track; 1 runtime bug surfaced (`HookServer.broadcast()` callers in `app_controller.py` + `events.py`); not merged; reconnaissance for `code_path_audit_20260607`; tier2 branch at 24 commits | (none — independent; **NEW 2026-06-21**; refactor + ai-readability + type-safety; ships: 3 new modules (`src/mcp_tool_specs.py`, `src/openai_schemas.py`, `src/provider_state.py`); 2 new audit scripts (`scripts/audit_dataclass_coverage.py` + `--strict` mode); styleguide `conductor/code_styleguides/type_aliases.md` §12 "When to Promote TypeAlias to dataclass"; type-registry regenerated; 130+ tests pass; **input artifact**: `docs/reports/ANY_TYPE_AUDIT_20260621.md`; **handoff docs**: `docs/handoffs/PROMPT_FOR_TIER_1.md` + `HANDOFF_FOLLOWUP_TRACK_FROM_any_type_componentization.md` + `HANDOFF_CODE_PATH_AUDIT_FROM_any_type_componentization.md`) | **Note on numbering:** the legacy file used `0a`, `0b`, `0c`... and `0d`, `0e`, `0f`, `0g` for tracks created 2026-06-06+. This is the **git-blame sort order**, not a logical execution order. The new structure re-orders by dependency. @@ -632,6 +634,38 @@ Lightweight chronology; full spec/plan/state per track is in the linked folder. *Link: [./tracks/code_path_audit_20260607/](./tracks/code_path_audit_20260607/), Spec: [./tracks/code_path_audit_20260607/spec.md](./tracks/code_path_audit_20260607/spec.md), Plan: [./tracks/code_path_audit_20260607/plan.md](./tracks/code_path_audit_20260607/plan.md) (to be authored by writing-plans skill)* *Goal: Build `src/code_path_audit.py` — a static-analysis tool that audits the 3 major actions (AI message lifecycle, discussion save/load, GUI startup) for expensive operations, redundant calls, and pipelining candidates. Output: custom postfix `.dsl` data + markdown + Mermaid + prefix tree text under `docs/reports/code_path_audit//`. The follow-up `pipeline_pruning_20260607` consumes the `.dsl` files; the markdown + tree are for human review. MMA worker spawn is **cold per user**. **Timing (revised 2026-06-08):** the audit must run *after* the 4 foundational tracks ship (`qwen_llama_grok`, `data_oriented_error_handling`, `data_structure_strengthening`, `mcp_architecture_refactor`); pre-4-tracks code is too stale to ground optimization decisions.* +*Pre-Flight Adjustments (2026-06-21, per `docs/handoffs/PROMPT_FOR_TIER_1.md` + `HANDOFF_CODE_PATH_AUDIT_FROM_any_type_componentization.md`):* +- *Add 2 new actions to per-action profiling: `provider_history_append` (the hot path Phase 3 will refactor; measures per-turn append latency + lock acquire time) + `websocket_broadcast` (the GUI thread's per-event cost; the path Phase 6a will fix)* +- *Add 5 micro-benchmarks to `optimization_candidates.md`: `NormalizedResponse.__init__` (<1μs), `WebSocketMessage.__init__` (<5μs), `UsageStats.__init__` (<500ns), `ProviderHistory.lock` (<500ns), `ToolSpec.__init__` (<2μs)* +- *Add the "no-TypeError-errors-on-any-thread" assertion: the audit fails if any `worker[queue_fallback] error: WebSocketServer.broadcast()` appears in harness output; backed by `tests/test_websocket_broadcast_regression.py`* +- *Add the 89 fat-struct sites from `ANY_TYPE_AUDIT_20260621.md` §3 as instrumented targets; tags each with `(file:line, hot_path, cold_path, init_path)`* +- *BLOCKER: `phase2_4_5_call_site_completion_20260621` (the broadcast() TypeError fix). The audit's per-action profiling is contaminated by the TypeError spam until Phase 6a merges. Recommended sequence: run the follow-up track first; after merge, launch the audit; the audit's per-action data informs the deferred Phase 3 + cross-phase coupling follow-up tracks* + +#### Track: Phase 2/4/5 Call-Site Completion (post any_type_componentization) `[track-created: 2026-06-21]` +*Link: [./tracks/phase2_4_5_call_site_completion_20260621/](./tracks/phase2_4_5_call_site_completion_20260621/), Spec: [./tracks/phase2_4_5_call_site_completion_20260621/spec.md](./tracks/phase2_4_5_call_site_completion_20260621/spec.md), Plan: [./tracks/phase2_4_5_call_site_completion_20260621/plan.md](./tracks/phase2_4_5_call_site_completion_20260621/plan.md), Metadata: [./tracks/phase2_4_5_call_site_completion_20260621/metadata.json](./tracks/phase2_4_5_call_site_completion_20260621/metadata.json), State: [./tracks/phase2_4_5_call_site_completion_20260621/state.toml](./tracks/phase2_4_5_call_site_completion_20260621/state.toml)* + +*Status: 2026-06-21 — Active, Tier 1 decision pending Tier 2 implementation. **SHRUNK scope** per `PROMPT_FOR_TIER_1.md` Decision 1 (Phase 6a + 6b + 6d only; defer Phase 3 to its own track post-audit).* + +*Goal: Three-phase focused track that **(a) fixes the `HookServer.broadcast()` runtime bug** introduced by `any_type_componentization_20260621` Phase 5 (the Phase 5 commit `e9fa69dd` changed `broadcast(channel, payload)` → `broadcast(message: WebSocketMessage)` but did not update internal callers in `src/app_controller.py`, `src/events.py`, `src/gui_2.py`); **(b) completes the `_send_grok` / `_send_minimax` / `_send_llama` Phase 2 migration** (the 3 OpenAI-compatible senders were deferred in t2_6 and still construct `OpenAICompatibleRequest(messages=[{"role": ..., "content": ...}])` instead of `messages=[ChatMessage(...)]`); **(c) updates those 3 senders' `NormalizedResponse` construction** to use the Phase 2 `UsageStats` dataclass. **Adds `tests/test_websocket_broadcast_regression.py` with a "no-TypeError-errors-on-any-thread" assertion that `code_path_audit_20260607` will reuse**.* + +*Scope (per Tier 1's shrink decision):* +- *Phase 6a (~7 commits): Fix `HookServer.broadcast()` callers in `src/app_controller.py:_run_pending_tasks_once_result` + `src/events.py` + `src/gui_2.py:_process_pending_gui_tasks`. Replace `broadcast(channel, payload)` with `broadcast(WebSocketMessage(channel=, payload=))`. Add regression test.* +- *Phase 6b (~5 commits): Migrate `_send_grok` (L2532) + `_send_minimax` (L2616) + `_send_llama` (L2856) to construct `OpenAICompatibleRequest(messages=[ChatMessage(...)], ...)`. Update provider tests.* +- *Phase 6d (~4 commits): Update those 3 senders' `NormalizedResponse` construction to use `usage=UsageStats(input_tokens=..., output_tokens=..., cache_read_tokens=..., cache_creation_tokens=...)` instead of 4 separate int fields.* +- *Total: ~16 atomic commits, ~3 hours Tier 2 work.* + +*Deferred (out of scope, per Tier 1's decision):* +- *Phase 3 (`provider_state.ProviderHistory` call-site migration in `src/ai_client.py`): 112 sites across 6 senders (`_send_anthropic` 25, `_send_deepseek` 20, `_send_minimax` 21, `_send_qwen` 12, `_send_grok` 13, `_send_llama` 21). Qualitative cost estimate: ~+1-2ms per session; +8-15μs per `_send_anthropic` turn. Full analysis: `docs/reports/PHASE3_HYPOTHETICAL_PROMOTION.md`. The audit will quantify this before the Phase 3 track runs.* +- *Cross-phase coupling: `OpenAICompatibleRequest.tools: list[dict[str, Any]]` → `list[ToolSpec]`. Deferred to a separate track.* +- *`audit_tier2_leaks.py` sandbox-pollution fixes (3 failures): `--allowlist` for `mcp_paths.toml`, `opencode.json`, `.opencode/*`. Infrastructure track.* +- *Pre-existing `test_gui2_custom_callback_hook_works` flake. Separate investigation.* + +*`blocks: code_path_audit_20260607` (the broadcast() TypeError contaminates the audit's per-action profiling; this track unblocks the audit). `blocked_by: any_type_componentization_20260621` (parent track; shipped 2026-06-21; the tier2 branch is NOT merged).* + +*Does NOT merge `tier2/any_type_componentization_20260621` branch per Tier 2's reconnaissance framing in `HANDOFF_CODE_PATH_AUDIT_FROM_any_type_componentization.md` ("Use as input for the audit, not as a merge candidate"). The branch stays at 24 commits as the audit's reconnaissance warm-up.* + +*Regression protocol (the lesson from `any_type_componentization_20260621`'s 10 test failures): after each Phase, run `uv run python scripts/run_tests_batched.py --tier tier-1-unit-core` FULLY (no stop-on-failure). After all phases complete, run all 11 tiers FULLY. The "no-TypeError" assertion is the canonical regression test.* + #### Track: GUI Architecture Refinement *Link: [./tracks/gui_architecture_refinement_20260512/](./tracks/gui_architecture_refinement_20260512/) (no spec.md; needs scoping before planning)* diff --git a/conductor/tracks/code_path_audit_20260607/spec.md b/conductor/tracks/code_path_audit_20260607/spec.md index 7e18db5c..1230a638 100644 --- a/conductor/tracks/code_path_audit_20260607/spec.md +++ b/conductor/tracks/code_path_audit_20260607/spec.md @@ -305,6 +305,79 @@ This track has **no blockers** and **no conflicts**. It can ship independently o This track's analysis is **read-only** — it doesn't modify `src/`, doesn't change the public API, doesn't add tests to the existing test suite. The only new files are `src/code_path_audit.py` (the tool), `tests/test_code_path_audit.py` (the tests), and the report under `docs/reports/code_path_audit/2026-06-07/`. +## Pre-Flight Adjustments (2026-06-21, per handoffs from `any_type_componentization_20260621`) + +The `any_type_componentization_20260621` track (shipped 2026-06-21 with 48/89 sites promoted) revealed that **the 4 foundational tracks this audit was deferred behind have evolved**. Specifically, 5 new hot-path dataclasses (`ToolSpec`, `ChatMessage`, `UsageStats`, `ToolCall`, `WebSocketMessage`) and 1 new module (`provider_state.ProviderHistory`) now exist. This audit must instrument them. + +**Per `docs/handoffs/PROMPT_FOR_TIER_1.md` and `HANDOFF_CODE_PATH_AUDIT_FROM_any_type_componentization.md`, the following 4 adjustments are added to this audit's scope:** + +### A1. Add 2 new actions to the per-action profiling + +The existing 3 actions (`ai_message_lifecycle`, `discussion_save_load`, `gui_startup`) become 5: + +| Action | Codepath | Measures | +|---|---|---| +| `provider_history_append` (NEW) | `get_history(p).append(msg)` (or legacy `_anthropic_history.append(msg)`) | Per-turn append latency + lock acquire time + memory allocation per call. The hot path Phase 3 will refactor. | +| `websocket_broadcast` (NEW) | `broadcast(WebSocketMessage(...))` (post-Phase 6a) | Per-broadcast overhead (allocation + JSON serialization + WebSocket send). The GUI thread's per-event cost. | +| `ai_message_lifecycle` (existing) | `_send_` end-to-end | Total per-turn latency delta pre/post Phase 3 (`provider_state.ProviderHistory`). The 3 OpenAI-compatible providers (`grok`, `minimax`, `llama`) are **newly instrumented** (currently unprofiled). | +| `discussion_save_load` (existing) | `reset_session()` + project switch | Cold-path cost. The `clear_all()` migration's per-call delta. | +| `gui_startup` (existing) | `_PROVIDER_HISTORIES` dict init at module load | One-time init cost (6 `ProviderHistory()` instances + 6 locks). | + +### A2. Add 5 micro-benchmarks to the audit's `optimization_candidates.md` + +The audit's per-call cost estimates should include these 5 micro-benchmarks (added per `HANDOFF_FOLLOWUP_TRACK_FROM_any_type_componentization.md` §7): + +| Micro-benchmark | Purpose | Expected overhead | +|---|---|---| +| `NormalizedResponse.__init__` | Dataclass construction vs the old 6-field dict literal | <1μs; immaterial | +| `WebSocketMessage.__init__` | Dataclass construction per broadcast | <5μs; the hot path concern | +| `UsageStats.__init__` | Nested dataclass construction per response | <500ns; negligible (4 int fields) | +| `ProviderHistory.lock` acquire | threading.Lock acquire overhead | <500ns; the threading hot path | +| `ToolSpec.__init__` | Dataclass construction per tool (45 tools, cold path) | <2μs; only at registration | + +The benchmarks are emitted to `docs/reports/code_path_audit//micro_benchmarks.md`. + +### A3. Add the "no-TypeError-errors-on-any-thread" assertion + +The audit's per-action profiling runs the 5 actions in a controlled harness. The audit MUST assert that no `worker[queue_fallback] error: WebSocketServer.broadcast() takes 2 positional arguments but 3 were given` (or any TypeError on any thread) appears in the harness output during profiling. + +This assertion catches the broadcast() regression that `any_type_componentization_20260621` introduced. The regression test that backs this assertion lives in `tests/test_websocket_broadcast_regression.py` (added by the `phase2_4_5_call_site_completion_20260621` follow-up track). + +If the assertion fires, the audit's output should: +1. Mark the affected action's profile as `INSTRUMENTATION_CONTAMINATED` +2. List the offending thread + traceback in the report's `errors.md` +3. Recommend re-running the audit AFTER `phase2_4_5_call_site_completion_20260621` merges + +### A4. Add the 89 fat-struct sites as instrumented targets + +The audit reads `docs/reports/ANY_TYPE_AUDIT_20260621.md` §3's table and tags each `Any` usage with `(file:line, hot_path, cold_path, init_path)`. The 89 sites become per-action cost estimates that flow into `optimization_candidates.md`. + +For the 48 promoted sites, the audit compares pre-refactor (legacy globals + dict literals) vs post-refactor (dataclass + registry). For the 41 deferred Phase 3 sites, the audit produces per-call cost estimates that inform the future Phase 3 follow-up track (see `docs/reports/PHASE3_HYPOTHETICAL_PROMOTION.md` for the qualitative estimates). + +### A5. Sequencing (BLOCKER) + +**This audit is now blocked by `phase2_4_5_call_site_completion_20260621` (the broadcast() fix).** Until Phase 6a merges, the GUI thread's `worker[queue_fallback]` TypeError spam contaminates the audit's per-action profiling. + +**Recommended sequence:** +``` +T0: Tier 1 approves follow-up track (decision: SHRINK to 6a + 6b + 6d) +T1: Tier 2 implements Phase 6a + 6b + 6d (~3 hours, ~16 commits) +T2: Tier 1 reviews + merges follow-up track +T3: Tier 1 launches code_path_audit_20260607 +T4: Tier 2 implements Phase 3 + cross-phase coupling (separate track, post-audit) +``` + +### A6. New coordination with `any_type_componentization_20260621` + +This audit now has **new dependencies** beyond the original 4 foundational tracks: + +| Track | Status | Provides to this audit | +|---|---|---| +| `any_type_componentization_20260621` | Shipped 2026-06-21 (48/89 promoted) | The 5 dataclasses + 1 module; the 200-site dataclass-coverage baseline | +| `phase2_4_5_call_site_completion_20260621` | Spec'd 2026-06-21; not yet merged | The fix for the broadcast() TypeError; the "no-TypeError" assertion | + +This audit is `blocked_by` both tracks (post-merge). + ## Follow-up - **`pipeline_runtime_profiling_20260607`** (the user-requested follow-up; NOT in this track): adds a runtime profiling harness using the existing `src/performance_monitor.py` + a per-action test fixture. Measures real costs for the 3 actions. Calibrates the heuristic cost model (`EXPENSIVE_THRESHOLD` + per-class weights). Catches "things that aren't easy to resolve statically" — import cost, JIT effects, GC pauses, C-extension call cost (imgui-bundle, tree-sitter native), decorator-driven dispatch. Output: `scripts/runtime_profiler.py` + updated `code_path_audit.py` cost model. diff --git a/conductor/tracks/phase2_4_5_call_site_completion_20260621/metadata.json b/conductor/tracks/phase2_4_5_call_site_completion_20260621/metadata.json new file mode 100644 index 00000000..8f56e63f --- /dev/null +++ b/conductor/tracks/phase2_4_5_call_site_completion_20260621/metadata.json @@ -0,0 +1,111 @@ +{ + "track_id": "phase2_4_5_call_site_completion_20260621", + "name": "Phase 2/4/5 Call-Site Completion (post any_type_componentization)", + "initialized": "2026-06-21", + "owner": "tier2-tech-lead", + "priority": "A", + "status": "active", + "type": "bugfix + refactor + test-infrastructure", + "scope": { + "new_files": [ + "tests/test_websocket_broadcast_regression.py", + "docs/reports/TRACK_COMPLETION_phase2_4_5_call_site_completion_20260621.md" + ], + "modified_files": [ + "src/app_controller.py", + "src/events.py", + "src/gui_2.py", + "src/ai_client.py", + "tests/test_grok_provider.py", + "tests/test_minimax_provider.py", + "tests/test_llama_provider.py" + ], + "deleted_files": [] + }, + "blocked_by": [], + "blocks": ["code_path_audit_20260607"], + "estimated_phases": 3, + "spec": "spec.md", + "plan": "plan.md", + "priority_order": "A (Phase 6a broadcast fix) > A (Phase 6b OpenAICompatibleRequest) > B (Phase 6d NormalizedResponse) > C (regression protocol)", + "parent_track": { + "id": "any_type_componentization_20260621", + "spec": "conductor/tracks/any_type_componentization_20260621/spec.md", + "handoff_docs": [ + "docs/handoffs/PROMPT_FOR_TIER_1.md", + "docs/handoffs/HANDOFF_FOLLOWUP_TRACK_FROM_any_type_componentization.md", + "docs/handoffs/HANDOFF_CODE_PATH_AUDIT_FROM_any_type_componentization.md" + ] + }, + "phases": { + "phase_6a": { + "name": "Fix HookServer.broadcast() callers", + "scope": "Migrate broadcast(channel, payload) callers in app_controller.py + events.py + gui_2.py to broadcast(WebSocketMessage(...))", + "estimated_commits": 7, + "new_test_file": "tests/test_websocket_broadcast_regression.py" + }, + "phase_6b": { + "name": "Complete OpenAICompatibleRequest migration", + "scope": "_send_grok + _send_minimax + _send_llama construct OpenAICompatibleRequest(messages=[ChatMessage(...)])", + "estimated_commits": 5 + }, + "phase_6d": { + "name": "Update NormalizedResponse construction", + "scope": "Same 3 senders: usage_input_tokens/etc -> usage=UsageStats(...)", + "estimated_commits": 4 + } + }, + "total_estimated_commits": 16, + "deferred_work": { + "phase_3_provider_state": { + "deferred_to": "separate track post code_path_audit_20260607", + "rationale": "Phase 3 has runtime hot-path concerns (per-LLM-turn history manipulation); the code_path_audit should measure cost BEFORE the refactor", + "estimated_sites": 112, + "estimation_method": "grep -c '__history(?!_)' on src/ai_client.py per HANDOFF_CODE_PATH_AUDIT" + }, + "cross_phase_coupling": { + "deferred_to": "separate track", + "rationale": "OpenAICompatibleRequest.tools: list[dict[str, Any]] -> list[ToolSpec] is a follow-up" + }, + "audit_tier2_leaks_fix": { + "deferred_to": "infrastructure track", + "rationale": "3 sandbox-pollution failures; need --allowlist for mcp_paths.toml, opencode.json, .opencode/*" + }, + "pre_existing_gui2_parity_flake": { + "deferred_to": "investigation", + "rationale": "test_gui2_custom_callback_hook_works flake; not introduced by this track" + } + }, + "unblocks": { + "code_path_audit_20260607": "TypeError spam from broadcast() contaminates per-action profiling; Phase 6a fixes the underlying regression" + }, + "verification_criteria": [ + "src/app_controller.py:_run_pending_tasks_once_result uses broadcast(WebSocketMessage(...))", + "src/events.py broadcast callers use WebSocketMessage", + "src/gui_2.py:_process_pending_gui_tasks broadcast callers use WebSocketMessage", + "tests/test_websocket_broadcast_regression.py exists; asserts no broadcast() TypeError", + "_send_grok constructs OpenAICompatibleRequest(messages=[ChatMessage(...)], ...)", + "_send_minimax constructs OpenAICompatibleRequest(messages=[ChatMessage(...)], ...)", + "_send_llama constructs OpenAICompatibleRequest(messages=[ChatMessage(...)], ...)", + "_send_grok constructs NormalizedResponse(text=..., usage=UsageStats(...), ...)", + "_send_minimax constructs NormalizedResponse(text=..., usage=UsageStats(...), ...)", + "_send_llama constructs NormalizedResponse(text=..., usage=UsageStats(...), ...)", + "All 11-tier batched test run passes (no stop-on-failure)", + "audit_weak_types.py --strict exits 0", + "audit_dataclass_coverage.py --strict exits 0", + "End-of-track report at docs/reports/TRACK_COMPLETION_phase2_4_5_call_site_completion_20260621.md" + ], + "sequencing_note": "This track unblocks code_path_audit_20260607. Run this track first; after merge, run the audit. The Phase 3 follow-up track runs AFTER the audit completes.", + "ai_performance_analysis": { + "win": "Fixes 1 runtime bug (broadcast() TypeError) + completes the Phase 2/5 migration for 3 senders (grok/minimax/llama). Makes code_path_audit_20260607 instrumentable.", + "cost": "~16 commits; ~3 hours Tier 2.", + "caveat": "The deferred Phase 3 (112 sites in ai_client.py) is still the biggest remaining work. The audit will quantify the cost before Phase 3 is migrated.", + "honest_assessment": "Tight, focused track. Fits Tier 2's 1-4 hour budget. Unblocks the audit without ballooning scope." + }, + "links": { + "parent_track": "conductor/tracks/any_type_componentization_20260621/", + "audit_track": "conductor/tracks/code_path_audit_20260607/", + "phase3_hypothetical_analysis": "docs/reports/PHASE3_HYPOTHETICAL_PROMOTION.md", + "handoff_docs": "docs/handoffs/" + } +} \ No newline at end of file diff --git a/conductor/tracks/phase2_4_5_call_site_completion_20260621/plan.md b/conductor/tracks/phase2_4_5_call_site_completion_20260621/plan.md new file mode 100644 index 00000000..851f34da --- /dev/null +++ b/conductor/tracks/phase2_4_5_call_site_completion_20260621/plan.md @@ -0,0 +1,508 @@ +# Phase 2/4/5 Call-Site Completion Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Fix the `HookServer.broadcast()` runtime bug + complete the Phase 2 `_send_grok` / `_send_minimax` / `_send_llama` migration to `OpenAICompatibleRequest(messages=[ChatMessage(...)])` and `NormalizedResponse(usage=UsageStats(...))`. Adds `tests/test_websocket_broadcast_regression.py` with a "no-TypeError-errors-on-any-thread" assertion that `code_path_audit_20260607` will reuse. + +**Architecture:** 3 phases (Phase 6a + 6b + 6d). Phase 6a is the runtime bug fix (broadcast callers in 3 files). Phase 6b completes the t2_6 deferred OpenAI-compatible sender migration. Phase 6d updates those senders' `NormalizedResponse` to use `UsageStats`. No new modules; only consumer migration + 1 new regression test file. + +**Tech Stack:** Python 3.11+ stdlib. Existing `src/openai_schemas.py` (Phase 2 of parent track) provides `ChatMessage`, `UsageStats`, `ToolCall`. Existing `src/api_hooks.py` (Phase 5 of parent track) provides `WebSocketMessage`. + +**Reference Files:** +- `docs/handoffs/PROMPT_FOR_TIER_1.md` — Tier 1 brief +- `docs/handoffs/HANDOFF_FOLLOWUP_TRACK_FROM_any_type_componentization.md` — test failure categorization +- `docs/handoffs/HANDOFF_CODE_PATH_AUDIT_FROM_any_type_componentization.md` — runtime cost framing +- `conductor/tracks/phase2_4_5_call_site_completion_20260621/spec.md` — the design +- `conductor/tracks/any_type_componentization_20260621/spec.md` — parent track +- `src/openai_schemas.py` — ChatMessage + UsageStats + NormalizedResponse + OpenAICompatibleRequest +- `src/api_hooks.py` — WebSocketMessage + HookServer.broadcast + +**Code Style:** 1-space indentation, CRLF line endings, no comments in source code, type hints mandatory (per `conductor/workflow.md` Code Style section). + +--- + +## File Structure + +``` +src/ + app_controller.py # MODIFIED (Phase 6a): _run_pending_tasks_once_result broadcast callers + events.py # MODIFIED (Phase 6a): broadcast callers + gui_2.py # MODIFIED (Phase 6a): _process_pending_gui_tasks broadcast callers + ai_client.py # MODIFIED (Phase 6b+6d): _send_grok/_send_minimax/_send_llama + api_hooks.py # UNCHANGED (the broadcast() change is correct) + +tests/ + test_websocket_broadcast_regression.py # NEW (Phase 6a): no-TypeError assertion + test_grok_provider.py # MODIFIED (Phase 6b+6d): verify ChatMessage + UsageStats + test_minimax_provider.py # MODIFIED (Phase 6b+6d): verify ChatMessage + UsageStats + test_llama_provider.py # MODIFIED (Phase 6b+6d): verify ChatMessage + UsageStats + +docs/reports/ + TRACK_COMPLETION_phase2_4_5_call_site_completion_20260621.md # NEW (verify) +``` + +--- + +## Phase 6a: Fix HookServer.broadcast() Callers + +Focus: Replace `broadcast(channel, payload)` with `broadcast(WebSocketMessage(channel=, payload=))` at all internal call sites in `src/`. + +### Task 6a.1: Catalog all broadcast() callers + +**Files:** +- Search: `src/app_controller.py`, `src/events.py`, `src/gui_2.py` + +- [ ] **Step 1: Grep for all internal callers** + +Run: `Select-String -Path src/app_controller.py,src/events.py,src/gui_2.py -Pattern '\.broadcast\('` +Expected: 5-10 sites (per HANDOFF_FOLLOWUP §5: app_controller.py:_run_pending_tasks_once_result 1-3, events.py 1-3, gui_2.py 1-3) + +- [ ] **Step 2: Document the list** + +For each call site, record `(file:line, current_call_signature, replacement_call_signature)` in your working notes. Example: +- `src/app_controller.py:N broadcast(channel_str, payload_dict)` → `broadcast(WebSocketMessage(channel=channel_str, payload=payload_dict))` + +### Task 6a.2: Write failing regression test + +**Files:** +- Create: `tests/test_websocket_broadcast_regression.py` + +- [ ] **Step 1: Write the test** + +```python +"""Regression test for the HookServer.broadcast() runtime TypeError bug. + +This test ensures that no internal caller of HookServer.broadcast() passes +the OLD (channel, payload) signature after Phase 5 changed it to +(message: WebSocketMessage). The audit (code_path_audit_20260607) reuses +this assertion. +""" +import asyncio +import sys +from src.api_hooks import WebSocketMessage + + +def test_broadcast_accepts_websocket_message() -> None: + """HookServer.broadcast must accept a single WebSocketMessage argument.""" + from src.api_hooks import HookServer + import inspect + sig = inspect.signature(HookServer.broadcast) + params = list(sig.parameters.keys()) + # self + 1 positional arg + assert len(params) == 2, f"expected 2 params (self + message), got {len(params)}: {params}" + + +def test_broadcast_rejects_legacy_2arg_call() -> None: + """Calling broadcast with 2 positional args (legacy signature) must raise TypeError.""" + from src.api_hooks import HookServer + server = HookServer() + try: + server.broadcast("channel", {"key": "value"}) + except TypeError as e: + assert "takes 2 positional arguments" in str(e) or "takes 1 positional argument" in str(e) + return + assert False, "broadcast should reject legacy 2-arg call" + + +def test_internal_callers_use_websocket_message_signature() -> None: + """Grep all internal callers of broadcast() and assert they use the new signature.""" + import subprocess + result = subprocess.run( + ["grep", "-rn", r"\.broadcast\(", "src/"], + capture_output=True, text=True, + ) + lines = [l for l in result.stdout.split("\n") if l and "tests/" not in l] + for line in lines: + file, lineno, content = line.split(":", 2) + # The new signature is broadcast(WebSocketMessage(...)) + # The old signature is broadcast("string", {...}) + if "WebSocketMessage(" not in content and 'broadcast("' in content: + assert False, f"{file}:{lineno} uses legacy signature: {content.strip()}" + + +def test_no_typeerror_during_gui_task_processing() -> None: + """Smoke test: simulate a GUI task that triggers broadcast; assert no TypeError on any thread.""" + import logging + import io + # Capture stderr to detect worker[queue_fallback] error spam + captured = io.StringIO() + handler = logging.StreamHandler(captured) + handler.setLevel(logging.ERROR) + logging.getLogger().addHandler(handler) + try: + # Trigger a task that would have hit the broadcast bug + # (This is a structural test — the actual GUI thread simulation is in live_gui tests) + import asyncio + from src.api_hooks import HookServer, WebSocketMessage + server = HookServer() + msg = WebSocketMessage(channel="test", payload={"key": "value"}) + server.broadcast(msg) # must not raise + finally: + logging.getLogger().removeHandler(handler) + stderr_output = captured.getvalue() + assert "WebSocketServer.broadcast()" not in stderr_output, f"TypeError detected: {stderr_output}" +``` + +- [ ] **Step 2: Run test to verify first one fails** + +Run: `uv run pytest tests/test_websocket_broadcast_regression.py -v` +Expected: The first test passes (the signature is already `(self, message)`); the second passes (legacy call raises); the THIRD may FAIL (internal callers still use old signature — that's what we're fixing); the fourth passes (the smoke test). + +### Task 6a.3: Fix `src/app_controller.py:_run_pending_tasks_once_result` broadcast callers + +- [ ] **Step 1: Find the call sites** + +Run: `Select-String -Path src/app_controller.py -Pattern '\.broadcast\('` +Expected: 1-3 lines in `_run_pending_tasks_once_result` + +- [ ] **Step 2: For each call site, replace** + +Old: +```python +self.web_socket_server.broadcast(channel_str, payload_dict) +``` + +New: +```python +from src.api_hooks import WebSocketMessage +self.web_socket_server.broadcast(WebSocketMessage(channel=channel_str, payload=payload_dict)) +``` + +(Add the import at the top of the function or file if not already present.) + +- [ ] **Step 3: Run regression test** + +Run: `uv run pytest tests/test_websocket_broadcast_regression.py::test_internal_callers_use_websocket_message_signature -v` +Expected: should fail for events.py + gui_2.py still; pass for app_controller.py + +### Task 6a.4: Fix `src/events.py` broadcast callers + +- [ ] **Step 1: Find call sites** + +Run: `Select-String -Path src/events.py -Pattern '\.broadcast\('` + +- [ ] **Step 2: Replace each with `WebSocketMessage(...)` wrapper** + +- [ ] **Step 3: Run regression test** + +Run: `uv run pytest tests/test_websocket_broadcast_regression.py::test_internal_callers_use_websocket_message_signature -v` + +### Task 6a.5: Fix `src/gui_2.py:_process_pending_gui_tasks` broadcast callers + +- [ ] **Step 1: Find call sites** + +Run: `Select-String -Path src/gui_2.py -Pattern '\.broadcast\('` + +- [ ] **Step 2: Replace each with `WebSocketMessage(...)` wrapper** + +- [ ] **Step 3: Run regression test** + +Run: `uv run pytest tests/test_websocket_broadcast_regression.py -v` +Expected: all 4 tests pass + +### Task 6a.6: Run tier-1-unit-core FULLY per the regression protocol + +- [ ] **Step 1: Run the full tier-1-unit-core tier (no stop-on-failure)** + +Run: `uv run python scripts/run_tests_batched.py --tier tier-1-unit-core` +Expected: all PASS (the "no-TypeError" assertion catches the broadcast bug; any other regressions surface) + +### Task 6a.7: Phase 6a checkpoint + +- [ ] **Step 1: Commit** + +```bash +git add src/app_controller.py src/events.py src/gui_2.py tests/test_websocket_broadcast_regression.py +git commit -m "fix(broadcast): migrate HookServer.broadcast() callers to WebSocketMessage signature + +Phase 5 of any_type_componentization_20260621 changed +HookServer.broadcast(channel, payload) -> broadcast(message: WebSocketMessage) +but did not update internal callers in app_controller.py, events.py, gui_2.py. +This produced worker[queue_fallback] TypeError spam on the GUI thread. + +Fix: wrap each call site with WebSocketMessage(channel=, payload=). +Adds tests/test_websocket_broadcast_regression.py with a no-TypeError assertion +that code_path_audit_20260607 will reuse." +git notes add -m "Phase 6a checkpoint: broadcast() TypeError fixed; 4 regression tests added; tier-1-unit-core passes FULLY" HEAD +``` + +Update `conductor/tracks/phase2_4_5_call_site_completion_20260621/state.toml` to mark phase_6a status="completed" + checkpointsha. + +--- + +## Phase 6b: Complete `_send_grok` / `_send_minimax` / `_send_llama` OpenAICompatibleRequest Migration + +Focus: Migrate the 3 OpenAI-compatible senders in `src/ai_client.py` to construct `OpenAICompatibleRequest(messages=[ChatMessage(...)])` instead of `messages=[{"role": ..., "content": ...}]`. + +### Task 6b.1: Identify existing provider tests + +- [ ] **Step 1: Check for provider-specific test files** + +Run: `Get-ChildItem tests/test_*provider*.py 2>&1 | Select-String -Pattern 'grok|minimax|llama'` +Expected: at least one of `tests/test_grok_provider.py`, `tests/test_minimax_provider.py`, `tests/test_llama_provider.py`; if any are missing, add a smoke test (Task 6b.1b). + +- [ ] **Step 1b: (if any missing) Add smoke test** + +For each missing provider, create `tests/test__provider.py`: +```python +"""Smoke tests for the OpenAI-compatible _send_ path.""" +def test__sends_chat_message() -> None: + """Verify _send_ constructs OpenAICompatibleRequest with ChatMessage.""" + from src.ai_client import _send_ + import inspect + src = inspect.getsource(_send_) + # Old signature: messages=[{"role": ... + # New signature: messages=[ChatMessage(... + assert "ChatMessage" in src or 'messages=[ChatMessage' in src, f"_send__constructs_openai_compatible_request_with_chat_message() -> None: + """_send_ must use ChatMessage, not dict literals.""" + from src.openai_schemas import OpenAICompatibleRequest, ChatMessage + # Mock the underlying API call; just verify the shape + # (Actual call is too expensive for a unit test) + import inspect + src = inspect.getsource(_send_) + # Look for the OpenAICompatibleRequest instantiation + assert "OpenAICompatibleRequest" in src + # Look for ChatMessage usage (not legacy dict shape) + assert "ChatMessage(" in src, f"_send__constructs_normalized_response_with_usage_stats() -> None: + """_send_ must use UsageStats, not separate int fields.""" + import inspect + src = inspect.getsource(_send_) + # Look for the old kwargs (4 separate int fields) + assert "usage_input_tokens=" not in src, f"_send__provider.py -v` + +### Task 6d.5: Run ALL 11 tiers FULLY per regression protocol + +- [ ] **Step 1: Run the full batched suite** + +Run: `uv run python scripts/run_tests_batched.py` +Expected: all 11 tiers PASS (no stop-on-failure per the regression protocol) + +### Task 6d.6: Phase 6d checkpoint + +```bash +git add src/ai_client.py tests/test_grok_provider.py tests/test_minimax_provider.py tests/test_llama_provider.py +git commit -m "refactor(ai_client): migrate _send_grok/_send_minimax/_send_llama NormalizedResponse to UsageStats + +Completes the NormalizedResponse migration for the 3 OpenAI-compatible senders. +They now construct UsageStats(input_tokens=, output_tokens=, cache_read_tokens=, +cache_creation_tokens=) instead of 4 separate int fields." +git notes add -m "Phase 6d checkpoint: 3 senders use UsageStats; all 11 tiers pass FULLY" HEAD +``` + +--- + +## Verify + Archive + +### Task V.1: Run all audits + +```bash +uv run python scripts/audit_weak_types.py --strict +uv run python scripts/audit_dataclass_coverage.py --strict +uv run python scripts/generate_type_registry.py --check +``` +Expected: all exit 0 + +### Task V.2: Write end-of-track report + +Create `docs/reports/TRACK_COMPLETION_phase2_4_5_call_site_completion_20260621.md` covering: +- Executive summary (16 commits; 3 phases; the broadcast() fix; the 3 OpenAI-compatible senders migrated) +- The broadcast() TypeError bug (root cause + fix) +- The Phase 2 migration completion (3 senders now use ChatMessage + UsageStats) +- The regression protocol (run all 11 tiers FULLY; the no-TypeError assertion) +- Verification commands + results +- What's still deferred (Phase 3 + cross-phase coupling + sandbox fixes) +- Follow-up: code_path_audit_20260607 (now unblocked) + +```bash +git add docs/reports/TRACK_COMPLETION_phase2_4_5_call_site_completion_20260621.md +git commit -m "docs(reports): TRACK_COMPLETION_phase2_4_5_call_site_completion_20260621" +``` + +### Task V.3: Archive + tracks.md update + +```bash +git mv conductor/tracks/phase2_4_5_call_site_completion_20260621 conductor/tracks/archive/ +``` + +Update `conductor/tracks.md` to move the entry to "Recently Completed." + +Update `state.toml` to mark all phases completed. + +```bash +git add -A +git commit -m "conductor(archive): ship phase2_4_5_call_site_completion_20260621 to archive" +git notes add -m "TRACK COMPLETE: phase2_4_5_call_site_completion_20260621. broadcast() TypeError fixed; 3 OpenAI-compatible senders migrated to ChatMessage + UsageStats; test_websocket_broadcast_regression.py added with no-TypeError assertion. Unblocks code_path_audit_20260607." HEAD +``` + +--- + +## Self-Review + +**1. Spec coverage check:** Every section in `spec.md` maps to a task in this plan. + +| Spec section | Plan coverage | +|---|---| +| §1 Overview | Background; goal stated at top of plan | +| §2 Goals (A/A/B/C/D) | Phase 6a (A: broadcast) + Phase 6b (A: OpenAICompatibleRequest) + Phase 6d (B: NormalizedResponse) + regression protocol across all phases | +| §3 Architecture | §3.1-3.3 → Phase 6a (broadcast fix) + Phase 6b-6d (sender migration) | +| §4 Per-Phase Plan | Phase 6a (Tasks 6a.1-6a.7) + Phase 6b (Tasks 6b.1-6b.7) + Phase 6d (Tasks 6d.1-6d.6) | +| §5 Configuration | No new deps (consistent throughout) | +| §6 Testing Strategy | Each Phase has tests; regression protocol task V.5 | +| §7 Migration / Rollout | 3 phases × ~5 commits each = ~16 atomic commits | +| §8 Risks | Addressed via regression protocol + Tier 1 audit-base verification | +| §9 Out of Scope | Phase 3 + cross-phase coupling + sandbox fixes + flake: documented as deferred | +| §10 Verification Criteria | All 14 items covered in tasks V.1-V.3 + per-phase tests | + +**2. Placeholder scan:** No "TBD", "TODO", "fill in details" in actionable steps. + +**3. Type consistency:** `WebSocketMessage`, `ChatMessage`, `UsageStats`, `NormalizedResponse`, `OpenAICompatibleRequest` used consistently with the parent track's `src/openai_schemas.py` + `src/api_hooks.py`. + +**4. Ambiguity:** Step descriptions are concrete (specific file:line refs, full code blocks, exact verification commands). + +--- + +## Execution Handoff + +Plan complete and saved to `conductor/tracks/phase2_4_5_call_site_completion_20260621/plan.md`. + +**Tier 2 autonomous sandbox command:** +``` +/tier-2-auto-execute phase2_4_5_call_site_completion_20260621 +``` +(or `uv run python scripts/mma_exec.py --role tier2-autonomous --track phase2_4_5_call_site_completion_20260621`) + +**Pre-flight:** +1. Tier 2 creates `tier2/phase2_4_5_call_site_completion_20260621` branch from `master` +2. Phase 6a starts immediately (the broadcast() bug fix is the unblocker for the audit) +3. After Phase 6a lands: run `tier-1-unit-core` FULLY per the regression protocol +4. After all phases: archive + end-of-track report +5. Tier 1 reviews + merges +6. After merge: launch `code_path_audit_20260607` (the audit's pre-flight adjustments are committed; it can start) + +**Estimated runtime:** ~3 hours Tier 2 work; ~16 atomic commits; 3 phases with checkpoint commits. \ No newline at end of file diff --git a/conductor/tracks/phase2_4_5_call_site_completion_20260621/spec.md b/conductor/tracks/phase2_4_5_call_site_completion_20260621/spec.md new file mode 100644 index 00000000..50eaf546 --- /dev/null +++ b/conductor/tracks/phase2_4_5_call_site_completion_20260621/spec.md @@ -0,0 +1,206 @@ +# Track: Phase 2/4/5 Call-Site Completion (post `any_type_componentization_20260621`) + +**Status:** Active (spec approved 2026-06-21) +**Initialized:** 2026-06-21 +**Owner:** Tier 2 Tech Lead (autonomous sandbox recommended) +**Priority:** A (blocks `code_path_audit_20260607`; runtime TypeError pollutes audit instrumentation) + +--- + +## 1. Overview + +The `any_type_componentization_20260621` track shipped 48 of 89 fat-struct promotions across 6 phases but **deferred Phase 3** (41 `ProviderHistory` call sites in `src/ai_client.py`) and **left 1 runtime bug**: the Phase 5 `HookServer.broadcast()` signature change (from `(channel, payload)` → `(message: WebSocketMessage)`) was not propagated to internal callers in `src/app_controller.py` and `src/events.py`. This produces `worker[queue_fallback] error: WebSocketServer.broadcast() takes 2 positional arguments but 3 were given` spam on the GUI thread. + +**Tier 1's decision (per `docs/handoffs/PROMPT_FOR_TIER_1.md`):** **SHINK** the follow-up to **Phases 6a + 6b + 6d** only. Defer Phase 3 (`provider_state` call-site migration) to a separate track after `code_path_audit_20260607` provides runtime cost data. + +**This track does 3 things:** +1. **Phase 6a** — Fix the runtime bug: migrate `HookServer.broadcast()` callers to the new `WebSocketMessage` signature. Adds a "no-TypeError-errors-on-any-thread" regression test that `code_path_audit_20260607` will reuse. +2. **Phase 6b** — Complete the Phase 2 t2_6 deferred task: migrate `_send_grok` / `_send_minimax` / `_send_llama` to construct `OpenAICompatibleRequest(messages=[ChatMessage(...)], ...)` instead of the legacy `messages=[{"role": ..., "content": ...}]` shape. The 3 OpenAI-compatible providers are currently unprofiled and untyped at the call site. +3. **Phase 6d** — Update those 3 senders' `NormalizedResponse(text=..., usage_input_tokens=..., ...)` construction to `NormalizedResponse(text=..., usage=UsageStats(...))` (the dataclass signature change from Phase 2). + +**Phase 6c (full ProviderHistory migration in `ai_client.py`) is explicitly OUT OF SCOPE.** It gets its own track after `code_path_audit_20260607` produces per-action cost data. + +## 2. Goals (Priority Order) + +| Priority | Goal | Why | +|---|---|---| +| **A (blocker)** | Phase 6a: Fix `HookServer.broadcast()` callers; no TypeError spam | Unblocks `code_path_audit_20260607` (TypeError spam contaminates per-action timing) | +| **A (blocker)** | Phase 6b: Complete `_send_grok` / `_send_minimax` / `_send_llama` `OpenAICompatibleRequest` migration | The 3 OpenAI-compatible providers were skipped in Phase 2; they're now the only un-migrated senders | +| **B (consistency)** | Phase 6d: Update those 3 senders' `NormalizedResponse` to use `UsageStats` | Mirrors the migration done for `_send_anthropic` and the openai_compatible.py internal functions | +| **C (audit-input)** | Establish a regression protocol: after any Phase-style refactor, run the FULL `tier-1-unit-core` tier, not targeted tests | The 10 test failures in `any_type_componentization_20260621` came from running targeted tests instead of the full tier | +| **D (audit-input)** | Add a "no-TypeError-errors-on-any-thread" assertion that `code_path_audit_20260607` will reuse | The assertion catches the broadcast() regression in any future Phase-style refactor | + +### 2.1 Non-Goals (this track) + +- **NOT** migrating the 41 `__history` call sites in `src/ai_client.py` to `provider_state.get_history('anthropic')`. Phase 3 deferred to a separate track post-audit. +- **NOT** the cross-phase coupling fix (`OpenAICompatibleRequest.tools: list[dict[str, Any]]` → `list[ToolSpec]`). Deferred. +- **NOT** the `audit_tier2_leaks.py` 3 sandbox-pollution failures. The user's `tier2/` sandbox harness modifies `mcp_paths.toml` + `opencode.json` + `.opencode/*`; the audit script needs an `--allowlist` for these (separate infra track). +- **NOT** the pre-existing `test_gui2_custom_callback_hook_works` flake. Pre-existing; not introduced by this track. +- **NOT** merging the `tier2/any_type_componentization_20260621` branch. Per Tier 2's recommendation, the branch stays as reconnaissance input; this track cherry-picks only the fixes, not the full branch. + +## 3. Architecture + +### 3.1 The Bug: Phase 5's `broadcast()` signature change + +Phase 5 commit `e9fa69dd` refactored `HookServer.broadcast()`: + +```python +# BEFORE Phase 5 +def broadcast(self, channel: str, payload: dict[str, Any]) -> None: + ... + +# AFTER Phase 5 (src/api_hooks.py) +def broadcast(self, message: WebSocketMessage) -> None: + ... +``` + +**Internal callers NOT updated by Phase 5:** +- `src/app_controller.py:_run_pending_tasks_once_result` — broadcasts task results to the WebSocket pipeline per pending GUI task +- `src/events.py` — broadcasts events emitted by the `AsyncEventQueue` +- `src/gui_2.py:_process_pending_gui_tasks` — broadcasts from the GUI thread's pending-task queue + +**Fix:** Replace `broadcast("channel", payload_dict)` with `broadcast(WebSocketMessage(channel="channel", payload=payload_dict))`. + +### 3.2 The Missing Senders: 3 OpenAI-Compatible Providers + +The 3 OpenAI-compatible senders in `src/ai_client.py`: +- `_send_grok` (L2532) +- `_send_minimax` (L2616) +- `_send_llama` (L2856) + +(Plus `_send_llama_native` at L2954, which is a different code path.) + +These senders construct `OpenAICompatibleRequest(messages=[...], model=..., ...)` with the **legacy** shape: +```python +messages=[{"role": "user", "content": user_content}] +``` + +After this track: +```python +messages=[ChatMessage(role="user", content=user_content)] +``` + +And `NormalizedResponse(text=..., usage_input_tokens=..., usage_output_tokens=...)`: +```python +NormalizedResponse(text=text, tool_calls=(), usage=UsageStats(input_tokens=t_in, output_tokens=t_out), raw_response=raw) +``` + +### 3.3 The Regression Protocol + +After this track, the protocol for any Phase-style refactor is: + +1. After implementing each phase, run the FULL `tier-1-unit-core` tier (not targeted tests). Targeted tests miss call sites in helper functions / cross-file consumers. +2. After all phases complete, run `tier-1-unit-core` + `tier-1-unit-mma` + `tier-2-mock-app-core` + `tier-3-live_gui` FULLY (no stop-on-failure). +3. The "no-TypeError-errors-on-any-thread" assertion in `tests/test_websocket_broadcast_regression.py` is the canonical regression test. `code_path_audit_20260607` will reuse this assertion in its per-action profiling. + +## 4. Per-Phase Plan + +### Phase 6a: Fix `HookServer.broadcast()` Callers + +**Files:** +- Modify: `src/app_controller.py:_run_pending_tasks_once_result` +- Modify: `src/events.py` (broadcast sites) +- Modify: `src/gui_2.py:_process_pending_gui_tasks` +- Create: `tests/test_websocket_broadcast_regression.py` + +**Approach:** +1. Grep `\.broadcast\(` in `src/` to find all internal callers +2. For each: replace `broadcast(channel_str, payload_dict)` with `broadcast(WebSocketMessage(channel=channel_str, payload=payload_dict))` +3. Add regression test: simulate a GUI task that triggers broadcast and assert no TypeError in stderr + +**Why this matters for code_path_audit:** +The audit's per-action profiling assumes no TypeError spam on the GUI thread. The Phase 6a fix makes the GUI's broadcast pipeline type-safe; the audit can then measure `WebSocketMessage.__init__` overhead per broadcast without TypeError contamination. + +### Phase 6b: Complete `_send_grok` / `_send_minimax` / `_send_llama` `OpenAICompatibleRequest` Migration + +**Files:** +- Modify: `src/ai_client.py:_send_grok` (L2532) +- Modify: `src/ai_client.py:_send_minimax` (L2616) +- Modify: `src/ai_client.py:_send_llama` (L2856) +- Modify: `tests/test_grok_provider.py` if it exists +- Modify: `tests/test_minimax_provider.py` if it exists +- Modify: `tests/test_llama_provider.py` if it exists + +**Approach:** +1. In each sender, replace `messages=[{"role": "user", "content": ...}]` with `messages=[ChatMessage(role="user", content=...)]` +2. Update `OpenAICompatibleRequest` field-by-field to use `ChatMessage` everywhere +3. Run provider tests + integration tests + +### Phase 6d: Update Those Senders' `NormalizedResponse` Construction + +**Files:** Same as 6b. + +**Approach:** +1. In each sender, replace `NormalizedResponse(text=..., usage_input_tokens=X, usage_output_tokens=Y, usage_cache_read_tokens=Z, usage_cache_creation_tokens=W, raw_response=R)` with `NormalizedResponse(text=..., tool_calls=(), usage=UsageStats(input_tokens=X, output_tokens=Y, cache_read_tokens=Z, cache_creation_tokens=W), raw_response=R)` +2. Add import: `from src.openai_schemas import ChatMessage, NormalizedResponse, OpenAICompatibleRequest, UsageStats` +3. Run provider tests + integration tests + +## 5. Configuration + +No new dependencies. No new config files. + +## 6. Testing Strategy + +| Test File | Purpose | +|---|---| +| `tests/test_websocket_broadcast_regression.py` (NEW) | Verify no TypeError spam on GUI thread after broadcast() callers are fixed | +| `tests/test_grok_provider.py` (extend) | Verify `_send_grok` uses ChatMessage + UsageStats | +| `tests/test_minimax_provider.py` (extend) | Verify `_send_minimax` uses ChatMessage + UsageStats | +| `tests/test_llama_provider.py` (extend) | Verify `_send_llama` uses ChatMessage + UsageStats | + +**Verification protocol (the lesson from `any_type_componentization_20260621`):** +- After each Phase, run `uv run python scripts/run_tests_batched.py --tier tier-1-unit-core` FULLY (no stop-on-failure) +- After all Phases complete, run all 11 tiers FULLY + +## 7. Migration / Rollout + +| Phase | What | Commits | +|---|---|---| +| 6a | `HookServer.broadcast()` callers fixed; `test_websocket_broadcast_regression.py` added | ~5-7 | +| 6b | `_send_grok/minimax/llama` OpenAICompatibleRequest migration | ~3-5 | +| 6d | `_send_grok/minimax/llama` NormalizedResponse migration | ~3-4 | +| Total | | ~11-16 | + +Each phase has its own checkpoint commit and git note. + +## 8. Risks & Mitigations + +| Risk | Likelihood | Impact | Mitigation | +|---|---|---|---| +| Grep misses an internal broadcast() caller | Low | Medium | Also check `tests/` for callers; assert "no TypeError spam" on the full 11-tier run | +| `_send_grok/minimax/llama` test coverage is thin | Medium | Low | The 3 providers are exercised in `tests/test_*provider*.py`; if tests don't exist, add a smoke test | +| The "no-TypeError" assertion is too strict (false positives) | Low | Low | Wrap in `try/except queue_fallback`; assert "no broadcast() TypeError specifically" | + +## 9. Out of Scope + +- **Phase 3 (`provider_state` call-site migration).** Deferred to a separate track after `code_path_audit_20260607` provides runtime cost data. +- **Cross-phase coupling** (`OpenAICompatibleRequest.tools: list[ToolSpec]`). Deferred. +- **`audit_tier2_leaks.py` sandbox-pollution failures.** Separate infra track. +- **Pre-existing `test_gui2_custom_callback_hook_works` flake.** Separate investigation. +- **Merging `tier2/any_type_componentization_20260621` branch.** Per Tier 2's recommendation, the branch stays as reconnaissance; this track cherry-picks only the fixes. + +## 10. Verification Criteria + +- [ ] `src/app_controller.py:_run_pending_tasks_once_result` uses `broadcast(WebSocketMessage(...))` +- [ ] `src/events.py` broadcast callers use `WebSocketMessage` +- [ ] `src/gui_2.py:_process_pending_gui_tasks` broadcast callers use `WebSocketMessage` +- [ ] `tests/test_websocket_broadcast_regression.py` exists; asserts no broadcast() TypeError +- [ ] `_send_grok` constructs `OpenAICompatibleRequest(messages=[ChatMessage(...)], ...)` +- [ ] `_send_minimax` constructs `OpenAICompatibleRequest(messages=[ChatMessage(...)], ...)` +- [ ] `_send_llama` constructs `OpenAICompatibleRequest(messages=[ChatMessage(...)], ...)` +- [ ] `_send_grok` constructs `NormalizedResponse(text=..., usage=UsageStats(...), ...)` +- [ ] `_send_minimax` constructs `NormalizedResponse(text=..., usage=UsageStats(...), ...)` +- [ ] `_send_llama` constructs `NormalizedResponse(text=..., usage=UsageStats(...), ...)` +- [ ] All 11-tier batched test run passes (no stop-on-failure) +- [ ] `audit_weak_types.py --strict` exits 0 +- [ ] `audit_dataclass_coverage.py --strict` exits 0 +- [ ] End-of-track report at `docs/reports/TRACK_COMPLETION_phase2_4_5_call_site_completion_20260621.md` + +## 11. See Also + +- `docs/handoffs/PROMPT_FOR_TIER_1.md` — Tier 1 brief from Tier 2 +- `docs/handoffs/HANDOFF_FOLLOWUP_TRACK_FROM_any_type_componentization.md` — test failure categorization +- `docs/handoffs/HANDOFF_CODE_PATH_AUDIT_FROM_any_type_componentization.md` — runtime cost framing +- `conductor/tracks/any_type_componentization_20260621/spec.md` — parent track spec +- `conductor/tracks/code_path_audit_20260607/spec.md` — the audit (this track unblocks it) +- `docs/reports/PHASE3_HYPOTHETICAL_PROMOTION.md` — the Phase 3 hypothetical analysis (separate doc) \ No newline at end of file diff --git a/conductor/tracks/phase2_4_5_call_site_completion_20260621/state.toml b/conductor/tracks/phase2_4_5_call_site_completion_20260621/state.toml new file mode 100644 index 00000000..9bd4e180 --- /dev/null +++ b/conductor/tracks/phase2_4_5_call_site_completion_20260621/state.toml @@ -0,0 +1,77 @@ +# Track state for phase2_4_5_call_site_completion_20260621 +# Updated by Tier 2 Tech Lead as tasks complete + +[meta] +track_id = "phase2_4_5_call_site_completion_20260621" +name = "Phase 2/4/5 Call-Site Completion (post any_type_componentization)" +status = "active" +current_phase = 0 +last_updated = "2026-06-21" + +[blocked_by] +# No blockers; this track unblocks the audit +[] + +[blocks] +code_path_audit_20260607 = "blocked_until_merge" + +[phases] +phase_6a = { status = "pending", checkpointsha = "", name = "Fix HookServer.broadcast() callers" } +phase_6b = { status = "pending", checkpointsha = "", name = "Complete OpenAICompatibleRequest migration" } +phase_6d = { status = "pending", checkpointsha = "", name = "Update NormalizedResponse construction" } + +[tasks] +# Phase 6a: Fix HookServer.broadcast() callers +t6a_1 = { status = "pending", commit_sha = "", description = "Grep src/ for all .broadcast( callers; document the list (expect ~5-10 sites)" } +t6a_2 = { status = "pending", commit_sha = "", description = "Red: tests/test_websocket_broadcast_regression.py (verify no broadcast() TypeError on GUI thread)" } +t6a_3 = { status = "pending", commit_sha = "", description = "Fix src/app_controller.py:_run_pending_tasks_once_result broadcast callers" } +t6a_4 = { status = "pending", commit_sha = "", description = "Fix src/events.py broadcast callers" } +t6a_5 = { status = "pending", commit_sha = "", description = "Fix src/gui_2.py:_process_pending_gui_tasks broadcast callers" } +t6a_6 = { status = "pending", commit_sha = "", description = "Run tier-1-unit-core FULLY (no stop-on-failure) per regression protocol" } +t6a_7 = { status = "pending", commit_sha = "", description = "Phase 6a checkpoint commit + git note" } +# Phase 6b: OpenAICompatibleRequest migration +t6b_1 = { status = "pending", commit_sha = "", description = "Identify tests/test_grok_provider.py + test_minimax_provider.py + test_llama_provider.py; if absent, add smoke tests" } +t6b_2 = { status = "pending", commit_sha = "", description = "Red: tests for ChatMessage in OpenAICompatibleRequest construction (grok/minimax/llama senders)" } +t6b_3 = { status = "pending", commit_sha = "", description = "Migrate src/ai_client.py:_send_grok messages construction to ChatMessage" } +t6b_4 = { status = "pending", commit_sha = "", description = "Migrate src/ai_client.py:_send_minimax messages construction to ChatMessage" } +t6b_5 = { status = "pending", commit_sha = "", description = "Migrate src/ai_client.py:_send_llama messages construction to ChatMessage" } +t6b_6 = { status = "pending", commit_sha = "", description = "Run tier-1-unit-core + provider tests FULLY" } +t6b_7 = { status = "pending", commit_sha = "", description = "Phase 6b checkpoint commit + git note" } +# Phase 6d: NormalizedResponse construction +t6d_1 = { status = "pending", commit_sha = "", description = "Red: tests for UsageStats in NormalizedResponse construction (grok/minimax/llama senders)" } +t6d_2 = { status = "pending", commit_sha = "", description = "Migrate src/ai_client.py:_send_grok NormalizedResponse to use UsageStats" } +t6d_3 = { status = "pending", commit_sha = "", description = "Migrate src/ai_client.py:_send_minimax NormalizedResponse to use UsageStats" } +t6d_4 = { status = "pending", commit_sha = "", description = "Migrate src/ai_client.py:_send_llama NormalizedResponse to use UsageStats" } +t6d_5 = { status = "pending", commit_sha = "", description = "Run tier-1-unit-core + provider tests FULLY" } +t6d_6 = { status = "pending", commit_sha = "", description = "All 11 tiers FULLY (no stop-on-failure) per regression protocol" } +t6d_7 = { status = "pending", commit_sha = "", description = "Phase 6d checkpoint commit + git note" } +# Verify + archive +tv_1 = { status = "pending", commit_sha = "", description = "Run audit_weak_types.py --strict + audit_dataclass_coverage.py --strict (both exit 0)" } +tv_2 = { status = "pending", commit_sha = "", description = "Run generate_type_registry.py --check (exit 0)" } +tv_3 = { status = "pending", commit_sha = "", description = "Write docs/reports/TRACK_COMPLETION_phase2_4_5_call_site_completion_20260621.md" } +tv_4 = { status = "pending", commit_sha = "", description = "git mv to conductor/tracks/archive/" } +tv_5 = { status = "pending", commit_sha = "", description = "Update conductor/tracks.md" } + +[verification] +phase_6a_broadcast_fixed = false +phase_6a_regression_test_passes = false +phase_6b_openai_compat_migrated = false +phase_6d_normalized_response_migrated = false +full_11_tier_regression_passes = false +audit_weak_types_strict_passes = false +audit_dataclass_coverage_strict_passes = false +type_registry_check_passes = false +track_archived = false + +[broadcast_callers_to_fix] +# Filled in t6a_1 +expected_sites = 8 +files_affected = ["src/app_controller.py", "src/events.py", "src/gui_2.py"] + +[deferred_from_parent_track] +phase_3_provider_state_sites = 112 +phase_3_deferred_to = "separate track post code_path_audit_20260607" +cross_phase_coupling = "OpenAICompatibleRequest.tools: list[dict] -> list[ToolSpec]; deferred" + +[unblocks] +code_path_audit_20260607 = "Phase 6a fixes broadcast() TypeError that contaminates audit instrumentation" \ No newline at end of file diff --git a/docs/reports/PHASE3_HYPOTHETICAL_PROMOTION.md b/docs/reports/PHASE3_HYPOTHETICAL_PROMOTION.md new file mode 100644 index 00000000..dfee66bb --- /dev/null +++ b/docs/reports/PHASE3_HYPOTHETICAL_PROMOTION.md @@ -0,0 +1,243 @@ +# Phase 3 Hypothetical Promotion: `ProviderHistory` Migration Analysis + +**Date:** 2026-06-21 +**Author:** Tier 1 Orchestrator +**Status:** Hypothetical — this is the analysis the deferred Phase 3 work would look like, NOT a track spec +**Input:** `docs/handoffs/HANDOFF_CODE_PATH_AUDIT_FROM_any_type_componentization.md` (Tier 2's runtime cost framing) + `src/provider_state.py` (the dataclass already on the tier2 branch) + +--- + +## 1. Purpose + +Phase 3 (`provider_state.ProviderHistory` call-site migration in `src/ai_client.py`) was deferred from `any_type_componentization_20260621` because: +1. It's the highest-risk phase (112 call sites across 6 senders) +2. The cost depends on whether each site is in a hot path, cold path, or init path +3. `code_path_audit_20260607` is the right tool to quantify that cost before refactoring + +This document presents **what the migration would look like** — the approximate dataclasses, the call-site catalog, and a **qualitative cost estimation** of each codepath. The actual numbers will come from the audit. This document is the **what**; the audit produces the **cost**. + +## 2. The Dataclass (already exists on `tier2/any_type_componentization_20260621` branch) + +```python +# src/provider_state.py:25-44 (verbatim from branch) +@dataclass +class ProviderHistory: + messages: list[HistoryMessage] = field(default_factory=list) + lock: threading.Lock = field(default_factory=threading.Lock) + + def append(self, message: HistoryMessage) -> None: + with self.lock: + self.messages.append(message) + + def get_all(self) -> list[HistoryMessage]: + with self.lock: + return list(self.messages) + + def replace_all(self, messages: list[HistoryMessage]) -> None: + with self.lock: + self.messages = list(messages) + + def clear(self) -> None: + with self.lock: + self.messages = [] +``` + +```python +# src/provider_state.py:47-69 (verbatim from branch) +_PROVIDER_HISTORIES: dict[str, ProviderHistory] = { + "anthropic": ProviderHistory(), + "deepseek": ProviderHistory(), + "minimax": ProviderHistory(), + "qwen": ProviderHistory(), + "grok": ProviderHistory(), + "llama": ProviderHistory(), +} + +def get_history(provider: str) -> ProviderHistory: + if provider not in _PROVIDER_HISTORIES: + raise KeyError(f"Unknown provider: {provider!r}") + return _PROVIDER_HISTORIES[provider] + +def clear_all() -> None: + for h in _PROVIDER_HISTORIES.values(): + h.clear() + +def providers() -> tuple[str, ...]: + return tuple(_PROVIDER_HISTORIES.keys()) +``` + +**Properties that hold:** +- `@dataclass` (NOT `frozen=True`) — the message list and lock are mutable; this is correct. +- `default_factory=list` for `messages` — each `ProviderHistory` gets its own list. +- `default_factory=threading.Lock` for `lock` — each `ProviderHistory` gets its own lock instance. +- The 4-method interface encapsulates the lock; consumers never see it. + +**This is already on the tier2 branch.** What Phase 3 does is migrate the consumers. + +## 3. The Hypothetical Migration + +The migration replaces direct module-global access (`_anthropic_history`, `_anthropic_history_lock`) with the typed accessor (`get_history("anthropic")`). + +### 3.1 Mechanical Translation Rules + +| Current | Hypothetical (typed) | Lock needed? | +|---|---|---| +| `_anthropic_history` (read) | `get_history("anthropic").get_all()` | Yes (returns copy under lock) | +| `_anthropic_history` (write ref) | `get_history("anthropic").messages` | Only inside `with h.lock:` | +| `_anthropic_history.append(m)` | `get_history("anthropic").append(m)` | Encapsulated | +| `len(_anthropic_history)` | `len(get_history("anthropic").messages)` | No (length is atomic in CPython) | +| `for m in _anthropic_history:` | `for m in get_history("anthropic").get_all():` | Yes | +| `with _anthropic_history_lock:` | `with get_history("anthropic").lock:` | Same | +| `_anthropic_history = []` | `get_history("anthropic").clear()` | Encapsulated | + +### 3.2 Pattern Categories (per `HANDOFF_CODE_PATH_AUDIT_FROM_any_type_componentization.md` §1) + +| Category | Sites | Path role | +|---|---:|---| +| `__history.append(message)` | 6 | Hot — called per LLM turn | +| `len(__history)` / `__history[-1]` / iteration | ~40 | Hot — called per LLM turn for trimming, tool-history cache breakpoint, strip_cache_controls | +| `with __history_lock:` | ~30 | Mixed — per-turn append is Hot; `reset_session` is Cold | +| `global __history` declarations | 4 | N/A — module-level, no runtime cost | +| `_strip_cache_controls(__history)` + `_repair__history()` + `_add_history_cache_breakpoint()` + `_trim__history()` | ~30 | Hot for Anthropic (cache controls); Mixed for others | + +### 3.3 Per-Provider Site Count (measured from current `src/ai_client.py`) + +| Provider | history refs | lock refs | global decls | Total sites | +|---|---:|---:|---:|---:| +| anthropic | 22 | 2 | 1 | 25 | +| deepseek | 13 | 6 | 1 | 20 | +| minimax | 15 | 5 | 1 | 21 | +| qwen | 7 | 4 | 1 | 12 | +| grok | 7 | 6 | 0 | 13 | +| llama | 12 | 9 | 0 | 21 | +| **Total** | **76** | **32** | **4** | **112** | + +(Note: this 112 count is **higher** than the HANDOFF's "41" estimate, because the grep counts every reference including duplicates in helper functions. The migration work is the same either way — every reference gets touched — but the codepath catalog is richer.) + +## 4. The Codepath Catalog (with Qualitative Cost Estimation) + +This is the **what the audit will quantify**. Each codepath is tagged with `path_role`, `call_frequency`, and **estimated qualitative cost delta** (positive = slower, negative = faster, zero = no change). + +### 4.1 `_send_anthropic` (L1407) — **HOT per-LLM-turn** + +**Codepaths inside `_send_anthropic` (per the grep):** + +| Codepath | Path role | Per-call freq | Qualitative cost delta | +|---|---|---|---| +| `_strip_cache_controls(_anthropic_history)` | Hot (called once per send) | 1× per LLM turn | **+0.5-1μs** (one extra dict lookup `get_history("anthropic")` per call) | +| `_repair_anthropic_history(_anthropic_history)` | Hot | 1× per LLM turn | **+0.5μs** (same) | +| `_anthropic_history.append(...)` (user message) | Hot | 1× per LLM turn | **+0.5μs** (method call vs. bare `.append()`) | +| `_add_history_cache_breakpoint(_anthropic_history)` | Hot | 1× per LLM turn | **+0.5μs** (same) | +| `_trim_anthropic_history(system_blocks, _anthropic_history)` | Hot | 1× per LLM turn | **+0.5μs** (one extra dict lookup) | +| `len(_anthropic_history)` | Hot | 2-3× per LLM turn (used in token estimation) | **+0.3μs** per call (`.messages` attribute access vs. global var lookup) | +| `_estimate_prompt_tokens(system_blocks, _anthropic_history)` | Hot | 1× per LLM turn | **+1μs** (the function takes a list; we pass `h.messages` under lock or `h.get_all()`; if the latter, that's a list copy — ~5μs for a 50-message history) | +| `for m in _anthropic_history:` (inside `_strip_cache_controls`) | Hot | 1× per LLM turn (iteration over ~10-50 messages) | **+5-10μs** (list copy via `get_all()`; the bare global just iterates directly) | + +**Per-turn overhead estimate:** +8-15μs per `_send_anthropic` call. At ~50 turns per session, that's **+400-750μs per session**. Negligible vs LLM latency (typically 1-30 seconds). + +**Recommendation (subject to audit):** Migrate, but use `with h.lock:` blocks for the hot paths inside `_strip_cache_controls` and `_estimate_prompt_tokens` to avoid the list-copy overhead of `get_all()`. + +### 4.2 `_send_deepseek` (L2167) — **HOT per-LLM-turn** + +**Similar pattern to `_send_anthropic` but simpler** (no cache controls). Estimated per-turn overhead: **+3-7μs**. At 50 turns/session, **+150-350μs/session**. + +### 4.3 `_send_minimax` (L2616) — **HOT per-LLM-turn** + +**Has `_trim_minimax_history` helper (L2484).** Estimated per-turn overhead: **+3-7μs**. **+150-350μs/session**. + +### 4.4 `_send_grok` (L2532) — **HOT per-LLM-turn** + +**No `_trim` or `_repair` helpers; simpler.** Estimated per-turn overhead: **+2-5μs**. **+100-250μs/session**. + +### 4.5 `_send_qwen` (L2771) — **HOT per-LLM-turn** + +**No helpers.** Estimated per-turn overhead: **+2-5μs**. **+100-250μs/session**. + +### 4.6 `_send_llama` (L2856) — **HOT per-LLM-turn** + +**Highest lock count (9 lock refs).** Estimated per-turn overhead: **+4-8μs**. **+200-400μs/session**. + +### 4.7 `cleanup()` (L454) — **COLD per project-switch** + +**Iterates over all 6 providers, calls `clear()` on each.** Current code does `with __history_lock: __history = []` 6 times. Hypothetical: `clear_all()` (already defined on branch) iterates and calls `clear()` once per provider. + +**Per-call cost:** **-2 to -5μs** (negative — slight speedup because `clear_all()` is one function call vs. 6 inline blocks). Called once per project switch; **negligible** in absolute terms. + +### 4.8 `reset_session()` (L461) — **COLD per project-switch** + +**Calls `cleanup()` (the cold path above).** Total per-call cost: **-2 to -5μs**. + +### 4.9 Init Path — **`_PROVIDER_HISTORIES` dict construction at module load** + +**One-time cost at module import.** 6 `ProviderHistory()` instances each with `default_factory=list` + `default_factory=threading.Lock`. Total: ~10-15μs. **Negligible.** + +## 5. Total Qualitative Cost Summary + +| Codepath | Path role | Est. overhead per call | Frequency | Total per session | +|---|---|---|---|---| +| `_send_anthropic` | Hot per turn | +8-15μs | ~50 turns | +400-750μs | +| `_send_deepseek` | Hot per turn | +3-7μs | ~50 turns | +150-350μs | +| `_send_minimax` | Hot per turn | +3-7μs | ~50 turns | +150-350μs | +| `_send_grok` | Hot per turn | +2-5μs | ~50 turns | +100-250μs | +| `_send_qwen` | Hot per turn | +2-5μs | ~50 turns | +100-250μs | +| `_send_llama` | Hot per turn | +4-8μs | ~50 turns | +200-400μs | +| `cleanup()` / `reset_session()` | Cold per project switch | -2-5μs | ~1× | -2-5μs | +| Init (module load) | Once | +10-15μs | 1× | +10-15μs | +| **Total per session** | | | | **~+1.1-2.4ms** | + +**Interpretation:** Even at the upper bound (+2.4ms per session), this is **3+ orders of magnitude smaller** than the LLM latency it lives alongside. The migration is **type-safety for free** in absolute runtime terms. + +**The actual audit will quantify these estimates.** If the audit finds a >50μs delta per turn (e.g., from lock contention or `get_all()` list copies), the migration strategy changes (use `with h.lock:` blocks instead of `get_all()` to avoid copies). + +## 6. The Risks (per `HANDOFF_CODE_PATH_AUDIT_FROM_any_type_componentization.md` §1) + +| Risk | Likelihood | Impact | Mitigation | +|---|---|---|---| +| `get_history("anthropic").get_all()` copies the list per access; `_estimate_prompt_tokens` is called per turn and iterates the copy | Medium | **+5-15μs per turn** | Use `with h.lock: msg_list = h.messages` pattern in hot iteration sites | +| Lock contention: multiple `_send_` calls in parallel (rare but possible during batch sends) | Low | **+1-10μs per turn under contention** | The lock is per-provider; no cross-provider contention; benchmark will reveal | +| `getattr` lookup overhead for `get_history(...)` vs. global var | Low | **+0.5μs per access** | Could inline as a module-level constant if needed; unlikely worth the readability cost | +| The `_send_anthropic` cache-control helpers iterate the list; a copy doubles memory bandwidth | Medium | **+10-30μs per turn** if hot | Refactor to operate on `h.messages` under lock without copying | +| Forgotten call site (one of the 76 history refs missed) | Medium | **Runtime AttributeError or NameError** | Run tier-1-unit-core + tier-2-mock-app-core FULLY per the regression protocol | + +## 7. The Codepath Audit Additions (per `PROMPT_FOR_TIER_1.md` Decision 4) + +Per Tier 1's sequencing decision, the `code_path_audit_20260607` will instrument: + +| Action | Codepath | Measures | +|---|---|---| +| `provider_history_append` | `get_history(p).append(msg)` (or current `_anthropic_history.append(msg)`) | Per-turn append latency + lock acquire time | +| `websocket_broadcast` | `broadcast(WebSocketMessage(...))` (post-Phase 6a) | Per-broadcast overhead | +| `ai_message_lifecycle` (existing) | `_send_` end-to-end | Total per-turn latency delta pre/post Phase 3 | +| `discussion_save_load` (existing) | `reset_session()` + project switch | Cold-path cost | +| `gui_startup` (existing) | `_PROVIDER_HISTORIES` init | One-time cost | + +## 8. Recommendation (subject to audit results) + +**If the audit confirms the qualitative estimates** (+1-2ms per session; <50μs per turn): +- Proceed with Phase 3 migration as planned (~10-15 commits). +- Use `with h.lock:` blocks for hot iteration sites (`_strip_cache_controls`, `_estimate_prompt_tokens`) to avoid `get_all()` copies. +- Run the 11-tier regression protocol per the follow-up track. + +**If the audit reveals a >50μs per-turn delta** (e.g., lock contention >10μs): +- Reconsider: do we even need to migrate the history aspect? It's `list[Metadata]` already typed. +- Alternative: keep the module globals but rename them with a `_HISTORY` suffix and document the pattern; defer full ProviderHistory migration. + +**The audit decides.** This analysis is the input to the audit, not the conclusion. + +## 9. Open Questions + +1. **Should the `ProviderHistory.messages` be `list[HistoryMessage]` or `list[dict[str, Any]]`?** Currently it's `list[HistoryMessage]` (= `list[Metadata]`). The legacy code uses `list[Metadata]` everywhere. The dataclass stays consistent with the type alias. +2. **Should we add a `__len__` method to `ProviderHistory` to avoid `len(h.messages)`?** + - Pros: cleaner consumer code + - Cons: minor; only saves attribute access +3. **Should `_PROVIDER_HISTORIES` be a `MappingProxyType` (read-only) for external code?** Currently it's a regular dict; external code could mutate `_PROVIDER_HISTORIES["anthropic"] = ProviderHistory()`. Probably not worth the indirection. +4. **Should `get_history(p)` validate `p` (raise on unknown)?** Currently it raises `KeyError`. Could be `Literal["anthropic", "deepseek", ...]` for static type checking. + +## 10. See Also + +- `docs/handoffs/HANDOFF_CODE_PATH_AUDIT_FROM_any_type_componentization.md` — the original runtime cost framing +- `docs/handoffs/PROMPT_FOR_TIER_1.md` — Tier 1's decision points +- `src/provider_state.py` — the actual dataclass (already on `tier2/any_type_componentization_20260621` branch) +- `conductor/tracks/any_type_componentization_20260621/spec.md` — parent track spec +- `conductor/tracks/code_path_audit_20260607/spec.md` — the audit that will quantify these estimates +- `conductor/tracks/phase2_4_5_call_site_completion_20260621/spec.md` — the follow-up track that unblocks the audit \ No newline at end of file From 6275c860bfb152e5198576c47b592b29917c52f5 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sun, 21 Jun 2026 18:55:54 -0400 Subject: [PATCH 2/2] conductor(spec+plan): add Phase 6e to follow-up - Tier 2 authoritative Phase 3 cost deduction The follow-up track now includes Phase 6e: Tier 2 produces the authoritative Phase 3 cost analysis as part of the follow-up work. Tier 2 is in src/ai_client.py doing Phase 6b/6d anyway; they have full context to produce the refined cost hypothesis that Tier 1's draft at PHASE3_HYPOTHETICAL_PROMOTION.md could not (Tier 1 worked without the 6b/6d ground-truth context). Tier 1's draft STAYS as the hypothesis doc. Tier 2's PHASE3_TIER2_ANALYSIS.md is the refined version (per-sender cost summary + hidden call sites table + recommendations for the future Phase 3 track + cross-reference to Tier 1 explicit). Phase 6e tasks (5 total, ~2 commits): - t6e_1: Profile the 6 senders (codepath catalog + hidden cross-refs) - t6e_2: Qualitative cost estimation per sender - t6e_3: Identify hot iteration sites needing 'with h.lock:' pattern - t6e_4: Author PHASE3_TIER2_ANALYSIS.md - t6e_5: Phase 6e checkpoint commit + git note Total estimated commits: 16 -> 18 (still within Tier 2 1-4 hour budget). Files updated: - conductor/tracks/phase2_4_5_call_site_completion_20260621/spec.md (+50 lines) - conductor/tracks/phase2_4_5_call_site_completion_20260621/plan.md (+146 lines) - conductor/tracks/phase2_4_5_call_site_completion_20260621/metadata.json (+13 lines) - conductor/tracks/phase2_4_5_call_site_completion_20260621/state.toml (+9 lines) - conductor/tracks.md (track 27 entry expanded with Phase 6e details) --- conductor/tracks.md | 2 +- .../metadata.json | 13 +- .../plan.md | 146 +++++++++++++++++- .../spec.md | 50 ++++++ .../state.toml | 9 +- 5 files changed, 213 insertions(+), 7 deletions(-) diff --git a/conductor/tracks.md b/conductor/tracks.md index 80f238b4..d2236278 100644 --- a/conductor/tracks.md +++ b/conductor/tracks.md @@ -63,7 +63,7 @@ Tracks that are unblocked and ready to start. Ordered by **dependency** (blocked | 21 | A | [Conductor Chronology (chronology.md canonical index)](#track-conductor-chronology) | spec ✓, plan ✓, 10/10 phases implemented; Phase 10 (user sign-off) pending; end-of-track report at `docs/reports/TRACK_COMPLETION_chronology_20260619.md` | (none — independent; **NEW 2026-06-19**; canonical-track infrastructure; the `superpowers_review_20260619` track is `blocked_by` this one) | | 22b | A (meta-tooling) | [Meta-Tooling Workflow Review — Past-Month LLM Behavior Analysis](#track-meta-tooling-workflow-review-past-month-llm-behavior-analysis) | spec ✓, plan ✓, metadata ✓, state ✓, **parked 2026-06-20** (current_phase=0); 11-phase plan; ≥4,000-LOC 4-part report; 13-15 atomic commits; Tier 1 anchor + 3 Tier 3 parallel sweeps | (none — independent; **NEW 2026-06-20**; sibling to nagent_review + fable_review + superpowers_review + intent_dsl_survey; produces workflow_improvements.md + implementation_sequencing.md as standalone inputs for a near-future "workflow improvements rebuild" track; research-only; no src/, tests/, AGENTS.md, conductor/*.md, .opencode/, or scripts/audit_*.py changes; **anti-sliming guard**: Phase 9 self-review + Phase 10 user review gate are literal hard gates per the chronology_20260619 handover) | | 26 | A (research) | [Video Analysis Campaign (12 videos, 5 clusters, Pass 1 of 3)](#track-video-analysis-campaign-20260621) | spec ✓, plan ✓, **14 folders scaffolded (1 umbrella + 12 children + 1 synthesis); Pass 1 of 3 (information extraction); awaiting Phase 0 tooling prerequisites (yt-dlp, cv2, imagehash install in repo venv)**; 12 children in execution order: CS229 → math foundations → Platonic/geometric → biological → CS336 → applied capstone; per-video target: 1000-10000 LOC markdown deep-dive report | (none — independent; **NEW 2026-06-21**; multi-track research campaign; 12 videos across 5 clusters (E: Stanford >1hr; A: math foundations; B: Platonic AI; C: biological/cognitive; D: applied); multi-pass handoff to Pass 2 (de-obfuscation via user's math encoding — USER must rediscover notation before Pass 2 starts) + Pass 3 (projection to applied domain — USER must articulate "own caveats" before Pass 3 starts); **lossless preservation directive**: Pass 1 artifacts must NOT be over-summarized (data cascades to Pass 2/3); **2 E-cluster videos failed oEmbed 401** (yt-dlp may still work; verify in Phase 1); reusable tooling: 5 TDD scripts in `scripts/video_analysis/` (download_video, extract_transcript, extract_keyframes, ocr_frames, synthesize_report) | -| 27 | A | [Phase 2/4/5 Call-Site Completion (post any_type_componentization)](#track-phase2-4-5-call-site-completion-20260621) | spec ✓, plan pending; **Tier 1 decided SHINK scope** to Phase 6a + 6b + 6d (~16 commits, ~3 hours Tier 2); **BLOCKER for `code_path_audit_20260607`** (the broadcast() TypeError contaminates audit instrumentation); see `docs/handoffs/PROMPT_FOR_TIER_1.md` | any_type_componentization_20260621 (parent; shipped 2026-06-21 with 48/89 sites + 1 runtime bug) | (**NEW 2026-06-21**; bugfix + refactor + test-infrastructure; Phase 6a: fix `HookServer.broadcast()` callers in `src/app_controller.py` + `src/events.py` + `src/gui_2.py` (5-10 sites) — migrate to `WebSocketMessage` signature; Phase 6b: complete `_send_grok` + `_send_minimax` + `_send_llama` `OpenAICompatibleRequest` migration (3 sites); Phase 6d: update those 3 senders' `NormalizedResponse` to use `UsageStats` (3 sites); adds `tests/test_websocket_broadcast_regression.py` with "no-TypeError" assertion that the audit will reuse; **deferred**: Phase 3 (`provider_state.ProviderHistory` call-site migration in `ai_client.py` — 112 sites) → separate track post-audit; cross-phase coupling → separate track; `audit_tier2_leaks.py` sandbox-pollution fixes → infra track; pre-existing `test_gui2_custom_callback_hook_works` flake → separate investigation; **does NOT merge `tier2/any_type_componentization_20260621` branch** per Tier 2's reconnaissance framing; analysis input for Phase 3 cost estimation: `docs/reports/PHASE3_HYPOTHETICAL_PROMOTION.md`) | +| 27 | A | [Phase 2/4/5 Call-Site Completion (post any_type_componentization)](#track-phase2-4-5-call-site-completion-20260621) | spec ✓, plan ✓, metadata ✓, state ✓; **Tier 1 decided SHINK scope** to Phase 6a + 6b + 6d + 6e (~18 commits, ~3 hours Tier 2); **BLOCKER for `code_path_audit_20260607`** (the broadcast() TypeError contaminates audit instrumentation); see `docs/handoffs/PROMPT_FOR_TIER_1.md` | any_type_componentization_20260621 (parent; shipped 2026-06-21 with 48/89 sites + 1 runtime bug) | (**NEW 2026-06-21**; bugfix + refactor + test-infrastructure + Tier 2 cost analysis; Phase 6a: fix `HookServer.broadcast()` callers in `src/app_controller.py` + `src/events.py` + `src/gui_2.py` (5-10 sites) — migrate to `WebSocketMessage` signature; Phase 6b: complete `_send_grok` + `_send_minimax` + `_send_llama` `OpenAICompatibleRequest` migration (3 sites); Phase 6d: update those 3 senders' `NormalizedResponse` to use `UsageStats` (3 sites); **Phase 6e: Tier 2 produces `docs/reports/PHASE3_TIER2_ANALYSIS.md` (authoritative Phase 3 cost hypothesis; supersedes Tier 1's draft at `PHASE3_HYPOTHETICAL_PROMOTION.md` which stays as the placeholder; profiles all 6 senders + discovers hidden cross-references + provides refined cost estimates + recommendations for the future Phase 3 track)**; adds `tests/test_websocket_broadcast_regression.py` with "no-TypeError" assertion that the audit will reuse; **deferred**: Phase 3 (`provider_state.ProviderHistory` call-site migration in `ai_client.py` — 112 sites) → separate track post-audit; cross-phase coupling → separate track; `audit_tier2_leaks.py` sandbox-pollution fixes → infra track; pre-existing `test_gui2_custom_callback_hook_works` flake → separate investigation; **does NOT merge `tier2/any_type_componentization_20260621` branch** per Tier 2's reconnaissance framing; **Tier 2 owns the Phase 3 cost analysis (Tier 1's draft at `docs/reports/PHASE3_HYPOTHETICAL_PROMOTION.md` is the hypothesis; Tier 2's `PHASE3_TIER2_ANALYSIS.md` is the refined authoritative version)**) | | 28 | A | [Any-Type Componentization (Promote dict[str, Any] to dataclass(frozen=True))](#track-any-type-componentization-promote-dictstr-any-to-dataclassfrozentrue) | spec ✓, plan ✓, metadata ✓, state ✓, **shipped 2026-06-21** with 48/89 fat-struct sites promoted (Phases 1, 2, 4, 5 complete); Phase 3 (`provider_state` call-site migration in `ai_client.py`) DEFERRED to a separate track; 1 runtime bug surfaced (`HookServer.broadcast()` callers in `app_controller.py` + `events.py`); not merged; reconnaissance for `code_path_audit_20260607`; tier2 branch at 24 commits | (none — independent; **NEW 2026-06-21**; refactor + ai-readability + type-safety; ships: 3 new modules (`src/mcp_tool_specs.py`, `src/openai_schemas.py`, `src/provider_state.py`); 2 new audit scripts (`scripts/audit_dataclass_coverage.py` + `--strict` mode); styleguide `conductor/code_styleguides/type_aliases.md` §12 "When to Promote TypeAlias to dataclass"; type-registry regenerated; 130+ tests pass; **input artifact**: `docs/reports/ANY_TYPE_AUDIT_20260621.md`; **handoff docs**: `docs/handoffs/PROMPT_FOR_TIER_1.md` + `HANDOFF_FOLLOWUP_TRACK_FROM_any_type_componentization.md` + `HANDOFF_CODE_PATH_AUDIT_FROM_any_type_componentization.md`) | **Note on numbering:** the legacy file used `0a`, `0b`, `0c`... and `0d`, `0e`, `0f`, `0g` for tracks created 2026-06-06+. This is the **git-blame sort order**, not a logical execution order. The new structure re-orders by dependency. diff --git a/conductor/tracks/phase2_4_5_call_site_completion_20260621/metadata.json b/conductor/tracks/phase2_4_5_call_site_completion_20260621/metadata.json index 8f56e63f..f283e3a6 100644 --- a/conductor/tracks/phase2_4_5_call_site_completion_20260621/metadata.json +++ b/conductor/tracks/phase2_4_5_call_site_completion_20260621/metadata.json @@ -24,10 +24,10 @@ }, "blocked_by": [], "blocks": ["code_path_audit_20260607"], - "estimated_phases": 3, + "estimated_phases": 4, "spec": "spec.md", "plan": "plan.md", - "priority_order": "A (Phase 6a broadcast fix) > A (Phase 6b OpenAICompatibleRequest) > B (Phase 6d NormalizedResponse) > C (regression protocol)", + "priority_order": "A (Phase 6a broadcast fix) > A (Phase 6b OpenAICompatibleRequest) > B (Phase 6d NormalizedResponse) > A (Phase 6e Tier 2 cost deduction)", "parent_track": { "id": "any_type_componentization_20260621", "spec": "conductor/tracks/any_type_componentization_20260621/spec.md", @@ -53,9 +53,16 @@ "name": "Update NormalizedResponse construction", "scope": "Same 3 senders: usage_input_tokens/etc -> usage=UsageStats(...)", "estimated_commits": 4 + }, + "phase_6e": { + "name": "Phase 3 Hypothetical Cost Deduction (Tier 2 authoritative deliverable)", + "scope": "Tier 2 produces docs/reports/PHASE3_TIER2_ANALYSIS.md while doing 6b/6d work in src/ai_client.py; profiles all 6 senders + discovers hidden cross-references + provides refined cost estimates + recommendations for the future Phase 3 track. Supersedes Tier 1's draft at docs/reports/PHASE3_HYPOTHETICAL_PROMOTION.md (which stays as the hypothesis doc).", + "estimated_commits": 2, + "new_doc_file": "docs/reports/PHASE3_TIER2_ANALYSIS.md", + "rationale": "Tier 2 is in src/ai_client.py anyway doing the 6b/6d migration work; they have full context to produce the authoritative Phase 3 cost analysis. The future Phase 3 track + the code_path_audit both need this data." } }, - "total_estimated_commits": 16, + "total_estimated_commits": 18, "deferred_work": { "phase_3_provider_state": { "deferred_to": "separate track post code_path_audit_20260607", diff --git a/conductor/tracks/phase2_4_5_call_site_completion_20260621/plan.md b/conductor/tracks/phase2_4_5_call_site_completion_20260621/plan.md index 851f34da..329b76ad 100644 --- a/conductor/tracks/phase2_4_5_call_site_completion_20260621/plan.md +++ b/conductor/tracks/phase2_4_5_call_site_completion_20260621/plan.md @@ -417,9 +417,151 @@ git notes add -m "Phase 6d checkpoint: 3 senders use UsageStats; all 11 tiers pa --- -## Verify + Archive +## Phase 6e: Phase 3 Hypothetical Cost Deduction (Tier 2 authoritative deliverable) -### Task V.1: Run all audits +Focus: While doing Phase 6b/6d work in `src/ai_client.py`, Tier 2 is reading and modifying the 3 senders anyway. They have the context to produce the authoritative Phase 3 cost analysis (deferred from `any_type_componentization_20260621`). This phase is the **Tier 2 deliverable** that supersedes Tier 1's hypothesis at `docs/reports/PHASE3_HYPOTHETICAL_PROMOTION.md`. + +**Tier 1's hypothesis** stays as the placeholder; Tier 2's `PHASE3_TIER2_ANALYSIS.md` is the refined version with in-context, post-Phase-6b/6d-grounded estimates. + +### Task 6e.1: Profile the 6 senders (during Phase 6b/6d work) + +**No new code; pure analysis.** While doing Tasks 6b.3-6b.5 (migrating `_send_grok` / `_send_minimax` / `_send_llama`) and Tasks 6d.2-6d.4 (updating their `NormalizedResponse`), Tier 2 reads the surrounding code and documents: + +For each of the 6 senders, capture in working notes: +- All `_anthropic_history` / `_anthropic_history_lock` references (categorized: append, len/iteration, lock-acquire, with-lock-block, global-decl, helper-call) +- Helper function call sites (`_repair__history`, `_trim__history`, `_strip_cache_controls`, `_add_history_cache_breakpoint`) +- **Hidden call sites** Tier 2 discovers that Tier 1's grep missed (e.g., `_repair_anthropic_history` is called from `_send_anthropic` AND from `cleanup()` — that's a hidden cross-reference Tier 1's grep didn't see) + +For the 3 senders NOT touched by 6b/6d (`_send_anthropic`, `_send_deepseek`, `_send_qwen`): +- Same profiling +- Tier 2 reads these while doing the 6b/6d work for context (they share helper patterns) + +### Task 6e.2: Qualitative cost estimation per sender + +For each of the 6 senders, for each codepath category: + +| Category | Current (dict globals) | Proposed (ProviderHistory dataclass) | Per-call delta | +|---|---|---|---| +| `__history.append(m)` | dict.append (~100ns) | dataclass method + lock acquire (~300ns) | **+200ns per call** | +| `len(__history)` | direct attribute (~50ns) | `.messages` attribute (~100ns) | **+50ns per call** | +| `for m in __history:` | direct iteration | `h.get_all()` (list copy) OR `with h.lock:` | **+5-10μs per call** (if `get_all()`) | +| `with __history_lock:` | direct lock | `with h.lock:` | **~0** (same lock) | +| `_global __history` (in cleanup) | N/A (declaration) | N/A (removed) | **N/A** | + +For each sender, sum the per-turn overhead: +- `_send_anthropic` (25 sites; per-turn): estimate total overhead per LLM turn +- `_send_deepseek` (20 sites; per-turn): estimate +- ... etc for all 6 + +### Task 6e.3: Identify the hot iteration sites that need `with h.lock:` pattern + +**Critical:** the `_strip_cache_controls(_anthropic_history)` and `_estimate_prompt_tokens(...)` callsites iterate the list per LLM turn. If the migration uses `h.get_all()`, they pay a list-copy cost (~5-10μs per call). + +Document each iteration site with: +- File:line +- Call frequency per LLM turn +- Recommended pattern: `with h.lock: msg_list = h.messages` vs `h.get_all()` +- Justification + +### Task 6e.4: Author `docs/reports/PHASE3_TIER2_ANALYSIS.md` + +**Files:** +- Create: `docs/reports/PHASE3_TIER2_ANALYSIS.md` + +Structure (Tier 2 produces this from the analysis in 6e.1-6e.3): + +```markdown +# Phase 3 Hypothetical Cost Analysis (Tier 2 authoritative version) + +**Author:** Tier 2 Tech Lead (autonomous sandbox) +**Date:** 2026-06-21 +**Context:** Produced during `phase2_4_5_call_site_completion_20260621` Phase 6e (after Phase 6b/6d work in `src/ai_client.py`). +**Supersedes:** Tier 1's hypothesis at `docs/reports/PHASE3_HYPOTHETICAL_PROMOTION.md` (kept as the hypothesis doc; this is the refined version). + +--- + +## 1. Methodology + +Tier 2 profiled the 6 senders in `src/ai_client.py` (`_send_anthropic`, `_send_deepseek`, `_send_minimax`, `_send_grok`, `_send_qwen`, `_send_llama`) while doing the Phase 6b/6d migration work. This analysis is grounded in actual code reading + Phase 6b/6d context. + +## 2. Per-Sender Codepath Catalog + +### 2.1 `_send_anthropic` (25 sites) +[Fill in from 6e.1 working notes] +- Direct sites: 22 `_anthropic_history` refs; 2 `_anthropic_history_lock` refs; 1 `global` decl +- Helper sites: `_strip_cache_controls`, `_repair_anthropic_history`, `_add_history_cache_breakpoint`, `_trim_anthropic_history` +- Hidden cross-references (Tier 2 found): [list any] + +### 2.2-2.6 [other senders; same structure] + +## 3. Qualitative Cost Estimation + +### 3.1 Per-call cost categories +[Fill in from 6e.2 table] + +### 3.2 Per-sender per-turn overhead +[Fill in from 6e.2 sum] + +### 3.3 Hot iteration sites (the `with h.lock:` pattern) +[Fill in from 6e.3] + +## 4. Comparison vs Tier 1's Hypothesis + +| Sender | Tier 1 hypothesis (μs/turn) | Tier 2 refined (μs/turn) | Delta | +|---|---|---|---| +| anthropic | +8-15 | [Tier 2 actual] | [reason] | +| deepseek | +3-7 | [Tier 2 actual] | [reason] | +| minimax | +3-7 | [Tier 2 actual] | [reason] | +| grok | +2-5 | [Tier 2 actual] | [reason] | +| qwen | +2-5 | [Tier 2 actual] | [reason] | +| llama | +4-8 | [Tier 2 actual] | [reason] | +| **Total** | **~+1.1-2.4ms/session** | [Tier 2 actual] | [reason] | + +## 5. Recommendations for Future Phase 3 Track + +1. **Anthropic first** (highest ROI; per-turn; cache controls) +2. **Use `with h.lock: msg_list = h.messages` pattern for hot iteration sites** (avoids `get_all()` list-copy cost) +3. **Simpler providers (qwen, grok) can use `get_all()`** since iteration is less frequent +4. **Lock semantics unchanged** — `ProviderHistory.lock` is per-instance; no cross-provider contention +5. **Hidden cross-references** discovered during this analysis [list] should be the first sites to migrate + +## 6. Open Questions + +[Fill in any unresolved questions; defer to the audit for runtime quantification] + +## 7. See Also + +- `docs/reports/PHASE3_HYPOTHETICAL_PROMOTION.md` — Tier 1's hypothesis (the "what we thought before Tier 2 looked") +- `conductor/tracks/phase2_4_5_call_site_completion_20260621/spec.md` — Phase 6e directives +- `conductor/tracks/code_path_audit_20260607/spec.md` — the audit that quantifies these estimates +- `docs/handoffs/PROMPT_FOR_TIER_1.md` — Tier 1 brief +``` + +### Task 6e.5: Phase 6e checkpoint + +- [ ] **Step 1: Commit the analysis** + +```bash +git add docs/reports/PHASE3_TIER2_ANALYSIS.md +git commit -m "docs(analysis): PHASE3_TIER2_ANALYSIS - authoritative Phase 3 cost hypothesis + +Tier 2 produced this analysis during phase2_4_5_call_site_completion_20260621 +Phase 6e. Supersedes Tier 1's draft at PHASE3_HYPOTHETICAL_PROMOTION.md (kept +as the hypothesis doc; this is the refined version with in-context data +from Phase 6b/6d work in src/ai_client.py). + +Covers all 6 senders (anthropic, deepseek, minimax, grok, qwen, llama) +with per-site cost estimates + hidden cross-references + recommendations +for the future Phase 3 track. The audit (code_path_audit_20260607) +quantifies these estimates after merge." +git notes add -m "Phase 6e checkpoint: Tier 2 authoritative Phase 3 cost analysis committed" HEAD +``` + +Update `state.toml` to mark phase_6e status="completed" + checkpointsha. + +--- + +## Verify + Archive ```bash uv run python scripts/audit_weak_types.py --strict diff --git a/conductor/tracks/phase2_4_5_call_site_completion_20260621/spec.md b/conductor/tracks/phase2_4_5_call_site_completion_20260621/spec.md index 50eaf546..27de451d 100644 --- a/conductor/tracks/phase2_4_5_call_site_completion_20260621/spec.md +++ b/conductor/tracks/phase2_4_5_call_site_completion_20260621/spec.md @@ -135,6 +135,56 @@ The audit's per-action profiling assumes no TypeError spam on the GUI thread. Th 2. Add import: `from src.openai_schemas import ChatMessage, NormalizedResponse, OpenAICompatibleRequest, UsageStats` 3. Run provider tests + integration tests +### Phase 6e: Phase 3 Hypothetical Cost Deduction (Tier 2 deliverable) + +**Goal:** Produce the authoritative Phase 3 hypothetical cost analysis as a Tier 2 deliverable. The deferred Phase 3 (`provider_state.ProviderHistory` call-site migration in `src/ai_client.py`) needs runtime cost data BEFORE the migration; Tier 2 produces this analysis as part of the follow-up track because they're already in `src/ai_client.py` doing the Phase 6b/6d work and have full context. + +**Tier 1's draft** at `docs/reports/PHASE3_HYPOTHETICAL_PROMOTION.md` stays as the hypothesis document (Tier 1's qualitative estimates). **Tier 2's authoritative analysis** is a separate document at `docs/reports/PHASE3_TIER2_ANALYSIS.md` that supersedes the hypothesis with in-context, post-Phase-6b/6d-grounded estimates. + +**Files:** +- Create: `docs/reports/PHASE3_TIER2_ANALYSIS.md` +- Modify: `conductor/tracks/phase2_4_5_call_site_completion_20260621/spec.md` (this section) + +**Approach:** +1. **For each of the 6 senders** (Tier 2 reads while doing 6b/6d work; cost analysis happens during 6b/6d + a final consolidation commit at end of 6e): + - `_send_anthropic` (25 sites; Hot per-turn; uses cache-control helpers) + - `_send_deepseek` (20 sites; Hot per-turn; has `_repair_deepseek_history` helper) + - `_send_minimax` (21 sites; Hot per-turn; has `_repair_minimax_history` + `_trim_minimax_history` helpers) + - `_send_grok` (13 sites; Hot per-turn; **being touched in 6b/6d**) + - `_send_qwen` (12 sites; Hot per-turn; simpler pattern) + - `_send_llama` (21 sites; Hot per-turn; highest lock count; **being touched in 6b/6d**) +2. **For each sender, document:** + - Direct `_anthropic_history` / `_anthropic_history_lock` sites (categorized as: append, len/iteration, lock-acquire, with-lock-block, global-decl, helper-call) + - Helper function call sites (`_repair__history`, `_trim__history`, `_strip_cache_controls`, `_add_history_cache_breakpoint`) + - Hidden call sites discovered while doing the 6b/6d work (e.g., `_repair_anthropic_history` is called from `_send_anthropic` AND from `cleanup()` — that's a hidden cross-reference) +3. **For each category, qualitatively estimate:** + - Per-call cost delta: `dict append` (current) vs `dataclass.append` (proposed) + - Lock acquire cost: `threading.Lock` (current) vs `ProviderHistory.lock` (proposed) — should be ~identical but document any surprises + - `get_all()` list-copy cost: bounded by history length (~10-50 messages); estimate ~5μs per copy + - **Critical:** the `_strip_cache_controls(_anthropic_history)` and `_estimate_prompt_tokens(...)` callsites iterate the list; if `get_all()` is used, they copy the list per call. Recommendation: use `with h.lock: msg_list = h.messages` pattern instead of `h.get_all()` for hot iteration sites +4. **Author `docs/reports/PHASE3_TIER2_ANALYSIS.md`:** + - Per-sender cost summary table (compare Tier 1's hypothesis vs Tier 2's refined estimate) + - Hidden call sites table (call sites Tier 2 discovered that Tier 1's grep missed) + - Recommendations for the future Phase 3 track: + - Use `with h.lock:` blocks for hot iteration sites + - The Anthropic cache-control helpers are the highest-value target (~25 sites, per-turn) + - The simpler providers (qwen, grok) can use `get_all()` since iteration is less frequent + - Cross-references Tier 1's hypothesis explicitly: "Tier 1's draft is the hypothesis; this is the refined version after Phase 6b/6d context." + - Roll-up: total estimated cost per session (~50 turns) for the Phase 3 migration; comparison vs Tier 1's hypothesis + +**Why this matters:** +- The future Phase 3 track needs this data to scope its phases correctly (e.g., "do the Anthropic helpers first because they're hot; defer the simpler providers to Phase 2") +- The audit will quantify these estimates after the merge; this is the pre-audit hypothesis refinement +- Tier 2 is the right entity to produce this because they have the actual code context after Phase 6b/6d + +**Verification:** +- `docs/reports/PHASE3_TIER2_ANALYSIS.md` committed +- All 6 senders profiled +- Total estimated cost per session documented +- Hidden call sites table documented +- Recommendations for future Phase 3 track documented +- Cross-reference to Tier 1's hypothesis explicit + ## 5. Configuration No new dependencies. No new config files. diff --git a/conductor/tracks/phase2_4_5_call_site_completion_20260621/state.toml b/conductor/tracks/phase2_4_5_call_site_completion_20260621/state.toml index 9bd4e180..e9004cab 100644 --- a/conductor/tracks/phase2_4_5_call_site_completion_20260621/state.toml +++ b/conductor/tracks/phase2_4_5_call_site_completion_20260621/state.toml @@ -10,7 +10,6 @@ last_updated = "2026-06-21" [blocked_by] # No blockers; this track unblocks the audit -[] [blocks] code_path_audit_20260607 = "blocked_until_merge" @@ -19,6 +18,7 @@ code_path_audit_20260607 = "blocked_until_merge" phase_6a = { status = "pending", checkpointsha = "", name = "Fix HookServer.broadcast() callers" } phase_6b = { status = "pending", checkpointsha = "", name = "Complete OpenAICompatibleRequest migration" } phase_6d = { status = "pending", checkpointsha = "", name = "Update NormalizedResponse construction" } +phase_6e = { status = "pending", checkpointsha = "", name = "Phase 3 Hypothetical Cost Deduction (Tier 2 authoritative deliverable)" } [tasks] # Phase 6a: Fix HookServer.broadcast() callers @@ -51,12 +51,19 @@ tv_2 = { status = "pending", commit_sha = "", description = "Run generate_type_r tv_3 = { status = "pending", commit_sha = "", description = "Write docs/reports/TRACK_COMPLETION_phase2_4_5_call_site_completion_20260621.md" } tv_4 = { status = "pending", commit_sha = "", description = "git mv to conductor/tracks/archive/" } tv_5 = { status = "pending", commit_sha = "", description = "Update conductor/tracks.md" } +# Phase 6e: Phase 3 Hypothetical Cost Deduction +t6e_1 = { status = "pending", commit_sha = "", description = "Profile the 6 senders (during 6b/6d work): codepath catalog + helper call sites + hidden cross-references Tier 1's grep missed" } +t6e_2 = { status = "pending", commit_sha = "", description = "Qualitative cost estimation per sender (per-call categories: append / len / iteration / lock-acquire / with-lock / global-decl / helper-call)" } +t6e_3 = { status = "pending", commit_sha = "", description = "Identify hot iteration sites that need 'with h.lock: msg_list = h.messages' pattern vs h.get_all() (avoids list-copy cost)" } +t6e_4 = { status = "pending", commit_sha = "", description = "Author docs/reports/PHASE3_TIER2_ANALYSIS.md (per-sender cost summary + hidden call sites table + recommendations + comparison vs Tier 1 hypothesis + cross-reference to Tier 1 draft)" } +t6e_5 = { status = "pending", commit_sha = "", description = "Phase 6e checkpoint commit + git note" } [verification] phase_6a_broadcast_fixed = false phase_6a_regression_test_passes = false phase_6b_openai_compat_migrated = false phase_6d_normalized_response_migrated = false +phase_6e_tier2_analysis_committed = false full_11_tier_regression_passes = false audit_weak_types_strict_passes = false audit_dataclass_coverage_strict_passes = false