Skip to content

feat: add fleet deploy, and let start/stop take --all - #147

Merged
outofcoffee merged 14 commits into
mainfrom
worktree-fleet-deploy
Sep 3, 2026
Merged

feat: add fleet deploy, and let start/stop take --all#147
outofcoffee merged 14 commits into
mainfrom
worktree-fleet-deploy

Conversation

@outofcoffee

@outofcoffee outofcoffee commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

A node's file field (or a resolved spinloop alias, or a same-named subdirectory beside the fleet file) names the Spinloop that describes what it runs — the link spinloop fleet deploy and spinloop fleet start both need and previously had no way to express.

Summary

  • Adds an optional file field to a fleet-file node entry (either kind). When absent, resolves in order: the node's own name as a registered spinloop alias, then a <name>/Spinloop subdirectory beside the fleet file.
  • Adds spinloop fleet deploy <node...> (or --all): batch-creates the AWS environment for kind: remote nodes, reusing remote deploy's derivation, consent, and registration behavior (--api-key-env included) — the two can never disagree about what a Spinloop file deploys.
  • spinloop fleet start/spinloop fleet stop now both take multiple node names or --all, replacing the old "exactly one node" restriction, via a shared runFleetDrive built on Config.FanOut and a new Config.OnlyNames.
  • BREAKING: on a kind: daemon node, fleet start now requires that node's Spinloop source to resolve, and pushes it via StartWith — the same mechanism a routed wake already uses — rather than a plain, config-less start. No fallback: this is an unreleased feature with no users to protect. Every example fleet with a daemon node has a file/alias/subdirectory added.
  • kind: remote node behavior is unaffected either way — what it serves is fixed at deploy time.

Implementation

  • cmd/spinloop/remote.go: runRemoteDeploy split into deriveDeployTarget + runDeploy, shared by remote deploy and the new fleet deploy.
  • cmd/spinloop/fleet.go: fleetDeployCmd, resolveNodeSpinloop, and a rewritten fleetStartCmd/fleetStopCmd on runFleetDrive (driveOneNode deleted).
  • internal/fleet/config.go: NodeConfig.File, Config.OnlyNames.
  • Every example fleet updated; examples/fleet-docker's real Docker Compose E2E suite re-run in full and passing, including new --all coverage.
  • Merged in upstream's --api-key-env deploy feature (landed on main mid-branch) and extended it to fleet deploy for parity with remote deploy.

Test plan

  • go build ./..., go vet ./..., gofmt -l . all clean.
  • go test ./... -cover: all packages passing, cmd/spinloop and internal/fleet both >90%.
  • examples/fleet-docker/run-tests.sh (real Docker stack): all assertions pass.
  • concord ci: no drift, no overlap.

Adds a spinloop field to kind: remote fleet nodes and a spinloop
fleet deploy command that batch-deploys their AWS environments,
instead of running spinloop remote deploy once per environment by
hand.
@outofcoffee outofcoffee added the documentation Improvements or additions to documentation label Sep 3, 2026
Renames the per-node deploy-source field from spinloop to file and
makes it optional: a kind: remote node's own name is checked against
the spinloop alias registry first, then against a same-named
<name>/Spinloop subdirectory beside the fleet file, before failing.
@outofcoffee outofcoffee linked an issue Sep 3, 2026 that may be closed by this pull request
A kind: daemon node does get told what to serve via a DeployConfig
derived from a Spinloop file -- routing already does this on every
wake, through Config.Wake/StartWith. The new file field is irrelevant
to daemon nodes only because it feeds fleet deploy specifically, which
persistently creates a cloud environment; a daemon node's machine has
no equivalent provisioning step. Reworded to say that precisely
instead of implying daemon nodes have no such mechanism at all.
Generalises the file/alias/subdirectory resolution mechanism from a
kind: remote-only concept to any fleet node: spinloop fleet start on
a kind: daemon node now resolves the same way and pushes the derived
config via StartWith (the same mechanism a routed wake already uses),
falling back to today's plain start when nothing resolves. Also
requires fleet deploy to name its target nodes explicitly, or pass
--all, rather than defaulting to the whole fleet.
No shipped release depends on today's fleet start behaviour for a
kind: daemon node, so there's no one to protect with a silent
fallback to a plain, config-less start. Make Spinloop-source
resolution mandatory for fleet start on daemon nodes, exactly as
fleet deploy already requires it for remote nodes: a node with no
file field, alias, or subdirectory now fails fleet start rather than
falling back, and every example fleet needs updating accordingly.
Marked BREAKING in the proposal.
fleet deploy already accepted multiple node names; fleet start
gains the same, plus --all for every node in the file (either
kind, since starting is meaningful for both, unlike deploy). Reuses
Config.FanOut via a new OnlyNames helper rather than a bespoke loop,
since every node fleet start targets already exists as a live Node
-- unlike deploy, which creates what a Node would later address.
fleet stop is deliberately left untouched: exactly one node, no
--all.
For symmetry with fleet start's new target selection. Both now
share a runFleetDrive helper (OnlyNames + FanOut) replacing
driveOneNode, which is deleted -- stop's own per-node call is
unchanged (a plain Stop, no config to resolve), only how its
target is selected changes.
Implements the fleet-deploy change: a node's `file` field (or a
resolved spinloop alias, or a same-named subdirectory beside the
fleet file) names the Spinloop that describes what it runs.

