Skip to content

Multi-machine v1: pick the machine a task runs on - #624

Merged
SawyerHood merged 27 commits into
mainfrom
bb/multi-daemon-setup-support-thr_tqsxqmfdmz
Jul 12, 2026
Merged

SawyerHood merged 27 commits into
mainfrom
bb/multi-daemon-setup-support-thr_tqsxqmfdmz

Conversation

@SawyerHood

Copy link
Copy Markdown
Collaborator

Summary

bb becomes honestly multi-machine: the server orchestrates daemons on any number of computers, gated behind a new Multi-machine experiment (independent of bb connect). Product plan + mockups + status: plans/multi-machine-v1.html.

  • Pick where work happens: the composer's environment picker groups options by machine (status dots, "this machine" badge, offline w/ last-seen); projectless threads get a machine chip too. Sticky last-used per project.
  • Machine-independent projects: a project anchors to its git remote; each machine holds a source (checkout). Guided setup clones onto a new machine in one dialog (or points at an existing folder); non-git projects link manually.
  • Machines management: Settings → Machines (rename, remove w/ revocation, live status) + Add-a-machine dialog minting a 15-min join code with a copyable one-liner.
  • One-command install: curl -fsSL <server>/install.sh | sh -s -- --join-code … --host-id … --server … — installs bb-app, joins, registers launchd/systemd service. Works over LAN or through bb connect (daemon x-bb-connect-machine credential wiring, gate /api/v1 machine access, defense-in-depth preserved).
  • Server-distributed updates: the server serves its own bb-app tarball (/install/bb-app.tgz); installs prefer it over npm (unpublished versions work by construction) and daemons self-update on protocol mismatch (newer-server-only, rate-limited, downgrade-refusing) with a "Needs update" state in the UI.
  • Remote-correct execution: injected skills sync via content-addressed pull (byte-identical staging verified); workspace AGENTS.md + project skills read via daemon RPC — fixes the silent instruction/skill loss that sank the earlier multi-host attempt (bb connect: multi-host execution, cloud tunnel, getbb.app site + prod staging #510/Remove multi-host execution; rename experiment to bb connect #536).
  • CLI: bb machine list, --machine on thread spawn / project source add (+ clone variant), guide/skill/docs updated.

Also fixes two pre-existing bugs found along the way: enroll keys lived ~10.4 days instead of 15 minutes (better-auth defaultExpiresIn is seconds), and join-code minting left phantom "pending" host rows.

Verification

Every step was live-verified on a two-daemon rig (second daemon with fresh identity/data dir) including browser QA of the picker, guided setup (success + target_not_empty error paths), Machines pane, add-machine dialog, and a projectless thread on the second machine. Capstone: one CLI command provisioned a managed worktree on the second machine and the agent answered from planted workspace instructions. Full repo typecheck green; all package suites green. The bb connect machine path is covered by a full workerd auth-matrix (cross-tenant 403s, header stripping, revocation) — a real staging tunnel run remains (needs staging gate/web deploys).

Migrations are consolidated into a single generated 0064 (dev data dirs that applied interim migrations need a reset).

🤖 Generated with Claude Code

SawyerHood and others added 27 commits July 11, 2026 19:32
Product plan and UI mockups for multi-daemon support: machine picker in the
environment picker, machine-independent projects, guided setup, Machines
settings pane, pairing over LAN and bb connect, skill sync design.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Add the independent multiMachine experiment across domain, database, server, app, and CLI fallback surfaces.
- Restore experiment-gated explicit host routing while preserving primary-host default resolution.
- Restore multi-host routing coverage and generate the Drizzle migration.
Composer environment picker (multiMachine experiment, >1 host):
- Menu groups options per machine: status dot, name, "this machine"
  badge, per-host Work locally / Work in checkout + New worktree rows
  encoded with that machine's host id, checkout-path hints.
- Machines without a project source render a disabled "Not set up for
  this project" row (guided setup lands in S7).
- Offline machines stay visible with options disabled and a relative
  "last seen" in the section header.
- Trigger chip names the machine when the selection isn't the primary
  host ("Mac Studio · New worktree").

Selection survival: resolveRootComposeEffectiveEnvironmentValue no
longer force-rewrites the host id to the primary host when the
experiment is on and the selected host still exists with a source for
the project; unknown/destroyed hosts fall back to primary as before.
ConnectedThreadModeBranch feeds the picker the selected machine instead
of always the primary host.

Machine surfacing elsewhere:
- ReuseThreadOption rows carry a right-aligned machine hint (>1 host).
- EnvironmentDisplayHostContext gains a nullable identity
  {name, connected}; ThreadMetadataContent's environment row names the
  machine and its connection state.
- Follow-up composer chip prefixes the machine name for non-primary
  hosts; the thread host-disconnected notice names the machine.

Also fixes pre-existing S5 typecheck fallout: test/story project
fixtures missing the new gitRemoteUrl field.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
buildProjectSourceFromOptions always builds a local_path source; narrow its
return type so project create keeps compiling against the S5 source union.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ProjectMachineSetupDialog (multi-machine plan §4.2, Mockup B): a one-time
"Set up <project> on <machine>" flow reached from the environment picker.
Machines without a project source now render an enabled "Set up on
<machine>…" row when their daemon is connected (offline machines keep the
disabled "Not set up for this project" row).

