Skip to content

Fleet Panel & Profile UI #350

Description

@jeonghun-jj-lee

Fleet Panel & Profile UI

Important

Problem

The fleet system (ADR 0005) provides multi-machine topology, a session registry state
machine, and CLI verbs — but no visual surface. Fleet management requires terminal
commands (amico fleet list/stop/steer/re-tier/sweep), topology health is checked only
at extension activation, Fleet Profiles don't exist as a first-class entity, there's
no way to launch fleet-managed sessions from the UI, and setting up a fleet requires
manual SSH commands across multiple machines
— no guided flow exists.

Approach

Three slices, delivered sequentially:

Slice 1 — Fleet Panel + Setup Wizard (VS Code native webview in the activity bar):

  • Setup wizard: guided fleet creation from standalone — provide SSH target, panel
    validates connectivity, configures both machines (fleet.json, guard, tunnel, server
    process), validates end-to-end. Full lifecycle: create fleet, add machines, remove
    machines, dismantle.
  • Topology graph (SVG star layout, clickable nodes with detail popovers)
  • Fleet Profile CRUD (stored as TOML under ~/.amico/ops/fleet/profiles/)
  • Launch via play-button per profile (silent spool, session appears in dropdown)
  • Aggregate stats (active sessions, tokens today) + sweep action
  • Status bar item → click focuses the panel

Slice 2 — Sessions Dropdown Enrichment (bridge extension → opencode app):

  • Fleet icon on fleet-managed sessions in the dropdown
  • Context menu: steer, stop, re-tier, view details
  • Bridge protocol: extension pushes fleet-state snapshots via chat_bridge; app posts
    fleet-action commands back

Approaches Considered

  1. VS Code native webview, panel-first (chosen) — extension has direct data access,
    Device Inspector pattern proven, self-contained first slice. Bridge deferred.
  2. Inside the opencode SolidJS app — SolidJS reactivity is nice, but fleet data lives
    in extension-side TOML files. Requires server routes or bridge for ALL data, pollutes
    the upstream fork. Rejected.
  3. Bridge-first, build dropdown enrichment before the panel — higher per-line value
    (dropdown used constantly) but the bridge is the riskiest piece and delays first value.
    Rejected for ordering; the work still happens in Slice 2.
  4. Full vertical (both slices at once) — largest blast radius, harder to review and
    ship. Rejected.
  5. CLI-only setup (no wizard) — the current state. Works, but requires terminal
    expertise across multiple machines. Not intuitive for newbies. Rejected as permanent
    state; the panel should be the primary setup surface.

Scope

In scope:

  • Fleet Panel webview (host provider + webview entrypoint + view builder)
  • Setup wizard (create fleet, add machine, remove machine, dismantle)
  • Remote host settings (session DB path, port, binary path, log dir) — read/write over SSH
  • Topology graph (SVG, star layout, node popovers)
  • Fleet Profile entity (TOML storage, CRUD from panel, profile schema)
  • Session launch from profile (spool via registry, silent)
  • Aggregate stats section
  • Status bar item evolution (click → focus panel)
  • Sessions dropdown fleet icon + context menu (Slice 2)
  • Bridge protocol additions (fleet-state inbound, fleet-action outbound)
  • CONTEXT.md domain term additions (Fleet Profile, Fleet-managed session)
  • ADR 0007

Deferred:

  • Budget enforcement (hard caps, auto-stop) — visibility only for now
  • Visual network graph with a layout algorithm (hand-positioned star suffices)
  • Distributed execution across multiple nodes (single canonical server model)
  • Profile import/export
  • Fleet-level batch actions beyond sweep (global stop, batch re-tier)
  • Server migration (moving the canonical server to a different machine)

Assumptions

  • The fleet topology remains a star (one canonical server + N clients) — no mesh
  • One fleet at a time — a machine belongs to at most one fleet; reconfiguring the
    existing fleet (change host, port, SSH alias) is supported, creating a second is not
  • SSH key-based auth to the remote machine is a prerequisite (the wizard validates but
    does not configure SSH keys)
  • The opencode binary can be copied to the remote machine via SCP if not present
  • Fleet Profiles are local to the fleet (not synced across fleets)
  • The opencode app's sessions list is accessible for enrichment via the existing
    chat_bridge relay mechanism
  • The Device Inspector pattern (WebviewViewProvider + postMessage) handles the data
    volume (fleet records change at session-lifecycle frequency, not per-iteration)
  • The canonical server is a headless system service — sessions and solves run
    independently of any client connection. Clients are viewers/controllers, not hosts.

Acceptance Criteria

Slice 1 — Fleet Panel + Setup Wizard

