Compare commits

..

4 Commits

13 changed files with 530 additions and 49 deletions

View File

@@ -2,11 +2,20 @@
"permissions": { "permissions": {
"allow": [ "allow": [
"mcp__manual-slop__run_powershell", "mcp__manual-slop__run_powershell",
"mcp__manual-slop__py_get_definition" "mcp__manual-slop__py_get_definition",
"mcp__manual-slop__read_file",
"mcp__manual-slop__py_get_code_outline",
"mcp__manual-slop__get_file_slice",
"mcp__manual-slop__py_find_usages",
"mcp__manual-slop__set_file_slice",
"mcp__manual-slop__py_check_syntax",
"mcp__manual-slop__get_file_summary",
"mcp__manual-slop__get_tree",
"mcp__manual-slop__list_directory"
] ]
}, },
"enableAllProjectMcpServers": true,
"enabledMcpjsonServers": [ "enabledMcpjsonServers": [
"manual-slop" "manual-slop"
], ]
"enableAllProjectMcpServers": true
} }

View File

@@ -30,3 +30,14 @@
--- ---
## 2026-03-02 (Session 2)
### Tracks Initialized: feature_bleed_cleanup + mma_agent_focus_ux |TASK:feature_bleed_cleanup_20260302| |TASK:mma_agent_focus_ux_20260302|
- **What**: Audited codebase for feature bleed; initialized 2 new conductor tracks
- **Why**: Entropy from Tier 2 track implementations — redundant code, dead methods, layout regressions, no tier context in observability
- **Bleed findings** (gui_2.py): Dead duplicate `_render_comms_history_panel` (3041-3073, stale `type` key, wrong method ref); dead `begin_main_menu_bar()` block (1680-1705, Quit has never worked); 4 duplicate `__init__` assignments; double "Token Budget" label with no collapsing header
- **Agent focus findings** (ai_client.py + conductors): No `current_tier` var; Tier 3 swaps callback but never stamps tier; Tier 2 doesn't swap at all; `_tool_log` is untagged tuple list
- **Result**: 2 tracks committed (4f11d1e, c1a86e2). Bleed cleanup is active; agent focus depends on it.
---

View File

@@ -3,32 +3,24 @@
<!-- Source of truth for task state is conductor/tracks/*/plan.md --> <!-- Source of truth for task state is conductor/tracks/*/plan.md -->
## Active Tracks ## Active Tracks
_None currently active._ - `feature_bleed_cleanup_20260302` — Dead code & conflicting design state cleanup (Phase 1-3)
## Completed This Session ## Completed This Session
- `context_token_viz_20260301` — Token budget panel (color bar, breakdown table, trim warning, cache status, auto-refresh). All phases verified. Commit: d577457. - `context_token_viz_20260301` — Token budget panel (color bar, breakdown table, trim warning, cache status, auto-refresh). All phases verified. Commit: d577457.
## Planned: Next Track ## Planned: Next Track
### `mma_agent_focus_ux` (not yet created) ### `mma_agent_focus_ux_20260302` (initialized — run after bleed cleanup)
**Priority:** High **Priority:** High
**Depends on:** nothing **Depends on:** `feature_bleed_cleanup_20260302` Phase 1 (dead comms panel removed)
**Origin:** User feedback 2026-03-02 — token viz is agent-agnostic; MMA observability panels (comms, tool calls, discussion history, token budget) show global/session-scoped data with no way to isolate a specific tier or agent. **Track dir:** `conductor/tracks/mma_agent_focus_ux_20260302/`
**The Gap (audit-confirmed):** **Audit-confirmed gaps:**
- `_comms_log` entries (gui_2.py:861895) have no tier/agent tag — only `direction`, `type`, `payload` - `ai_client._append_comms` emits entries with no `source_tier` key
- `_tool_log` entries (gui_2.py:897900) are `(script, result, ts)` — no tier tag - `ai_client` has no `current_tier` module variable — no way for tiers to self-identify
- `mma_streams` dict uses `stream_id` (`"Tier 1"` etc.) — the **only** existing per-agent key - `_tool_log` is `list[tuple[str,str,float]]` — no tier field, tuple must migrate to dict
- `_on_comms_entry` never attaches caller tier/agent context - `run_worker_lifecycle` replaces `comms_log_callback` but never stamps `source_tier`
- Token stats are global (single `ai_client` provider, no per-tier history separation) - `generate_tickets` (Tier 2) does NOT replace callback at all
- No Focus Agent selector widget in Operations Hub
**Intent:** **Scope:** Phase 1 (tier tagging) → Phase 2 (tool log dict migration) → Phase 3 (Focus Agent UI + filter). Per-tier token stats deferred to sub-track.
1. Add a `source_tier` / `agent_id` field to comms log entries and tool log tuples at the point of emission
2. Add a "Focus Agent" selector widget to the MMA Dashboard (None = global; Tier 14 = filtered)
3. Filter `_render_comms_history_panel`, `_render_tool_calls_panel`, and `_render_discussion_panel` by the selected agent when focus is active
4. Token budget panel: when a tier is focused, show token stats for that tier's model/history (requires per-tier history tracking in ai_client or conductor engine)
5. Discussion history entries emitted by MMA workers (via `history_add` comms kind) already carry a `role` — use that to group by tier
**Scope note:** Item 4 (per-tier token stats) is the most architectural — may warrant a sub-track or phased deferral.
**To initialize:** Run `/conductor-new-track mma_agent_focus_ux` at start of next session after reading this file.

View File

@@ -0,0 +1,5 @@
# Track feature_bleed_cleanup_20260302 Context
- [Specification](./spec.md)
- [Implementation Plan](./plan.md)
- [Metadata](./metadata.json)

View File