Dialog behavior:
- Git-anchored projects default to "Clone from the project remote",
  showing the remote URL and the daemon's default destination (new
  GET /hosts/:id/clone-default-path helper + query) with an inline
  "change" edit. Non-git projects see only the existing-folder option,
  without radio chrome.
- "Use an existing folder" reuses RemotePathBrowser and re-validates the
  picked folder via /hosts/:id/paths/exist before submitting.
- Submit posts to POST /projects/:id/sources ({type:"clone"} or
  {type:"local_path"}) through a new useAddProjectSource mutation with
  the global error toast suppressed: clone failures (git stderr,
  target_not_empty with a "pick another path / use the folder option"
  hint) render inline with retry. While a clone runs the form is
  disabled, shows "Cloning onto <machine>…", and the dialog refuses to
  dismiss.
- Success invalidates project-source-dependent queries and moves the
  composer selection onto the machine in worktree mode; the existing
  non-git downgrade effect falls back to local work when needed.

Project Settings' add-source flow is left on its local-host picker; S8
covers that surface.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Settings → Machines (Mockup C): live host list with status dot, 'this
  machine' badge on the primary host, platform + project-count meta line,
  and a ⋯ menu with rename (small dialog → PATCH /hosts/:id) and remove
  (confirm dialog → DELETE /hosts/:id, disabled client-side for the
  primary; server errors render inline). Nav entry and section are gated
  on the multiMachine experiment.
- Add-a-machine dialog (Mockup D): mints a join code on open, shows the
  copyable install one-liner with an expiry countdown and re-mint, and
  flips the waiting row to '<name> connected' live by diffing the host
  list against the set captured at open time.
- Project Settings → Sources (Mockup E): rows show their machine (status
  dot + name, offline label); with >1 host the add flow becomes an
  'Add on a machine' menu — the local host keeps the native picker, other
  machines open ProjectMachineSetupDialog.
- Extract MachineStatusDot as the shared machine-status presentation
  (environment picker, settings pane, source rows).
- Add createHostJoinCode/updateHost/deleteHost API client functions,
  host rename/remove mutations, and invalidateHostListQueries cache
  effect.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
project.clone's optional targetPath joins the intentional-optional allowlist
(omission means the daemon derives the default checkout path). Rebuild the
plugin-sdk bundled types and regenerate the templates that embed them after
the S5 source-union change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Minting a join code pre-created a pending-<id> host row, so every open of the
Add-machine dialog (and any unredeemed code) left an offline phantom in the
Machines pane, and stray rows could break single-host primary fallback. Mint
now only issues the key; the host row is created at enroll with the
daemon-reported name, so the mint-time hostName field is gone from the
join-codes contract.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
When composing without a project (multiMachine experiment, >1 host),
the environment slot now shows a machine chip that picks which
machine's personal workspace the thread runs in. The effective-value
resolver keeps a known-host selection for projectless threads
(normalized to local mode, no project source required) and still
falls back to the primary host for stale or unknown selections.
Selection persists via the existing personal-project-scoped
environment preference; experiment off or single host keeps
today's chip-less UI.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Squash the branch's three drizzle migrations (multi_machine experiment,
projects.git_remote_url, hosts.last_rejected_protocol_version) into a single
generated 0064 before review. Dev data dirs that applied the interim
migrations need a reset.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
toHostDaemonProcessEnv serialized a boolean config field straight into
ProcessEnv; env values are strings, so booleans now serialize to the form
parseBooleanEnvValue accepts. The bb-app artifact service trusted
NODE_ENV to pick its package layout, which broke when CI's integration
harness ran repo sources under a production env (resolved apps/ as the
package root); it now probes both layouts and validates each candidate's
package.json, and the detected layout also picks the build strategy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- /system/config now carries the server-resolved primaryHostId (null only
  on a fresh server with no enrolled host); selectPrimaryHost consumes it
  and keeps the connected-first heuristic only as a null fallback
- Add-machine pairing command targets the connect serverUrl when a machine
  code is present; browser origin only for the direct/LAN variant
- Root compose provider-CLI status, update action, and submit blocking key
  off the effective selected host instead of always the primary
- Project source rows show machine chrome only with >1 host (same gate as
  the pickers)
- bb machine list checks the server's Multi-machine experiment and errors
  when it is off (same for --json)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@SawyerHood
SawyerHood force-pushed the bb/multi-daemon-setup-support-thr_tqsxqmfdmz branch from 51ccd80 to 69adac6 Compare July 12, 2026 02:32
@SawyerHood
SawyerHood merged commit 85fbd4f into main Jul 12, 2026
10 checks passed
@SawyerHood
SawyerHood deleted the bb/multi-daemon-setup-support-thr_tqsxqmfdmz branch July 12, 2026 02:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant