Web dashboard: multi-host agent launch + monitoring (paid control plane) #327

Open
opened 2026-07-01 19:32:09 -04:00 by didericis-claude · 4 comments
Collaborator

Summary

A web-based control-plane dashboard for bot-bottle: pick a host, launch an agent into an isolated bottle, and watch running agents — from a browser. This is the operator-facing surface for the paid control plane (per monetization-positioning: give away the sandbox, charge for the control plane). Cross-host launch + fleet visibility is exactly the "cross-run aggregation + central enforcement + identity/fleet = paid" line.

This is a design sketch to align on shape before a PRD.

Goals

  • Web-based, reachable from a browser (incl. mobile) on the operator's network.
  • Zero build step, minimal dependencies — vanilla HTML/CSS, served from the Python stdlib (http.server / socketserver), same discipline as supervise_server.py. Small amount of vanilla JS (fetch + polling) only where it earns its keep; no framework, no bundler, no npm.
  • Show the hosts we can launch agents on, and each host's backend/capabilities.
  • Launch an agent (choose host + bottle + prompt/task) from the UI.
  • Show running agents with live-ish status, and let you drill in / stop.
  • Define & store bottles and agents under an account — author/edit them in the UI and persist them account-scoped, so they're reusable across launches and (later) synced across hosts, not just hand-written local files.
  • Store env vars / secrets account-scoped, as custody (encrypted at rest, injected into the sidecar at launch, never exposed to the agent) — this is the secret-custody wedge, made a first-class surface.
  • Ships behind the paid tier — this is control-plane, not the OSS runtime.

Non-goals (for the first cut)

  • Not a replacement for ./cli.py — it drives the same orchestrator, doesn't fork logic.
  • No SSO/RBAC/retention yet (those are the deeper paid lines; land the surface first).
  • No in-browser terminal/streaming logs in v1 (polling status + tail is enough to start).

Why served via stdlib / vanilla

  • Matches the existing trust posture: small, auditable, no supply-chain surface (the whole product is about not trusting fat dependency trees).
  • The orchestrator (bot_bottle/orchestrator/) already owns lifecycle/state/events; the dashboard is a thin read/command layer over it. A stdlib HTTP server + a handful of JSON endpoints is enough.
  • Consistent with supervise_server.py, which is already a stdlib-only HTTP+JSON server.

Proposed shape

img

Layout (sidebar + main area)

┌─────────┬──────────────────────────────────────────────────────────────────┐
│ Home    │  RUNNING AGENTS                                                  │
│ ─────── │                                                                  │
│ Hosts   │  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐               │
│ Bottles │  │ fix-iss-421 │  │ refactor-e… │  │ docs-sweep  │               │
│ Agents  │  │  [●running] │  │  [●running] │  │ [◌starting] │               │
│ Vault   │  │  delphi     │  │  papyros    │  │  delphi     │               │
│ Forge   │  └─────────────┘  └─────────────┘  └─────────────┘               │
│         │                                                                  │
│         │  ┌─── terminal ──────────────┐  ┌─── info ─────────────────────┐ │
│         │  │ $ ...                     │  │ [Manifest] [Logs] [Proposals]│ │
│         │  │                           │  │                              │ │
│         │  │                           │  │ ...                          │ │
│         │  └───────────────────────────┘  └──────────────────────────────┘ │
└─────────┴──────────────────────────────────────────────────────────────────┘
  • Sidebar — navigation with: Home (at top), Hosts, Bottles, Agents, Vault, Forge.
  • Home view — running agents displayed as cards with their labels. Animation state reflects whether an agent is running (animated) or frozen (static).
  • Agent detail — clicking an agent card activates a terminal section and an info section, displayed side by side below the agent list.
  • Info section — tabbed: Manifest, Logs, Proposals.

Initial version scope

The first cut is intentionally minimal — home view only:

  • Sidebar navigation renders but only Home view is implemented.
  • No search or filtering.
  • No ability to create new agents from the UI.
  • Logs tab in info section is omitted.
  • All views other than Home are out of scope.