@@ -0,0 +1,8 @@
{
"track_id": "feature_bleed_cleanup_20260302",
"type": "fix",
"status": "new",
"created_at": "2026-03-02T00:00:00Z",
"updated_at": "2026-03-02T00:00:00Z",
"description": "Audit-driven removal of dead duplicate code, conflicting menu bar design, and layout regressions introduced by feature bleed across multiple tracks."
}

View File

@@ -0,0 +1,111 @@
# Implementation Plan: Feature Bleed Cleanup
Architecture reference: [docs/guide_architecture.md](../../../docs/guide_architecture.md)
---
## Phase 1: Dead Code Removal
Focus: Delete the two confirmed dead code blocks — no behavior change, pure deletion.
- [ ] Task 1.1: In `gui_2.py`, delete the first `_render_comms_history_panel` definition.
- **Location**: Lines 3041-3073 (use `py_get_code_outline` to confirm current line numbers before editing).
- **What**: The entire method body from `def _render_comms_history_panel(self) -> None:` through `imgui.end_child()` and the following blank line. The live version begins at ~line 3435 after this deletion shifts lines.
- **How**: Use `set_file_slice` to delete lines 3041-3073 (replace with empty string). Then run `py_get_code_outline` to confirm only one `_render_comms_history_panel` remains.
- **Verify**: `grep -n "_render_comms_history_panel" gui_2.py` should show exactly 2 hits: the `def` line and the call site in `_gui_func`.
- [ ] Task 1.2: In `gui_2.py` `__init__`, delete the duplicate state variable assignments.
- **Location**: Second occurrences of `ui_conductor_setup_summary`, `ui_new_track_name`, `ui_new_track_desc`, `ui_new_track_type`. Currently at lines 308-311 (grep to confirm exact lines before editing: `grep -n "ui_conductor_setup_summary" gui_2.py`).
- **What**: Delete these 4 lines. The first correct assignments remain at lines 218-221.
- **How**: Use `set_file_slice` to remove lines 308-311 (replace with empty string).
- **Verify**: Each variable should appear exactly once in `__init__` (grep to confirm).
- [ ] Task 1.3: Write/run tests to confirm no regressions.
- Run `uv run pytest tests/ -x -q` and confirm all tests pass.
- Run `uv run python -c "from gui_2 import App; print('import ok')"` to confirm no syntax errors.
- [ ] Task 1.4: Conductor — User Manual Verification
- Start the app with `uv run python gui_2.py` and confirm it launches without error.
- Open "Operations Hub" → "Comms History" tab and confirm the comms panel renders (color legend visible).
---
## Phase 2: Menu Bar Consolidation
Focus: Remove the dead inline menubar block and add a working Quit item to `_show_menus`.
- [ ] Task 2.1: Delete the dead `begin_main_menu_bar()` block from `_gui_func`.
- **Location**: `gui_2.py` lines 1679-1705 (the comment `# ---- Menubar` through `imgui.end_main_menu_bar()`). Use `get_file_slice(1676, 1712)` to confirm exact boundaries before editing.
- **What**: Delete the `# ---- Menubar` comment line and the entire `if imgui.begin_main_menu_bar(): ... imgui.end_main_menu_bar()` block (~27 lines total). The `# --- Hubs ---` comment and hub rendering that follows must be preserved.
- **How**: Use `set_file_slice` to replace lines 1679-1705 with a single blank line.
- **Verify**: `grep -n "begin_main_menu_bar" gui_2.py` returns 0 hits.
- [ ] Task 2.2: Add working "Quit" to `_show_menus`.
- **Location**: `gui_2.py` `_show_menus` method (lines 1620-1647 — confirm with `py_get_definition`).
- **What**: Before the existing `if imgui.begin_menu("Windows"):` line, insert:
```python
if imgui.begin_menu("manual slop"):
if imgui.menu_item("Quit", "Ctrl+Q", False)[0]:
self.runner_params.app_shall_exit = True
imgui.end_menu()
```
- **Note**: `self.runner_params` is set in `run()` before `immapp.run()` is called, so it is valid here.
- **How**: Use `set_file_slice` or `Edit` to insert the block before the "Windows" menu.
- **Verify**: Launch app, confirm "manual slop" > "Quit" appears in menubar and clicking it closes the app cleanly.
- [ ] Task 2.3: Write/run tests.
- Run `uv run pytest tests/ -x -q`.
- [ ] Task 2.4: Conductor — User Manual Verification
- Launch app. Confirm menubar has: "manual slop" (with Quit), "Windows", "Project".
- Confirm "View" menu is gone (was dead duplicate of "Windows").
- Confirm Quit closes the app.
---
## Phase 3: Token Budget Layout Fix
Focus: Give the token budget panel its own collapsing header in AI Settings; remove the double label from the provider panel.
- [ ] Task 3.1: Remove the double label + embedded call from `_render_provider_panel`.
- **Location**: `gui_2.py` `_render_provider_panel` (lines ~2687-2746 — use `py_get_definition` to confirm). The block to remove is:
```python
imgui.text("Token Budget:")
imgui.separator()
imgui.text("Token Budget")
self._render_token_budget_panel()
```
These are 4 consecutive lines at the end of the method (before `if self._gemini_cache_text:`).
- **What**: Delete those 4 lines. The `if self._gemini_cache_text:` block that follows them must be preserved in place.
- **How**: Use `Edit` with `old_string` set to those exact 4 lines.
- **Verify**: `_render_provider_panel` ends with the `if self._gemini_cache_text:` block and no "Token Budget" text labels.
- [ ] Task 3.2: Add `collapsing_header("Token Budget")` to AI Settings in `_gui_func`.
- **Location**: `gui_2.py` `_gui_func`, AI Settings window block (currently lines ~1719-1723 — `get_file_slice(1715, 1730)` to confirm). Current content:
```python
if imgui.collapsing_header("Provider & Model"):
self._render_provider_panel()
if imgui.collapsing_header("System Prompts"):
self._render_system_prompts_panel()
```
- **What**: Add after the System Prompts header:
```python
if imgui.collapsing_header("Token Budget"):
self._render_token_budget_panel()
```
- **How**: Use `Edit` to insert after the `_render_system_prompts_panel()` call.
- **Verify**: AI Settings window now shows three collapsing sections: "Provider & Model", "System Prompts", "Token Budget".
- [ ] Task 3.3: Write/run tests.
- Run `uv run pytest tests/ -x -q`.
- [ ] Task 3.4: Conductor — User Manual Verification
- Launch app. Open "AI Settings" window.
- Confirm "Token Budget" appears as a collapsing header (expand it — panel renders correctly).
- Confirm "Provider & Model" section no longer shows any "Token Budget" label.
---
## Phase Completion Checkpoint
After all phases pass manual verification:
- Run `uv run pytest tests/ -x -q` one final time.
- Commit: `fix(bleed): remove dead comms panel dup, consolidate menubar, fix token budget layout`
- Update TASKS.md to mark this track complete.
- Update JOURNAL.md with What/Why/How/Issues/Result.

