Files
bot-bottle/docs/prds/0049-strip-dashboard-to-supervisor-tui.md
T
didericis-codex f12b0f754e
test / unit (pull_request) Successful in 37s
test / integration (pull_request) Successful in 55s
test / unit (push) Successful in 51s
test / integration (push) Successful in 59s
docs(prd): reactivate PRD 0049 without tmux alert
2026-06-03 17:35:10 +00:00

344 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
- **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 00130016: 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 00130016 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 00130016
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 <slug>` 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 00130016 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/<slug>/`; the audit log still
lives at `~/.bot-bottle/audit/<component>-<slug>.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 <slug>` and `./cli.py pipelock edit <slug>`, 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.