# Code Assessment: Agents Dashboard

**Date**: 2026-05-28
**Scope**: Full source code review of all Python backend, JavaScript frontend, and infrastructure files.
**Revision**: 42 — Maintenance reassessment. New since rev 41: **migration 024 `024_update_default_model_to_opus_4_8.py`** bumps the default model from `claude-opus-4-7` to **`claude-opus-4-8`** across `items` and `agent_config` (with a clean `down()` reversing it); `constants.py` adds Opus 4.8 to `AVAILABLE_MODELS` and sets `DEFAULT_MODEL = "claude-opus-4-8"` (Opus 4.7/4.6/4.5 stay selectable). A dedicated migration test was added at `tests/unit/migrations/test_default_model_024.py` (4 tests). The agent **extended-thinking budget was raised from 10,000 to 32,000 tokens** in `src/agent/session.py`. Test counts grew organically (notably `test_item_state.py` 27→66, `test_workflow_service.py` 74→97, `test_routes.py` 85→94, `test_mcp_tool_servers.py` 52→57, `test_git_operations.py` 67→70, `test_create_todo_autostart.py` 13→21). The Phases 1–3 refactor still describes current state: the **`ItemState` finite state machine** in `src/domain/item_state.py` is the source of truth for the 13 reachable item states (encoded in DB as the existing `(column_name, status)` pair); `WorkflowService` routes start/pause/resume/cancel, clarify ask/answer, merge/conflict/complete, retry agent/merge through this FSM; **`ItemRepository`** and **`EpicRepository`** facade `DatabaseService` and own the column whitelists (`_WRITABLE_*_COLUMNS`); **`AgentConfig`'s** `tools`/`mcp_servers`/`plugins`/`allowed_commands`/`allowed_builtin_tools` are real Python types with JSON-string-tolerant validators. The auto-review agent (migration 022, `src/agent/review_agent.py`) and pause messages (migration 023) remain in place. **24 migrations**. **1035 tests across 33 Python test files** (excluding `conftest.py`); 7 E2E `.mjs` files (5 agent-spawning + 2 pure-HTTP). Python backend ~8,900 lines across 40 source files (excluding migrations). JavaScript frontend ~9,300 lines across 27 files. CSS ~3,850 lines across 5 files.

---

## Executive Summary

Agents Dashboard is a well-architected, production-quality AI agent orchestration platform. The architecture follows clean separation of concerns with 5 focused service classes on the backend, a new domain/repository layer underneath them, and 12 specialized dialog modules on the frontend. Since the previous assessment:

- **`ItemState` finite state machine (`src/domain/item_state.py`)**: the 13 reachable item states (BACKLOG, QUEUED, RUNNING, PAUSED, FAILED, CONFLICT, RESOLVING_CONFLICTS, CLARIFY, MERGE_BLOCKED, REVIEW, DONE, ARCHIVED, CANCELLED) and their transitions are now an explicit FSM. Storage encoding stays unchanged (`(column_name, status)` pair) via `from_columns` / `to_columns`. `WorkflowService` routes start/pause/resume/cancel, clarify ask/answer, merge/conflict/complete, retry agent/merge, and DnD-produced encodings through `transition(state, event)`. A startup state-encoding audit + stale-worktree cleanup uses the FSM to validate persisted state.
- **Repository layer (`src/repositories/`)**: `ItemRepository` provides read APIs (`get_or_raise`, `list_running`, `list_in_state`) plus state-changing operations (`transition()`, `update_fields()`, `move_item`); it owns `_WRITABLE_ITEM_COLUMNS` (renamed from `ALLOWED_ITEM_COLUMNS` in rev 41 — same semantics, now private to the module). `EpicRepository` provides the same shape for epics with `_WRITABLE_EPIC_COLUMNS`; the old `ALLOWED_EPIC_COLUMNS` whitelist was deleted from `database_service.py`. The repos are thin facades over `DatabaseService` for now.
- **AgentConfig Phase 3**: `tools`, `mcp_servers`, `plugins`, `allowed_commands`, `allowed_builtin_tools` are real Python types in the Pydantic model; validators tolerate JSON strings on input so DB rows still load.
- **Auto-approve workflow (migration 022)**: A new `auto_approve` integer column on `items` enables a per-item review-loop mode. After the main agent completes, `WorkflowService` invokes `run_auto_review` from `src/agent/review_agent.py` — a strictly read-only Claude session (`Read`/`Glob`/`Grep` allowed; `Edit`/`Write`/`Bash` denied via `PermissionResultDeny`) that emits a structured `DECISION` block. WorkflowService parses it and either approves the item or sends comments back as a request-changes cycle. Capped at 3 round trips (`_auto_approve_retries`) before falling back to human review. Two short-circuit paths exist: `_auto_approve_no_changes` (no diff → mark Done) and `_auto_approve_direct` (mode 2 → skip the reviewer call).
- **Pause messages (migration 023)**: A new `pause_message TEXT` column on `items` stores an optional comment supplied when pausing. The message is prepended to the resume prompt and cleared once the agent resumes — gives the user a way to redirect the agent without canceling.

Earlier capabilities still in place include **default model = Claude Opus 4.8** with Opus 4.7/4.6/4.5 and Sonnet 4.6 selectable (migrations 018, 019, 024), **multi-repo workspace support** (migration 020) with nullable `repo` column and `idx_items_repo` index, **annotation summary** (009), **epic grouping** (010), **item dependencies** (011), **auto-start pipelines** (012), **shortcuts bar** with **create_shortcut MCP tool**, **worktree file browsing**, **retry merge**, **bulk operations**, **animated flame background** (013) with intensity multiplier, **start_copy** (014), **has_file_changes detection** (015), **Ollama provider** (016) gated behind `--experimental`, and **WIP limit** (017) with queued auto-start.

The test suite includes **1035 automated tests across 33 Python test files** plus **7 E2E `.mjs` tests** via `run-e2e-tests.sh`, with comprehensive coverage for all 5 services, HTTP routes, WebSocket, git operations, agent sessions, MCP tools, diff isolation, command filtering, file browser routes, mini-MCP server protocol, epics, auto-start pipelines, annotation summary/prompt, Ollama provider configuration, multi-repo workspace mode, clarification context flow, the default-model migration (024), and orchestrator lifecycle. Auto-approve and pause-message coverage rides on `test_workflow_service.py`; migration 024's data migration has a dedicated test (`test_default_model_024.py`).

**Overall Rating**: **A** (Strong — clean architecture, well-decomposed services, robust security posture)

---

## Architecture Assessment

```mermaid
graph TB
    subgraph Frontend["Frontend (Vanilla JS)"]
        UI[board.html + Jinja2]
        WS_Client[WebSocket Client]
        JS_Modules["app.js | board.js | stats.js<br/>api.js | diff.js | theme.js"]
        Dialog_Modules["dialogs.js (coordinator)<br/>dialog-core.js | dialog-utils.js<br/>item-dialog.js | detail-dialog.js<br/>review-dialog.js | config-dialog.js<br/>clarification-dialog.js | search-dialog.js<br/>request-changes-dialog.js<br/>attachments.js | annotation-canvas.js"]
        Annotate[annotate.js]
        FileBrowser[file-browser.js]
    end

    subgraph Backend["Backend (Python / FastAPI)"]
        Routes[routes.py<br/>HTTP + WebSocket endpoints]
        FileRoutes[file_routes.py<br/>File browser API]
        WSManager[ConnectionManager<br/>websocket.py]
        Orchestrator[AgentOrchestrator<br/>orchestrator.py facade]
        subgraph Services["Service Layer"]
            Workflow[WorkflowService<br/>workflow_service.py]
            DBService[DatabaseService<br/>database_service.py]
            Notify[NotificationService<br/>notification_service.py]
            GitSvc[GitService<br/>git_service.py]
            SessSvc[SessionService<br/>session_service.py]
        end
        Session[AgentSession<br/>session.py]
        DB[Database<br/>database.py + aiosqlite]
        Migrations[MigrationRunner<br/>runner.py + versions/]
    end

    subgraph MCP["Built-in MCP Tools"]
        Clarify[ask_user<br/>clarification.py]
        Todo[create_todo<br/>todo.py]
        Commit[set_commit_message<br/>commit_message.py]
        CmdAccess[request_command_access<br/>command_access.py]
        BoardView[view_board<br/>board_view.py]
        ToolAccess[request_tool_access<br/>tool_access.py]
        Shortcut[create_shortcut<br/>shortcut.py]
    end

    subgraph Git["Git Layer"]
        GitOps[operations.py<br/>diff, merge, commit]
        Worktree[worktree.py<br/>create, remove, cleanup]
    end

    subgraph External["External"]
        ClaudeSDK[Claude Agent SDK]
        SQLite[(SQLite DB)]
        GitRepo[(Target Git Repo)]
    end

    UI -->|HTTP| Routes
    WS_Client <-->|WebSocket| WSManager
    Routes --> Orchestrator
    Orchestrator --> Workflow
    Workflow --> DBService
    Workflow --> GitSvc
    Workflow --> Notify
    Workflow --> SessSvc
    SessSvc --> Session
    Session --> ClaudeSDK
    Session --> Clarify
    Session --> Todo
    Session --> Commit
    DBService --> DB
    DB --> Migrations
    DB --> SQLite
    GitSvc --> GitOps
    GitSvc --> Worktree
    GitOps --> GitRepo
    Worktree --> GitRepo
    Notify --> WSManager
```

### Strengths

1. **Clean service layer architecture**: Orchestrator is now a thin facade delegating to 5 focused services
2. **Single-responsibility modules**: Each service has a clear, bounded responsibility (DB, git, notifications, sessions, workflows)
3. **Modular frontend**: Dialog functionality split into 12 specialized modules with a coordinator pattern
4. **Async-first design**: Proper use of `asyncio` throughout — non-blocking agent starts, event-based clarification flow
5. **Real-time streaming**: WebSocket broadcasting with reconnection keeps the UI responsive
6. **Isolation via worktrees**: Each agent gets its own git worktree — safe parallel execution
7. **Defense in depth**: Rate limiting, path traversal protection, configurable timeouts, input validation
8. **Template decomposition**: Base template extracted, card partial for reuse

### Concerns

1. **No dependency injection**: Components are wired via `app.state` — works for a single-server app but limits testability
2. **Legacy compatibility layer**: Orchestrator retains `_update_item`, `_log`, `_format_tool_use`, `_get_agent_config` methods for backward compatibility — these could be removed once all callers use services directly

---

## Module-by-Module Assessment

### Backend Python — Service Layer (new)

| Module | Lines | Quality | Notes |
|--------|-------|---------|-------|
| `services/__init__.py` | 14 | A | Clean re-exports of all 5 services |
| `services/workflow_service.py` | ~1,705 | A | Core workflow coordination with callback factory pattern, merge conflict auto-resolution, dirty repo overlap detection, has_file_changes detection, auto-start of dependent items, WIP limit queue management, `_multi_repo_session_kwargs` for multi-repo session wiring, clarification context plumbing, **auto-approve cycle bookkeeping (`_auto_approve_retries`, `_auto_approve_direct`, `_auto_approve_no_changes`)**, and pause-message plumbing on resume. All state transitions are routed through the `ItemState` FSM via `ItemRepository.transition()` |
| `services/database_service.py` | ~566 | A | All DB operations; parameterized queries throughout; item dependency management; `repo` column included in start-copy insert; clarification CRUD persists/retrieves the optional `context` field; `auto_approve` and `pause_message` columns supported via the items SELECT/INSERT paths. Column whitelisting moved out into the repositories (`_WRITABLE_ITEM_COLUMNS` lives in `item_repository.py`; the old `ALLOWED_EPIC_COLUMNS` is deleted) |
| `domain/item_state.py` | 183 | A | Explicit finite state machine over the 13 reachable item states with `Event` enum, `transition(state, event)`, and `from_columns`/`to_columns` translators that keep the existing two-string DB encoding unchanged |
| `repositories/item_repository.py` | ~190 | A | Read-only + state-changing facade over `DatabaseService` for items; owns `_WRITABLE_ITEM_COLUMNS` (private to the module); provides `get_or_raise`, `list_running`, `list_in_state`, `transition()`, `update_fields()`, `move_item` |
| `repositories/epic_repository.py` | 69 | A | Facade for epic CRUD; owns `_WRITABLE_EPIC_COLUMNS` |
| `services/notification_service.py` | 125 | A | WebSocket broadcasting + tool formatting; clean separation |
| `services/git_service.py` | 140 | A | Git worktree and merge operations with proper error handling; repo path resolution helpers for multi-repo mode |
| `services/session_service.py` | 277 | A | Session lifecycle, commit messages, plugin parsing, Ollama config; passes `workspace_root` and `sibling_repo_paths` for multi-repo agents |

### Backend Python — Core

| Module | Lines | Quality | Notes |
|--------|-------|---------|-------|
| `main.py` | 182 | A | Clean entry point, proper git validation (single-repo or multi-repo workspace), port discovery, `--experimental` flag |
| `manage.py` | 166 | A | Migration CLI (status, migrate, rollback, init) with argument parsing and DB path override |
| `config.py` | 117 | A | Well-organized constants; timeouts, WS rate limiting, defaults, and file browser configuration |
| `constants.py` | 53 | A | Centralized `AVAILABLE_MODELS` list (with experimental flag), `DEFAULT_MODEL` (Opus 4.8), `DEFAULT_OLLAMA_BASE_URL`, `AUTO_APPROVE_*` modes, `OPTIONAL_BUILTIN_TOOLS`, `EPIC_COLORS` |
| `models.py` | 140 | A | Clean Pydantic models, imports `DEFAULT_MODEL` from constants; `start_copy`, `has_file_changes`, `wip_limit`, `flame_intensity_multiplier`, `repo` fields. **Phase 3**: `AgentConfig.tools`, `mcp_servers`, `plugins`, `allowed_commands`, `allowed_builtin_tools` are now real Python types (`list`/`dict`) with validators that tolerate JSON strings on input from SQLite TEXT columns |
| `database.py` | 55 | A- | Clean async context manager; no connection pooling (acceptable for localhost) |
| `web/app.py` | 228 | A | Proper lifespan management (auto-runs DB migrations + state-encoding audit + stale-worktree scanner), clean factory pattern, CORS middleware (localhost-only), security headers; `repos` arg switches the app to multi-repo mode |
| `web/routes.py` | 1,584 | A- | Comprehensive REST API; stats caching with TTL; search endpoint; shortcuts CRUD + stop endpoint; dependency management; worktree browsing; bulk operations; item detail page; Ollama model discovery; WIP limit config; multi-repo `_resolve_item_repo_path()` helper; auto-approve mode persisted via `PATCH /api/items/{id}` |
| `web/file_routes.py` | 378 | A | File browser endpoints with path validation, secret hiding, .browserhidden support, binary detection, language mapping, lazy tree scanning |
| `web/websocket.py` | 187 | A | Rate limiting by IP, connection attempt tracking, stats endpoint, dead-connection cleanup |
| `agent/orchestrator.py` | 154 | A | Clean facade pattern — delegates all operations to services; multi-repo aware via `repos` parameter; backward compatibility preserved |
| `agent/session.py` | 737 | A- | Clean SDK wrapper; good token extraction with fallbacks; `can_use_tool` returns `PermissionResult`; Ollama env passthrough; `workspace_root` + `sibling_repo_paths` injected for multi-repo agents (read-only `add_dirs` + system prompt note); `use_advisor` enables an Opus advisor sub-agent (Anthropic-only, skipped under Ollama) |
| `agent/review_agent.py` | 249 | A | **New** — auto-review agent for the auto-approve workflow (migration 022). Spins up a short-lived, read-only Claude session that inspects the diff (capped at 60K chars, with on-demand `Read`/`Glob`/`Grep` for more context) and emits a structured `DECISION` block parsed by `WorkflowService`. `can_use_tool` strictly denies `Edit`/`Write`/`Bash`. Stateless — cycle bookkeeping (max 3 retries) lives in `WorkflowService`. |
| `agent/clarification.py` | 63 | A | Clean MCP tool definition; accepts optional `context` field passed through to the clarification row |
| `agent/todo.py` | 165 | A | Clean MCP tool definition with epic and dependency support |
| `agent/commit_message.py` | 50 | A | Clean MCP tool definition |
| `agent/command_access.py` | 42 | A | Clean MCP tool for runtime command approval |
| `agent/command_filter.py` | 90 | A | PreToolUse hook for bash command filtering with shell operator rejection and shlex parsing |
| `agent/board_view.py` | 42 | A | Board introspection MCP tool |
| `agent/tool_access.py` | 42 | A | Runtime tool access request MCP tool |
| `agent/path_guard.py` | 146 | A | PreToolUse hook preventing agents from editing files outside their worktree (and outside `workspace_root` for reads in multi-repo mode); avoids parent-root false positives across sibling repos |
| `agent/tool_filter.py` | 38 | A | PreToolUse hook for optional built-in tool filtering |
| `agent/shortcut.py` | 59 | A | Create shortcut MCP tool for agents to add bash command shortcuts to the board |
| `git/operations.py` | 354 | A- | Correct logic; async file reads; `validate_file_path()` prevents path traversal; configurable timeouts |
| `git/worktree.py` | 79 | A | Simple and correct; returns base branch for tracking |
| `migrations/runner.py` | 198 | A- | Solid migration system; class discovery uses string comparison (justified) |
| `migrations/migration.py` | 28 | A | Clean base class |
| `migrations/versions/001_initial_schema.py` | 158 | A | Complete initial schema with all 8 tables |
| `migrations/versions/002_add_base_branch.py` | 32 | A | Adds `base_branch` column to items table |
| `migrations/versions/003_add_allowed_commands.py` | 35 | A | Adds `allowed_commands` to agent_config |
| `migrations/versions/004_add_bash_yolo.py` | 26 | A | Adds `bash_yolo` flag to agent_config |
| `migrations/versions/005_add_base_commit.py` | 31 | A | Adds `base_commit` SHA to items table |
| `migrations/versions/006_add_allowed_builtin_tools.py` | 30 | A | Adds `allowed_builtin_tools` JSON array to agent_config |
| `migrations/versions/007_add_done_at.py` | 34 | A | Adds `done_at` timestamp to items, backfills existing done items |
| `migrations/versions/008_add_merge_commit.py` | 32 | A | Adds `merge_commit` SHA to items table |
| `migrations/versions/009_add_annotation_summary.py` | 32 | A | Adds `annotation_summary` to attachments table |
| `migrations/versions/010_add_epics.py` | 39 | A | Creates `epics` table and adds `epic_id` FK to items |
| `migrations/versions/011_add_item_dependencies.py` | 32 | A | Creates `item_dependencies` join table for dependency tracking |
| `migrations/versions/012_add_auto_start.py` | 38 | A | Adds `auto_start` column to items for automatic agent start on dependency resolution |
| `migrations/versions/013_add_flame_settings.py` | 42 | A | Adds `flame_enabled` setting to agent_config for animated flame background |
| `migrations/versions/014_add_start_copy.py` | 38 | A | Adds `start_copy` flag to items for configurable Start Copy button |
| `migrations/versions/015_add_has_file_changes.py` | 40 | A | Adds `has_file_changes` flag to items for tracking whether agent produced file changes |
| `migrations/versions/016_add_ollama_config.py` | 41 | A | Adds `ollama_enabled` flag and `ollama_base_url` to agent_config for local Ollama provider |
| `migrations/versions/017_add_wip_limit.py` | ~35 | A | Adds `wip_limit` column to agent_config; caps concurrent running agents with queued auto-start |
| `migrations/versions/018_update_default_model.py` | ~40 | A | Updates default model from Claude Sonnet 4 to Claude Opus 4.6 across items and agent_config |
| `migrations/versions/019_update_default_model_to_opus_4_7.py` | 56 | A | Upgrades selectable models: Opus 4.6 → 4.7, Sonnet 4 (dated `claude-sonnet-4-20250514`) → Sonnet 4.6, including `+advisor` variants; applied to both `items.model` and `agent_config.model` |
| `migrations/versions/020_add_repo_to_items.py` | 32 | A | Multi-repo support: adds nullable `repo TEXT` column to `items` and `idx_items_repo` index; NULL preserves single-repo behavior |
| `migrations/versions/021_add_context_to_clarifications.py` | ~30 | A | Clarification context: adds nullable `context TEXT` column to `clarifications` so the `ask_user` MCP tool can attach background information for the user to read alongside the prompt |
| `migrations/versions/022_add_auto_approve.py` | ~36 | A | Auto-approve workflow: adds `auto_approve INTEGER DEFAULT 0` column to `items` (modes 0=off, 1=review, 2=direct). Wires up `src/agent/review_agent.py` for an automated read-only diff review with up to 3 cycle retries before falling back to human review |
| `migrations/versions/023_add_pause_message.py` | ~32 | A | Pause messages: adds nullable `pause_message TEXT` column to `items`. Stores an optional user-supplied comment on pause; prepended to the resume prompt and cleared on resume |
| `migrations/versions/024_update_default_model_to_opus_4_8.py` | ~46 | A | Default model bump: migrates `claude-opus-4-7` → `claude-opus-4-8` across `items.model` and `agent_config.model`; `down()` reverses it. Covered by `test_default_model_024.py` |

### Frontend JavaScript

| Module | Lines | Quality | Notes |
|--------|---------|---------|-------|
| `app.js` | 493 | A- | Full WebSocket reconnection with exponential backoff, visibility-aware, manual reconnect |
| `board.js` | 1,051 | A- | Drag-drop, card rendering, Done column day grouping with collapsible sections and bulk archive, Start Copy support, has_file_changes display, WIP limit queue indicator, multi-repo `repo` badge support |
| `dialogs.js` | 86 | A | Clean coordinator pattern — delegates to 12 specialized modules |
| `dialog-core.js` | 82 | A | Core dialog open/close/confirm utilities |
| `dialog-utils.js` | 114 | A | Shared utilities (markdown rendering, model display names) |
| `item-dialog.js` | 535 | A- | New/edit item forms with attachment handling, epic assignment, dependency selection, auto-start toggle, repo selector for multi-repo workspaces |
| `detail-dialog.js` | 265 | A- | Item detail view with tabbed interface, copy-link button |
| `review-dialog.js` | 1,026 | A | Review dialog with diff viewer, work log, and tabbed interface |
| `config-dialog.js` | 347 | A | Agent configuration (system prompt, MCP, plugins, WIP limit, flame intensity) |
| `clarification-dialog.js` | ~225 | A | Clean clarification prompt/response UI; renders a context panel above the prompt when the agent attaches background context to `ask_user` |
| `notification-dialog.js` | 116 | A | System notification display, bell icon, badge counter |
| `search-dialog.js` | 246 | A | Spotlight-style search across items and work logs |
| `request-changes-dialog.js` | 24 | A | Focused request-changes form |
| `attachments.js` | 43 | A | Attachment viewing and deletion |
| `annotation-canvas.js` | 97 | A | Canvas annotation integration bridge |
| `annotate.js` | 1,150 | A- | Self-contained canvas component with freehand drawing, fill colors, on-image toggle |
| `file-browser.js` | 894 | A | Full-featured file browser with tree view, tabbed viewer, lazy loading, keyboard navigation, filter, breadcrumbs, markdown/mermaid rendering, refresh |
| `shortcuts.js` | 429 | A | Quick-launch bash command bar with process management, streaming output, stop (preserves log), reset, auto-reset mode, progress dialog |
| `api.js` | 103 | A | Clean HTTP helpers |
| `diff.js` | 62 | A- | Functional diff viewer |
| `theme.js` | 53 | A | Theme toggle with persistence |
| `stats.js` | 189 | A- | Good auto-refresh and WebSocket update pattern |
| `sound.js` | 76 | A | Notification sound effects for agent events |
| `flame.js` | 328 | A | Animated flame background with activity-driven intensity behind board columns |

**Note**: JavaScript total is ~8,017 lines across 24 modules.

### Frontend CSS

| Module | Lines | Quality | Notes |
|--------|-------|---------|-------|
| `style.css` | 1,814 | A- | Main styles with CSS variables |
| `board.css` | 591 | A | Board layout, card styles, Done day grouping with collapsible sections, repo badge styles |
| `dialog.css` | 454 | A | Dialog component styles |
| `file-browser.css` | 690 | A | File browser layout, tree, tabs, viewer, code/markdown/image styles, Prism.js light theme overrides, responsive |
| `theme.css` | 153 | A | Light/dark theme definitions with epic color palette |

**Note**: CSS total is ~3,702 lines across 5 modules.

---

## Data Flow Analysis

```mermaid
sequenceDiagram
    participant User
    participant Frontend
    participant Routes
    participant Orchestrator
    participant WorkflowService
    participant SessionService
    participant ClaudeSDK
    participant GitService
    participant DBService

    User->>Frontend: Create item + Start agent
    Frontend->>Routes: POST /api/items + POST /api/items/{id}/start
    Routes->>Orchestrator: start_agent(item_id)
    Orchestrator->>WorkflowService: start_agent(item_id)
    WorkflowService->>GitService: create_or_reuse_worktree()
    WorkflowService->>DBService: Update item (doing, running, base_branch)
    WorkflowService->>SessionService: create_session(item_id, worktree, config)
    WorkflowService-->>Routes: Return item (non-blocking)

    loop Agent Execution
        SessionService->>ClaudeSDK: Stream events
        ClaudeSDK-->>SessionService: AssistantMessage / ToolUse / Thinking
        SessionService-->>WorkflowService: Callbacks (on_message, on_tool_use, on_thinking)
        WorkflowService-->>DBService: Log to work_log
        WorkflowService-->>Frontend: WebSocket broadcast (via NotificationService)
    end

    ClaudeSDK-->>SessionService: ResultMessage
    SessionService-->>WorkflowService: on_complete(AgentResult)
    WorkflowService->>DBService: Save token_usage, update item (review)
    WorkflowService-->>Frontend: WebSocket: item_updated

    User->>Frontend: Approve
    Frontend->>Routes: POST /api/items/{id}/approve
    Routes->>Orchestrator: approve_item()
    Orchestrator->>WorkflowService: approve_item()
    WorkflowService->>GitService: merge_agent_work + cleanup_worktree_and_branch
    WorkflowService->>DBService: Update item (done)
    WorkflowService-->>Frontend: WebSocket: item_updated

    alt Merge conflict
        GitService-->>WorkflowService: (False, conflict message)
        WorkflowService->>GitService: Capture agent diff
        WorkflowService->>GitService: Reset worktree to latest base
        WorkflowService->>SessionService: Restart agent with diff as context
    end
```

---

## Item Lifecycle State Machine

```mermaid
stateDiagram-v2
    [*] --> Todo: Create item
    Todo --> Doing: Start agent
    Doing --> Clarify: Agent asks question
    Clarify --> Doing: User responds
    Doing --> Review: Agent completes
    Doing --> Failed: Agent error
    Failed --> Doing: Retry
    Review --> Done: Approve (merge)
    Review --> Doing: Request changes
    Review --> Todo: Cancel review
    Done --> Archive: Archive
    Todo --> [*]: Delete
    Doing --> Todo: Cancel agent
    Review --> Doing: Merge conflict (auto-retry)
```

---

## Security Assessment

| Area | Status | Details |
|------|--------|---------|
| Network binding | **Good** | Localhost only (127.0.0.1) |
| Authentication | **None** | No auth — acceptable for localhost dev tool |
| SQL injection | **Good** | Parameterized queries throughout (now centralized in DatabaseService) |
| Path traversal | **Good** | `validate_file_path()` blocks `..`, absolute paths, null bytes, and control characters; `serve_asset` checks `is_relative_to`; `validate_file_browser_path()` adds symlink-escape detection |
| Input validation | **Good** | Pydantic models validate API inputs |
| Secret handling | **Good** | API key from env var, never logged; file browser hides `.env`, `*.key`, `*.pem`, credentials, and SSH keys |
| Agent permissions | **Good** | `acceptEdits` mode, not `bypassPermissions` |
| Path guard | **Good** | `PreToolUse` hook prevents agents from editing files outside their worktree (`path_guard.py`) |
| WebSocket rate limiting | **Good** | Per-IP connection limits (5 concurrent, 10 per 60s window), connection attempt tracking |
| Git timeouts | **Good** | Configurable timeouts: operations (5min), merge (10min), HTTP requests (11min) |

### Recommendations

1. **Sanitize work log content** before rendering in frontend (markdown injection risk)

---

## Code Quality Findings

### Issues Resolved Since Last Assessment

| # | Issue | Resolution |
|---|-------|------------|
| 1 | Duplicate session creation logic | ✅ Extracted to `SessionService.create_session()` |
| 2 | Synchronous file read in async context | ✅ Uses `asyncio.to_thread()` |
| 3 | Unused `resume_id` variable | ✅ Passed to `start_session_task()` as `resume_session_id` |
| 4 | Double `_update_item` on merge conflict | ✅ Reduced to single call |
| 5 | No WebSocket reconnection in frontend | ✅ Full implementation with exponential backoff, visibility awareness, manual reconnect |
| 6 | `delete_item` cleanup inline in routes | ✅ Moved to `WorkflowService.delete_item()` |
| 7 | Hardcoded model strings | ✅ Centralized in `constants.py` |
| 8 | Path traversal via `git show` | ✅ `validate_file_path()` added |
| 9 | Stats endpoint multiple sequential queries | ✅ Stats caching with 30s TTL, invalidated on mutations |
| 10 | No WebSocket rate limiting | ✅ Per-IP rate limiting with concurrent connection limits and windowed attempt tracking |
| 11 | No request timeout for blocking operations | ✅ `asyncio.wait_for()` with `HTTP_REQUEST_TIMEOUT` on approve route |
| 12 | Migration class discovery uses string comparison | ✅ Justified — `issubclass` fails with dynamic module loading |
| 13 | Orchestrator too large (667 lines) | ✅ Decomposed into 5 services: WorkflowService (~1,705), DatabaseService (~566), NotificationService (~125), GitService (~140), SessionService (~277). Orchestrator now 154-line facade. WorkflowService growth since the decomposition is feature-driven (auto-approve cycles, WIP queueing, multi-repo, FSM-routed transitions) — not a regression in cohesion |
| 14 | `dialogs.js` too large (801 lines) | ✅ Split into 12 specialized modules with coordinator pattern. Largest module is `search-dialog.js` at 246 lines |

