chore(conductor): Archive track 'mma_agent_focus_ux_20260302'
This commit is contained in:
@@ -1,5 +0,0 @@
|
||||
# Track mma_agent_focus_ux_20260302 Context
|
||||
|
||||
- [Specification](./spec.md)
|
||||
- [Implementation Plan](./plan.md)
|
||||
- [Metadata](./metadata.json)
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"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."
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
# 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 [checkpoint: bc1a570]
|
||||
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.
|
||||
|
||||
- [x] Task 1.1: Add `current_tier` module variable to `ai_client.py`. 8d9f25d
|
||||
- [x] Task 1.2: Stamp `source_tier` in `_append_comms`. 8d9f25d
|
||||
- [x] Task 1.3: Set/clear `current_tier` in `run_worker_lifecycle` (Tier 3). 8d9f25d
|
||||
- [x] Task 1.4: Set/clear `current_tier` in `generate_tickets` (Tier 2). 8d9f25d
|
||||
- [x] Task 1.5: Migrate `_tool_log` from tuple to dict; update emission and storage. 8d9f25d
|
||||
- [x] Task 1.6: Write tests for Phase 1. 8 tests, 12/12 passed. 8d9f25d
|
||||
|
||||
- [x] Task 1.7: Conductor — User Manual Verification. App renders, comms history panel intact. 00a196c
|
||||
- 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 [checkpoint: 865d8dd]
|
||||
Focus: Update `_render_tool_calls_panel` to read dicts. No UI change — just fixes the access pattern before Phase 3 adds filter logic.
|
||||
|
||||
- [x] Task 2.1: Update `_render_tool_calls_panel` to use dict access. 865d8dd
|
||||
- **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.
|
||||
|
||||
- [x] Task 2.2: Write/run tests. 12/12 passed. 865d8dd
|
||||
- Run `uv run pytest tests/ -x -q`. Confirm tool log panel simulation tests (if any) pass.
|
||||
|
||||
- [x] Task 2.3: Conductor — User Manual Verification. 865d8dd
|
||||
- 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 [checkpoint: b30e563]
|
||||
Focus: Add the combo selector and filter the two log panels.
|
||||
|
||||
- [x] Task 3.1: Add `ui_focus_agent` state var to `App.__init__`. b30e563
|
||||
- [x] Task 3.2: Add Focus Agent selector widget in Operations Hub. b30e563
|
||||
- **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`.
|
||||
|
||||
- [x] Task 3.3: Add filter logic to `_render_comms_history_panel`. b30e563
|
||||
- **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.
|
||||
|
||||
- [x] Task 3.4: Add filter logic to `_render_tool_calls_panel`. b30e563
|
||||
- **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`.
|
||||
|
||||
- [x] Task 3.5: Write tests for Phase 3. 6 tests, 18/18 passed. b30e563
|
||||
- Test that `ui_focus_agent = "Tier 3"` filters out entries with `source_tier = "Tier 2"`.
|
||||
- Run `uv run pytest tests/ -x -q`.
|
||||
|
||||
- [x] Task 3.6: Conductor — User Manual Verification. UI confirmed by user. b30e563
|
||||
- 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: Review Fixes
|
||||
- [x] Task: Apply review suggestions febcf3b
|
||||
@@ -1,95 +0,0 @@
|
||||
# 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 1–4 stream panels (already tier-scoped via `mma_streams` stream_id key).
|
||||
Reference in New Issue
Block a user