You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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)
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
VS Code native webview, panel-first (chosen) — extension has direct data access,
Device Inspector pattern proven, self-contained first slice. Bridge deferred.
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.
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.
Full vertical (both slices at once) — largest blast radius, harder to review and
ship. Rejected.
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.
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
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.
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.tssessionView() — 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
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 onlyat 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):
validates connectivity, configures both machines (fleet.json, guard, tunnel, server
process), validates end-to-end. Full lifecycle: create fleet, add machines, remove
machines, dismantle.
~/.amico/ops/fleet/profiles/)Slice 2 — Sessions Dropdown Enrichment (bridge extension → opencode app):
fleet-statesnapshots via chat_bridge; app postsfleet-actioncommands backApproaches Considered
Device Inspector pattern proven, self-contained first slice. Bridge deferred.
in extension-side TOML files. Requires server routes or bridge for ALL data, pollutes
the upstream fork. Rejected.
(dropdown used constantly) but the bridge is the riskiest piece and delays first value.
Rejected for ordering; the work still happens in Slice 2.
ship. Rejected.
expertise across multiple machines. Not intuitive for newbies. Rejected as permanent
state; the panel should be the primary setup surface.
Scope
In scope:
fleet-stateinbound,fleet-actionoutbound)Deferred:
Assumptions
existing fleet (change host, port, SSH alias) is supported, creating a second is not
does not configure SSH keys)
chat_bridge relay mechanism
volume (fleet records change at session-lifecycle frequency, not per-iteration)
independently of any client connection. Clients are viewers/controllers, not hosts.
Acceptance Criteria
Slice 1 — Fleet Panel + Setup Wizard
Setup Wizard
"Create Fleet" button
canonicalcoordinates 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.jsonpreservescanonicalcoordinates when going standalone (Go Standalonewrites
role: "standalone"but keeps thecanonicalobject intact)user@host; validates SSH connectivityremote (offer SCP if missing), port available
fleet.json(role: server), installsguard, starts canonical server as launchd service, generates fleet token
fleet.json(role: client), installs guard +tunnel plist, stores fleet token
to two nodes with solid connection line
setup (server already running); new node appears in graph on success
standalone to fleet.json, unloads tunnel/guard; node disappears from graph
stops canonical server, reverts all machines to standalone
panel without dismantling; validates new connection before committing the change
Panel (monitoring + profiles + host settings)
amicode.fleetwebview view registered inpackage.jsonunderviews.amicodewhencontext-key gate)connected, dotted for configured-but-offline; click a node shows popover (hostname,
last-seen, health, active session count)
settings for the canonical server — read/written over SSH:
offers a one-click restart (stops + starts the launchd/systemd service remotely)
~/.amico/ops/fleet/profiles/*.toml;each row shows name + model/variant + play button + overflow menu (edit/duplicate/delete)
task_type, skills, gates, permissions); saves to
<slug>.tomlappears in the sessions dropdown within 2s
today; updates within 1s of record change
amico fleet sweep)Slice 2 — Sessions Dropdown Enrichment
View details — each gated by session state (
steer/re-tieronly when running/blocked)chat_bridge.tsallowlist includesfleet-state(inbound) andfleet-action(outbound) message types
FleetStateSnapshoton record change (debounced 500ms)Key Decisions
~/.amico/ops/fleet/profiles/Data Contracts
Fleet Profile TOML (
~/.amico/ops/fleet/profiles/<slug>.toml):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
(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.
fleet.jsondeclaresa 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.
role: "standalone"butpreserves the
canonicalcoordinates infleet.json. This lets the panel distinguish"never had a fleet" (no
canonical→ Create Fleet) from "disconnected from a fleet"(
canonicalpresent → Reconnect Fleet). Only "Dismantle Fleet" removes thecanonicalobject entirely.
All actions enqueue signal files (same as CLI verbs); the harness applies them. The panel
is a read-view + signal-enqueuer.
Inspector which hide until revealed). Standalone users still benefit from profiles + launch.
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.
iframe relay during burst changes (e.g. sweep marking 10 sessions).
(derived from name via kebab-case).
current state, skips completed steps, validates final state).
Prior Art
per-entity state buffering, TS-composed DOM)
fleet_verb.tssessionView()— designed to back this panel; returns exactly whatthe aggregate stats section needs
fleet_registry.tstransition table — the state machine the panel visualizestools/fleet/install.sh— the existing CLI installer the wizard automatesSource
ADR 0007:
docs/adr/0007-fleet-panel-and-profile-ui.md