Endpoints (stdlib server, JSON)

  • GET /api/hosts — hosts + backend + reachability + running count.
  • GET /api/agents — running/recent bottles with status (polled every ~2–3s).
  • GET /api/agents/{slug} — detail: manifest summary, provenance, recent egress log tail.
  • POST /api/agents — launch {host, bottle, prompt} → orchestrator start.
  • POST /api/agents/{slug}/stop — stop/teardown.
  • Static GET / + /app.css + /app.js served from an embedded dashboard/ dir.

All command endpoints go through the existing orchestrator (lifecycle.py / runner.py / store.py / targeting.py) rather than re-implementing launch/stop.

Account-scoped definitions (bottles, agents, env)

Today a bottle is a hand-written manifest under ~/.bot-bottle/bottles/, and agents can be shipped by repos (but bottles cannot — the $HOME trust boundary). The dashboard adds an account as the owning scope for these definitions, so an operator can author them once and reuse/launch them anywhere.

  • Define bottles in the UI — a form/editor over the manifest schema (manifest_bottle.py / manifest_schema.py): skills, env, git identity/remotes, egress routes. Validate with the existing loader (manifest_loader.py) so the UI can't produce a manifest the runtime would reject; unknown keys still die at load.
  • Define agents in the UI — author/edit agent definitions (manifest_agent.py) and compose bottles via extends: (keep provider/runtime policy in a base like claude, overlay task bottles).
  • Store under an account — definitions live account-scoped (server-side), the operator's library. On launch, the chosen definition is materialized into the host's ~/.bot-bottle/bottles/ (preserving the trust boundary: bottles still resolve from $HOME, the account store is a synced source, not a new load path the agent can reach).

Env vars / secrets — custody, not just storage

This must not weaken the existing secret model: host secrets live in a sidecar, the agent dials http://sidecar:9099/<path>, and the proxy injects the real token — printenv in the agent shows proxy URLs only. So env-var storage here is custody, not "hand the agent a .env":

  • Secrets are stored encrypted at rest, account-scoped, and never returned in plaintext by any read API (write-only / masked on read, like every other secrets manager).
  • At launch, secret values are delivered to the sidecar (the injection point), not into the agent's environment — same posture as today's sidecar token injection.
  • A definition references a secret by name; the account store resolves the value at launch time. The UI shows names + last-updated, never values.
  • Clear split: non-secret env (safe to show/edit inline) vs. secret env (custody-managed, masked). Reuse manifest_egress.py / sidecar auth-injection semantics rather than inventing a second secret path.

Additional endpoints

  • GET/POST/PUT/DELETE /api/bottles[/{name}] — CRUD account-scoped bottle definitions (validated through the manifest loader).
  • GET/POST/PUT/DELETE /api/agents-def[/{name}] — CRUD account-scoped agent definitions. (Distinct from /api/agents, which is running instances.)
  • GET /api/secrets — list secret names + metadata only (never values).
  • PUT /api/secrets/{name} — set/rotate a secret value (write-only).
  • DELETE /api/secrets/{name} — remove.

Open questions

  • Host inventory source of truth — reuse orchestrator targeting.py/config.py, or a new hosts config? How are remote hosts reached (SSH? a remote backend agent)?
  • Auth on the dashboard itself — even v1 needs something (bound to loopback + token? reverse-proxy assumption?). It launches agents, so it can't be open.
  • Live status transport — start with polling; is SSE worth it for the running-agents table later?
  • Where it lives — part of the (closed) bot-bottle-orchestrator binary per the open/paid split, or a dashboard module gated behind the paid build?
  • Provenance surfacing — show the signed run-provenance per agent in the detail drawer (audit-at-a-glance)?
  • Account store backend — where do account-scoped definitions/secrets live (local encrypted file per account? the closed orchestrator's store)? What's the account model in a single-operator vs. team world — does "account" imply the SSO/RBAC line arrives with it, or is v1 a single implicit account?
  • Secret encryption / KMS — encryption-at-rest key source (OS keychain, passphrase-derived, external KMS)? How do secrets reach a remote host's sidecar without transiting the agent or being logged?
  • Definition ↔ host sync — is materializing into ~/.bot-bottle/bottles/ at launch enough, or do we need explicit push/pull so a definition edited on one host is usable on another?

Acceptance (v1 — home view only)

  • Sidebar renders with Home (at top), Hosts, Bottles, Agents, Vault, Forge — only Home view is functional.
  • Home view shows running agents as cards with labels; animation state reflects running vs. frozen.
  • Clicking an agent card activates a terminal section and an info section (Manifest and Proposals tabs) displayed side by side below the cards.
  • Running list reflects status transitions (starting → running → exited).
  • Stop from the UI tears the bottle down.
  • No search, filtering, create-new-agent, Logs tab, or non-Home views in v1.
  • No npm/build step; page is vanilla HTML/CSS + minimal fetch JS.
## Summary A web-based **control-plane dashboard** for bot-bottle: pick a host, launch an agent into an isolated bottle, and watch running agents — from a browser. This is the operator-facing surface for the paid control plane (per [monetization-positioning](https://gitea.dideric.is/didericis/bot-bottle/src/branch/main/docs/research/monetization-positioning.md): *give away the sandbox, charge for the control plane*). Cross-host launch + fleet visibility is exactly the "cross-run aggregation + central enforcement + identity/fleet = paid" line. This is a **design sketch** to align on shape before a PRD. ## Goals - Web-based, reachable from a browser (incl. mobile) on the operator's network. - **Zero build step, minimal dependencies** — vanilla HTML/CSS, served from the Python stdlib (`http.server` / `socketserver`), same discipline as `supervise_server.py`. Small amount of vanilla JS (fetch + polling) only where it earns its keep; no framework, no bundler, no npm. - Show the **hosts** we can launch agents on, and each host's backend/capabilities. - **Launch an agent** (choose host + bottle + prompt/task) from the UI. - **Show running agents** with live-ish status, and let you drill in / stop. - **Define & store bottles and agents under an account** — author/edit them in the UI and persist them account-scoped, so they're reusable across launches and (later) synced across hosts, not just hand-written local files. - **Store env vars / secrets** account-scoped, as *custody* (encrypted at rest, injected into the sidecar at launch, never exposed to the agent) — this is the secret-custody wedge, made a first-class surface. - Ships behind the **paid tier** — this is control-plane, not the OSS runtime. ## Non-goals (for the first cut) - Not a replacement for `./cli.py` — it drives the same orchestrator, doesn't fork logic. - No SSO/RBAC/retention yet (those are the *deeper* paid lines; land the surface first). - No in-browser terminal/streaming logs in v1 (polling status + tail is enough to start). ## Why served via stdlib / vanilla - Matches the existing trust posture: small, auditable, no supply-chain surface (the whole product is about *not* trusting fat dependency trees). - The orchestrator (`bot_bottle/orchestrator/`) already owns lifecycle/state/events; the dashboard is a thin read/command layer over it. A stdlib HTTP server + a handful of JSON endpoints is enough. - Consistent with `supervise_server.py`, which is already a stdlib-only HTTP+JSON server. ## Proposed shape ![img](https://gitea.dideric.is/attachments/316ea958-ef52-4280-afa6-779b77c58276) ### Layout (sidebar + main area) ``` ┌─────────┬──────────────────────────────────────────────────────────────────┐ │ Home │ RUNNING AGENTS │ │ ─────── │ │ │ Hosts │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ Bottles │ │ fix-iss-421 │ │ refactor-e… │ │ docs-sweep │ │ │ Agents │ │ [●running] │ │ [●running] │ │ [◌starting] │ │ │ Vault │ │ delphi │ │ papyros │ │ delphi │ │ │ Forge │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ │ │ │ ┌─── terminal ──────────────┐ ┌─── info ─────────────────────┐ │ │ │ │ $ ... │ │ [Manifest] [Logs] [Proposals]│ │ │ │ │ │ │ │ │ │ │ │ │ │ ... │ │ │ │ └───────────────────────────┘ └──────────────────────────────┘ │ └─────────┴──────────────────────────────────────────────────────────────────┘ ``` - **Sidebar** — navigation with: Home (at top), Hosts, Bottles, Agents, Vault, Forge. - **Home view** — running agents displayed as cards with their labels. Animation state reflects whether an agent is running (animated) or frozen (static). - **Agent detail** — clicking an agent card activates a terminal section and an info section, displayed side by side below the agent list. - **Info section** — tabbed: Manifest, Logs, Proposals. ### Initial version scope The first cut is intentionally minimal — home view only: - Sidebar navigation renders but only Home view is implemented. - No search or filtering. - No ability to create new agents from the UI. - Logs tab in info section is omitted. - All views other than Home are out of scope. ### Endpoints (stdlib server, JSON) - `GET /api/hosts` — hosts + backend + reachability + running count. - `GET /api/agents` — running/recent bottles with status (polled every ~2–3s). - `GET /api/agents/{slug}` — detail: manifest summary, provenance, recent egress log tail. - `POST /api/agents` — launch `{host, bottle, prompt}` → orchestrator start. - `POST /api/agents/{slug}/stop` — stop/teardown. - Static `GET /` + `/app.css` + `/app.js` served from an embedded `dashboard/` dir. All command endpoints go through the existing orchestrator (`lifecycle.py` / `runner.py` / `store.py` / `targeting.py`) rather than re-implementing launch/stop. ## Account-scoped definitions (bottles, agents, env) Today a bottle is a hand-written manifest under `~/.bot-bottle/bottles/`, and agents can be shipped by repos (but bottles cannot — the `$HOME` trust boundary). The dashboard adds an **account** as the owning scope for these definitions, so an operator can author them once and reuse/launch them anywhere. - **Define bottles in the UI** — a form/editor over the manifest schema (`manifest_bottle.py` / `manifest_schema.py`): skills, env, git identity/remotes, egress routes. Validate with the *existing* loader (`manifest_loader.py`) so the UI can't produce a manifest the runtime would reject; unknown keys still die at load. - **Define agents in the UI** — author/edit agent definitions (`manifest_agent.py`) and compose bottles via `extends:` (keep provider/runtime policy in a base like `claude`, overlay task bottles). - **Store under an account** — definitions live account-scoped (server-side), the operator's library. On launch, the chosen definition is materialized into the host's `~/.bot-bottle/bottles/` (preserving the trust boundary: bottles still resolve from `$HOME`, the account store is a synced source, not a new load path the agent can reach). ### Env vars / secrets — custody, not just storage This must **not** weaken the existing secret model: host secrets live in a sidecar, the agent dials `http://sidecar:9099/<path>`, and the proxy injects the real token — `printenv` in the agent shows proxy URLs only. So env-var storage here is **custody**, not "hand the agent a `.env`": - Secrets are stored **encrypted at rest**, account-scoped, and never returned in plaintext by any read API (write-only / masked on read, like every other secrets manager). - At launch, secret *values* are delivered to the **sidecar** (the injection point), not into the agent's environment — same posture as today's sidecar token injection. - A definition references a secret **by name**; the account store resolves the value at launch time. The UI shows names + last-updated, never values. - Clear split: non-secret env (safe to show/edit inline) vs. secret env (custody-managed, masked). Reuse `manifest_egress.py` / sidecar auth-injection semantics rather than inventing a second secret path. ### Additional endpoints - `GET/POST/PUT/DELETE /api/bottles[/{name}]` — CRUD account-scoped bottle definitions (validated through the manifest loader). - `GET/POST/PUT/DELETE /api/agents-def[/{name}]` — CRUD account-scoped agent definitions. (Distinct from `/api/agents`, which is *running* instances.) - `GET /api/secrets` — list secret **names** + metadata only (never values). - `PUT /api/secrets/{name}` — set/rotate a secret value (write-only). - `DELETE /api/secrets/{name}` — remove. ## Open questions - **Host inventory source of truth** — reuse orchestrator `targeting.py`/`config.py`, or a new hosts config? How are remote hosts reached (SSH? a remote backend agent)? - **Auth on the dashboard itself** — even v1 needs *something* (bound to loopback + token? reverse-proxy assumption?). It launches agents, so it can't be open. - **Live status transport** — start with polling; is SSE worth it for the running-agents table later? - **Where it lives** — part of the (closed) `bot-bottle-orchestrator` binary per the open/paid split, or a `dashboard` module gated behind the paid build? - **Provenance surfacing** — show the signed run-provenance per agent in the detail drawer (audit-at-a-glance)? - **Account store backend** — where do account-scoped definitions/secrets live (local encrypted file per account? the closed orchestrator's store)? What's the account model in a single-operator vs. team world — does "account" imply the SSO/RBAC line arrives with it, or is v1 a single implicit account? - **Secret encryption / KMS** — encryption-at-rest key source (OS keychain, passphrase-derived, external KMS)? How do secrets reach a *remote* host's sidecar without transiting the agent or being logged? - **Definition ↔ host sync** — is materializing into `~/.bot-bottle/bottles/` at launch enough, or do we need explicit push/pull so a definition edited on one host is usable on another? ## Acceptance (v1 — home view only) - [ ] Sidebar renders with Home (at top), Hosts, Bottles, Agents, Vault, Forge — only Home view is functional. - [ ] Home view shows running agents as cards with labels; animation state reflects running vs. frozen. - [ ] Clicking an agent card activates a terminal section and an info section (Manifest and Proposals tabs) displayed side by side below the cards. - [ ] Running list reflects status transitions (starting → running → exited). - [ ] Stop from the UI tears the bottle down. - [ ] No search, filtering, create-new-agent, Logs tab, or non-Home views in v1. - [ ] No npm/build step; page is vanilla HTML/CSS + minimal fetch JS.
didericis added the Kind/Feature label 2026-07-06 13:49:02 -04:00
Owner

I think the dashboard for desktop should look like this:

  • Sidebar should contain menu with hosts, bottles, agents, vault, home and forge buttons
  • Home view (depicted) shows running agents and their labels. Animation state will reflect whether they’re running or frozen.
  • Clicking an agent activates the terminal and “status” sections depicted
  • ”status” section contain tabs to show the manifest, logs, and proposals.

Initial version will omit search, filtering, the ability to create new agents, logs, and all other views except the home view. Should be very basic.

(Also “home” menu button should be at the top of the menu, not second from bottom like depicted)

I think the dashboard for desktop should look like this: - Sidebar should contain menu with hosts, bottles, agents, vault, home and forge buttons - Home view (depicted) shows running agents and their labels. Animation state will reflect whether they’re running or frozen. - Clicking an agent activates the terminal and “status” sections depicted - ”status” section contain tabs to show the manifest, logs, and proposals. Initial version will omit search, filtering, the ability to create new agents, logs, and all other views except the home view. Should be very basic. (Also “home” menu button should be at the top of the menu, not second from bottom like depicted)
Author
Collaborator

Updated the issue to reflect this — terminal and status are now side by side in the diagram, sidebar words are no longer truncated, and the photo is embedded above the layout sketch.

Updated the issue to reflect this — terminal and status are now side by side in the diagram, sidebar words are no longer truncated, and the photo is embedded above the layout sketch.
Owner

@didericis-claude we should probably rename the "status" window to "info"

@didericis-claude we should probably rename the "status" window to "info"
Author
Collaborator

Good call — updated throughout (diagram, descriptions, and acceptance criteria).

Good call — updated throughout (diagram, descriptions, and acceptance criteria).
Sign in to join this conversation.
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: didericis/bot-bottle#327