- spinloop fleet deploy <node...>|--all creates the AWS environment
  for one or more kind: remote nodes, reusing remote deploy's own
  derivation/consent/registration (deriveDeployTarget + runDeploy,
  factored out of runRemoteDeploy).
- spinloop fleet start/stop now take multiple node names or --all,
  replacing driveOneNode with a shared runFleetDrive built on
  Config.FanOut and a new Config.OnlyNames.
- start on a kind: daemon node now requires its Spinloop source to
  resolve, pushing the derived config via StartWith -- no fallback to
  a plain start. BREAKING: every daemon node needs a file/alias/
  subdirectory now, or start fails for it.

Every example fleet updated accordingly; fleet-docker's real Docker
Compose E2E suite re-run in full and passing, including new
--all coverage.
resolveNodeSpinloop succeeding isn't the same as the config it
points at being usable -- add coverage for the gap in between:
fleetStartCall never calls Start/StartWith when the resolved
source is unparseable, names an unsupported provider (the same
llamacpp/vllm-only limit a routed wake already has -- MLX included),
has no model, or has an engine token that resolves to nothing; and
deployOneNode fails only the one node whose resolved Spinloop is
itself undeployable (no REMOTE), leaving its siblings unaffected.
# Conflicts:
#	cmd/spinloop/remote.go
#	docs/commands/remote.md
@outofcoffee outofcoffee changed the title docs: propose the fleet-deploy change feat: add fleet deploy, and let start/stop take --all Sep 3, 2026
@outofcoffee outofcoffee added enhancement New feature or request and removed documentation Improvements or additions to documentation labels Sep 3, 2026
Real usage of fleet deploy --all surfaced two problems: several
nodes' output ran together with nothing marking where one ended and
the next began, and a slow --all run gave no feedback while AWS
calls were still in flight.

Adds a live per-node Braille spinner while a node's deploy is still
running (skipped for a non-TTY run -- gated the same way fleet
dashboard already checks for one), a coloured tick/cross/warning
header on each node's final report, and a closing summary line.
Adds the requirement the previous commit's fix implements:
in-progress feedback while nodes are still deploying, a clear
per-node boundary in the report, a closing summary, and no
terminal-control output when piped or redirected. Technology-
neutral -- doesn't mandate a spinner or specific escape codes,
just the observable behaviour.
Syncs its delta specs into fleet-config and fleet-client, then
moves the change to archive/2026-09-03-fleet-deploy/ now that
implementation, tests and docs are all complete.
Every existing example in this repo names its Spinloop file
literally "Spinloop", one per directory -- the fleet-deploy work
broke that convention by inventing a "<name>.Spinloop" suffix for
files that needed to sit beside a fleet.yaml. Moves each into its
own <node-name>/ subdirectory instead, which also means the
explicit file field naming them was redundant with the subdirectory
resolution tier and gets dropped -- examples/fleet-docker's
client/Spinloop remains the one place demonstrating file pointing
somewhere that doesn't match the node's own name.
@outofcoffee
outofcoffee merged commit 0d852dc into main Sep 3, 2026
2 checks passed
@outofcoffee
outofcoffee deleted the worktree-fleet-deploy branch September 3, 2026 21:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: add fleet deploy command

1 participant