View File

@@ -0,0 +1,67 @@
# Track Specification: Feature Bleed Cleanup
## Overview
Multiple tracks added code to `gui_2.py` without removing the old versions, leaving
dead duplicate methods, conflicting menu bar designs, and redundant state initializations.
This track removes confirmed dead code, resolves the two-menubar conflict, and cleans
up the token budget layout regression — restoring a consistent, non-contradictory design state.
## Current State Audit (as of 0ad47af)
### Already Implemented (DO NOT re-implement)
- **Live comms history panel** (`_render_comms_history_panel`, `gui_2.py:3435-3560`): Full-featured version with color legend, blink effects, prior-session tinted background, correct `entry.get('kind')` data key. **This is the version Python actually uses.**
- **`_show_menus` callback** (`gui_2.py:1620-1647`): HelloImGui-registered menu callback. Has "Windows" and "Project" menus. This is what actually renders in the app menubar.
- **Token budget panel** (`_render_token_budget_panel`, `gui_2.py:2748-2819`): Fully implemented with color bar, breakdown table, trim warning, cache status. Called from within `_render_provider_panel`.
- **`__init__` first-pass state vars** (`gui_2.py:218-221`): `ui_new_track_name`, `ui_new_track_desc`, `ui_new_track_type`, `ui_conductor_setup_summary` — correct first assignment.
### Gaps / Confirmed Bugs (This Track's Scope)
1. **Dead `_render_comms_history_panel` at lines 3041-3073**: Python silently discards the first definition when the second (3435) is encountered. The dead version uses the stale `entry.get('type')` key (current data model uses `kind`), calls `self._cb_load_prior_log()` (method does not exist — correct name is `cb_load_prior_log`), and uses `begin_child("scroll_area")` which collides with the ID used in `_render_tool_calls_panel`. This is ~33 lines of noise that misleads future workers.
2. **Dead inline `begin_main_menu_bar()` block at lines 1680-1705**: HelloImGui renders the main menu bar before invoking `show_gui` (`_gui_func`). By the time `_gui_func` runs, the menubar is already committed; `imgui.begin_main_menu_bar()` returns `False`, so the entire 26-line block never executes. Consequences:
- The "manual slop" > "Quit" menu item (sets `self.should_quit = True`) is dead — `should_quit` is never checked anywhere else, so even if it ran, the app would not quit.
- The "View" menu (toggling `show_windows`) duplicates the live "Windows" menu in `_show_menus`.
- The "Project" menu duplicates the live "Project" menu in `_show_menus`, with a slightly different `_handle_reset_session()` call vs direct `ai_client.reset_session()` call.
3. **Duplicate `__init__` state assignments at lines 308-311**: `ui_conductor_setup_summary`, `ui_new_track_name`, `ui_new_track_desc`, `ui_new_track_type` are each assigned twice — first at lines 218-221, then again at 308-311. The second assignments are harmless (same values) but create false ambiguity about initialization order and intent.
4. **Redundant double "Token Budget" labels in `_render_provider_panel` (lines 2741-2743)**: `imgui.text("Token Budget:")` followed by `imgui.separator()` followed by `imgui.text("Token Budget")` followed by the panel call. Two labels appear before the panel, one with trailing colon and one without. The journal entry says "Token panel visible in AI Settings under 'Token Budget'" — but there is no `collapsing_header("Token Budget")` in `_gui_func`; the panel is embedded inside the "Provider & Model" collapsing section with duplicate labels.
5. **Missing "Quit" in live `_show_menus`**: The only functional quit path is the window close button. HelloImGui's proper quit API is `runner_params.app_shall_exit = True` (accessible via `self.runner_params.app_shall_exit`).
## Goals
1. Remove dead `_render_comms_history_panel` duplicate (lines 3041-3073).
2. Remove dead inline `begin_main_menu_bar()` block (lines 1680-1705).
3. Add working "Quit" to `_show_menus` using `self.runner_params.app_shall_exit = True`.
4. Remove duplicate `__init__` state assignments (lines 308-311).
5. Fix double "Token Budget" labels; give the panel its own `collapsing_header` in AI Settings.
## Functional Requirements
### Phase 1 — Dead Code Removal
- Delete lines 3041-3073 (`_render_comms_history_panel` first definition) from `gui_2.py` entirely. Do not replace — the live version at (renumbered) ~3400 is the only version needed.
- Delete lines 308-311 (second assignments of `ui_new_track_name`, `ui_new_track_desc`, `ui_new_track_type`, `ui_conductor_setup_summary`) from `__init__`. Keep the first assignments at lines 218-221.
### Phase 2 — Menu Bar Consolidation
- Delete lines 1680-1705 (the dead `if imgui.begin_main_menu_bar(): ... imgui.end_main_menu_bar()` block) from `_gui_func`. The `# ---- Menubar` comment at line 1679 must also be removed.
- In `_show_menus` (lines 1620-1647), add a "manual slop" menu before the existing menus, containing a "Quit" item that sets `self.runner_params.app_shall_exit = True`.
### Phase 3 — Token Budget Layout Fix
- In `_render_provider_panel` (lines ~2741-2744): remove the two text labels (`imgui.text("Token Budget:")`, `imgui.separator()`, `imgui.text("Token Budget")`) and the `self._render_token_budget_panel()` call. The separator before them (line ~2740) should remain to close off the Telemetry section cleanly.
- In `_gui_func` AI Settings window (around lines 1719-1723), add a new `collapsing_header("Token Budget")` section that calls `self._render_token_budget_panel()`. It should appear after the "System Prompts" header.
## Non-Functional Requirements
- Zero behavior change to any feature that currently works.
- No new dependencies.
- After Phase 1-2, run `uv run pytest tests/ -x -q` to verify no test regressions.
- Each phase should be committed independently for clean git history.
## Architecture Reference
- [docs/guide_architecture.md](../../../docs/guide_architecture.md): HelloImGui runner params, callback lifecycle
- [conductor/workflow.md](../../workflow.md): Task lifecycle and TDD protocol
## Out of Scope
- Refactoring `_render_mma_dashboard` content organization.
- Changing `mma_tier_usage` default model names (runtime concern, not code quality).
- The `mma_agent_focus_ux` track (planned separately in TASKS.md).
- Any new feature work.

View File

@@ -0,0 +1,5 @@
# Track mma_agent_focus_ux_20260302 Context
- [Specification](./spec.md)
- [Implementation Plan](./plan.md)
- [Metadata](./metadata.json)

View File

@@ -0,0 +1,8 @@
{
"track_id": "mma_agent_focus_ux_20260302",
"type": "feat",
"status": "new",
"created_at": "2026-03-02T00:00:00Z",
"updated_at": "2026-03-02T00:00:00Z",
"description": "Add per-tier agent focus to MMA observability panels: tag comms/tool log entries with source_tier at emission, then filter comms, tool, and discussion panels by selected agent."
}

View File

@@ -0,0 +1,160 @@
# Implementation Plan: MMA Agent Focus UX
Architecture reference: [docs/guide_mma.md](../../../docs/guide_mma.md)
**Prerequisite:** `feature_bleed_cleanup_20260302` Phase 1 must be complete (dead comms panel removed, line numbers stabilized).
---
## Phase 1: Tier Tagging at Emission
Focus: Add `current_tier` context variable to `ai_client` and stamp it on every comms/tool entry at the point of emission. No UI changes — purely data layer.
- [ ] Task 1.1: Add `current_tier` module variable to `ai_client.py`.
- **Location**: `ai_client.py` line 91 (beside `tool_log_callback`). Confirm with `get_file_slice(87, 95)`.
- **What**: Add `current_tier: str | None = None` as a module-level variable.
- **How**: Use `Edit` to insert after `tool_log_callback: Callable[[str, str], None] | None = None`.
- **Verify**: `grep -n "current_tier" ai_client.py` returns the new line.
- [ ] Task 1.2: Stamp `source_tier` in `_append_comms`.
- **Location**: `ai_client._append_comms` (`ai_client.py:136-147`). Confirm with `py_get_definition`.
- **What**: Add `"source_tier": current_tier` as a key in the `entry` dict (after `"model"`).
- **How**: Use `Edit` to insert the key into the dict literal.
- **Note**: Add comment: `# current_tier is set/cleared by caller tiers; safe — ai_client.send() calls are serialized by the MMA engine executor.`
- **Verify**: Manually check the dict has `source_tier` key.
- [ ] Task 1.3: Set/clear `current_tier` in `run_worker_lifecycle` (Tier 3).
- **Location**: `multi_agent_conductor.run_worker_lifecycle` (`multi_agent_conductor.py:224-354`). The `try:` block that calls `ai_client.send()` starts at line ~296. Confirm with `py_get_definition`.
- **What**: Before the `try:` block, add `ai_client.current_tier = "Tier 3"`. In the existing `finally:` block (which already restores `ai_client.comms_log_callback`), add `ai_client.current_tier = None`.
- **How**: Use `Edit` to insert before `try:` and inside `finally:`.
- **Verify**: After edit, `py_get_definition(run_worker_lifecycle)` shows both lines.
- [ ] Task 1.4: Set/clear `current_tier` in `generate_tickets` (Tier 2).
- **Location**: `conductor_tech_lead.generate_tickets` (`conductor_tech_lead.py:6-48`). The `try:` block starts at line ~21. Confirm with `py_get_definition`.
- **What**: Before the `try:` block (before `response = ai_client.send(...)`), add `ai_client.current_tier = "Tier 2"`. In the existing `finally:` block (which restores `_custom_system_prompt`), add `ai_client.current_tier = None`.
- **How**: Use `Edit`.
- **Verify**: `py_get_definition(generate_tickets)` shows both lines.
- [ ] Task 1.5: Migrate `_tool_log` from tuple to dict; update emission and storage.
- **Step A — `_on_tool_log`** (`gui_2.py:897-900`): Change to read `ai_client.current_tier` and pass it: `self._append_tool_log(script, result, ai_client.current_tier)`.
- **Step B — `_append_tool_log`** (`gui_2.py:1496-1503`): Change signature to `_append_tool_log(self, script: str, result: str, source_tier: str | None = None)`. Change `self._tool_log.append((script, result, time.time()))` to `self._tool_log.append({"script": script, "result": result, "ts": time.time(), "source_tier": source_tier})`.
- **Step C — type hint in `__init__`**: Change `self._tool_log: list[tuple[str, str, float]] = []` to `self._tool_log: list[dict] = []`.
- **How**: Use `Edit` for each step. Confirm with `py_get_definition` after each.
- **Verify**: `grep -n "_tool_log" gui_2.py` — all references confirmed; `_render_tool_calls_panel` still uses tuple destructure (fixed in Phase 2).
- [ ] Task 1.6: Write tests for Phase 1.
- Confirm `ai_client._append_comms` produces entries with `source_tier` key (even if `None`).
- Confirm `_append_tool_log` stores a dict with `source_tier` key.
- Run `uv run pytest tests/ -x -q`.
- [ ] Task 1.7: Conductor — User Manual Verification
- Launch app. Open a send in normal mode — confirm comms entries in Operations Hub > Comms History still render.
- (MMA run not required at this phase — data layer only.)
---
## Phase 2: Tool Log Reader Migration
Focus: Update `_render_tool_calls_panel` to read dicts. No UI change — just fixes the access pattern before Phase 3 adds filter logic.
- [ ] Task 2.1: Update `_render_tool_calls_panel` to use dict access.
- **Location**: `gui_2.py:2989-3039`. Confirm with `get_file_slice(2989, 3042)`.
- **What**: Replace `script, result, _ = self._tool_log[i_minus_one]` with:
```python
entry = self._tool_log[i_minus_one]
script = entry["script"]
result = entry["result"]
```
- All subsequent uses of `script` and `result` in the same loop body are unchanged.
- **How**: Use `Edit` targeting the destructure line.
- **Verify**: `py_check_syntax(gui_2.py)` passes; run tests.
- [ ] Task 2.2: Write/run tests.
- Run `uv run pytest tests/ -x -q`. Confirm tool log panel simulation tests (if any) pass.
- [ ] Task 2.3: Conductor — User Manual Verification
- Launch app. Generate a script send (or use existing tool call in history). Confirm "Tool Calls" tab in Operations Hub renders correctly.
---
## Phase 3: Focus Agent UI + Filter Logic
Focus: Add the combo selector and filter the two log panels.
- [ ] Task 3.1: Add `ui_focus_agent` state var to `App.__init__`.
- **Location**: `gui_2.py` `__init__`, after `self.active_tier: str | None = None` (line ~283 — confirm with `grep -n "self.active_tier" gui_2.py`).
- **What**: Insert `self.ui_focus_agent: str | None = None`.
- **How**: Use `Edit`.
- **Verify**: `grep -n "ui_focus_agent" gui_2.py` returns exactly 1 hit (the new line, before Phase 3.3 adds more).
- [ ] Task 3.2: Add Focus Agent selector widget in Operations Hub.
- **Location**: `gui_2.py` `_gui_func`, Operations Hub block (line ~1774). Confirm with `get_file_slice(1774, 1792)`. Current content:
```python
if imgui.begin_tab_bar("OperationsTabs"):
```
- **What**: Insert immediately before `if imgui.begin_tab_bar("OperationsTabs"):`:
```python
imgui.text("Focus Agent:")
imgui.same_line()
focus_label = self.ui_focus_agent or "All"
if imgui.begin_combo("##focus_agent", focus_label, imgui.ComboFlags_.width_fit_preview):
if imgui.selectable("All", self.ui_focus_agent is None)[0]:
self.ui_focus_agent = None
for tier in ["Tier 2", "Tier 3", "Tier 4"]:
if imgui.selectable(tier, self.ui_focus_agent == tier)[0]:
self.ui_focus_agent = tier
imgui.end_combo()
imgui.same_line()
if self.ui_focus_agent:
if imgui.button("x##clear_focus"):
self.ui_focus_agent = None
imgui.separator()
```
- **Note**: Tier 1 omitted — Tier 1 (Claude Code) never calls `ai_client.send()`, so it produces no comms entries.
- **How**: Use `Edit`.
- [ ] Task 3.3: Add filter logic to `_render_comms_history_panel`.
- **Location**: `gui_2.py` `_render_comms_history_panel` (after bleed cleanup, line ~3400). Confirm with `py_get_definition`.
- **What**: After the `log_to_render = self.prior_session_entries if self.is_viewing_prior_session else list(self._comms_log)` line, add:
```python
if self.ui_focus_agent and not self.is_viewing_prior_session:
log_to_render = [e for e in log_to_render if e.get("source_tier") == self.ui_focus_agent]
```
- Also add a `source_tier` label in the entry header row (after the `provider/model` text):
```python
tier_label = entry.get("source_tier") or "main"
imgui.text_colored(C_SUB, f"[{tier_label}]")
imgui.same_line()
```
Insert this after the `imgui.text_colored(C_LBL, f"{entry.get('provider', '?')}/{entry.get('model', '?')}")` line.
- **How**: Use `Edit` for each insertion.
- [ ] Task 3.4: Add filter logic to `_render_tool_calls_panel`.
- **Location**: `gui_2.py:2989`. Confirm with `get_file_slice(2989, 3000)`.
- **What**: After `imgui.begin_child("scroll_area")` + clipper setup, change the render source:
- Replace `clipper.begin(len(self._tool_log))` with a pre-filtered list:
```python
tool_log_filtered = self._tool_log if not self.ui_focus_agent else [
e for e in self._tool_log if e.get("source_tier") == self.ui_focus_agent
]
```
- Then `clipper.begin(len(tool_log_filtered))`.
- Inside the loop use `tool_log_filtered[i_minus_one]` instead of `self._tool_log[i_minus_one]`.
- **How**: Use `Edit`.
- [ ] Task 3.5: Write tests for Phase 3.
- Test that `ui_focus_agent = "Tier 3"` filters out entries with `source_tier = "Tier 2"`.
- Run `uv run pytest tests/ -x -q`.
- [ ] Task 3.6: Conductor — User Manual Verification
- Launch app. Open Operations Hub.
- Confirm "Focus Agent:" combo appears above tabs with options: All, Tier 2, Tier 3, Tier 4.
- With "All" selected: all entries show with `[main]` or `[Tier N]` labels in comms history.
- With "Tier 3" selected: comms history shows only entries tagged `source_tier = "Tier 3"`.
- Confirm "x" clear button resets to "All".
---
## Phase Completion Checkpoint
After all phases pass manual verification:
- Run `uv run pytest tests/ -x -q` one final time.
- Commit: `feat(mma): per-tier agent focus — source_tier tagging + Focus Agent filter UI`
- Update TASKS.md: move `mma_agent_focus_ux` from Planned to Active/Completed.
- Update JOURNAL.md with What/Why/How/Issues/Result.

View File

@@ -0,0 +1,95 @@
# Track Specification: MMA Agent Focus UX
## Overview
All MMA observability panels (comms history, tool calls, discussion) display
global/session-scoped data. When 4 tiers are running concurrently, their traffic
is indistinguishable. This track adds a `source_tier` field to every comms and
tool log entry at the point of emission, then adds a "Focus Agent" selector that
filters the Operations Hub panels to show only one tier's traffic at a time.
**Depends on:** `feature_bleed_cleanup_20260302` (Phase 1 removes the dead comms
panel duplicate; this track extends the live panel at gui_2.py:~3400).
## Current State Audit (as of 0ad47af)
### Already Implemented (DO NOT re-implement)
- **`ai_client._append_comms`** (`ai_client.py:136-147`): Emits entries with keys `ts`, `direction`, `kind`, `provider`, `model`, `payload`. No `source_tier` key.
- **`ai_client.comms_log_callback`** (`ai_client.py:87`): Module-level `Callable | None`. Tier 3 workers temporarily replace it in `run_worker_lifecycle` (`multi_agent_conductor.py:224-354`); Tier 2 (`conductor_tech_lead.py:6-48`) does NOT replace it.
- **`ai_client.tool_log_callback`** (`ai_client.py:91`): Module-level `Callable[[str,str],None] | None`. Never replaced by any tier — fires from whichever tier is active via `_run_script` (`ai_client.py:490-500`).
- **`self._tool_log`** (`gui_2.py:__init__`): `list[tuple[str, str, float]]` — stored as `(script, result, timestamp)`. Destructured in `_render_tool_calls_panel` as `script, result, _ = self._tool_log[i_minus_one]`.
- **`self._comms_log`** (`gui_2.py:__init__`): `list[dict]` — each entry is the raw dict from `_append_comms` plus `local_ts` stamped in `_on_comms_entry`.
- **`self.active_tier`** (`gui_2.py:__init__`): `str | None` — set by `_push_mma_state_update` when the engine reports tier activity. Tracks the *current* active tier but is not stamped onto individual log entries.
- **`run_worker_lifecycle` stream_id** (`multi_agent_conductor.py:299`): Uses `f"Tier 3 (Worker): {ticket.id}"` as `stream_id` for mma_streams. This is already tier+ticket scoped for the stream panels.
- **`_render_comms_history_panel`** (`gui_2.py:~3435`): Renders `self._comms_log` entries with `direction`, `kind`, `provider`, `model` fields. No tier column or filter.
- **`_render_tool_calls_panel`** (`gui_2.py:2989-3039`): Renders `self._tool_log` entries. No tier column or filter.
- **`disc_entries`** (`gui_2.py:__init__`, `_render_discussion_panel:gui_2.py:2482-2685`): List of dicts with `role`, `content`, `collapsed`, `ts`. Role values include "User", "AI", "Tool", "Vendor API" — MMA workers inject via `history_add` kind with a `role` field.
### Gaps to Fill (This Track's Scope)
1. **No `source_tier` on comms entries**: `_append_comms` never reads tier context. Tier 3 callbacks call the old callback chain but don't stamp tier info. Result: all comms from all tiers are visually identical in `_render_comms_history_panel`.
2. **No `source_tier` on tool log entries**: `_on_tool_log` receives `(script, result)` with no tier context. `_tool_log` is a flat list of tuples — no way to filter by tier.
3. **No `current_tier` module variable in `ai_client`**: There is no mechanism for callers to declare which tier is currently active. Both `run_worker_lifecycle` and `generate_tickets` call `ai_client.send()` without setting any "who am I" context that `_append_comms` could read.
4. **No Focus Agent UI widget**: No selector in Operations Hub or MMA Dashboard to choose a tier to filter on.
5. **No filter logic in `_render_comms_history_panel` or `_render_tool_calls_panel`**: Both render all entries unconditionally.
## Goals
1. Add `current_tier: str | None` module-level variable to `ai_client.py`; `_append_comms` reads and includes it as `source_tier` on every entry.
2. Set `ai_client.current_tier` in `run_worker_lifecycle` (Tier 3) and `generate_tickets` (Tier 2) around the `ai_client.send()` call; clear it in `finally`.
3. Change `_tool_log` from `list[tuple]` to `list[dict]` to support `source_tier` field; update all three access sites.
4. Add `self.ui_focus_agent: str | None = None` state var to `App.__init__`.
5. Add a "Focus Agent" combo widget in the Operations Hub tab bar header (or above it in MMA Dashboard).
6. Filter `_render_comms_history_panel` and `_render_tool_calls_panel` by `ui_focus_agent` when it is non-None.
7. Defer per-tier token stats (Phase 4 of TASKS.md intent) to a separate sub-track.
## Functional Requirements
### Phase 1 — Tier Tagging at Emission (ai_client.py + conductors)
- `ai_client.py`: Add `current_tier: str | None = None` module-level variable after line 91 (beside `tool_log_callback`).
- `ai_client._append_comms` (`ai_client.py:136-147`): Add `"source_tier": current_tier` to the entry dict (can be `None` for main-session calls).
- `multi_agent_conductor.run_worker_lifecycle` (`multi_agent_conductor.py:224-354`): Before the `try:` block that calls `ai_client.send()` (line ~296), add `ai_client.current_tier = "Tier 3"`. In the `finally:` block, add `ai_client.current_tier = None`.
- `conductor_tech_lead.generate_tickets` (`conductor_tech_lead.py:6-48`): In the `try:` block before `ai_client.send()`, add `ai_client.current_tier = "Tier 2"`. In `finally:`, add `ai_client.current_tier = None`.
- `gui_2.py _on_tool_log` (`gui_2.py:897-900`): Capture `ai_client.current_tier` at call time and pass it along.
- `gui_2.py _append_tool_log` (`gui_2.py:1496-1503`): Change stored format from `(script, result, time.time())` to dict: `{"script": script, "result": result, "ts": time.time(), "source_tier": source_tier}`.
- Update `_on_tool_log` signature to accept and pass `source_tier`, OR read it directly from `ai_client.current_tier` inside `_append_tool_log`.
### Phase 2 — Tool Log Reader Migration
- `_render_tool_calls_panel` (`gui_2.py:2989-3039`): Replace `script, result, _ = self._tool_log[i_minus_one]` with dict access: `entry = self._tool_log[i_minus_one]`, `script = entry["script"]`, `result = entry["result"]`.
- No filtering yet — just migrate readers so Phase 3 can add filter logic cleanly.
### Phase 3 — Focus Agent UI + Filter Logic
- `App.__init__`: Add `self.ui_focus_agent: str | None = None` after `self.active_tier` (line ~283).
- `_render_tool_calls_panel` AND `_render_comms_history_panel`: At top of each method, derive `log_to_render` by filtering on `entry.get("source_tier") == self.ui_focus_agent` when `ui_focus_agent` is not None.
- **Focus Agent selector widget**: In `_gui_func` Operations Hub block (line ~1774-1783), before the `imgui.begin_tab_bar("OperationsTabs")` call, add:
```python
imgui.text("Focus Agent:")
imgui.same_line()
focus_label = self.ui_focus_agent or "All"
if imgui.begin_combo("##focus_agent", focus_label):
if imgui.selectable("All", self.ui_focus_agent is None)[0]:
self.ui_focus_agent = None
for tier in ["Tier 1", "Tier 2", "Tier 3", "Tier 4"]:
if imgui.selectable(tier, self.ui_focus_agent == tier)[0]:
self.ui_focus_agent = tier
imgui.end_combo()
```
- Show `source_tier` label in `_render_comms_history_panel` entry header row (after `provider/model` field).
## Non-Functional Requirements
- `current_tier` must be cleared in `finally` blocks — never left set after a send call.
- Thread safety: `current_tier` is a module-level var. Because `ai_client.send()` calls are serialized (one tier at a time in the MMA engine's executor), race conditions are negligible. Document this assumption in a code comment.
- No new Python package dependencies.
- `_tool_log` dict format change must be handled as a breaking change — confirm no simulation tests directly inspect raw `_tool_log` tuples.
## Architecture Reference
- [docs/guide_architecture.md](../../../docs/guide_architecture.md): Threading model, event system
- [docs/guide_mma.md](../../../docs/guide_mma.md): Worker lifecycle, tier context
## Out of Scope
- Per-tier token stats / token budget panel filtering (separate sub-track).
- Discussion panel role-based tier filtering (the `role` values don't consistently map to tier names; out of scope here).
- Tier 1 (Claude Code conductor) comms — Tier 1 never calls `ai_client.send()`.
- Filtering the Tier 14 stream panels (already tier-scoped via `mma_streams` stream_id key).

View File

@@ -1,5 +1,5 @@
[ai] [ai]
provider = "gemini_cli" provider = "gemini"
model = "gemini-2.5-flash-lite" model = "gemini-2.5-flash-lite"
temperature = 0.0 temperature = 0.0
max_tokens = 8192 max_tokens = 8192

View File

@@ -79,7 +79,7 @@ DockId=0x0000000F,2
[Window][Theme] [Window][Theme]
Pos=0,17 Pos=0,17
Size=947,824 Size=32,824
Collapsed=0 Collapsed=0
DockId=0x00000005,1 DockId=0x00000005,1
@@ -89,14 +89,14 @@ Size=900,700
Collapsed=0 Collapsed=0
[Window][Diagnostics] [Window][Diagnostics]
Pos=949,17 Pos=34,17
Size=1326,447 Size=596,326
Collapsed=0 Collapsed=0
DockId=0x00000010,1 DockId=0x00000010,1
[Window][Context Hub] [Window][Context Hub]
Pos=0,17 Pos=0,17
Size=947,824 Size=32,824
Collapsed=0 Collapsed=0
DockId=0x00000005,0 DockId=0x00000005,0
@@ -107,43 +107,43 @@ Collapsed=0
DockId=0x0000000D,0 DockId=0x0000000D,0
[Window][Discussion Hub] [Window][Discussion Hub]
Pos=2277,17 Pos=632,17
Size=1048,811 Size=1048,592
Collapsed=0 Collapsed=0
DockId=0x00000012,0 DockId=0x00000012,0
[Window][Operations Hub] [Window][Operations Hub]
Pos=949,17 Pos=34,17
Size=1326,447 Size=596,326
Collapsed=0 Collapsed=0
DockId=0x00000010,0 DockId=0x00000010,0
[Window][Files & Media] [Window][Files & Media]
Pos=0,843 Pos=0,843
Size=947,794 Size=32,357
Collapsed=0 Collapsed=0
DockId=0x00000006,1 DockId=0x00000006,1
[Window][AI Settings] [Window][AI Settings]
Pos=0,843 Pos=0,843
Size=947,794 Size=32,357
Collapsed=0 Collapsed=0
DockId=0x00000006,0 DockId=0x00000006,0
[Window][Approve Tool Execution] [Window][Approve Tool Execution]
Pos=1009,547 Pos=3,524
Size=416,325 Size=416,325
Collapsed=0 Collapsed=0
[Window][MMA Dashboard] [Window][MMA Dashboard]
Pos=2277,830 Pos=632,611
Size=1048,807 Size=1048,589
Collapsed=0 Collapsed=0
DockId=0x00000013,0 DockId=0x00000013,0
[Window][Log Management] [Window][Log Management]
Pos=2277,17 Pos=632,17
Size=1048,811 Size=1048,592
Collapsed=0 Collapsed=0
DockId=0x00000012,1 DockId=0x00000012,1
@@ -153,29 +153,39 @@ Size=262,209
Collapsed=0 Collapsed=0
[Window][Tier 1: Strategy] [Window][Tier 1: Strategy]
Pos=2277,830 Pos=632,611
Size=1048,807 Size=1048,589
Collapsed=0 Collapsed=0
DockId=0x00000013,1 DockId=0x00000013,1
[Window][Tier 2: Tech Lead] [Window][Tier 2: Tech Lead]
Pos=1687,1038 Pos=366,763
Size=588,599 Size=264,437
Collapsed=0 Collapsed=0
DockId=0x00000017,0 DockId=0x00000017,0
[Window][Tier 4: QA] [Window][Tier 4: QA]
Pos=949,1038 Pos=34,763
Size=736,599 Size=330,437
Collapsed=0 Collapsed=0
DockId=0x00000016,0 DockId=0x00000016,0
[Window][Tier 3: Workers] [Window][Tier 3: Workers]
Pos=949,466 Pos=34,345
Size=1326,570 Size=596,416
Collapsed=0 Collapsed=0
DockId=0x00000014,0 DockId=0x00000014,0
[Window][Approve PowerShell Command]
Pos=649,435
Size=381,329
Collapsed=0
[Window][Last Script Output]
Pos=60,60
Size=800,600
Collapsed=0
[Table][0xFB6E3870,4] [Table][0xFB6E3870,4]
RefScale=13 RefScale=13
Column 0 Width=80 Column 0 Width=80
@@ -202,7 +212,7 @@ Column 3 Weight=1.0000
DockNode ID=0x00000008 Pos=3125,170 Size=593,1157 Split=Y DockNode ID=0x00000008 Pos=3125,170 Size=593,1157 Split=Y
DockNode ID=0x00000009 Parent=0x00000008 SizeRef=1029,147 Selected=0x0469CA7A DockNode ID=0x00000009 Parent=0x00000008 SizeRef=1029,147 Selected=0x0469CA7A
DockNode ID=0x0000000A Parent=0x00000008 SizeRef=1029,145 Selected=0xDF822E02 DockNode ID=0x0000000A Parent=0x00000008 SizeRef=1029,145 Selected=0xDF822E02
DockSpace ID=0xAFC85805 Window=0x079D3A04 Pos=0,17 Size=3325,1620 Split=Y DockSpace ID=0xAFC85805 Window=0x079D3A04 Pos=0,17 Size=1680,1183 Split=Y
DockNode ID=0x0000000C Parent=0xAFC85805 SizeRef=1362,1041 Split=X Selected=0x5D11106F DockNode ID=0x0000000C Parent=0xAFC85805 SizeRef=1362,1041 Split=X Selected=0x5D11106F
DockNode ID=0x00000003 Parent=0x0000000C SizeRef=2056,1183 Split=X DockNode ID=0x00000003 Parent=0x0000000C SizeRef=2056,1183 Split=X
DockNode ID=0x0000000B Parent=0x00000003 SizeRef=404,1186 Split=Y Selected=0xF4139CA2 DockNode ID=0x0000000B Parent=0x00000003 SizeRef=404,1186 Split=Y Selected=0xF4139CA2
@@ -211,7 +221,7 @@ DockSpace ID=0xAFC85805 Window=0x079D3A04 Pos=0,17 Size=3325,162
DockNode ID=0x00000005 Parent=0x00000007 SizeRef=295,824 Selected=0xF4139CA2 DockNode ID=0x00000005 Parent=0x00000007 SizeRef=295,824 Selected=0xF4139CA2
DockNode ID=0x00000006 Parent=0x00000007 SizeRef=295,995 CentralNode=1 Selected=0x7BD57D6A DockNode ID=0x00000006 Parent=0x00000007 SizeRef=295,995 CentralNode=1 Selected=0x7BD57D6A
DockNode ID=0x0000000E Parent=0x00000002 SizeRef=1326,858 Split=Y Selected=0x418C7449 DockNode ID=0x0000000E Parent=0x00000002 SizeRef=1326,858 Split=Y Selected=0x418C7449
DockNode ID=0x00000010 Parent=0x0000000E SizeRef=868,447 Selected=0x418C7449 DockNode ID=0x00000010 Parent=0x0000000E SizeRef=868,447 Selected=0xB4CBF21A
DockNode ID=0x00000011 Parent=0x0000000E SizeRef=868,1171 Split=Y Selected=0x655BC6E9 DockNode ID=0x00000011 Parent=0x0000000E SizeRef=868,1171 Split=Y Selected=0x655BC6E9
DockNode ID=0x00000014 Parent=0x00000011 SizeRef=1469,570 Selected=0x655BC6E9 DockNode ID=0x00000014 Parent=0x00000011 SizeRef=1469,570 Selected=0x655BC6E9
DockNode ID=0x00000015 Parent=0x00000011 SizeRef=1469,599 Split=X Selected=0x5CDB7A4B DockNode ID=0x00000015 Parent=0x00000011 SizeRef=1469,599 Split=X Selected=0x5CDB7A4B