Files
bot-bottle/docs/prds/0049-strip-dashboard-to-supervisor-tui.md
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

15 KiB
Raw Permalink Blame History

  • 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 — 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.