Setup Wizard

  • Standalone state: when no fleet exists, panel shows single-node graph + prominent
    "Create Fleet" button
  • Disconnected state: when fleet was previously configured (fleet.json has canonical
    coordinates but role = standalone), panel shows "Reconnect Fleet" button instead of
    "Create Fleet" — one click re-validates the connection and switches back to client mode
  • fleet.json preserves canonical coordinates when going standalone (Go Standalone
    writes role: "standalone" but keeps the canonical object intact)
  • Create Fleet flow:
    • Choose topology: "This machine is the server" or "A remote machine is the server"
    • For remote: text input for SSH alias or user@host; validates SSH connectivity
    • Pre-flight checks displayed as a live checklist: SSH reachable, binary present on
      remote (offer SCP if missing), port available
    • Configures remote machine over SSH: writes fleet.json (role: server), installs
      guard, starts canonical server as launchd service, generates fleet token
    • Configures local machine: writes fleet.json (role: client), installs guard +
      tunnel plist, stores fleet token
    • End-to-end validation: tunnel connects, health probe passes, topology graph updates
      to two nodes with solid connection line
    • Failure at any step is actionable (specific error message + suggested fix)
  • Add Machine: button in topology section; same flow as Create but skips server
    setup (server already running); new node appears in graph on success
  • Remove Machine: node popover → "Remove from Fleet"; SSHs into target, writes
    standalone to fleet.json, unloads tunnel/guard; node disappears from graph
  • Dismantle Fleet: offered when removing the last client or explicitly via menu;
    stops canonical server, reverts all machines to standalone
  • Reconfigure Fleet: edit the canonical server's host, port, or SSH alias from the
    panel without dismantling; validates new connection before committing the change

