docs(prd): add PRD 0050 -- strip dashboard to supervisor tui
This commit is contained in:
@@ -0,0 +1,346 @@
|
|||||||
|
|
||||||
|
- **Status:** Draft
|
||||||
|
- **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 <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 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/<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 +
|
||||||
|
green highlight 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`, `_is_recent`, `_detail_lines`,
|
||||||
|
`_failed_url_host`, `_proposed_payload_label`,
|
||||||
|
`_suffix_for_tool`, `_REFRESH_INTERVAL_MS`,
|
||||||
|
`_NEW_PROPOSAL_HIGHLIGHT_SEC`) 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 + green highlight + (if in tmux) the
|
||||||
|
`tmux select-pane` jump back to the supervise pane stay,
|
||||||
|
because they're real wins for the operator's "I was typing at
|
||||||
|
claude and a proposal landed" case. They don't require any of
|
||||||
|
the pane-management code being removed.
|
||||||
|
|
||||||
|
### 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,
|
||||||
|
_is_recent, _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`,
|
||||||
|
`_is_recent`, `_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.
|
||||||
Reference in New Issue
Block a user