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:
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user