Panel (monitoring + profiles + host settings)

  • amicode.fleet webview view registered in package.json under views.amicode
  • Panel always visible in the activity bar (no when context-key gate)
  • Topology graph: SVG renders canonical server node + client nodes; solid lines for
    connected, dotted for configured-but-offline; click a node shows popover (hostname,
    last-seen, health, active session count)
  • Standalone mode: graph shows single node, no edges, label "Standalone — all local"
  • Host Settings section: when in fleet mode (server or client), exposes configurable
    settings for the canonical server — read/written over SSH:
    • Session database path (where the opencode server stores sessions)
    • Server port
    • Binary path (which opencode binary the server runs)
    • Log directory
    • Any other host-level settings the canonical server reads from its config
    • Changes require a server restart — panel shows "Restart required" indicator and
      offers a one-click restart (stops + starts the launchd/systemd service remotely)
    • When "this machine" is the server, settings are read/written locally (no SSH needed)
  • Profiles section: lists all profiles from ~/.amico/ops/fleet/profiles/*.toml;
    each row shows name + model/variant + play button + overflow menu (edit/duplicate/delete)
  • Create profile: "+" button opens inline form (name, base, model, variant,
    task_type, skills, gates, permissions); saves to <slug>.toml
  • Edit profile: overflow → edit opens pre-filled form; save updates the TOML
  • Launch: play button spools a fleet-managed session from the profile; session
    appears in the sessions dropdown within 2s
  • Aggregate stats: shows active session count (with state breakdown), total tokens
    today; updates within 1s of record change
  • Sweep button: marks orphaned crashed sessions (same as amico fleet sweep)
  • Status bar: existing item click focuses/reveals the Fleet panel
  • Fleet Profile TOML schema documented; round-trips without data loss

Slice 2 — Sessions Dropdown Enrichment

  • Fleet-managed sessions in the dropdown show a fleet icon (distinguishes from plain)
  • Right-click context menu on fleet-managed sessions offers: Steer, Stop, Re-tier,
    View details — each gated by session state (steer/re-tier only when running/blocked)
  • chat_bridge.ts allowlist includes fleet-state (inbound) and fleet-action
    (outbound) message types
  • Extension pushes FleetStateSnapshot on record change (debounced 500ms)
  • App-side store hydrates from bridge messages; sessions list reads it for icon/menu
  • Fleet actions from context menu enqueue signal files (same path as CLI verbs)

Key Decisions

Decision Rationale
VS Code native webview over opencode app Data lives in extension-side TOML; no bridge needed for the panel itself
Activity bar sidebar over bottom panel Persistent visibility, full height, doesn't compete with terminal
Panel-first delivery (Slice 1 before Slice 2) Self-contained, validates data model, ships value without bridge risk
Setup wizard fully automated (SSH in, configure) A newbie should never need to touch a terminal for fleet setup; the panel is the provisioner
SSH key auth as prerequisite (wizard validates, doesn't configure) Setting up SSH keys is an OS-level concern; the wizard can't reliably automate it cross-platform without becoming an SSH manager
Fleet Profile as TOML under ~/.amico/ops/fleet/profiles/ Consistent with fleet directory convention; human-readable; one file per profile
Silent spool on launch (no chat tab opened) Panel is command center, not session-opening UI; user switches when ready
SVG star graph (hand-positioned, no layout lib) Fleet is always star topology with small N; external lib is overkill
Fleet icon = "fleet-managed" (not "remote host") Single canonical server model means all sessions are co-located; the meaningful distinction is registry-tracked vs. plain
Bridge push (extension → app) over server-reads-TOML Keeps fleet as amicode concern; no upstream opencode pollution
Full lifecycle (create/add/remove/dismantle) in one slice The wizard is useless without remove; the graph is useless without setup. They're one coherent surface.
Remote host settings via SSH A fleet user shouldn't need to SSH in manually to change the database path or restart the server. The panel owns the full host lifecycle.

Data Contracts

Fleet Profile TOML (~/.amico/ops/fleet/profiles/<slug>.toml):

schema = 1
name = "researcher-opus"
base = "pulse-designer"
model = "anthropic.claude-opus-4-6-v1"
variant = ""
task_type = "interactive"
skills = ["transmon", "atoms", "bosonic"]
gates = []

[permissions]
bash = "allow"
file_write = "allow"

Bridge message: fleet-state (extension → app):

{
  "type": "fleet-state",
  "payload": {
    "sessions": [
      {
        "session_id": "ses_abc123",
        "state": "running",
        "profile_name": "researcher-opus",
        "tokens": 42000,
        "host": "canonical-server",
        "current_step": "solving CZ gate"
      }
    ]
  }
}

Bridge message: fleet-action (app → extension):

{
  "type": "fleet-action",
  "verb": "stop",
  "session_id": "ses_abc123",
  "params": {}
}

Constraints & Invariants

  • Sessions survive client disconnect: the canonical server is a system service
    (launchd/systemd) — NOT tied to any VS Code editor lifetime. Fleet-managed sessions
    and solves continue running on the server when a client closes their laptop, quits
    VS Code, or loses network. On reconnect, clients see the sessions that ran while they
    were away (state updated, tokens accumulated, solves completed). This is the primary
    value of the fleet.
  • One fleet at a time: a machine belongs to at most one fleet. fleet.json declares
    a single canonical server — there is no multi-fleet concept. To switch fleets, dismantle
    the current one and create a new one (or reconfigure the existing one via the panel to
    point at a different host). The topology graph shows THE fleet, not a fleet picker.
  • Standalone remembers the fleet: "Go Standalone" writes role: "standalone" but
    preserves the canonical coordinates in fleet.json. This lets the panel distinguish
    "never had a fleet" (no canonical → Create Fleet) from "disconnected from a fleet"
    (canonical present → Reconnect Fleet). Only "Dismantle Fleet" removes the canonical
    object entirely.
  • Single-writer discipline preserved: the panel NEVER writes fleet records directly.
    All actions enqueue signal files (same as CLI verbs); the harness applies them. The panel
    is a read-view + signal-enqueuer.
  • No context-key gate: the panel is always registered (unlike Run Inspector / Device
    Inspector which hide until revealed). Standalone users still benefit from profiles + launch.
  • SSH prerequisite: the wizard requires key-based SSH access to the remote machine.
    Password-only SSH is not supported (no interactive auth in the extension context).
    The wizard checks connectivity before proceeding and gives actionable guidance on failure.
  • Bridge debounce: fleet-state pushes are debounced at 500ms to avoid flooding the
    iframe relay during burst changes (e.g. sweep marking 10 sessions).
  • Profile slug uniqueness: enforced at creation — two profiles cannot have the same slug
    (derived from name via kebab-case).
  • Wizard idempotency: re-running setup on an already-configured machine is safe (checks
    current state, skips completed steps, validates final state).

Prior Art

  • Device Inspector — the structural template (WebviewViewProvider, postMessage protocol,
    per-entity state buffering, TS-composed DOM)
  • fleet_verb.ts sessionView() — designed to back this panel; returns exactly what
    the aggregate stats section needs
  • ADR 0005 (Managed Fleet) — the fleet system this panel surfaces
  • Context tab graph (opencode app) — visual precedent for the topology graph style
  • fleet_registry.ts transition table — the state machine the panel visualizes
  • tools/fleet/install.sh — the existing CLI installer the wizard automates

Source

ADR 0007: docs/adr/0007-fleet-panel-and-profile-ui.md

Metadata

Metadata

Labels

enhancementNew feature or request

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions