Skip to content

Fleet-mode First Officer: one session driving multiple commissions (skill-only, user-selectable) #437

Description

@gcko

Summary

Add a fleet-mode First Officer so a single spacedock claude session can drive multiple commissions (workflows) at once — while keeping today's single-workflow FO fully supported and the default. The two operating models are user preferences, not a migration:

  • Reading A — one FO, many ships in one commission. The current model: one session binds one workflow and fans ensigns out across its entities. Stays the default; unchanged.
  • Reading B — one FO across multiple commissions. New, opt-in: one session adopts several discovered workflows and runs the dispatch loop across their union.

Spacedock (and Bridge) should be willing and able to conform to either, depending on operator preference. Neither is hardcoded away.

Filed per CONTRIBUTING.md (issue → discuss → PR). Implementation is intended to roll into PR #435 (bridge-seam-inbox-events) at the author's request — see the Scope note below; it is a separable commit.


⚠️ Revised after an antagonistic review of the original draft

The first draft of this issue made three claims that do not survive contact with the code on bridge-seam-inbox-events. Corrections, with citations:

  1. It is a SKILL-only change — there is no need for a --fleet-dir CLI/Go change. The binary already supports a multi-workflow repo:
    • status --discover already returns the full list of commissioned workflow dirs (internal/status/native_discover_test.go:57-75, TestNativeDiscoverParity). Today the FO prose just picks one (skills/first-officer/references/first-officer-shared-core.md:15"multiple → present the list"). Fleet mode changes what the FO does with that list, not the binary.
    • spacedock new already disambiguates by --workflow-dir when a repo holds more than one workflow (first-officer-shared-core.md:185"if the repo holds more than one, new reports the candidates and you pass --workflow-dir"). Multi-workflow-in-one-repo is an already-anticipated topology.
    • Every dispatch/status call is already --workflow-dir-parameterized, so a per-workflow loop composes from existing commands. The original draft's --fleet-dir option is dropped as unnecessary scope.
  2. The "reconcile is per-workflow" constraint was wrong. «roster-reconcile» (spacedock dispatch reconcile) sweeps the host roster (the session's live workers) for drift — it is session/team-scoped, not workflow-scoped (fo-dispatch-core.md:100-102). In fleet mode it already covers every worker the session spawned regardless of workflow, so it is not a blocker — it's a thing that already works in our favor.
  3. The "separate rate-limit bucket per session" benefit was wrong. Rate limits are account-level, not per-session. The genuine isolation benefits of one-FO-per-commission are context-window pressure and blast radius, not rate budget.

Current state (why one session can't do Reading B today)

Two real constraints bind one FO session to one workflow:

  1. Startup binds to exactly one workflow dir. first-officer-shared-core.md:14-16"Discover the workflow directory… one path → use it; zero → STOP; multiple → present the list." The whole boot then reads one {workflow_dir}/README.md taxonomy (step 4) and one status --boot (step 5).
  2. The event loop is scoped to one {workflow_dir}. status --next, dispatch build, status --set all carry a single --workflow-dir (fo-dispatch-core.md:111-120, :140).

So covering N commissions today requires N sessions (the multi-session model Bridge's rail already hints at, and that PR #435's per-target drain serves).

The omitted hard part — per-workflow state & team (what makes this non-trivial)

A fleet loop is not just "call status --next in a for loop." Three subsystems are per-workflow and must be handled once per member workflow each tick/boot:

  • Per-workflow boot + state convergence. «state.boot», «state.ensure-ready», «state.sweep-merged» (first-officer-shared-core.md:150-171) each operate on one workflow's state backend. Critically, each workflow may carry its own split-root .spacedock-state checkout on a separate branch (AGENTS.md:43"each workflow may use a per-workflow .spacedock-state checkout"). Fleet mode must run ensure-ready / pull-on-boot / sweep-merged per member workflow, juggling N state checkouts — and honor each one's rebase-conflict halt independently.
  • Team & standing teammates. The team is session-scoped; standing teammates are workflow-declared and injected at that workflow's first dispatch (fo-dispatch-core.md:7, first-officer-shared-core.md:206). In fleet mode one team accumulates the union of every member workflow's standing teammates (e.g. multiple comm-officer-style polishers / intake mods). Routing-usage reads stay per-mod, but collision/identity semantics across workflows need a decision.
  • Greet & gate authoring scale per workflow. Interactive boot presents one summary + ready gates (first-officer-shared-core.md:31); fleet boot presents N. Headless drives all dispatchables across N workflows to gates/terminal. Gate stops stay per-entity/per-workflow and unambiguous — that part composes cleanly.

Bridge side (already mostly there)

Bridge renders a multi-workflow fleet today and tracks per-workflow heartbeats (fo.<slug>.json). PR #435's bridge-inbox drain mod already routes captain intent by a per-record target (<slug> | all; absent ⇒ all). That field — built for the multi-session case — gives fleet mode per-workflow conn/tell granularity for free: a single fleet-mode FO drains all targets, so target=all is fleet-wide conn and target=<slug> scopes to one workflow. A single fleet-mode FO is in fact simpler to route to than N sessions (no addressing race; one cursor owner). The dual-mode principle must hold on Bridge too: present and control a fleet whether driven by one fleet-mode FO or N single-workflow FOs.

Proposed change (sketch — skill-only, compatibility-first)

  • Selection (open decision — see PR discussion): an explicit, opt-in operator signal that puts the FO in fleet mode (e.g. a quotable launch directive like the existing single-entity-mode trigger and conn-grant phrasing in SKILL.md:7-14 / first-officer-shared-core.md:33). Default (no signal) = today's single-workflow behavior, byte-for-byte unchanged.
  • Startup: on the fleet signal, adopt all status --discover paths instead of presenting the list; read each workflow's taxonomy + run each workflow's «state.boot»/«state.ensure-ready»/«state.sweep-merged». Zero-discover still reports-and-stops (must not trip detectBroadSearchAtBoot; verified by internal/ensigncycle/broad_search_detect_test.go + the live TestLiveZeroDiscoverReportsAndStops).
  • Event loop: an outer loop over member workflows wrapping the existing «dispatch.next-action»() per {workflow_dir}; round-robin until nothing is dispatchable across the union, then idle-hook + reconcile + recheck.
  • Tests: per AGENTS.md:54 ("Add skill smoke tests before changing first-officer or ensign command text"), add a fleet-mode smoke test under skills/integration/, and a //go:build live behavioral test mirroring TestLiveEnsignCycle for the multi-workflow drive.

Scope note (antagonistic flag, author-acknowledged)

PR #435 is the bridge seam (drain mod, normalized FO event hooks, event script). Fleet-mode FO is FO-dispatch-loop work — a different subsystem. Rolling it into #435 is at the author's explicit request; it should land as a separable commit so reviewers can isolate the seam from the dispatch-loop change, and so it can be reverted independently if the design needs another pass.

Acceptance criteria

  • A single spacedock claude session, given the opt-in fleet signal, dispatches ensigns across entities in more than one workflow within one session.
  • Single-workflow mode is the default and byte-for-byte unchanged for existing users (no forced migration; zero-discover still reports-and-stops).
  • Fleet mode is explicitly, opt-in selectable by the operator.
  • Per-workflow «state.ensure-ready»/«state.sweep-merged»/«state.commit» run per member workflow, including independent split-root .spacedock-state checkouts and their rebase-conflict halts.
  • Per-workflow _mods (intake, pr-merge, bridge-inbox) fire correctly, keyed by each workflow's $SLUG.
  • conn/tell honor the bridge-inbox target field under fleet mode (all = fleet-wide, <slug> = scoped).
  • Gate stops and ## Stage Report review remain per-entity/per-workflow and unambiguous.
  • A skills/integration/ smoke test and a //go:build live behavioral test cover the fleet drive; go test ./... stays green.

Risks / open questions

  • Selection mechanism (launch directive vs skill arg vs a fleet manifest) — the one genuine product-API fork; decide before editing the boot-resident core.
  • Context pressure: one session juggling N workflows' state in one context window — the real reason the per-workflow model exists. Document the trade so operators choose deliberately.
  • Standing-teammate identity across workflows: union in one team — naming/collision/lifetime semantics need a decision.
  • Reconcile / boot cost: N per-workflow boots + sweeps per idle tick may need staggering so the loop stays responsive.
  • Fleet boundary: the discovered-workflow set must be unambiguous (the project root's commissioned workflows), distinct from the split-root entity/state dir.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions