diff --git a/plans/PLAN-tool-result-display.md b/plans/PLAN-tool-result-display.md
new file mode 100644
index 0000000..494dcdf
--- /dev/null
+++ b/plans/PLAN-tool-result-display.md
@@ -0,0 +1,456 @@
+# Plan: Tool Result Display in AMC Dashboard
+
+> **Status:** Draft — awaiting review and mockup phase
+> **Author:** Claude + Taylor
+> **Created:** 2026-02-27
+
+## Summary
+
+Add the ability to view tool call results (diffs, bash output, file contents) directly in the AMC dashboard conversation view. Currently, users see that a tool was called but cannot see what it did. This feature brings Claude Code's result visibility to the multi-agent dashboard.
+
+### Goals
+
+1. **See code changes as they happen** — diffs from Edit/Write tools always visible
+2. **Debug agent behavior** — inspect Bash output, Read content, search results
+3. **Match Claude Code UX** — familiar expand/collapse behavior with latest results expanded
+
+### Non-Goals (v1)
+
+- Codex agent support (different JSONL format — deferred to v2)
+- Copy-to-clipboard functionality
+- Virtual scrolling / performance optimization
+- Editor integration (clicking paths to open files)
+
+---
+
+## User Workflows
+
+### Workflow 1: Watching an Active Session
+
+1. User opens a session card showing an active Claude agent
+2. Agent calls Edit tool to modify a file
+3. User immediately sees the diff expanded below the tool call pill
+4. Agent calls Bash to run tests
+5. User sees bash output expanded, previous Edit diff stays expanded (it's a diff)
+6. Agent sends a text message explaining results
+7. Bash output collapses (new assistant message arrived), Edit diff stays expanded
+
+### Workflow 2: Reviewing a Completed Session
+
+1. User opens a completed session to review what the agent did
+2. All tool calls are collapsed by default (no "latest" assistant message)
+3. Exception: Edit/Write diffs are still expanded
+4. User clicks a Bash tool call to see what command ran and its output
+5. User clicks "Show full output" when output is truncated
+6. Lightweight modal opens with full scrollable content
+7. User closes modal and continues reviewing
+
+### Workflow 3: Debugging a Failed Tool Call
+
+1. Agent runs a Bash command that fails
+2. Tool result block shows with red-tinted background
+3. stderr content is visible, clearly marked as error
+4. User can see what went wrong without leaving the dashboard
+
+---
+
+## Acceptance Criteria
+
+### Display Behavior
+
+- **AC-1:** Tool calls render as expandable elements showing tool name and summary
+- **AC-2:** Clicking a collapsed tool call expands to show its result
+- **AC-3:** Clicking an expanded tool call collapses it
+- **AC-4:** Tool results in the most recent assistant message are expanded by default
+- **AC-5:** When a new assistant message arrives, previous tool results collapse
+- **AC-6:** Edit and Write tool diffs remain expanded regardless of message age
+- **AC-7:** Tool calls without results display as non-expandable with muted styling
+
+### Diff Rendering
+
+- **AC-8:** Edit/Write results display structuredPatch data as syntax-highlighted diff
+- **AC-9:** Diff additions render with VS Code dark theme green background (rgba(46, 160, 67, 0.15))
+- **AC-10:** Diff deletions render with VS Code dark theme red background (rgba(248, 81, 73, 0.15))
+- **AC-11:** Full file path displays above each diff block
+- **AC-12:** Diff context lines use structuredPatch as-is (no recomputation)
+
+### Other Tool Types
+
+- **AC-13:** Bash results display stdout in monospace, stderr separately if present
+- **AC-14:** Read results display file content with syntax highlighting based on file extension
+- **AC-15:** Grep/Glob results display file list with match counts
+- **AC-16:** WebFetch results display URL and response summary
+
+### Truncation
+
+- **AC-17:** Long outputs truncate at thresholds matching Claude Code behavior
+- **AC-18:** Truncated outputs show "Show full output (N lines)" link
+- **AC-19:** Clicking "Show full output" opens a dedicated lightweight modal
+- **AC-20:** Modal displays full content with syntax highlighting, scrollable
+
+### Error States
+
+- **AC-21:** Failed tool calls display with red-tinted background
+- **AC-22:** Error content (stderr, error messages) is clearly distinguishable from success content
+- **AC-23:** is_error flag from tool_result determines error state
+
+### API Contract
+
+- **AC-24:** /api/conversation response includes tool results nested in tool_calls
+- **AC-25:** Each tool_call has: name, id, input, result (when available)
+- **AC-26:** Result structure varies by tool type (documented in IMP-SERVER)
+
+---
+
+## Architecture
+
+### Why Two-Pass JSONL Parsing
+
+The Claude Code JSONL stores tool_use and tool_result as separate entries linked by tool_use_id. To nest results inside tool_calls for the API response, the server must:
+
+1. First pass: Build a map of tool_use_id → toolUseResult
+2. Second pass: Parse messages, attaching results to matching tool_calls
+
+This adds parsing overhead but keeps the API contract simple. Alternatives considered:
+- **Streaming/incremental:** More complex, doesn't help since we need full conversation anyway
+- **Client-side joining:** Shifts complexity to frontend, increases payload size
+
+### Why Render Everything, Not Virtual Scroll
+
+Sessions typically have 20-80 tool calls. Modern browsers handle hundreds of DOM elements efficiently. Virtual scrolling adds significant complexity (measuring, windowing, scroll position management) for marginal benefit.
+
+Decision: Ship simple, measure real-world performance, optimize if >100ms render times observed.
+
+### Why Dedicated Modal Over Inline Expansion
+
+Full output can be thousands of lines. Inline expansion would:
+- Push other content out of view
+- Make scrolling confusing
+- Lose context of surrounding conversation
+
+A modal provides a focused reading experience without disrupting conversation layout.
+
+### Component Structure
+
+```
+MessageBubble
+├── Content (text)
+├── Thinking (existing)
+└── ToolCallList (new)
+ └── ToolCallItem (repeated)
+ ├── Header (pill: chevron, name, summary, status)
+ └── ResultContent (conditional)
+ ├── DiffResult (for Edit/Write)
+ ├── BashResult (for Bash)
+ ├── FileListResult (for Glob/Grep)
+ └── GenericResult (fallback)
+
+FullOutputModal (new, top-level)
+├── Header (tool name, file path)
+├── Content (full output, scrollable)
+└── CloseButton
+```
+
+---
+
+## Implementation Specifications
+
+### IMP-SERVER: Parse and Attach Tool Results
+
+**Fulfills:** AC-24, AC-25, AC-26
+
+**Location:** `amc_server/mixins/conversation.py`
+
+**Changes to `_parse_claude_conversation`:**
+
+Two-pass parsing:
+1. First pass: Scan all entries, build map of `tool_use_id` → `toolUseResult`
+2. Second pass: Parse messages as before, but when encountering `tool_use`, lookup and attach result
+
+**Tool call schema after change:**
+```python
+{
+ "name": "Edit",
+ "id": "toolu_abc123",
+ "input": {"file_path": "...", "old_string": "...", "new_string": "..."},
+ "result": {
+ "content": "The file has been updated successfully.",
+ "is_error": False,
+ "structuredPatch": [...],
+ "filePath": "...",
+ # ... other fields from toolUseResult
+ }
+}
+```
+
+**Result Structure by Tool Type:**
+
+| Tool | Result Fields |
+|------|---------------|
+| Edit | `structuredPatch`, `filePath`, `oldString`, `newString` |
+| Write | `filePath`, content confirmation |
+| Read | `file`, `type`, content in `content` field |
+| Bash | `stdout`, `stderr`, `interrupted` |
+| Glob | `filenames`, `numFiles`, `truncated` |
+| Grep | `content`, `filenames`, `numFiles`, `numLines` |
+
+---
+
+### IMP-TOOLCALL: Expandable Tool Call Component
+
+**Fulfills:** AC-1, AC-2, AC-3, AC-4, AC-5, AC-6, AC-7
+
+**Location:** `dashboard/lib/markdown.js` (refactor `renderToolCalls`)
+
+**New function: `ToolCallItem`**
+
+Renders a single tool call with:
+- Chevron for expand/collapse (when result exists and not Edit/Write)
+- Tool name (bold, colored)
+- Summary (from existing `getToolSummary`)
+- Status icon (checkmark or X)
+- Result content (when expanded)
+
+**State Management:**
+
+Track expanded state per message. When new assistant message arrives:
+- Compare latest assistant message ID to stored ID
+- If different, reset expanded set to empty
+- Edit/Write tools bypass this logic (always expanded via CSS/logic)
+
+---
+
+### IMP-DIFF: Diff Rendering Component
+
+**Fulfills:** AC-8, AC-9, AC-10, AC-11, AC-12
+
+**Location:** `dashboard/lib/markdown.js` (new function `renderDiff`)
+
+**Add diff language to highlight.js:**
+```javascript
+import langDiff from 'https://esm.sh/highlight.js@11.11.1/lib/languages/diff';
+hljs.registerLanguage('diff', langDiff);
+```
+
+**Diff Renderer:**
+
+1. Convert `structuredPatch` array to unified diff text:
+ - Each hunk: `@@ -oldStart,oldLines +newStart,newLines @@`
+ - Followed by hunk.lines array
+2. Syntax highlight with hljs diff language
+3. Sanitize with DOMPurify before rendering
+4. Wrap in container with file path header
+
+**CSS styling:**
+- Container: dark border, rounded corners
+- Header: muted background, monospace font, full file path
+- Content: monospace, horizontal scroll
+- Additions: `background: rgba(46, 160, 67, 0.15)`
+- Deletions: `background: rgba(248, 81, 73, 0.15)`
+
+---
+
+### IMP-BASH: Bash Output Component
+
+**Fulfills:** AC-13, AC-21, AC-22
+
+**Location:** `dashboard/lib/markdown.js` (new function `renderBashResult`)
+
+Renders:
+- `stdout` in monospace pre block
+- `stderr` in separate block with error styling (if present)
+- "Command interrupted" notice (if interrupted flag)
+
+Error state: `is_error` or presence of stderr triggers error styling (red tint, left border).
+
+---
+
+### IMP-TRUNCATE: Output Truncation
+
+**Fulfills:** AC-17, AC-18
+
+**Truncation Thresholds (match Claude Code):**
+
+| Tool Type | Max Lines | Max Chars |
+|-----------|-----------|-----------|
+| Bash stdout | 100 | 10000 |
+| Bash stderr | 50 | 5000 |
+| Read content | 500 | 50000 |
+| Grep matches | 100 | 10000 |
+| Glob files | 100 | 5000 |
+
+**Note:** These thresholds need verification against Claude Code behavior. May require adjustment based on testing.
+
+**Truncation Helper:**
+
+Takes content string, returns `{ text, truncated, totalLines }`. If truncated, result renderers show "Show full output (N lines)" link.
+
+---
+
+### IMP-MODAL: Full Output Modal
+
+**Fulfills:** AC-19, AC-20
+
+**Location:** `dashboard/components/FullOutputModal.js` (new file)
+
+**Structure:**
+- Overlay (click to close)
+- Modal container (click does NOT close)
+- Header: title (tool name + file path), close button
+- Content: scrollable pre/code block with syntax highlighting
+
+**Integration:** Modal state managed at App level or ChatMessages level. "Show full output" link sets state with content + metadata.
+
+---
+
+### IMP-ERROR: Error State Styling
+
+**Fulfills:** AC-21, AC-22, AC-23
+
+**Styling:**
+- Tool call header: red-tinted background when `result.is_error`
+- Status icon: red X instead of green checkmark
+- Bash stderr: red text, italic, distinct from stdout
+- Overall: left border accent in error color
+
+---
+
+## Rollout Slices
+
+### Slice 1: Design Mockups (Pre-Implementation)
+
+**Goal:** Validate visual design before building
+
+**Deliverables:**
+1. Create `/mockups` test route with static data
+2. Implement 3-4 design variants (card-based, minimal, etc.)
+3. Use real tool result data from session JSONL
+4. User reviews and selects preferred design
+
+**Exit Criteria:** Design direction locked
+
+---
+
+### Slice 2: Server-Side Tool Result Parsing
+
+**Goal:** API returns tool results nested in tool_calls
+
+**Deliverables:**
+1. Two-pass parsing in `_parse_claude_conversation`
+2. Tool results attached with `id` field
+3. Unit tests for result attachment
+4. Handle missing results gracefully (return tool_call without result)
+
+**Exit Criteria:** AC-24, AC-25, AC-26 pass
+
+---
+
+### Slice 3: Basic Expand/Collapse UI
+
+**Goal:** Tool calls are expandable, show raw result content
+
+**Deliverables:**
+1. Refactor `renderToolCalls` to `ToolCallList` component
+2. Implement expand/collapse with chevron
+3. Track expanded state per message
+4. Collapse on new assistant message
+5. Keep Edit/Write always expanded
+
+**Exit Criteria:** AC-1 through AC-7 pass
+
+---
+
+### Slice 4: Diff Rendering
+
+**Goal:** Edit/Write show beautiful diffs
+
+**Deliverables:**
+1. Add diff language to highlight.js
+2. Implement `renderDiff` function
+3. VS Code dark theme styling
+4. Full file path header
+
+**Exit Criteria:** AC-8 through AC-12 pass
+
+---
+
+### Slice 5: Other Tool Types
+
+**Goal:** Bash, Read, Glob, Grep render appropriately
+
+**Deliverables:**
+1. `renderBashResult` with stdout/stderr separation
+2. `renderFileContent` for Read
+3. `renderFileList` for Glob/Grep
+4. Generic fallback for unknown tools
+
+**Exit Criteria:** AC-13 through AC-16 pass
+
+---
+
+### Slice 6: Truncation and Modal
+
+**Goal:** Long outputs truncate with modal expansion
+
+**Deliverables:**
+1. Truncation helper with Claude Code thresholds
+2. "Show full output" link
+3. `FullOutputModal` component
+4. Syntax highlighting in modal
+
+**Exit Criteria:** AC-17 through AC-20 pass
+
+---
+
+### Slice 7: Error States and Polish
+
+**Goal:** Failed tools visually distinct, edge cases handled
+
+**Deliverables:**
+1. Error state styling (red tint)
+2. Muted styling for missing results
+3. Test with interrupted sessions
+4. Cross-browser testing
+
+**Exit Criteria:** AC-21 through AC-23 pass, feature complete
+
+---
+
+## Open Questions
+
+1. **Exact Claude Code truncation thresholds** — need to verify against Claude Code source or experiment
+2. **Performance with 100+ tool calls** — monitor after ship, optimize if needed
+3. **Codex support timeline** — when should we prioritize v2?
+
+---
+
+## Appendix: Research Findings
+
+### Claude Code JSONL Format
+
+Tool calls and results are stored as separate entries:
+
+```json
+// Assistant sends tool_use
+{"type": "assistant", "message": {"content": [{"type": "tool_use", "id": "toolu_abc", "name": "Edit", "input": {...}}]}}
+
+// Result in separate user entry
+{"type": "user", "message": {"content": [{"type": "tool_result", "tool_use_id": "toolu_abc", "content": "Success"}]}, "toolUseResult": {...}}
+```
+
+The `toolUseResult` object contains rich structured data varying by tool type.
+
+### Missing Results Statistics
+
+Across 55 sessions with 2,063 tool calls:
+- 11 missing results (0.5%)
+- Affected tools: Edit (4), Read (2), Bash (1), others
+
+### Interrupt Handling
+
+User interrupts create a separate user message:
+```json
+{"type": "user", "message": {"content": [{"type": "text", "text": "[Request interrupted by user for tool use]"}]}}
+```
+
+Tool results for completed tools are still present; the interrupt message indicates the turn ended early.
diff --git a/plans/subagent-visibility.md b/plans/subagent-visibility.md
index 7bf368c..878b89a 100644
--- a/plans/subagent-visibility.md
+++ b/plans/subagent-visibility.md
@@ -1,1631 +1,51 @@
# Subagent & Agent Team Visibility for AMC
-> **Version**: 1.0
> **Status**: Draft
-> **Last Updated**: 2026-02-26
+> **Last Updated**: 2026-02-27
## Summary
-Add comprehensive visibility into Claude Code subagents and agent teams within AMC. This enables users to monitor, understand, and debug multi-agent workflows by surfacing subagent activity, team coordination, and task progress directly in the dashboard.
+Add a button in the turn stats section showing the count of active subagents/team members. Clicking it opens a list with names and lifetime stats (time taken, tokens used). Mirrors Claude Code's own agent display.
---
-## Problem Statement
+## User Workflow
-Claude Code supports two multi-agent patterns that are currently invisible to AMC:
-
-1. **Subagents** (via Task tool): Lightweight, isolated workers that execute in parallel and report results back to a parent session. Their conversations are stored in separate JSONL files but never surfaced.
-
-2. **Agent Teams**: Full independent sessions with bidirectional messaging, shared task lists, and coordinated work. Team state lives in separate directories but is never parsed.
-
-Without visibility into these, users cannot:
-- See what subagents are doing in real-time
-- Understand which tasks are being worked on by which agents
-- Debug multi-agent coordination failures
-- Monitor team member status and progress
-- Trace the parent-child relationships in complex workflows
-
----
-
-## User Workflows
-
-### Workflow 1: Monitor Active Subagents
-
-**Actor**: User with a Claude Code session that spawned subagents
-
-**Flow**:
-1. User views session card in AMC dashboard
-2. Dashboard shows "3 subagents active" indicator
-3. User clicks to expand subagent panel
-4. Each subagent shows: type, status (running/complete), duration, last activity
-5. User clicks a subagent to view its conversation in a modal
-6. Real-time updates show subagent progress without refresh
-
-### Workflow 2: Team Dashboard
-
-**Actor**: User running an agent team
-
-**Flow**:
-1. AMC discovers active team from `~/.claude/teams/`
-2. Dashboard shows team card with: team name, description, member count
-3. User expands to see all team members: name, type, model, status
-4. Task list panel shows: tasks by status (pending/in_progress/completed), assignments, dependencies
-5. User can view any teammate's conversation
-6. Real-time updates show task transitions and team messaging
-
-### Workflow 3: Trace Parent-Child Relationships
-
-**Actor**: User debugging a multi-agent workflow
-
-**Flow**:
-1. User views a subagent's conversation
-2. UI shows "Spawned by: [parent session]" with link
-3. User can navigate between parent and child conversations
-4. Parent conversation shows inline markers: "[Spawned: code-reviewer → completed 2m ago]"
-5. User understands the full workflow lineage
+1. User views a session card in AMC
+2. Turn stats area shows: `2h 15m | 84k tokens | 3 agents`
+3. User clicks "3 agents" button
+4. List opens showing:
+ ```
+ claude-code-guide (running) 12m 42,000 tokens
+ Explore (completed) 3m 18,500 tokens
+ Explore (completed) 5m 23,500 tokens
+ ```
+5. List updates in real-time as agents complete
---
## Acceptance Criteria
-### Subagent Discovery
+### Discovery
-- **AC-1**: AMC discovers subagent JSONL files in `{session_dir}/subagents/agent-*.jsonl`
-- **AC-2**: Subagent metadata (agentId, type, status) is extracted from JSONL headers
-- **AC-3**: Subagents are associated with their parent session via shared `sessionId`
-- **AC-4**: Subagent discovery runs during state collection (existing poll interval)
+- **AC-1**: Subagent JSONL files discovered at `{session_dir}/subagents/agent-*.jsonl`
+- **AC-2**: Both regular subagents (Task tool) and team members (Task with `team_name`) are discovered from same location
-### Subagent Display
+### Status Detection
-- **AC-5**: Session cards show subagent count badge when subagents exist
-- **AC-6**: Expandable panel lists all subagents with: type, status, duration, last activity
-- **AC-7**: Clicking a subagent opens its conversation in a modal
-- **AC-8**: Subagent conversation parsing reuses existing `_parse_claude_conversation` logic
+- **AC-3**: Subagent is "running" if: parent session is alive AND last assistant entry has `stop_reason != "end_turn"`
+- **AC-4**: Subagent is "completed" if: last assistant entry has `stop_reason == "end_turn"` OR parent session is dead
-### Subagent Status
+### Stats Extraction
-- **AC-9**: Running subagents show animated activity indicator
-- **AC-10**: Completed subagents show completion status with final message summary
-- **AC-11**: Subagent status is derived from: file mtime recency, presence of stop markers
+- **AC-5**: Subagent name extracted from parent's Task tool invocation: use `name` if present (team member), else `subagent_type`
+- **AC-6**: Lifetime duration = first entry timestamp to last entry timestamp (or now if running)
+- **AC-7**: Lifetime tokens = sum of all assistant entries' `usage.input_tokens + usage.output_tokens`
-### Team Discovery
+### UI
-- **AC-12**: AMC discovers teams from `~/.claude/teams/*/config.json`
-- **AC-13**: Team config is parsed: name, description, leadAgentId, members[]
-- **AC-14**: Team tasks are discovered from `~/.claude/tasks/{team_name}/`
-- **AC-15**: Team member sessions are linked to their corresponding session files
-
-### Team Display
-
-- **AC-16**: Teams appear as distinct cards in the dashboard (separate from individual sessions)
-- **AC-17**: Team card shows: name, description, member count, active task count
-- **AC-18**: Expanded team view shows member list with: name, type, model, status
-- **AC-19**: Task panel shows tasks grouped by status with assignment indicators
-
-### Parent-Child Linking
-
-- **AC-20**: Subagent conversations show "Spawned by: [parent]" header with navigation link
-- **AC-21**: Parent conversations show inline markers for spawned subagents with status
-- **AC-22**: Users can navigate bidirectionally between parent and child conversations
-
-### Real-Time Updates
-
-- **AC-23**: Subagent status updates in real-time via existing SSE/polling mechanism
-- **AC-24**: Team state changes (task status, member joins) propagate without refresh
-- **AC-25**: File mtime tracking extends to subagent and team state files
-
----
-
-## Architecture
-
-### Data Model Extensions
-
-```python
-# Existing Session model gains new fields:
-{
- "session_id": "uuid",
- "agent": "claude",
- # ... existing fields ...
-
- # NEW: Subagent information
- "subagents": [
- {
- "agent_id": "a510a6b",
- "subagent_type": "Explore", # from Task tool input
- "status": "running|completed",
- "started_at": "iso-timestamp",
- "completed_at": "iso-timestamp|null",
- "last_activity_at": "iso-timestamp",
- "message_count": 42,
- "transcript_path": "/path/to/agent-xxx.jsonl"
- }
- ],
-
- # NEW: Team membership (if this session is a team member)
- "team": {
- "name": "mission-control",
- "role": "lead|member",
- "member_name": "team-lead" # from team config
- }
-}
-
-# NEW: Team model (separate from sessions)
-{
- "type": "team",
- "name": "mission-control",
- "description": "Building Mission Control...",
- "lead_agent_id": "team-lead@mission-control",
- "lead_session_id": "uuid",
- "members": [
- {
- "agent_id": "team-lead@mission-control",
- "name": "team-lead",
- "agent_type": "team-lead",
- "model": "claude-opus-4-5-20251101",
- "status": "active|idle|offline",
- "session_id": "uuid|null" # linked to session if found
- }
- ],
- "tasks": {
- "pending": 5,
- "in_progress": 2,
- "completed": 12,
- "items": [...] # optional: full task list
- },
- "created_at": "iso-timestamp",
- "config_mtime_ns": 123456789
-}
-```
-
-### File System Locations
-
-| Data Type | Path Pattern |
-|-----------|--------------|
-| Parent session | `~/.claude/projects/{project}/{session_id}.jsonl` |
-| Subagent transcript | `~/.claude/projects/{project}/{session_id}/subagents/agent-{agentId}.jsonl` |
-| Team config | `~/.claude/teams/{team_name}/config.json` |
-| Team inboxes | `~/.claude/teams/{team_name}/inboxes/` |
-| Team tasks | `~/.claude/tasks/{team_name}/` |
-
-### Parsing Logic
-
-#### Subagent Discovery (in StateMixin)
-
-```python
-def _discover_subagents(self, session_id, project_dir):
- """Discover subagent JSONL files for a session."""
- subagents = []
- session_dir = self._get_session_dir(session_id, project_dir)
- subagents_dir = session_dir / "subagents"
-
- if not subagents_dir.exists():
- return subagents
-
- for jsonl_file in subagents_dir.glob("agent-*.jsonl"):
- # Extract agent_id from filename: agent-{agentId}.jsonl
- agent_id = jsonl_file.stem.replace("agent-", "")
-
- # Parse first entry for metadata
- metadata = self._parse_subagent_metadata(jsonl_file)
-
- subagents.append({
- "agent_id": agent_id,
- "subagent_type": metadata.get("subagent_type", "unknown"),
- "status": self._determine_subagent_status(jsonl_file),
- "started_at": metadata.get("started_at"),
- "last_activity_at": self._get_file_mtime_iso(jsonl_file),
- "message_count": metadata.get("message_count", 0),
- "transcript_path": str(jsonl_file),
- })
-
- return subagents
-```
-
-#### Subagent Type Extraction
-
-The subagent type is found in the parent session's Task tool invocation:
-
-```python
-def _extract_subagent_type_from_parent(self, parent_jsonl, agent_id):
- """Extract subagent_type from parent's Task tool call that spawned this agent."""
- for line in parent_jsonl.read_text().splitlines():
- entry = json.loads(line)
-
- # Look for progress entries with this agent's ID
- if entry.get("type") == "progress":
- data = entry.get("data", {})
- if data.get("agentId") == agent_id:
- # The parentToolUseID links to the Task tool invocation
- parent_tool_use_id = entry.get("parentToolUseID")
- # Now find the Task tool_use with that ID to get subagent_type
- ...
-```
-
-#### Team Discovery (new mixin: TeamMixin)
-
-```python
-class TeamMixin:
- TEAMS_DIR = Path.home() / ".claude" / "teams"
- TASKS_DIR = Path.home() / ".claude" / "tasks"
-
- def _discover_teams(self):
- """Discover all active teams."""
- teams = []
-
- if not self.TEAMS_DIR.exists():
- return teams
-
- for team_dir in self.TEAMS_DIR.iterdir():
- if not team_dir.is_dir():
- continue
-
- config_file = team_dir / "config.json"
- if not config_file.exists():
- continue
-
- try:
- config = json.loads(config_file.read_text())
- team = self._build_team_model(config, team_dir.name)
- teams.append(team)
- except (json.JSONDecodeError, OSError):
- continue
-
- return teams
-
- def _build_team_model(self, config, team_name):
- """Build team model from config.json."""
- members = []
- for m in config.get("members", []):
- members.append({
- "agent_id": m.get("agentId"),
- "name": m.get("name"),
- "agent_type": m.get("agentType"),
- "model": m.get("model"),
- "status": self._determine_member_status(m),
- "session_id": self._find_member_session(m),
- })
-
- return {
- "type": "team",
- "name": config.get("name", team_name),
- "description": config.get("description", ""),
- "lead_agent_id": config.get("leadAgentId"),
- "lead_session_id": config.get("leadSessionId"),
- "members": members,
- "tasks": self._load_team_tasks(team_name),
- "created_at": self._timestamp_to_iso(config.get("createdAt")),
- }
-```
-
-### API Extensions
-
-```
-GET /api/state
- Response adds:
- {
- "sessions": [...], // existing, now includes subagents[]
- "teams": [...], // NEW: active teams
- "server_time": "..."
- }
-
-GET /api/conversation/{session_id}
- Existing endpoint works for subagents too (by agent_id)
-
-GET /api/subagent/{session_id}/{agent_id}
- NEW: Get subagent conversation specifically
-
-GET /api/team/{team_name}
- NEW: Get detailed team info including tasks
-
-GET /api/team/{team_name}/tasks
- NEW: Get full task list for a team
-```
-
-### Dashboard Components
-
-#### SubagentPanel.js
-
-```javascript
-function SubagentPanel({ subagents, onSelectSubagent }) {
- if (!subagents?.length) return null;
-
- return (
-
- );
-}
-```
-
-### IMP-9: App.js State Integration
-
-**Fulfills**: AC-23, AC-24
-
-**File**: `dashboard/components/App.js` (modifications)
-
-Update state management to include teams:
-
-```javascript
-// Add teams to state
-const [teams, setTeams] = useState([]);
-
-// Update fetchState to handle teams
-const fetchState = useCallback(async () => {
- const response = await fetch('/api/state');
- const data = await response.json();
- setSessions(data.sessions || []);
- setTeams(data.teams || []); // NEW
-}, []);
-
-// Add handler for subagent selection
-const handleSelectSubagent = useCallback((session, subagent) => {
- setSelectedSubagent({ session, subagent });
- setModalOpen(true);
-}, []);
-
-// Render teams section
-{teams.length > 0 && (
-
-
Agent Teams
- {teams.map(team => (
-
- ))}
-
-)}
-```
-
----
-
-## Rollout Slices
-
-### Slice 1: Subagent Discovery (Backend)
-
-**Goal**: Discover and expose subagent metadata through the API
-
-**Tasks**:
-1. Create `SubagentMixin` class in `amc_server/mixins/subagent.py`
-2. Implement `_discover_subagents_for_session()`
-3. Implement `_parse_subagent_metadata()`
-4. Implement `_extract_subagent_type_from_parent()`
-5. Integrate with `StateMixin._collect_sessions()`
-6. Add unit tests for subagent parsing
-
-**Verification**:
-- `GET /api/state` returns `subagents[]` for sessions with subagents
-- Subagent type is correctly extracted from parent
-- Status detection (running/completed) works
-
-### Slice 2: Subagent UI (Frontend)
-
-**Goal**: Display subagents in the dashboard
-
-**Tasks**:
-1. Create `SubagentPanel.js` component
-2. Add subagent styles to `styles.css`
-3. Integrate panel into `SessionCard.js`
-4. Add subagent conversation endpoint (`/api/subagent/{session_id}/{agent_id}`)
-5. Add modal view for subagent conversations
-6. Test with real sessions
-
-**Verification**:
-- Session cards show subagent badge
-- Expanding shows subagent list
-- Clicking subagent opens conversation modal
-- Running subagents show activity indicator
-
-### Slice 3: Team Discovery (Backend)
-
-**Goal**: Discover and expose team metadata through the API
-
-**Tasks**:
-1. Create `TeamMixin` class in `amc_server/mixins/team.py`
-2. Implement `_discover_teams()`
-3. Implement `_build_team_model()`
-4. Implement `_load_team_tasks_summary()`
-5. Implement `_link_team_members_to_sessions()`
-6. Integrate with `StateMixin._build_state_payload()`
-7. Add unit tests
-
-**Verification**:
-- `GET /api/state` returns `teams[]` when teams exist
-- Team members are linked to sessions
-- Task counts are accurate
-
-### Slice 4: Team UI (Frontend)
-
-**Goal**: Display teams in the dashboard
-
-**Tasks**:
-1. Create `TeamCard.js` component
-2. Create `TeamMemberList` and `TeamTaskSummary` subcomponents
-3. Add team styles to `styles.css`
-4. Integrate into `App.js`
-5. Add team detail endpoint (`/api/team/{team_name}`)
-6. Add navigation from team member to session
-
-**Verification**:
-- Teams appear in dashboard
-- Member list shows status
-- Task progress bar renders correctly
-- Can navigate to member sessions
-
-### Slice 5: Polish & Integration
-
-**Goal**: Complete integration and edge case handling
-
-**Tasks**:
-1. Add parent-child navigation in conversation modals
-2. Handle stale/cleaned-up subagent files gracefully
-3. Add loading states for async data
-4. Test with real multi-agent workflows
-5. Performance testing with many subagents
-6. Documentation update
-
-**Verification**:
-- End-to-end workflow works
-- No errors with missing/stale files
-- Performance acceptable with 10+ subagents
-- README updated
-
----
-
-## Testing Strategy
-
-### Unit Tests
-
-1. **Subagent parsing**:
- - Extract agent_id from various filename formats
- - Parse metadata from sample JSONL
- - Extract subagent_type from parent Task tool calls
- - Status detection logic
-
-2. **Team parsing**:
- - Parse sample config.json
- - Load task summaries
- - Timestamp conversion
- - Member-session linking
-
-### Integration Tests
-
-1. **API endpoints**:
- - `/api/state` includes subagents and teams
- - `/api/subagent/{session_id}/{agent_id}` returns conversation
- - `/api/team/{team_name}` returns team details
-
-### Manual Testing
-
-1. Run Claude Code with Task tool to create subagents
-2. Verify subagents appear in dashboard
-3. Create agent team with TeamCreate
-4. Verify team appears in dashboard
-5. Test navigation between parent and child conversations
-
----
-
-## Open Questions
-
-1. **Performance**: With many subagents (10+), should we lazy-load conversations or batch?
-
-2. **Staleness**: How long should completed subagent data persist? Follow session cleanup rules?
-
-3. **Team member sessions**: How to reliably link team members to their sessions when multiple sessions exist?
-
-4. **Real-time updates**: Should we add file watchers for subagent/team directories, or rely on polling?
-
----
-
-## References
-
-- Claude Code Task tool documentation
-- Claude Code agent teams documentation
-- Existing AMC codebase (mixins, dashboard)
-- Sample JSONL files from actual sessions
+- **AC-8**: Turn stats area shows agent count button when subagents exist
+- **AC-9**: Button shows count + running indicator (e.g., "3 agents" or "2 agents (1 running)")
+- **AC-10**: Clicking button opens popover with: name, status, duration, token count
+- **AC-11**: Running agents show activity indicator
+- **AC-12**: List updates via existing polling/SSE