Side concerns for Phase 2:
1. PROVIDERS: src/models.py:56 now includes 'qwen' alongside the existing
5 vendors. The other 4 references to PROVIDERS in src/gui_2.py and
src/app_controller.py import from this centralized list, so this
one edit propagates everywhere. State task t2.8 was scoped to
'gui_2.py and app_controller.py' but the actual change is at the
centralized registry, per the project's single-source-of-truth
pattern (per src/models.py module docstring and the Phase 5 audit
script audit_no_models_config_io.py which enforces that PROVIDERS
lives in models.py).
2. cost_tracker.py: added 7 regex pricing entries for the Qwen models
shipped in Phase 1's vendor_capabilities.py:
- qwen-turbo: 0.05 / 0.10
- qwen-plus: 0.40 / 1.20
- qwen-max: 2.00 / 6.00
- qwen-long: 0.07 / 0.28
- qwen-vl-plus: 0.21 / 0.63
- qwen-vl-max: 0.50 / 1.50
- qwen-audio: 0.10 / 0.30
(all per 1M tokens, USD; matches the structure of existing entries)
Spot check: estimate_cost('qwen-max', 1000, 500) = 0.005 (= 0.002 + 0.003)
3. SKIPPED t2.7 (credentials template): no credentials_template.toml
exists in the project. The only credentials file is the active
credentials.toml which the user maintains directly with their own
API keys. The plan's assumption of a template file does not match
the project's actual structure. Documented in the commit log
rather than modifying the user's actual credentials.toml with a
placeholder key (which would be inconsistent with the rest of
that file's pattern of real keys). When the user obtains a
DashScope API key, they can add a [qwen] section directly.
4. t2.9 (Qwen models in capability registry) was completed in Phase 1's
initial population of 22 entries (commit 6be04bc). The 8 qwen
entries (1 wildcard + 7 specific models) are in src/vendor_capabilities.py.
Verification: 30/30 tests pass in batch
(test_qwen_provider, test_minimax_provider, test_ai_client_no_top_level_sdk_imports,
test_vendor_capabilities, test_openai_compatible, test_cost_tracker)
Eliminates 22 call sites that bypassed the AppController state owner
and read/wrote config.toml directly. AppController is now the single
source of truth for self.config; gui_2.py, commands.py, etc. go
through controller.save_config() / controller.load_config().
Production changes:
- src/models.py: rename load_config -> _load_config_from_disk,
save_config -> _save_config_to_disk (private I/O primitives)
- src/app_controller.py: add public load_config()/save_config() methods
that own the state. Update 3 internal call sites and 3 ConductorEngine
call sites to pass max_workers from self.config
- src/multi_agent_conductor.py: ConductorEngine.__init__ now takes
max_workers as a parameter (caller responsibility, not I/O primitive)
- src/external_editor.py: get_default_launcher() takes config as a
parameter; gui_2.py:1311,4776 pass app.config
- src/gui_2.py: 17 sites of models.save_config(X.config) replaced with
X.save_config() (delegates via __getattr__ to controller)
- src/commands.py: save_all() uses app.save_config()
Test changes (route through controller, not I/O primitive):
- tests/conftest.py: mock_app and app_instance fixtures now patch
AppController.load_config/save_config instead of models I/O primitives
- 18 other test files: patches renamed from models._save_config_to_disk
to AppController.save_config (and same for load_config)
- tests/test_app_controller_mcp.py: use SLOP_CONFIG env var instead of
patching removed CONFIG_PATH module constant
- tests/test_parallel_execution.py: pass max_workers=2 explicitly to
ConductorEngine (caller no longer reads config)
- tests/test_gui_paths.py: add save_config=MagicMock() to MockApp;
assert on controller method, not I/O primitive
- tests/test_models_no_top_level_tomli_w.py: still calls private
_save_config_to_disk directly (the only allowed exception; tests
the lazy-load behavior of the primitive itself)
New files:
- scripts/audit_no_models_config_io.py: enforces the rule (--strict,
--json modes; AST-based docstring detection to avoid false positives)
- conductor/code_styleguides/config_state_owner.md: documents the rule
Verification:
- 67 targeted tests pass
- scripts/audit_no_models_config_io.py --strict returns 0
This is the architectural cleanup that surfaced during the
audit_architectural_cheats_20260607 review. Closes the smoke-gun
CONFIG_PATH module constant (already done in 0c7ebf22) AND the
free-function models.load_config/save_config smell.
[conductor(checkpoint): config-iO-refactor-20260607]
ROOT CAUSE: src/models.py had `CONFIG_PATH = get_config_path()`
at module level. Every test that imported `src.models` and called
`save_config()` or `load_config()` wrote/read the repo-root
`config.toml` via this cached constant. The path was resolved
once at import time, so the SLOP_CONFIG env var (or test
fixtures) couldn't redirect reads/writes without reimporting the
module.
This silently corrupted the user's config.toml on every test
run. The diff between runs showed: 'config.toml changed in
working copy' — caused by tests, not the user.
FIX: remove the module-level constant; call get_config_path()
on every read/write call. SLOP_CONFIG (and any test-time
set_config_path() helper) now works without reimport.
Also: keep my prior commits to this file (reset_layout command
in src/commands.py; the RUN_MMA_INTEGRATION skipif in
test_mma_step_mode_sim.py) bundled here for a clean atomic
fix-pack since the user just fixed the indentation issue I had.
Verified: src.models imports cleanly; load_config/save_config
work as expected. Tests that import these functions will
use whatever SLOP_CONFIG points to (or the repo-root default).
Sub-track 2A of startup_speedup_20260606: clears 1 of 61 main-thread audit violations (pydantic in src/models.py).
Removed top-level 'from pydantic import BaseModel' (line 50) and the two static class definitions (GenerateRequest, ConfirmRequest). Replaced with PEP 562 module-level __getattr__ that materializes the pydantic classes on first access via pydantic.create_model() + _require_warmed('pydantic').
Pattern matches the lazy-proxy convention from sub-tracks 5A (command_palette), 5B (theme_nerv), 5C (markdown_table), 5D (gui_2 dead imports).
Result:
- pydantic NOT in sys.modules after 'import src.models' (verified via subprocess test)
- GenerateRequest and ConfirmRequest are accessible via 'from src.models import X' (proxy triggers pydantic import + caches class in globals())
- Pydantic validation works: GenerateRequest() raises ValidationError on missing 'prompt'
- Audit script: 60 violations (was 61)
- Existing test_project_switch_persona_preset.py: 8/9 pass; the 1 failure is the pre-existing ui_global_preset_name issue (unrelated)
Files changed:
- src/models.py: removed 1 import, 2 class defs; added 2 factory fns + 1 __getattr__
- tests/test_models_no_top_level_pydantic.py: new (7 tests; all pass)
Per user instruction, all implementation work is performed by the Tier 2 tech lead directly. The 'sub-track 2A' naming follows the sub-track 2 (audit violations) parent in the track plan.
Sub-track 2 of startup_speedup_20260606. Removes the top-level
'import tomli_w' from src/models.py and moves it inside save_config().
tomli_w (~30ms cold load) is now loaded only when the user saves
config, not on every src.models import.
This drops the audit violation count from 63 to 62.
Pydantic BaseModel (the other src/models.py violation) is left for
a future sub-track: deferring a class base requires a metaclass or
proxy pattern that's higher risk for the small (~50ms) saving.
3 new tests in tests/test_models_no_top_level_tomli_w.py:
- tomli_w NOT in sys.modules after import src.models
- save_config() still works (because tomli_w loads on-demand)
- save_config() actually triggers the import on first call
17 existing model tests pass (test_persona_models, test_bias_models,
test_context_presets_models, test_per_ticket_model, test_file_item_model).
Track.get_executable_tickets (in models.py) called TrackDAG at
runtime, forcing a top-level import of src.dag_engine into models.py
and creating a 2-cycle that broke whichever module loaded second
(Ticket was not yet defined when models.py loaded first; TrackDAG
was not yet defined when dag_engine.py loaded first).
Fix: hoist the method out of the Track dataclass and into a free
function get_executable_tickets(track) in dag_engine.py. models.py
no longer needs TrackDAG at all, so the cycle is one-directional
(models -> dag_engine) and resolves cleanly in any import order.
Tests updated:
- tests/test_mma_models.py: import get_executable_tickets and call
it instead of track.get_executable_tickets() (4 call sites)
- tests/test_conductor_engine_v2.py: comment update
Verified both import orders resolve cleanly:
forward: import src.models; import src.dag_engine -> OK
reverse: import src.dag_engine; import src.models -> OK
34 tests pass (test_mma_models, test_dag_engine, test_execution_engine,
test_arch_boundary_phase3, test_track_state_schema).
- TextEditorConfig.from_dict no longer requires 'name' field since name comes from dict key
- Added try/except around _render_external_editor_panel to prevent tab bar mismatch
- Add TextEditorConfig and ExternalEditorConfig dataclasses to models.py
- Create src/external_editor.py with ExternalEditorLauncher class
- Add tests for configuration and launcher functionality
- Support for config.toml [tools.text_editors] and manual_slop.toml default_editor