### Remaining Issues

#### Medium Priority

3. **~10 minute delay between agent's last message and SDK completion callback**: Observed a consistent ~10 minute gap between the agent finishing its work (setting commit message, sending final text) and the `claude-agent-sdk` reporting the session as complete. The agent is idle during this time but the item stays in "Doing" instead of moving to "Review". This appears to be SDK/API-side overhead (possibly extended thinking finalization or session teardown). Consider detecting "agent is done talking" heuristically (e.g., after `set_commit_message` + no tool use for N seconds) to move items to Review earlier, before the SDK formally completes.

#### Low Priority

1. **No connection pooling**: Each DB operation opens/closes a connection via `aiosqlite.connect()`
   - Acceptable for localhost use but would bottleneck under load

2. **Legacy compatibility methods in orchestrator**: `_update_item`, `_log`, `_format_tool_use`, `_get_agent_config` remain as pass-throughs
   - **Recommendation**: Remove once all callers migrate to using services directly

---

## Test Coverage

**Current state**: 1035 automated tests across 33 Python test files (excluding `conftest.py`) via `./run-tests.sh`, plus 7 E2E `.mjs` test files via `./run-e2e-tests.sh` (5 agent-spawning + 2 pure-HTTP regressions for DnD encoding canonicalization and `AgentConfig` round-trip). Database has 24 migrations. Auto-approve / pause-message coverage rides on `test_workflow_service.py` and `test_advisor.py`; the column-add migrations are covered by the generic migration runner suite, and the default-model bump (024) has a dedicated `test_default_model_024.py` (4 tests).

| Test File | Type | Tests | Focus |
|-----------|------|-------|-------|
| `tests/smoke/test_basic_functionality.py` | Smoke | 12 | Imports, DB basics, config |
| `tests/smoke/test_multi_repo.py` | Smoke | 8 | Multi-repo workspace detection, sibling repo wiring |
| `tests/unit/test_item_state.py` | Unit | 66 | `ItemState` FSM: states, events, transitions, encoding round-trips |
| `tests/unit/test_item_repository.py` | Unit | 25 | `ItemRepository` reads, `transition()`, `update_fields()`, `move_item`, column whitelist |
| `tests/unit/test_epic_repository.py` | Unit | 9 | `EpicRepository` CRUD facade and column whitelist |
| `tests/unit/test_workflow_service.py` | Unit | 97 | State transitions (via FSM), lifecycle, conflict resolution, auto-start, clarification context plumbing |
| `tests/unit/test_routes.py` | Unit | 94 | HTTP endpoints for items, review, epics, config, stats, item detail, WIP limit, clarification context retrieval |
| `tests/unit/test_git_operations.py` | Unit | 70 | Diff generation, merge, commit, path validation |
| `tests/unit/test_file_routes.py` | Unit | 66 | File browser path validation, secret detection, .browserhidden |
| `tests/unit/test_session.py` | Unit | 69 | AgentSession SDK wrapper, token extraction, events, Ollama provider |
| `tests/unit/test_session_service.py` | Unit | 51 | SessionService lifecycle, commit messages, plugins, Ollama config |
| `tests/unit/test_mcp_tool_servers.py` | Unit | 57 | MCP tool server creation, invocation, request flow, `ask_user` context field |
| `tests/unit/test_database_service.py` | Unit | 58 | DatabaseService CRUD, dependencies, column whitelist, clarification context column |
| `tests/unit/test_websocket.py` | Unit | 45 | WebSocket connection, rate limiting, cleanup |
| `tests/unit/test_notification_service.py` | Unit | 41 | WebSocket broadcasting, tool formatting |
| `tests/unit/test_main.py` | Unit | 34 | Server startup, port discovery, git validation |
| `tests/unit/test_allowed_commands.py` | Unit | 26 | Command filter, shell operators, YOLO mode |
| `tests/unit/test_manage.py` | Unit | 24 | Migration CLI commands |
| `tests/unit/test_app.py` | Unit | 26 | FastAPI factory, middleware, CORS, security headers |
| `tests/unit/test_epics.py` | Unit | 19 | Epic CRUD, progress, assignment, filtering |
| `tests/unit/test_git_worktree.py` | Unit | 15 | Worktree create/cleanup, base branch tracking |
| `tests/unit/test_path_validation.py` | Unit | 14 | Path traversal prevention |
| `tests/unit/migrations/test_migration_runner.py` | Unit | 14 | Migration engine |
| `tests/unit/migrations/test_migration_edge_cases.py` | Unit | 14 | Migration edge cases |
| `tests/unit/migrations/test_default_model_024.py` | Unit | 4 | Migration 024 default-model bump (Opus 4.7 → 4.8) up/down |
| `tests/unit/test_create_todo_autostart.py` | Unit | 21 | Todo creation with auto-start |
| `tests/unit/test_advisor.py` | Unit | 13 | Advisor logic |
| `tests/unit/test_mini_mcp.py` | Unit | 11 | Mini-MCP server stdio protocol |
| `tests/unit/test_diff_mixing.py` | Unit | 6 | Diff isolation, concurrent diffs, base commit pinning |
| `tests/unit/test_git_timeout.py` | Unit | 5 | Git timeout configuration and recovery |
| `tests/unit/test_annotation_prompt.py` | Unit | 5 | Annotation prompt formatting |
| `tests/unit/test_annotation_summary.py` | Unit | 2 | Annotation summary generation |
| `tests/integration/test_orchestrator_lifecycle.py` | Integration | 14 | Full orchestrator lifecycle |

### Recommended Additional Tests

| Priority | Area | Type | Effort |
|----------|------|------|--------|
| **P2** | Token usage extraction edge cases | Unit | Low |
| **P2** | Stats caching and invalidation | Unit | Low |
| **P2** | Card render parity (Jinja2 vs JS) | E2E | Medium |
| **P3** | Frontend dialog modules | E2E (Playwright) | High |

---

## Performance Considerations

```mermaid
graph LR
    subgraph Bottlenecks
        A[SQLite single-writer] --> B[OK for localhost]
        C[No connection pool] --> D[OK for low concurrency]
        E[Stats caching 30s TTL] --> F[Reduced DB load]
        G[Git operations via CLI] --> H[Pragmatic, slightly slower than libgit2]
        I[WS rate limiting] --> J[Prevents resource exhaustion]
    end
```

- **SQLite**: Single-writer limitation is fine for localhost, but concurrent agents writing logs could contend
- **Stats caching**: 30s TTL with active invalidation on mutations — good balance of freshness and performance
- **Git operations**: Shell-out to `git` CLI is pragmatic but slower than libgit2 bindings
- **Git timeouts**: Configurable per operation type prevents hung processes

---

## Positive Patterns Worth Preserving

1. **Service layer decomposition**: 5 focused services with clear responsibilities replace monolithic orchestrator
2. **Facade pattern**: `AgentOrchestrator` provides a stable API while delegating to services
3. **Callback factory pattern**: `WorkflowService._create_on_*_callback()` methods keep callback creation centralized and consistent
4. **`_log_and_notify` helper**: Centralizes DB logging + WebSocket broadcast — prevents missed notifications
5. **Dialog coordinator pattern**: `dialogs.js` delegates to 12 specialized modules while preserving backward compatibility
6. **Commit message via MCP tool**: Agents produce meaningful commit messages rather than generic ones
7. **Worktree reuse on retry**: Preserves agent's previous work when retrying
8. **Dead WebSocket cleanup**: Broadcast loop silently removes failed connections
9. **Lifespan-managed shutdown**: Graceful agent cancellation on server stop via `SessionService.cleanup_all_sessions()`
10. **`validate_file_path()`**: Thorough path traversal prevention with multiple layers of checks
11. **Stats caching with invalidation**: Reduces DB pressure while keeping data fresh
12. **WebSocket reconnection**: Exponential backoff, visibility-aware, manual override — robust implementation
13. **Centralized constants**: `AVAILABLE_MODELS` and `DEFAULT_MODEL` in `constants.py` prevent string duplication
14. **WebSocket rate limiting**: Per-IP connection limits with configurable windows prevent resource exhaustion
15. **Base branch tracking**: Worktree creation returns and stores the base branch for reliable merge targeting
16. **Configurable git timeouts**: Separate timeouts for operations (5min) and merges (10min) prevent hung processes
17. **Template decomposition**: Base template extracted with card partial for consistent rendering
18. **Merge conflict auto-resolution**: On conflict, captures agent's diff, resets worktree to latest base, and restarts agent with previous diff as context — fully automated recovery
19. **File browser with defense in depth**: Path validation (null bytes, control chars, traversal, symlink escape), secret file hiding via configurable patterns, binary detection, configurable size limits, excluded dirs/files — all constants centralized in `config.py`
20. **Lazy tree loading**: File browser loads directory children on-demand with configurable depth limit, reducing initial payload for large projects
21. **Done column day grouping**: Completed items organized by date with collapsible sections, compact title lists, and per-day bulk archive — keeps the Done column manageable
22. **Done timestamp tracking**: `done_at` column with migration backfill ensures accurate completion tracking without relying on `updated_at` which changes on any modification
23. **Start Copy**: Creates a duplicate item and starts an agent on the copy, preserving the original Todo item for reuse — useful for iterative task refinement
24. **Merge commit tracking**: `merge_commit` column stores the SHA of the merge commit on approval, enabling traceability from board items to git history
25. **Dirty repo overlap detection**: Before merge, checks if the base repo has uncommitted changes overlapping with agent's files — blocks merge and moves to "questions" column with guidance, preventing silent data loss
26. **Archive cleanup**: Archiving items automatically cleans up worktree and session resources, preventing orphaned state
27. **Search dialog**: Spotlight-style search (Cmd/Ctrl+K) across items and work logs with keyboard navigation — fast item discovery in large boards
28. **Epic grouping**: Separate entity model (not items), collapsible progress panel, Todo column grouping, card badges, board filtering, inline creation, and agent MCP integration — clean implementation reusing existing patterns (day grouping for collapsible sections)
29. **Annotation summary**: Text description of annotation shapes stored in DB and included in agent prompts — gives agents context about visual annotations without needing to parse images
30. **Item dependencies**: Join table (`item_dependencies`) with cascading deletes tracks which items require other items — agents can declare dependencies via `create_todo` MCP tool's `requires` parameter, and `view_board` includes dependency info for coordination
31. **Auto-start pipelines**: Items with `auto_start` enabled (migration 012) automatically start an agent when all dependency items are resolved — `WorkflowService.notify_and_auto_start_dependents()` is invoked after each merge **and** after manual drag-to-done/archive moves, enabling pipeline-style workflows without manual intervention
32. **Shortcuts bar**: Quick-launch bash commands from a persistent bar at the bottom of the board — CRUD endpoints with in-memory storage, subprocess management with streaming output, stop (preserves output log with "STOPPED BY USER" marker), reset capability, auto-reset mode, progress dialog with dynamic Stop/Reset button, and automatic cleanup on delete
33. **Worktree file browsing**: Review dialog can browse the agent's worktree directory tree and file contents, reusing the same security infrastructure as the project file browser
34. **Bulk operations**: Archive or delete items by date or by epic via dedicated endpoints, reducing manual cleanup effort for large boards
35. **Retry merge**: Re-attempt a failed merge without restarting the agent — preserves the agent's work and avoids unnecessary re-runs
36. **Create shortcut MCP tool**: Agents can add quick-launch bash command shortcuts to the board's shortcut bar via `create_shortcut` MCP tool — useful for setting up project-specific commands (test runners, build commands, linters) as part of task completion
37. **Animated flame background**: Optional animated flame effect behind board columns with activity-driven intensity — configurable via `flame_enabled` in agent_config (migration 013), adds visual feedback for active agent work
38. **File change detection**: When an agent completes, `WorkflowService` checks for changed files and stores the result in `has_file_changes` (migration 015) — review cards display "Done" (no changes) or "Approve & Merge" (has changes) to avoid unnecessary merge steps
39. **Standalone item detail page**: Each item has a shareable URL via the item detail endpoint — Done detail dialog includes a copy-link button for easy sharing
40. **Ollama provider (experimental)**: Local model support via Ollama's Anthropic-compatible API, gated behind `--experimental` flag — dynamic model discovery via REST API, connection status indicator, provider badges on cards, per-item model selection from Ollama dropdown; env vars (`ANTHROPIC_AUTH_TOKEN`, `ANTHROPIC_BASE_URL`) injected per-agent subprocess
41. **WIP limit**: Configurable cap on concurrent running agents (migration 017) — items started beyond the limit are placed in 'doing' with status='queued' and auto-started in position order when a slot opens; prevents resource exhaustion when launching many agents
42. **Default model progression**: Migrations 018, 019, then 024 update the default model (Sonnet 4 → Opus 4.6 → Opus 4.7 → Opus 4.8) and migrate the `+advisor` variants in lockstep across existing items and agent_config — clean, repeatable data migration pattern with reversible `down()` at each step
43. **Flame intensity multiplier**: `flame_intensity_multiplier` field in agent_config allows fine-tuning the animated flame background intensity beyond the on/off toggle
44. **Multi-repo workspace mode**: Migration 020 adds a nullable `repo` column on `items` (NULL = single-repo mode preserved). When `target_project` is a folder of sibling git repos, each item names one repo; the worktree is created inside that subrepo, `WorkflowService._multi_repo_session_kwargs` injects `workspace_root` + `sibling_repo_paths` into the session, `AgentSession` adds the siblings to `add_dirs` (read-only) and explains the layout in the system prompt, and `path_guard.py` is multi-repo aware to avoid false positives across siblings. The `idx_items_repo` index keeps repo-scoped queries cheap.
45. **Clarification context**: Migration 021 adds a nullable `context TEXT` column to `clarifications`. The `ask_user` MCP tool exposes a matching optional `context` field; agents pass relevant background and the Question dialog renders it as a panel above the prompt. The clarification row is created **before** the `item_updated` WebSocket broadcast, so the dialog has full context on first open (closing a regression where context was missing on the initial event).
46. **Explicit `ItemState` finite state machine** (`src/domain/item_state.py`): the 13 reachable item states and their transitions are now an enum-based FSM with a centralized `transition(state, event)` function. Storage encoding stays as the existing `(column_name, status)` pair via `from_columns`/`to_columns`, so no migration was needed. `WorkflowService` routes start/pause/resume/cancel, clarify ask/answer, merge/conflict/complete, retry agent/merge, and DnD-produced encodings through the FSM. Startup runs a state-encoding audit + stale-worktree cleanup using the same FSM. New tests cover the FSM directly (`test_item_state.py`, 66 tests).
47. **Repository layer for items and epics** (`src/repositories/`): `ItemRepository` and `EpicRepository` are facade classes over `DatabaseService` that own column whitelists and expose intent-named methods (`get_or_raise`, `list_running`, `list_in_state`, `transition()`, `update_fields()`, `move_item`). The old `ALLOWED_ITEM_COLUMNS` was moved here and `ALLOWED_EPIC_COLUMNS` was deleted entirely. New tests cover both repos (`test_item_repository.py` 25 tests, `test_epic_repository.py` 9 tests).
48. **Typed `AgentConfig`** (Phase 3): the `tools`, `mcp_servers`, `plugins`, `allowed_commands`, and `allowed_builtin_tools` fields are now real Python types (`list`/`dict`) on the Pydantic model. Pre-validators tolerate JSON strings on input so DB rows stored as TEXT still load through the model unchanged.
49. **Auto-approve workflow with read-only reviewer** (migration 022): The `auto_approve` integer mode on `items` (0=off / 1=review / 2=direct) drives `WorkflowService` to invoke `src/agent/review_agent.py` after the main agent completes. The reviewer is a strictly read-only Claude session (`Read`/`Glob`/`Grep` only; `Edit`/`Write`/`Bash` denied via `PermissionResultDeny`) that emits a structured `DECISION` block. Cycle bookkeeping (max 3 retries) lives in `_auto_approve_retries` outside the SDK call, so a misbehaving reviewer cannot loop indefinitely. Two short-circuit paths (`_auto_approve_no_changes` for empty diffs, `_auto_approve_direct` for mode 2) keep the cheap cases cheap.
50. **Pause messages** (migration 023): The `pause_message TEXT` column on `items` lets the user attach a redirection comment when pausing — "investigate a different approach using async/await" — which is then prepended to the resume prompt and cleared on resume. Lower-friction than canceling and re-creating the item.

---

## Codebase Statistics

| Category | Files | Lines |
|----------|-------|-------|
| Python backend (src/, excl. migrations) | 40 | ~8,900 |
| Database migrations | 24 | ~1,010 |
| JavaScript frontend | 27 | ~9,300 |
| CSS styles | 5 | ~3,850 |
| HTML templates (incl. partials) | 3 | ~895 |
| Python tests | 33 | ~13,500 |
| E2E tests (`.mjs`) | 7 (+ `helpers.mjs`) | ~1,200 |
| **Grand total** | **~139** | **~38,600** |

---

## Summary of Recommendations

| Priority | Recommendation | Effort |
|----------|---------------|--------|
| **Low** | Remove legacy compatibility methods from orchestrator | Low |
| **Low** | Sanitize work log markdown rendering | Low |
