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.
`implementer-a3f9`) and leaves color unset; Enter with no changes accepts
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,
replacing the bare manifest key. Inject the label and color into the
in-container `claude.json` as `name` / `color` so Claude Code can surface them
in its own harness when upstream support lands.
rendered in the chosen ANSI color — in `cli list active` output, replacing
the bare manifest key. Inject the label and color into the in-container
`claude.json` as `name` / `color` so Claude Code can surface them in its own
harness when upstream support lands.
## Problem
The supervisor's agents pane identifies each running instance by its manifest
agent key (e.g., `implementer`) plus a random slug suffix. When an operator
runs three `implementer` bottles simultaneously — one each for three different
repos — the pane shows:
`cli list active` identifies each running instance by its manifest agent key
(e.g., `implementer`) plus a random slug suffix. When an operator runs three
`implementer` bottles simultaneously — one each for three different repos —
the output shows:
```
[docker] a3f9 implementer started 14:02:11 [egress,pipelock]
[docker] b81c implementer started 14:03:45 [egress,pipelock]
[docker] d220 implementer started 14:05:01 [egress,pipelock]
docker a3f9 implementer egress,pipelock
docker b81c implementer egress,pipelock
docker d220 implementer egress,pipelock
```
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
1. After the operator selects an agent (supervisor picker or CLI argument), a
curses modal appears before the backend picker / preflight. The modal
pre-fills the label with `<agent_name>-<slug_suffix>` (the same pattern
currently shown in the agents pane). No color is pre-selected.
1. After the operator selects an agent (picker or CLI argument) and backend,
a curses modal appears before the preflight. The modal pre-fills the label
with `<agent_name>-<slug_suffix>` (the same pattern currently shown in
`list active`). No color is pre-selected.
2. In the modal, any printable keystroke immediately replaces the pre-filled
label and starts building the new name. Backspace edits normally. Enter
at any point confirms — accepting the pre-fill if nothing was typed, or
the in-progress text otherwise.
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
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
bottle's `metadata.json`. Both fields default to `""` (empty / unset).
5. `ActiveAgent` carries `label` and `color`; `enumerate_active()` reads them
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,
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
copy them into `BottleMetadata`.
8. `ClaudeAgentProvider.provision_plan()` writes `label``"name"` and
`color``"color"` into the generated `claude.json`. Fields are omitted
when empty.
9. The supervisor's `_new_agent_flow` includes the modal between agent
selection and the backend picker.
10. `cmd_start` (CLI) shows the same modal (via the shared `tui` module)
before `_launch_bottle`; passes `label` / `color` into `BottleSpec`.
11. All existing unit tests stay green; no new tests are required for this
9. `cmd_start` calls `name_color_modal` after backend selection and before
`_launch_bottle`; passes `label` / `color` into `BottleSpec`.
10. 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
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
title, custom header). That requires upstream Claude Code / codex support.
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.
- Persisting color-pair state across supervisor restarts (color pairs are
initialized fresh each session).
- Editing the label or color of an already-running bottle.
- Exposing label/color via `./cli.py list` (out of scope for v1).
## Design
@@ -96,13 +91,13 @@ BottleSpec.label, BottleSpec.color
└─► contrib/claude/agent_provider.py → claude.json {"name": label, "color": color}
(omitted when empty)
supervisor refresh
cli list active
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
@@ -162,42 +157,48 @@ class ActiveAgent:
constructing each `ActiveAgent`. The smolmachines backend gets the same
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
display_name = a.label if a.label else a.agent_name
```
Color rendering uses the existing `_try_init_green()` pattern as a model.
A `_color_pair_for(color_name)` helper initialises a fresh curses color pair
for the requested named color and returns its attr (or 0 on failure). Color
pairs are allocated lazily and cached in a `dict[str, int]` that lives for
the duration of the supervisor session.
Color is rendered via ANSI escape codes. A small `_ansi_color(color_name)`
helper returns the appropriate escape prefix for the 16 named colors, or `""`
when the name is unrecognised or the terminal doesn't support color
(`NO_COLOR` env var or `not sys.stdout.isatty()`).
The 16 ANSI color name → curses constant mapping:
The 16 ANSI color name → escape mapping:
| Name | curses constant |
|------|----------------|
| `black` | `curses.COLOR_BLACK` |
| `red` | `curses.COLOR_RED` |
| `green` | `curses.COLOR_GREEN` |
| `yellow` | `curses.COLOR_YELLOW` |
| `blue` | `curses.COLOR_BLUE` |
| `magenta` | `curses.COLOR_MAGENTA` |
| `cyan` | `curses.COLOR_CYAN` |
| `white` | `curses.COLOR_WHITE` |
| `bright-*` | same constant + `curses.A_BOLD` |
| Name | ANSI code |
|------|-----------|
| `black` | `\033[30m` |
| `red` | `\033[31m` |
| `green` | `\033[32m` |
| `yellow` | `\033[33m` |
| `blue` | `\033[34m` |
| `magenta` | `\033[35m` |
| `cyan` | `\033[36m` |
| `white` | `\033[37m` |
| `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
0, which ORed in is a no-op).
Reset is `\033[0m`. Applied around the label substring only.
### The label+color modal
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
called identically from the supervisor flow and from `cmd_start`.
both label and color in two sequential steps within the same window.
```python
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
confirms the highlighted choice; Esc or `q` skips color (equivalent to
selecting `(none)`). Each color name in the list is rendered in its own color
so the operator can preview the palette.
confirms the highlighted choice; Esc or `q` skips color. Each color name in
the list is rendered in its own curses color so the operator can preview the
palette.
The function returns `(label, color)` — both strings, `color` is `""` when
`(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
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
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`.
last four characters of the slug (the same short hash shown in `list active`).
In `cmd_start` the slug is not yet known at the time the modal appears (it is
minted inside `prepare`). The modal is therefore called with
`default_label=args.name` (the manifest agent key) as a simpler fallback; the
supervisor path, which has the slug available from `_new_agent_flow`, uses the
full `<agent_name>-<slug_suffix>` form.
In `cmd_start` the slug is minted inside `prepare`, after the modal appears.
The modal is therefore called with the manifest agent key as a fallback
(`default_label=agent_name`). Once `prepare` returns the plan (which contains
the slug), the `BottleSpec` is not reconstructed — the label entered by the
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
@@ -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
`AgentProvider` ABC and `ClaudeAgentProvider`, and to the
`agent_provision_plan()` shim in `agent_provider.py`. Both `prepare.py`
modules pass `spec.label` / `spec.color`; other providers accept the params
and ignore them.
modules pass `spec.label` / `spec.color`; `CodexAgentProvider` accepts the
params and ignores them.
In `ClaudeAgentProvider.provision_plan()`:
@@ -301,18 +301,17 @@ Two PRs, each independently mergeable.
`AgentProvider.provision_plan()` (ABC), `ClaudeAgentProvider.provision_plan()`
(uses them in the `claude.json` write), and the `agent_provision_plan()` shim.
`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.
### Chunk 2 — modal + display
### Chunk 2 — modal
- `bot_bottle/cli/tui.py`: add `name_color_modal(default_label)` implementing
the two-step curses window described above.
- Supervisor `_new_agent_flow`: call `name_color_modal` after agent selection,
pass `label` / `color` into `BottleSpec`.
- `cmd_start`: call `name_color_modal(default_label=args.name)` before
`_launch_bottle`; pass `label` / `color` into `BottleSpec`.
- Supervisor agent row: add `_color_pair_for` helper; update row rendering to
use `a.label` with color.
- `cmd_start`: call `name_color_modal(default_label=agent_name)` after backend
selection and before `_launch_bottle`; pass `label` / `color` into
`BottleSpec`.
## Open questions