docs(prd): remove dashboard references, align with current codebase

- Dashboard no longer exists; remove all references to it
- Active agent display surface is cli list active, not a TUI pane
- Label/color rendered with ANSI escape codes in list output
- Modal called from cmd_start only, no supervisor _new_agent_flow
- Remove _format_agent_row/_color_pair_for curses design (list is
  plain text); add _ansi_color() helper design instead
- Clarify slug-suffix caveat: modal appears before prepare() mints
  the slug so default label falls back to agent_name

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-07 12:04:17 -04:00
parent 39e0976ace
commit f6f47c2f23
+72 -73
View File
@@ -12,22 +12,22 @@ set a human-readable label and color for the agent before it launches. The
modal pre-fills the label with the current agent name pattern (e.g. modal pre-fills the label with the current agent name pattern (e.g.
`implementer-a3f9`) and leaves color unset; Enter with no changes accepts `implementer-a3f9`) and leaves color unset; Enter with no changes accepts
those defaults. Store both in the bottle's `metadata.json`. Display the label — those defaults. Store both in the bottle's `metadata.json`. Display the label —
rendered in the chosen color — in the supervisor's active-agents pane, rendered in the chosen ANSI color — in `cli list active` output, replacing
replacing the bare manifest key. Inject the label and color into the the bare manifest key. Inject the label and color into the in-container
in-container `claude.json` as `name` / `color` so Claude Code can surface them `claude.json` as `name` / `color` so Claude Code can surface them in its own
in its own harness when upstream support lands. harness when upstream support lands.
## Problem ## Problem
The supervisor's agents pane identifies each running instance by its manifest `cli list active` identifies each running instance by its manifest agent key
agent key (e.g., `implementer`) plus a random slug suffix. When an operator (e.g., `implementer`) plus a random slug suffix. When an operator runs three
runs three `implementer` bottles simultaneously — one each for three different `implementer` bottles simultaneously — one each for three different repos —
repos — the pane shows: the output shows:
``` ```
[docker] a3f9 implementer started 14:02:11 [egress,pipelock] docker a3f9 implementer egress,pipelock
[docker] b81c implementer started 14:03:45 [egress,pipelock] docker b81c implementer egress,pipelock
[docker] d220 implementer started 14:05:01 [egress,pipelock] docker d220 implementer egress,pipelock
``` ```
There is no way to tell which bottle is working on which task without attaching There is no way to tell which bottle is working on which task without attaching
@@ -37,34 +37,33 @@ which breaks the moment they switch windows.
## Goals / Success Criteria ## Goals / Success Criteria
1. After the operator selects an agent (supervisor picker or CLI argument), a 1. After the operator selects an agent (picker or CLI argument) and backend,
curses modal appears before the backend picker / preflight. The modal a curses modal appears before the preflight. The modal pre-fills the label
pre-fills the label with `<agent_name>-<slug_suffix>` (the same pattern with `<agent_name>-<slug_suffix>` (the same pattern currently shown in
currently shown in the agents pane). No color is pre-selected. `list active`). No color is pre-selected.
2. In the modal, any printable keystroke immediately replaces the pre-filled 2. In the modal, any printable keystroke immediately replaces the pre-filled
label and starts building the new name. Backspace edits normally. Enter label and starts building the new name. Backspace edits normally. Enter
at any point confirms — accepting the pre-fill if nothing was typed, or at any point confirms — accepting the pre-fill if nothing was typed, or
the in-progress text otherwise. the in-progress text otherwise.
3. After the label field is confirmed, the modal presents color selection: 3. After the label field is confirmed, the modal presents color selection:
a list of the 16 ANSI color names the operator can navigate with arrow a list of the 16 ANSI color names the operator can navigate with arrow
keys, or Enter with no selection to skip color entirely. keys, or Enter / Esc with no selection to skip color entirely.
4. `label` and `color` are stored in `BottleMetadata` and written to the 4. `label` and `color` are stored in `BottleMetadata` and written to the
bottle's `metadata.json`. Both fields default to `""` (empty / unset). bottle's `metadata.json`. Both fields default to `""` (empty / unset).
5. `ActiveAgent` carries `label` and `color`; `enumerate_active()` reads them 5. `ActiveAgent` carries `label` and `color`; `enumerate_active()` reads them
from `metadata.json`. from `metadata.json`.
6. The supervisor's agent row uses the label when non-empty (falling back to 6. `cli list active` shows the label when non-empty (falling back to
`agent_name`). If a non-empty color is set and the terminal supports it, `agent_name`). If a non-empty color is set and the terminal supports it,
the label substring is rendered in that color. the label is prefixed with the appropriate ANSI escape code and reset
afterward.
7. `BottleSpec` carries `label` and `color`; both backends' `prepare` steps 7. `BottleSpec` carries `label` and `color`; both backends' `prepare` steps
copy them into `BottleMetadata`. copy them into `BottleMetadata`.
8. `ClaudeAgentProvider.provision_plan()` writes `label``"name"` and 8. `ClaudeAgentProvider.provision_plan()` writes `label``"name"` and
`color``"color"` into the generated `claude.json`. Fields are omitted `color``"color"` into the generated `claude.json`. Fields are omitted
when empty. when empty.
9. The supervisor's `_new_agent_flow` includes the modal between agent 9. `cmd_start` calls `name_color_modal` after backend selection and before
selection and the backend picker. `_launch_bottle`; passes `label` / `color` into `BottleSpec`.
10. `cmd_start` (CLI) shows the same modal (via the shared `tui` module) 10. All existing unit tests stay green; no new tests are required for this
before `_launch_bottle`; passes `label` / `color` into `BottleSpec`.
11. All existing unit tests stay green; no new tests are required for this
change (the label/color fields are thin plumbing with no branching logic change (the label/color fields are thin plumbing with no branching logic
worth unit-testing beyond the already-tested metadata read/write path). worth unit-testing beyond the already-tested metadata read/write path).
@@ -73,12 +72,8 @@ which breaks the moment they switch windows.
- Showing the agent label inside the Claude Code TUI (status line, terminal - Showing the agent label inside the Claude Code TUI (status line, terminal
title, custom header). That requires upstream Claude Code / codex support. title, custom header). That requires upstream Claude Code / codex support.
Writing to `claude.json` is best-effort scaffolding for when that lands. Writing to `claude.json` is best-effort scaffolding for when that lands.
- Per-bottle color affecting anything outside the supervisor agents pane.
- Validating or constraining label content beyond the 64-byte printable cap. - Validating or constraining label content beyond the 64-byte printable cap.
- Persisting color-pair state across supervisor restarts (color pairs are
initialized fresh each session).
- Editing the label or color of an already-running bottle. - Editing the label or color of an already-running bottle.
- Exposing label/color via `./cli.py list` (out of scope for v1).
## Design ## Design
@@ -96,13 +91,13 @@ BottleSpec.label, BottleSpec.color
└─► contrib/claude/agent_provider.py → claude.json {"name": label, "color": color} └─► contrib/claude/agent_provider.py → claude.json {"name": label, "color": color}
(omitted when empty) (omitted when empty)
supervisor refresh cli list active
enumerate_active() → read_metadata(slug) → ActiveAgent.label / .color enumerate_active() → read_metadata(slug) → ActiveAgent.label / .color
agent row → label (colored) in the row string cmd_list → label (with ANSI color) in the row string
``` ```
### BottleSpec changes ### BottleSpec changes
@@ -162,42 +157,48 @@ class ActiveAgent:
constructing each `ActiveAgent`. The smolmachines backend gets the same constructing each `ActiveAgent`. The smolmachines backend gets the same
additions for symmetry. additions for symmetry.
### Agent row rendering ### `cli list active` rendering
The display name logic becomes: The current row format is tab-separated:
`{backend}\t{slug}\t{agent_name}\t{services}`
With labels it becomes:
```python ```python
display_name = a.label if a.label else a.agent_name display_name = a.label if a.label else a.agent_name
``` ```
Color rendering uses the existing `_try_init_green()` pattern as a model. Color is rendered via ANSI escape codes. A small `_ansi_color(color_name)`
A `_color_pair_for(color_name)` helper initialises a fresh curses color pair helper returns the appropriate escape prefix for the 16 named colors, or `""`
for the requested named color and returns its attr (or 0 on failure). Color when the name is unrecognised or the terminal doesn't support color
pairs are allocated lazily and cached in a `dict[str, int]` that lives for (`NO_COLOR` env var or `not sys.stdout.isatty()`).
the duration of the supervisor session.
The 16 ANSI color name → curses constant mapping: The 16 ANSI color name → escape mapping:
| Name | curses constant | | Name | ANSI code |
|------|----------------| |------|-----------|
| `black` | `curses.COLOR_BLACK` | | `black` | `\033[30m` |
| `red` | `curses.COLOR_RED` | | `red` | `\033[31m` |
| `green` | `curses.COLOR_GREEN` | | `green` | `\033[32m` |
| `yellow` | `curses.COLOR_YELLOW` | | `yellow` | `\033[33m` |
| `blue` | `curses.COLOR_BLUE` | | `blue` | `\033[34m` |
| `magenta` | `curses.COLOR_MAGENTA` | | `magenta` | `\033[35m` |
| `cyan` | `curses.COLOR_CYAN` | | `cyan` | `\033[36m` |
| `white` | `curses.COLOR_WHITE` | | `white` | `\033[37m` |
| `bright-*` | same constant + `curses.A_BOLD` | | `bright-black` | `\033[90m` |
| `bright-red` | `\033[91m` |
| `bright-green` | `\033[92m` |
| `bright-yellow` | `\033[93m` |
| `bright-blue` | `\033[94m` |
| `bright-magenta` | `\033[95m` |
| `bright-cyan` | `\033[96m` |
| `bright-white` | `\033[97m` |
Terminals that don't support color fall back to plain text (the helper returns Reset is `\033[0m`. Applied around the label substring only.
0, which ORed in is a no-op).
### The label+color modal ### The label+color modal
A single curses modal (`name_color_modal` in `bot_bottle/cli/tui.py`) handles A single curses modal (`name_color_modal` in `bot_bottle/cli/tui.py`) handles
both label and color in two sequential steps within the same window. It is both label and color in two sequential steps within the same window.
called identically from the supervisor flow and from `cmd_start`.
```python ```python
label, color = name_color_modal(default_label=f"{agent_name}-{slug_suffix}") label, color = name_color_modal(default_label=f"{agent_name}-{slug_suffix}")
@@ -237,9 +238,9 @@ the field was never edited, or the typed text otherwise.
``` ```
The list starts with `(none)` selected. Arrow keys move the cursor; Enter The list starts with `(none)` selected. Arrow keys move the cursor; Enter
confirms the highlighted choice; Esc or `q` skips color (equivalent to confirms the highlighted choice; Esc or `q` skips color. Each color name in
selecting `(none)`). Each color name in the list is rendered in its own color the list is rendered in its own curses color so the operator can preview the
so the operator can preview the palette. palette.
The function returns `(label, color)` — both strings, `color` is `""` when The function returns `(label, color)` — both strings, `color` is `""` when
`(none)` is selected or the step is skipped. `(none)` is selected or the step is skipped.
@@ -247,16 +248,15 @@ The function returns `(label, color)` — both strings, `color` is `""` when
### Slug suffix for the default label ### Slug suffix for the default label
The default label is `<agent_name>-<slug_suffix>`, where `slug_suffix` is the The default label is `<agent_name>-<slug_suffix>`, where `slug_suffix` is the
last four characters of the slug (the same short hash shown in the agents last four characters of the slug (the same short hash shown in `list active`).
pane). Both the supervisor and `cmd_start` have access to the slug after
`bottle_identity()` is called; the default label is computed there and passed
to `name_color_modal`.
In `cmd_start` the slug is not yet known at the time the modal appears (it is In `cmd_start` the slug is minted inside `prepare`, after the modal appears.
minted inside `prepare`). The modal is therefore called with The modal is therefore called with the manifest agent key as a fallback
`default_label=args.name` (the manifest agent key) as a simpler fallback; the (`default_label=agent_name`). Once `prepare` returns the plan (which contains
supervisor path, which has the slug available from `_new_agent_flow`, uses the the slug), the `BottleSpec` is not reconstructed — the label entered by the
full `<agent_name>-<slug_suffix>` form. operator is already in the spec. The full `<agent_name>-<slug_suffix>` form is
only available for display in subsequent `list active` calls once the bottle
is running.
### Claude Code config injection ### Claude Code config injection
@@ -266,8 +266,8 @@ Per PRD 0050, the `claude.json` trust-marker file is written by
`color: str = ""` keyword parameters to `provision_plan()` on both the `color: str = ""` keyword parameters to `provision_plan()` on both the
`AgentProvider` ABC and `ClaudeAgentProvider`, and to the `AgentProvider` ABC and `ClaudeAgentProvider`, and to the
`agent_provision_plan()` shim in `agent_provider.py`. Both `prepare.py` `agent_provision_plan()` shim in `agent_provider.py`. Both `prepare.py`
modules pass `spec.label` / `spec.color`; other providers accept the params modules pass `spec.label` / `spec.color`; `CodexAgentProvider` accepts the
and ignore them. params and ignores them.
In `ClaudeAgentProvider.provision_plan()`: In `ClaudeAgentProvider.provision_plan()`:
@@ -301,18 +301,17 @@ Two PRs, each independently mergeable.
`AgentProvider.provision_plan()` (ABC), `ClaudeAgentProvider.provision_plan()` `AgentProvider.provision_plan()` (ABC), `ClaudeAgentProvider.provision_plan()`
(uses them in the `claude.json` write), and the `agent_provision_plan()` shim. (uses them in the `claude.json` write), and the `agent_provision_plan()` shim.
`CodexAgentProvider` accepts the params and ignores them. `CodexAgentProvider` accepts the params and ignores them.
- `cmd_list`: update `list active` row to use `label` when non-empty, with
ANSI color escape codes.
- No prompt changes; no UI changes. All existing behavior is identical. - No prompt changes; no UI changes. All existing behavior is identical.
### Chunk 2 — modal + display ### Chunk 2 — modal
- `bot_bottle/cli/tui.py`: add `name_color_modal(default_label)` implementing - `bot_bottle/cli/tui.py`: add `name_color_modal(default_label)` implementing
the two-step curses window described above. the two-step curses window described above.
- Supervisor `_new_agent_flow`: call `name_color_modal` after agent selection, - `cmd_start`: call `name_color_modal(default_label=agent_name)` after backend
pass `label` / `color` into `BottleSpec`. selection and before `_launch_bottle`; pass `label` / `color` into
- `cmd_start`: call `name_color_modal(default_label=args.name)` before `BottleSpec`.
`_launch_bottle`; pass `label` / `color` into `BottleSpec`.
- Supervisor agent row: add `_color_pair_for` helper; update row rendering to
use `a.label` with color.
## Open questions ## Open questions