- **Status:** Active - **Author:** didericis - **Created:** 2026-06-03 - **Issue:** #174 ## Summary The `./cli.py dashboard` command has grown from its PRD 0013 roots (triage supervise proposals) into a parallel-agent control surface (PRDs 0019/0020/0021): an active-agents pane, agent picker + start, re-attach, per-bottle stop, tmux split-pane handoff, operator- initiated `routes`/`pipelock` edits. Each chunk is reasonable on its own; together they make the dashboard the largest CLI file in the repo and the thing most likely to break on a rough edge (curses / tmux / docker-exec / metadata-discovery interactions). This PRD reverses that scope creep. The dashboard is reduced to the **supervise-plane triage TUI** it was in PRDs 0013–0016: list pending proposals, approve / modify / reject each one, write audit entries, deliver the response that unblocks the agent's tool call. Everything that's about *starting / re-entering / stopping* bottles, or about *operator-initiated* config edits, comes out. The command is renamed `./cli.py supervise` so the name matches what it does after the cut. Future agent-management UX is explicitly punted: if and when a control surface for parallel agents resurfaces, the working assumption (per the issue) is that a web GUI — usable from mobile — is a better second pass than another round of curses iteration. That decision is not in this PRD's scope; this PRD only removes the half-built local-curses path so we stop maintaining it. ## Problem Three concrete pains, all downstream of the dashboard's growth: 1. **Surface area vs. polish.** `dashboard.py` is ~1740 lines; `dashboard_model.py` adds another ~420. The interactions among curses, modals, tmux split-pane, docker-exec handoff, agent provider templates, metadata-driven re-attach, and ExitStack-free bottle ownership are intricate enough that shipping the next polish increment costs more than it returns. 2. **No clear ownership of "starts and stops bottles".** Today that responsibility is split: `./cli.py start` owns one-shot sessions; the dashboard owns multi-session bottles it started itself; `./cli.py cleanup` owns everything else. The dashboard tracking its own `bottles: dict[str, (cm, bottle, identity)]` that doesn't survive a quit is a confusing third lane. 3. **Wrong target shape for a "manage many agents" UI.** The parallel-agent experience the dashboard reaches for is mobile- meaningful — checking in on agents from a phone is the high- value case — and curses inside an SSH session is the wrong tool for that. Continuing to polish a local-only TUI delays the right next investment. The triage half of the dashboard isn't suffering from any of these. Pending proposals are a small, well-scoped, real workload, and the PRD 0013–0016 surface for handling them is the right shape. The problem is everything that got bolted onto that core after. ## Goals / Success Criteria 1. The supervise TUI starts up, lists pending proposals across all running bottles, and supports approve / modify / reject + the `--once` non-interactive mode — exactly as PRDs 0013–0016 specified, minus everything 0019/0020/0021 added. 2. The CLI subcommand is renamed `supervise` (was `dashboard`). The old name is not aliased — this PRD is intentionally a compat/breaking change (the issue carries the `Compat/Breaking` label). 3. `dashboard.py` shrinks to a single proposal-triage curses loop: no agents pane, no Tab pane switching, no agent picker, no start / re-attach / stop verbs, no tmux split-pane, no `e`/`p` operator-edit verbs, no per-process `bottles` dict. 4. `dashboard_model.py` is collapsed into whatever `supervise.py` (CLI) needs; the model module is removed if it has no purpose after the cut. 5. The proposal-side apply paths in `bot_bottle/backend/docker/ egress_apply.py`, `pipelock_apply.py`, and `capability_apply.py` are unchanged — they are still called by the approve path. 6. The supervise-sidecar / proposal-queue protocol (PRD 0013) is unchanged: the agent's experience is identical. 7. The previously-active PRDs that this one undoes are marked `Superseded by PRD 0049`: - PRD 0019 — active-agents pane + agent-scoped edit verbs - PRD 0020 — start / re-attach / stop from the dashboard - PRD 0021 — tmux split-pane ## Non-goals - **A web GUI for managing agents.** The issue floats this as a second pass; this PRD does not design or commit to it. The cut is "remove the path we no longer want to invest in", not "build the replacement". - **A separate CLI for operator-initiated routes / pipelock edits.** Today those edits live as `e` / `p` keys inside the dashboard. After this PRD they don't exist anywhere — operators who need ad-hoc edits use the same path the agents do (call the supervise tool from inside the bottle) or hand-edit the host- side files and restart the sidecar. Adding a `./cli.py routes edit ` verb is a follow-up if the loss bites. - **Removing `./cli.py start` or changing its semantics.** Start remains the one-shot launch path. PRD 0020's bottle-outlives- process model is removed; the only path to a long-running bottle is `./cli.py start` (foreground) plus `cli.py cleanup` for teardown. - **Removing the supervise-sidecar protocol or any of the three block-remediation engines.** PRDs 0013–0016 stay Active. The agent's view of the world doesn't change. - **Renaming `dashboard` anywhere other than the CLI entry point.** The dashboard-related docs (PRDs, decision records, research notes) keep their historical references — they describe the state of the world at the time they were written, and the Status: Superseded line is the marker that the world has moved on. - **Migrating the proposal-queue file layout.** The queue still lives at `~/.bot-bottle/queue//`; the audit log still lives at `~/.bot-bottle/audit/-.log`. The CLI surface changes; the on-disk surface does not. ## Scope ### In scope - **Rename the subcommand.** `./cli.py dashboard` becomes `./cli.py supervise`. The module moves from `bot_bottle/cli/ dashboard.py` to `bot_bottle/cli/supervise.py`. The dispatcher in `bot_bottle/cli/__init__.py` and the help text both update. - **Strip the curses loop to proposal-only.** The remaining surface is: list pending proposals (with the new-arrival bell from PRD 0013), Enter for detail view, `a`/`m`/`r` for approve / modify / reject, `q` to quit. No agents pane, no Tab, no agent picker, no `n`/`x`/`e`/`p`, no tmux dispatch, no `bottles` dict on the main loop. - **Drop unused helpers.** `_picker_modal`, `_preflight_modal`, `_backend_picker_modal`, `_new_agent_flow`, `_attach_to_bottle`, `_attach_in_tmux`, `_attach_via_handoff`, `_tmux_*`, `_ensure_right_pane`, `_redirect_stderr_to_file`, `_route_op_to_right_pane`, `_stop_bottle_flow`, `_operator_edit_*_flow`, `operator_edit_routes`, `operator_edit_allowlist`, and their imports come out. - **Collapse the model module.** `dashboard_model.py`'s proposal-side helpers (`QueuedProposal`, `discover_pending`, `_approval_status`, `_detail_lines`, `_failed_url_host`, `_proposed_payload_label`, `_suffix_for_tool`, `_REFRESH_INTERVAL_MS`) move back into `supervise.py` (CLI) or into `bot_bottle/supervise.py` (the daemon-side module) — wherever they fit. The agents / picker / tmux helpers in that module (`PANE_*`, `_filter_agents`, `_running_counts`, `_format_agent_row`, `_selection_status`, `_selected_agent`, `_bottle_for_slug`, `_pick_next_after_stop`, `_agent_runtime_args`, `_build_resume_argv_with_fallback`, `_build_split_pane_argv`, `_build_respawn_pane_argv`, `_in_tmux`, `discover_active_agents`) are deleted. - **Mark superseded PRDs.** The Status line on PRDs 0019, 0020, and 0021 changes to `Superseded by [PRD 0049](0049-strip- dashboard-to-supervisor-tui.md)`. - **Test cleanup.** Any test that targets a removed surface (the agent picker, the tmux split helpers, the start-from-dashboard flow, the operator-edit flows, `discover_active_agents`) comes out. Tests covering proposal triage stay. - **Help / usage strings.** `bot_bottle/cli/__init__.py`'s usage block updates the command name and one-liner. ### Out of scope - Any new feature in the supervise TUI. The cut is purely subtractive (except for the rename). - Behavior changes in `./cli.py start`, `cli.py cleanup`, `cli.py resume`, `cli.py list`, `cli.py info`, `cli.py edit`, `cli.py init` — unchanged. - Changes to the supervise sidecar (`supervise_server.py`, `supervise.py` daemon module). The wire protocol stays. - Changes to the routes / pipelock / capability apply engines. - Migration helpers, deprecation warnings, or a transitional `dashboard` alias for `supervise`. The label on the issue says Compat/Breaking; the rename is a hard cutover. ## Proposed design ### Final shape of the TUI After this PRD the `./cli.py supervise` curses surface is: ``` bot-bottle supervise (3 pending) ───────────────────────────────────────────────────────── > 03:14:22 [implementer-cy7a6] egress-block abc123… add github.com/foo 03:13:55 [researcher-9xqs1] pipelock-block def456… allow registry.npmjs.org 03:13:10 [implementer-cy7a6] capability-block ghi789… install ripgrep ───────────────────────────────────────────────────────── [j/k] move [Enter] view [a] approve [m] modify [r] reject [q] quit ``` - One pane. No Tab. `j` / `k` / arrows move through the queue. - Enter opens the existing detail view (justification + proposed-file body + the green pipelock host-extraction hint). `a` / `m` / `r` work from both the list view and the detail view, same as today. - `q` / Esc quits. There are no dashboard-owned bottles, so no per-process teardown decision — `q` just exits. - The new-arrival bell stays, because it is a real win for the operator's "I was typing at claude and a proposal landed" case. No tmux-specific focus management remains. ### Code organisation After the cut, the CLI module looks roughly like: ``` bot_bottle/cli/supervise.py - cmd_supervise(argv) - _list_once() # --once mode - _main_loop(stdscr) # proposal-only - _render(stdscr, pending, ...) - _detail_view(stdscr, qp, ...) - _modify(stdscr, qp) - _prompt(stdscr, label) - _write_crash_log(exc) - approve(qp, *, notes, final_file) - reject(qp, *, reason) - QueuedProposal, discover_pending - _detail_lines, _approval_status, _failed_url_host, _proposed_payload_label, _suffix_for_tool ``` `dashboard_model.py` has no purpose once the agents / picker / tmux helpers are gone, so it is removed and the surviving proposal-side helpers move into `supervise.py` directly. The PRD-0013 refactor that split model out (`refactor: extract dashboard state/model layer into dashboard_model.py`) was load-bearing for the bigger dashboard surface; with the surface shrunk back, the split is no longer justified. ### Removed PRDs: how to mark them The three superseded PRDs keep their bodies intact. Only the Status line at the top changes: ``` - **Status:** Superseded by [PRD 0049](0049-strip-dashboard-to-supervisor-tui.md) ``` The PRD's own Goals / Success Criteria are left as the historical record of what the feature shipped — readers tracing back from the code or the git log land in a PRD that explains what once was, with a clear pointer forward. No PRD body is rewritten. ### Tests to keep, tests to remove Keep: - `tests/cli/test_dashboard*.py` cases that exercise `discover_pending`, `approve`, `reject`, `_detail_lines`, `_approval_status`, `_failed_url_host`, `_proposed_payload_label`, `_suffix_for_tool`, `_modify` / `edit_in_editor`. - `tests/cli/test_dashboard_once.py` (or equivalent) — the `--once` listing mode. Remove: - Any test of `_picker_modal`, `_preflight_modal`, `_backend_picker_modal`, `_new_agent_flow`, `_attach_*`, `_tmux_*`, `_route_op_to_right_pane`, `_redirect_stderr_to_file`, `_stop_bottle_flow`, `_operator_edit_*`, `_filter_agents`, `_running_counts`, `_format_agent_row`, `_selection_status`, `_selected_agent`, `_bottle_for_slug`, `_pick_next_after_stop`, `_agent_runtime_args`, `_build_*_argv`, `discover_active_agents`. - The test files that exist solely to cover those (e.g., `test_dashboard_picker.py`, `test_dashboard_tmux.py`, `test_dashboard_attach.py`, `test_dashboard_agents.py` — whichever of these exist after the file walk). Files are renamed `test_supervise_*.py` to mirror the module rename. The rename is mechanical; no test logic changes. ## Implementation chunks Sized for a single PR each. 1. **Strip + rename in one cut.** Move `bot_bottle/cli/ dashboard.py` to `bot_bottle/cli/supervise.py`, delete the removed helpers, delete `dashboard_model.py`, inline the surviving helpers, update the dispatcher + usage in `bot_bottle/cli/__init__.py`, rename tests to match, mark PRDs 0019/0020/0021 as superseded. One commit per logical piece inside the PR (rename, strip, supersede notes, tests). 2. **Activate PRD 0049.** Flip this PRD's Status line from Draft to Active in the same PR as chunk 1 once the implementation lands. (The repo convention is that a PRD's shipping commit is also the Status flip — see the recent `docs(prd): activate PRD 0048…` commit shape.) The PR closes issue #174. ## Open questions 1. **`e` / `p` operator-initiated edits — gone for good or moved to a separate CLI verb?** The PRD removes them with no replacement. The simplest replacement is `./cli.py routes edit ` and `./cli.py pipelock edit `, sharing the existing `apply_routes_change` / `apply_allowlist_change` engines. If the loss is felt within the first parallel run after this lands, that follow-up is a small PR. Leaving it for a separate PRD so this one stays subtractive. 2. **`--once` output shape.** The text listing today emits one proposal per line. Worth keeping exactly as-is for scripting consumers; this PRD does not change it. Flagging only because the rename could tempt a tweak. 3. **Audit-log entry shape for an unprompted edit applied via a future `routes edit` CLI verb.** Today's `operator_edit_routes` writes an `ACTION_OPERATOR_EDIT` audit entry. With those flows removed the constant has no callers inside this PRD's scope. Keep the constant exported from `supervise.py` (it's already an `__all__` member) so a follow-up CLI verb can re-use the same audit shape without re-introducing dead code first. ## References - Issue [#174](https://gitea.dideric.is/didericis/bot-bottle/issues/174) — the request: "strip the dashboard down into just a TUI for managing agent requests for new egress routes and new capabilities." - PRD 0013 — supervise plane foundation (the floor this PRD reverts the dashboard to). - PRDs 0014 / 0015 / 0016 — block-remediation engines that the supervise TUI continues to drive on approve. - PRDs 0019 / 0020 / 0021 — the bolted-on capabilities this PRD removes.