1858 lines
66 KiB
Markdown
1858 lines
66 KiB
Markdown
# Mixed Team Per-Member Runtime Lanes Plan
|
|
|
|
**Status**: proposed implementation plan after code-level confidence pass
|
|
**Chosen path**: preserve the existing primary team bootstrap lane, add secondary per-member runtime lanes only where the primary lane cannot truthfully own a member, starting with `OpenCode`
|
|
**Score**: `🎯 9 🛡️ 9 🧠 8`
|
|
**Expected size**: roughly `1700-2900` lines in `claude_team`, plus only small additive follow-up work in `agent_teams_orchestrator` if a provider-specific side-lane seam needs extra metadata
|
|
|
|
## Executive Decision
|
|
|
|
The first version of mixed teams should **not** try to turn every provider/model difference into its own lane.
|
|
|
|
That would be too abstract for the current code and would create avoidable risk.
|
|
|
|
Instead, V1 should do this:
|
|
|
|
- keep the current **primary team bootstrap lane** as the owner for the lead and for teammates that the existing primary team bootstrap can already launch safely
|
|
- introduce **secondary member lanes** only for runtime families that cannot truthfully live on the primary lane
|
|
- start with **`OpenCode` as the first secondary-lane provider**
|
|
- ship the first mixed rollout for **`Anthropic` / `Codex` / `Gemini` leads with one `OpenCode` teammate**
|
|
- explicitly defer **`OpenCode`-led mixed teams** until canonical team ownership through the adapter path is hardened
|
|
- keep **secondary lanes single-member in V1**
|
|
- allow **at most one `OpenCode` secondary lane/member per team in V1** until provider-local OpenCode runtime stores become lane-scoped
|
|
- keep **team-level backend/fast UI** in V1
|
|
- make member-level backend/fast an **additive internal contract** first, not a renderer editing surface
|
|
- explicitly **exclude the current one-shot scheduler** from this phase because it is not a team-runtime lifecycle system
|
|
|
|
This is more reliable than the earlier generic framing and matches the real code much better.
|
|
|
|
### What this feature is really about
|
|
|
|
This is **not** a generic rewrite of all mixed-provider behavior.
|
|
|
|
The current system already has a working primary-lane notion of mixed teammate `provider/model/effort` for non-`OpenCode` teammates.
|
|
The real missing capability is:
|
|
|
|
- providers that require a different runtime owner than the primary team bootstrap owner
|
|
|
|
So V1 should be implemented as a **side-lane extension**, not as a universal per-provider decomposition rewrite.
|
|
|
|
## Why The Earlier Draft Needed Tightening
|
|
|
|
The earlier draft had three low-confidence assumptions that were too loose:
|
|
|
|
1. It treated all mixed-provider differences as if they needed lane separation.
|
|
2. It implied schedule parity through the same coordinator, but the current scheduler does not launch persistent teams.
|
|
3. It talked about replacing `team -> run` maps wholesale, while a safer path is to keep the primary run maps and add side-lane tracking additively.
|
|
|
|
After checking the code, those assumptions should be corrected.
|
|
|
|
## What The Current Code Actually Proves
|
|
|
|
### 1. The primary team bootstrap path already supports member-level provider/model/effort
|
|
|
|
`TeamProvisioningService` already materializes member-level runtime intent through:
|
|
|
|
- `materializeEffectiveTeamMemberSpecs(...)`
|
|
- `resolveAndValidateLaunchIdentity(...)`
|
|
- deterministic team bootstrap inputs built from `effectiveMemberSpecs`
|
|
|
|
That means the primary lane is already more capable than a purely lead-only launch contract.
|
|
|
|
This is important because it means we should preserve and wrap that path, not bypass it.
|
|
|
|
### 2. The runtime adapter registry is currently provider-specific and effectively `OpenCode`-specific
|
|
|
|
The runtime adapter layer in:
|
|
|
|
- `src/main/services/team/runtime/TeamRuntimeAdapter.ts`
|
|
- `src/main/services/team/runtime/OpenCodeTeamRuntimeAdapter.ts`
|
|
|
|
is not a generic multi-provider lane system today.
|
|
|
|
In practice, this means:
|
|
|
|
- `Anthropic`, `Codex`, and `Gemini` still live on the primary team bootstrap path
|
|
- `OpenCode` is the provider that currently forces an alternate launch owner
|
|
|
|
That is the real reason `vector-room-7` broke.
|
|
|
|
### 3. Current live tracking is fundamentally primary-team-scoped
|
|
|
|
`TeamProvisioningService` still maintains:
|
|
|
|
- `provisioningRunByTeam`
|
|
- `aliveRunByTeam`
|
|
- `runtimeAdapterRunByTeam`
|
|
|
|
These encode a strong `team -> run` assumption.
|
|
|
|
Replacing them all in one shot would be high risk.
|
|
The safer path is:
|
|
|
|
- keep them as the **primary lane truth**
|
|
- add **secondary lane tracking** next to them
|
|
|
|
### 4. `team.meta.json` is already lead-level launch truth
|
|
|
|
`TeamMetaStore` already persists:
|
|
|
|
- `providerId`
|
|
- `providerBackendId`
|
|
- `model`
|
|
- `effort`
|
|
- `fastMode`
|
|
- `launchIdentity`
|
|
|
|
This makes `team.meta.json` the natural **lead-level desired and resolved runtime source**.
|
|
|
|
### 5. `members.meta.json` is still missing the runtime fields needed for mixed-lane truth
|
|
|
|
`TeamMembersMetaStore` currently persists `TeamMember[]`, which already carries:
|
|
|
|
- `providerId`
|
|
- `model`
|
|
- `effort`
|
|
|
|
but not:
|
|
|
|
- `providerBackendId`
|
|
- `fastMode`
|
|
|
|
So the current member metadata is not rich enough to reconstruct mixed-lane runtime identity deterministically.
|
|
|
|
### 6. The current scheduler is not a team-runtime lifecycle engine
|
|
|
|
`ScheduledTaskExecutor` is a one-shot CLI runner:
|
|
|
|
- it spawns `claude -p`
|
|
- it uses `ScheduleLaunchConfig`
|
|
- it validates fast mode and provider env
|
|
- it does **not** create or reconcile a persistent team with member lanes
|
|
|
|
Therefore the current scheduler must be treated as **out of scope** for mixed-team lane rollout.
|
|
The earlier draft was too optimistic there.
|
|
|
|
### 7. Backup already includes the right metadata files
|
|
|
|
`TeamBackupService` already backs up:
|
|
|
|
- `config.json`
|
|
- `team.meta.json`
|
|
- `members.meta.json`
|
|
|
|
This is good news: additive mixed-lane persistence does not require a separate backup architecture.
|
|
|
|
What it does require is:
|
|
|
|
- additive field compatibility
|
|
- restore/reconcile tests proving those new fields survive backup/restore intact
|
|
|
|
### 8. The current `OpenCode` adapter flattens failures across its own `expectedMembers`
|
|
|
|
`OpenCodeTeamRuntimeAdapter` maps blocked launch and readiness failures across every member in its `input.expectedMembers`.
|
|
|
|
This means the coordinator must treat adapter input subsets as a hard invariant:
|
|
|
|
- if the coordinator passes unrelated members into a side-lane adapter input, the adapter will correctly flatten that failure across all of them
|
|
- therefore adapter-local `expectedMembers` must contain only lane-owned members
|
|
|
|
This is not just an optimization. It is required for truthful failure attribution.
|
|
|
|
### 9. The primary lane already supports member-level `provider/model/effort`
|
|
|
|
This is an important positive fact from the code:
|
|
|
|
- teammate spawn prompts include provider/model/effort overrides
|
|
- `buildAgentToolArgsSuffix(...)` passes `provider`, `model`, and `effort` into persistent teammate spawn requests
|
|
- relaunch reconstruction already restores member `providerId/model/effort` from `members.meta.json`
|
|
|
|
So `Anthropic` / `Codex` / `Gemini` variation inside the primary lane is not hypothetical. The current primary bootstrap already has a real member-level provider/model/effort seam.
|
|
|
|
That is why V1 should preserve those members on the primary lane instead of over-normalizing them into new side lanes.
|
|
|
|
### 10. The primary lane does not yet honor member-level `providerBackendId/fastMode`
|
|
|
|
The current teammate spawn seam for the primary lane does **not** pass member-level:
|
|
|
|
- `providerBackendId`
|
|
- `fastMode`
|
|
|
|
through the Agent-team spawn contract.
|
|
|
|
That means the plan must be explicit:
|
|
|
|
- primary-lane teammates in V1 continue to inherit backend/fast behavior from the lead-side launch/runtime owner
|
|
- member-level backend/fast additions are additive persistence contracts first
|
|
- side-lane owners may consume those fields earlier than the primary lane
|
|
|
|
Without this clarification, the plan would over-promise primary-lane capability that the code does not currently have.
|
|
|
|
### 11. `restoreTeam(...)` is not backup restoration
|
|
|
|
There is another place where terminology can easily mislead implementation:
|
|
|
|
- `restoreTeam(...)` in `TeamDataService` simply undeletes `config.deletedAt`
|
|
- startup backup restoration is handled separately by `TeamBackupService.restoreIfNeeded()`
|
|
|
|
So any mixed-lane "restore" design must be explicit about which path it means:
|
|
|
|
- undelete
|
|
- startup recovery from persisted files
|
|
- backup restoration after source disappearance
|
|
|
|
### 12. `members.meta.json` already contains a root `providerBackendId`, but it is not member truth
|
|
|
|
`TeamMembersMetaStore` already persists a file-level:
|
|
|
|
- `providerBackendId`
|
|
|
|
at the root of `members.meta.json`.
|
|
|
|
That is useful as compatibility metadata, but it is **not** member-scoped runtime truth.
|
|
|
|
So V1 must be explicit:
|
|
|
|
- keep the existing root field for backward compatibility
|
|
- do not reinterpret that root field as if it were per-member backend truth
|
|
- add member-level backend/fast fields separately
|
|
|
|
### 13. `config.json` is intentionally normalized to lead-only before launch
|
|
|
|
`normalizeTeamConfigForLaunch(...)` removes teammates from `config.json` before the primary CLI launch, and `assertConfigLeadOnlyForLaunch(...)` enforces that invariant.
|
|
|
|
This is a critical architectural fact:
|
|
|
|
- `config.json` is not a reliable full-roster artifact during launch
|
|
- mixed-lane design must not rely on `config.json` as canonical teammate truth
|
|
- full desired roster must live in `members.meta.json`, not in the launch-time `config.json`
|
|
|
|
### 14. Live `addMember` and `restartMember` currently go through the lead-side Agent tool seam
|
|
|
|
`buildAddMemberSpawnMessage(...)` and `buildRestartMemberSpawnMessage(...)` tell the lead to spawn teammates through the Agent tool with:
|
|
|
|
- `provider`
|
|
- `model`
|
|
- `effort`
|
|
|
|
and no member-level:
|
|
|
|
- `providerBackendId`
|
|
- `fastMode`
|
|
- lane ownership metadata
|
|
|
|
That means the current add/restart seam is only safe for primary-lane teammates.
|
|
It cannot be reused unchanged for `OpenCode` or any future secondary-lane member.
|
|
|
|
### 15. Launch roster fallbacks are degraded for mixed-lane truth
|
|
|
|
`resolveLaunchExpectedMembers(...)` falls back in this order:
|
|
|
|
1. `members.meta.json`
|
|
2. inboxes merged with `config.json` provider/model/effort overrides
|
|
3. `config.json` fallback
|
|
|
|
Only the first path can become authoritative mixed-lane truth.
|
|
The inbox/config fallbacks recover at most:
|
|
|
|
- name
|
|
- role
|
|
- workflow
|
|
- isolation
|
|
- provider/model/effort
|
|
|
|
They do **not** recover:
|
|
|
|
- member-level backend
|
|
- member-level fast mode
|
|
- lane attribution
|
|
- resolved member launch identity
|
|
|
|
So mixed-lane relaunch/startup recovery must treat those fallbacks as degraded and non-authoritative.
|
|
|
|
### 16. Primary launch env and provider args are resolved once at the root request
|
|
|
|
The current create/launch flow calls `buildProvisioningEnv(request.providerId, request.providerBackendId)` once for the primary request and then carries:
|
|
|
|
- `shellEnv`
|
|
- `providerArgs`
|
|
- auth/runtime warnings
|
|
|
|
into the primary spawn.
|
|
|
|
That means secondary lanes must not inherit primary launch env blindly.
|
|
Each secondary lane needs its own provider-aware env/auth/provider-args resolution.
|
|
|
|
### 17. `TeamMemberResolver` and `TeamMemberSnapshot` are not mixed-lane-aware today
|
|
|
|
Current team-view membership and runtime labels are still resolved mainly from:
|
|
|
|
- `config.members`
|
|
- `members.meta.json`
|
|
- inbox names
|
|
|
|
and `TeamMemberSnapshot` currently carries:
|
|
|
|
- `providerId`
|
|
- `model`
|
|
- `effort`
|
|
|
|
but not:
|
|
|
|
- `providerBackendId`
|
|
- `selectedFastMode`
|
|
- `resolvedFastMode`
|
|
- `laneId`
|
|
- `laneKind`
|
|
|
|
So renderer truth will diverge from runtime truth unless these contracts are upgraded explicitly.
|
|
|
|
### 18. `launch-state.json` normalization will drop new mixed-lane fields unless upgraded
|
|
|
|
`normalizePersistedLaunchSnapshot(...)` and `normalizePersistedMemberState(...)` rebuild the persisted launch-state shape field-by-field.
|
|
|
|
That means additive mixed-lane fields are **not** automatically safe just because they were written once.
|
|
|
|
Any mixed-lane schema rollout must therefore include:
|
|
|
|
- shared type updates
|
|
- launch-state write-path updates
|
|
- launch-state normalize/read-path updates
|
|
|
|
Otherwise read-normalize-write cycles will silently erase the new fields.
|
|
|
|
### 19. `expectedMembers` currently means the full expected teammate roster across many callers
|
|
|
|
Today the codebase uses `expectedMembers` as a durable "all expected teammates" field in:
|
|
|
|
- launch-state summaries
|
|
- bootstrap recovery
|
|
- provisioning progress/presentation
|
|
- Team list/detail launch badges
|
|
|
|
This is **not** a private primary-lane-only field.
|
|
|
|
So mixed-lane rollout must not silently repurpose existing `expectedMembers` to mean "primary bootstrap subset".
|
|
|
|
If V1 needs both concepts, it must add them explicitly, for example:
|
|
|
|
- `expectedMembers` as full mixed-team desired roster
|
|
- `primaryExpectedMembers` or `bootstrapExpectedMembers` as the primary-lane subset
|
|
|
|
instead of redefining the old field in place.
|
|
|
|
### 20. `choosePreferredLaunchSnapshot(...)` currently prefers by timestamp only
|
|
|
|
`TeamConfigReader` and other readers currently pick between bootstrap and persisted launch snapshots using only `updatedAt`.
|
|
|
|
That is safe enough today because the snapshots are structurally similar.
|
|
It becomes unsafe for mixed teams if:
|
|
|
|
- bootstrap snapshot is structurally poorer and only knows about the primary lane
|
|
- persisted launch-state is structurally richer and includes lane-aware mixed truth
|
|
|
|
In that world, a newer bootstrap snapshot could silently clobber richer mixed truth.
|
|
|
|
So the mixed plan must include an explicit precedence rule:
|
|
|
|
- richer mixed-lane persisted launch-state must not be overridden by a structurally poorer bootstrap snapshot just because it is newer
|
|
|
|
### 21. Team summary readers currently have a much smaller launch-state size budget than the main store
|
|
|
|
Today:
|
|
|
|
- `TeamLaunchStateStore` reads up to `256 KB`
|
|
- `TeamConfigReader` summary path reads only up to `32 KB`
|
|
- `team-fs-worker` summary path also reads only up to `32 KB`
|
|
|
|
So a richer mixed-lane `launch-state.json` can become perfectly valid for the main runtime path while silently disappearing from Team list / dashboard summary readers.
|
|
|
|
The plan therefore needs an explicit size-budget rule for mixed-lane persistence.
|
|
|
|
### 22. Team summary logic is duplicated between main-thread and fs-worker paths
|
|
|
|
Mixed launch-state summary behavior is not centralized today:
|
|
|
|
- `TeamConfigReader` has its own launch-state summary path and bootstrap snapshot precedence logic
|
|
- `team-fs-worker` has a separate launch-state summary implementation
|
|
|
|
They are already not perfectly symmetric, and mixed-lane schema/precedence changes would widen that drift unless the plan explicitly treats them as a parity surface.
|
|
|
|
### 23. Post-launch `config.json` runtime projection is currently lossy
|
|
|
|
`applyEffectiveLaunchStateToConfig(...)` projects runtime state back into `config.members`, but only for:
|
|
|
|
- `providerId`
|
|
- `model`
|
|
- `effort`
|
|
|
|
It does **not** project:
|
|
|
|
- member backend
|
|
- fast mode
|
|
- lane ownership
|
|
- resolved launch identity
|
|
|
|
So post-launch `config.json` is not just non-canonical in principle - it is concretely a lossy projection today.
|
|
|
|
Mixed rollout must treat it only as a compatibility-facing artifact, never as authoritative runtime reconstruction input.
|
|
|
|
### 24. Live roster mutation on running teams is still lead-message-driven and not mixed-safe
|
|
|
|
Today, when a team is alive:
|
|
|
|
- `addMember` persists metadata, then tells the lead to spawn the teammate
|
|
- `replaceMembers` diffs the roster, sends lead spawn messages for added members, then sends a summary message
|
|
- `removeMember` marks metadata and sends a generic lead message
|
|
|
|
That means live roster mutation is fundamentally coupled to:
|
|
|
|
- lead-owned teammate spawning
|
|
- lead-side interpretation of roster changes
|
|
- no side-lane ownership contract
|
|
|
|
So V1 should not treat only "lane migration" as unsafe.
|
|
The safer rule is broader: live roster mutation of a running mixed team is out of scope.
|
|
|
|
### 25. `OpenCode` bridge launch is still single-model and team-scoped
|
|
|
|
`OpenCodeLaunchTeamCommandBody` currently carries:
|
|
|
|
- one `selectedModel`
|
|
- one `teamName`
|
|
- one `runId`
|
|
|
|
for the whole `OpenCode` launch command.
|
|
|
|
That is a hard seam, not just an implementation detail.
|
|
|
|
So even before considering runtime stores, the current state-changing bridge contract is not yet a natural fit for:
|
|
|
|
- multiple independent `OpenCode` side lanes inside one mixed team
|
|
- different `OpenCode` side-lane members choosing different raw models concurrently
|
|
|
|
### 26. `OpenCode` provider-local runtime stores are team-scoped, not lane-scoped
|
|
|
|
The current provider-local runtime store namespace lives under:
|
|
|
|
- `<team>/.opencode-runtime/manifest.json`
|
|
- `<team>/.opencode-runtime/launch-state.json`
|
|
- `<team>/.opencode-runtime/opencode-launch-transaction.json`
|
|
- `<team>/.opencode-runtime/opencode-permissions.json`
|
|
|
|
And those stores currently encode:
|
|
|
|
- one `activeRunId` per team manifest
|
|
- one `activeCapabilitySnapshotId` per team manifest
|
|
- one active `OpenCode` launch transaction per team
|
|
|
|
This means the provider-local `OpenCode` state layer is still **team-scoped single-run state**, not a lane namespace that can safely host multiple concurrent `OpenCode` side lanes for the same logical team.
|
|
|
|
So V1 must be explicit:
|
|
|
|
- keep global mixed-team `launch-state.json` separate from provider-local `.opencode-runtime/launch-state.json`
|
|
- support **at most one `OpenCode` secondary lane/member per team**
|
|
- defer multi-`OpenCode` mixed teams until the provider-local runtime store namespace is lane-scoped
|
|
|
|
## The Real Problem We Need To Solve
|
|
|
|
`vector-room-7` failed because runtime ownership answered the wrong question.
|
|
|
|
Today the system effectively asks:
|
|
|
|
- "does this team contain OpenCode anywhere?"
|
|
|
|
But it should ask:
|
|
|
|
- "which runtime owner is allowed to launch each member?"
|
|
|
|
That distinction matters because:
|
|
|
|
- the lead and same-family teammates may still be valid on the primary lane
|
|
- a specific member such as an `OpenCode` teammate may require a side lane
|
|
- a side-lane failure must stay attributed to that member or lane only
|
|
|
|
## Goals
|
|
|
|
- Support mixed teams inside one logical team without splitting into fake subteams.
|
|
- Keep one lead and one canonical team identity.
|
|
- Make runtime ownership member-scoped where needed.
|
|
- Preserve the current primary team bootstrap path for the cases it already handles well.
|
|
- Add secondary lanes only where truth requires it.
|
|
- Make relaunch and restore deterministic.
|
|
- Keep renderer truth honest for partial success and partial failure.
|
|
- Roll out additively and safely.
|
|
|
|
## Non-Goals
|
|
|
|
- No hidden split-team illusion under the hood.
|
|
- No big-bang rewrite of every provider into lane adapters.
|
|
- No immediate per-member backend/fast editing UI.
|
|
- No claim that the current one-shot schedule system becomes mixed-team aware in this phase.
|
|
- No optimistic multi-member secondary lane grouping in V1.
|
|
|
|
## V1 Scope Decision
|
|
|
|
### ✅ In scope
|
|
|
|
- primary lane + secondary side-lane coordinator
|
|
- `OpenCode` as the first secondary-lane provider
|
|
- mixed teams where the lead remains on the existing primary team bootstrap path
|
|
- exactly one `OpenCode` secondary teammate per mixed team
|
|
- member-level lane attribution and persistence
|
|
- mixed create/launch/relaunch/restore/stop/reconcile for persistent teams
|
|
- truthful renderer diagnostics
|
|
|
|
### ❌ Out of scope for V1
|
|
|
|
- schedule parity through the current `ScheduledTaskExecutor`
|
|
- per-member backend/fast editing controls in the dialogs
|
|
- multi-member secondary lane grouping
|
|
- more than one `OpenCode` teammate inside the same mixed team
|
|
- converting `Codex` or `Anthropic` into side-lane providers unless a future runtime seam truly requires it
|
|
- `OpenCode`-led mixed teams where the canonical team owner would need to move onto the adapter path
|
|
|
|
## Chosen Runtime Ownership Model
|
|
|
|
## Two Kinds Of Lanes
|
|
|
|
### 1. Primary lane
|
|
|
|
The primary lane is the existing deterministic team bootstrap lane owned by the main team process.
|
|
|
|
It remains responsible for:
|
|
|
|
- the lead
|
|
- canonical team prompt/session semantics
|
|
- teammates that the current primary bootstrap owner can still launch truthfully
|
|
|
|
This is not a new idea. It is the current working system, formalized as a lane.
|
|
|
|
### 2. Secondary lanes
|
|
|
|
A secondary lane is a side runtime owner for a member that cannot be truthfully launched by the primary lane.
|
|
|
|
In V1:
|
|
|
|
- secondary lanes are **single-member only**
|
|
- the first supported owner is `OpenCode`
|
|
- a secondary lane never becomes the canonical team owner
|
|
|
|
This keeps the model simple and reliable.
|
|
|
|
## Compatibility Rule
|
|
|
|
The compatibility question in V1 is **not**:
|
|
|
|
- "same provider?"
|
|
- "same model?"
|
|
- "same effort?"
|
|
|
|
The real question is:
|
|
|
|
- "is this member allowed to remain owned by the current primary team bootstrap lane?"
|
|
|
|
### V1 compatibility policy
|
|
|
|
A member stays on the primary lane when:
|
|
|
|
- their provider/runtime family is supported by the primary team bootstrap owner
|
|
- no runtime family declares that the member must be isolated into a secondary lane
|
|
|
|
A member moves to a secondary lane when:
|
|
|
|
- their runtime family explicitly requires a separate runtime owner
|
|
|
|
### Initial concrete policy
|
|
|
|
In V1:
|
|
|
|
- `anthropic` -> primary-lane eligible
|
|
- `codex` -> primary-lane eligible
|
|
- `gemini` -> primary-lane eligible
|
|
- `opencode` -> secondary-lane required
|
|
|
|
This is intentionally specific because that is what the code currently supports.
|
|
|
|
### Lead policy in V1
|
|
|
|
For the first shipping phase:
|
|
|
|
- mixed teams are supported when the lead is `anthropic`, `codex`, or `gemini`
|
|
- pure `opencode` teams continue to use the existing runtime adapter path
|
|
- `opencode`-led mixed teams are deferred
|
|
|
|
This keeps canonical team ownership on the already-proven primary bootstrap path for V1 mixed support.
|
|
|
|
## Why We Are Not Using “Any Difference = New Lane”
|
|
|
|
That approach looks elegant on paper but is the wrong tradeoff here.
|
|
|
|
It would force us to:
|
|
|
|
- duplicate or replace working bootstrap behavior for `Codex` / `Anthropic` / `Gemini`
|
|
- redesign the renderer editor prematurely
|
|
- massively widen relaunch and restore risk
|
|
|
|
The better architecture is:
|
|
|
|
- generic coordinator
|
|
- specific V1 ownership policy
|
|
- a separate later phase for adapter-owned mixed primary lanes if we need them
|
|
|
|
That gives us scalability later without pretending the current code is more symmetric than it really is.
|
|
|
|
## Source Of Truth Matrix
|
|
|
|
The most important plan improvement is to separate **desired** truth from **resolved** truth.
|
|
|
|
| Artifact | Responsibility in V1 | Source kind |
|
|
| --- | --- | --- |
|
|
| `team.meta.json` | Lead-level desired runtime request and resolved lead launch identity | desired + resolved |
|
|
| `members.meta.json` | Member desired runtime contract overrides | desired |
|
|
| `launch-state.json` | Per-member resolved launch outcome, lane assignment, resolved launch identity | resolved |
|
|
| in-memory run maps | Live lane ownership and stop/reconcile handles | live runtime only |
|
|
|
|
### Rules
|
|
|
|
- `team.meta.json` is the source of truth for the lead request
|
|
- `members.meta.json` is the source of truth for member overrides
|
|
- `launch-state.json` is the source of truth for the most recent resolved lane/member execution
|
|
- live maps must never become the only source of member lane ownership
|
|
- `config.json` is not canonical teammate-roster truth during launch because it is intentionally normalized to lead-only
|
|
- post-launch `config.json` runtime projection is lossy and compatibility-facing only
|
|
- provider-local `OpenCode` runtime stores under `.opencode-runtime/*` are a separate provider-owned namespace, not the canonical mixed-team snapshot
|
|
- if a compact launch summary projection is introduced for Team list / dashboard, it must be derived from the same canonical snapshot precedence rules as the main runtime path
|
|
- the root `members.meta.json.providerBackendId` remains compatibility metadata, not member-scoped runtime truth
|
|
- team-view/runtime labels must eventually be derived from lane-aware snapshot truth, not only from `config + meta + inboxes`
|
|
- existing `launch-state.expectedMembers` semantics must remain explicit and backward-safe; do not silently redefine it to mean primary-only subset
|
|
|
|
## Contracts To Add Or Tighten
|
|
|
|
## 1. `TeamProvisioningMemberInput`
|
|
|
|
Extend `src/shared/types/team.ts`:
|
|
|
|
```ts
|
|
export interface TeamProvisioningMemberInput {
|
|
name: string
|
|
role?: string
|
|
workflow?: string
|
|
isolation?: 'worktree'
|
|
providerId?: TeamProviderId
|
|
providerBackendId?: TeamProviderBackendId
|
|
model?: string
|
|
effort?: EffortLevel
|
|
fastMode?: TeamFastMode
|
|
}
|
|
```
|
|
|
|
Reason:
|
|
|
|
- member desired runtime contract must be able to express full future lane identity
|
|
- even if the renderer does not expose backend/fast editors in V1, the contract must exist additively
|
|
|
|
### V1 semantic rule
|
|
|
|
In the shipping V1 scope:
|
|
|
|
- member `providerId/model/effort` are meaningful for both primary-lane and secondary-lane members
|
|
- member `providerBackendId/fastMode` are meaningful for secondary-lane planning and future compatibility
|
|
- primary-lane teammates still inherit effective backend/fast behavior from the lead/runtime owner
|
|
|
|
## 2. `TeamMember`
|
|
|
|
Extend `TeamMember` additively with:
|
|
|
|
- `providerBackendId?: TeamProviderBackendId`
|
|
- `fastMode?: TeamFastMode`
|
|
|
|
### Guardrail
|
|
|
|
`config.json` writers must **not** start depending on those fields.
|
|
They may remain omitted in canonical CLI-facing config writes until the CLI actually needs them.
|
|
|
|
This lets `members.meta.json` evolve without silently changing `config.json` semantics.
|
|
|
|
## 3. `TeamMemberSnapshot`
|
|
|
|
Extend renderer-facing member truth with:
|
|
|
|
- `providerBackendId?: TeamProviderBackendId`
|
|
- `selectedFastMode?: TeamFastMode`
|
|
- `resolvedFastMode?: boolean`
|
|
- `laneId?: string`
|
|
- `laneKind?: 'primary' | 'secondary'`
|
|
|
|
This is needed so Team Detail can show truthful member runtime identity without flattening everything into team-level labels.
|
|
|
|
### Guardrail
|
|
|
|
This is not optional polish.
|
|
|
|
The mixed feature must stay dark until `TeamMemberSnapshot` and the resolver path can consume lane-aware truth. Otherwise runtime behavior will be correct while Team Detail still lies.
|
|
|
|
## 4. `PersistedTeamLaunchMemberState`
|
|
|
|
Additive fields required:
|
|
|
|
- `providerId?: TeamProviderId`
|
|
- `providerBackendId?: TeamProviderBackendId`
|
|
- `launchIdentity?: ProviderModelLaunchIdentity`
|
|
- `laneId?: string`
|
|
- `laneKind?: 'primary' | 'secondary'`
|
|
- `laneOwnerProviderId?: TeamProviderId`
|
|
|
|
This is the minimum needed for deterministic relaunch and accurate diagnostics.
|
|
|
|
### Guardrail
|
|
|
|
Because `normalizePersistedLaunchSnapshot(...)` rebuilds the persisted structure explicitly, this schema work is incomplete until the normalizer also roundtrips these additive fields safely.
|
|
|
|
## 5. `TeamAgentRuntimeEntry`
|
|
|
|
Extend live runtime snapshot entries with:
|
|
|
|
- `providerId?: TeamProviderId`
|
|
- `providerBackendId?: TeamProviderBackendId`
|
|
- `laneId?: string`
|
|
- `laneKind?: 'primary' | 'secondary'`
|
|
|
|
This is a live presentation contract only.
|
|
It does not replace persisted truth.
|
|
|
|
## 6. `MemberDraft`
|
|
|
|
Do **not** expose member-level backend/fast in the UI in V1.
|
|
|
|
But for future-proofing, the internal draft type may be extended additively later if needed.
|
|
|
|
In V1:
|
|
|
|
- renderer editing remains `providerId/model/effort`
|
|
- backend/fast remain inherited internal fields
|
|
|
|
That is the safer scope boundary.
|
|
|
|
## Domain Model
|
|
|
|
## Desired member contract
|
|
|
|
```ts
|
|
type DesiredTeamMemberRuntimeContract = {
|
|
memberName: string
|
|
providerId: TeamProviderId
|
|
providerBackendId: TeamProviderBackendId | null
|
|
model: string | null
|
|
effort: EffortLevel | null
|
|
fastMode: TeamFastMode | null
|
|
cwd: string
|
|
}
|
|
```
|
|
|
|
This is reconstructed from:
|
|
|
|
- `team.meta.json` lead defaults
|
|
- `members.meta.json` member overrides
|
|
|
|
## Resolved member runtime identity
|
|
|
|
```ts
|
|
type ResolvedTeamMemberRuntimeIdentity = {
|
|
memberName: string
|
|
role: 'lead' | 'member'
|
|
desired: DesiredTeamMemberRuntimeContract
|
|
launchIdentity: ProviderModelLaunchIdentity
|
|
laneRequirement: 'primary-eligible' | 'secondary-required'
|
|
}
|
|
```
|
|
|
|
This is computed after provider-aware resolution.
|
|
|
|
## Lane plan
|
|
|
|
```ts
|
|
type TeamRuntimeLanePlan = {
|
|
laneId: string
|
|
laneKind: 'primary' | 'secondary'
|
|
ownerProviderId: TeamProviderId
|
|
memberNames: string[]
|
|
launchMode: 'existing-primary-bootstrap' | 'runtime-adapter'
|
|
}
|
|
```
|
|
|
|
### Important V1 simplification
|
|
|
|
Secondary lanes are single-member only, so:
|
|
|
|
- `laneKind === 'secondary'` always implies `memberNames.length === 1`
|
|
- in the shipping V1 scope, only one secondary `OpenCode` lane may exist for a team
|
|
- if a mixed draft includes more than one `OpenCode` teammate, the planner must reject it explicitly instead of silently grouping or serializing them
|
|
|
|
This is deliberate and should be written into the code as a policy, not left as an implicit side effect.
|
|
|
|
## Runtime Ownership Architecture
|
|
|
|
## New coordinator
|
|
|
|
Introduce a feature-owned coordinator:
|
|
|
|
`src/features/team-runtime-lanes/main/composition/createTeamRuntimeLaneCoordinator.ts`
|
|
|
|
Suggested facade:
|
|
|
|
`TeamRuntimeLaneCoordinator`
|
|
|
|
### Responsibilities
|
|
|
|
- read desired lead/member runtime contracts
|
|
- resolve member runtime identity
|
|
- partition members into primary vs secondary lanes
|
|
- launch the primary lane through the existing `TeamProvisioningService` path
|
|
- launch each secondary lane through an adapter owner
|
|
- merge lane results back into one aggregate team snapshot
|
|
- stop and reconcile all lanes
|
|
|
|
### Hard rules
|
|
|
|
- adapters never get unrelated members
|
|
- a secondary-lane failure cannot overwrite primary-lane member states
|
|
- in the shipping V1 mixed scope, the lead remains owned by the existing primary bootstrap lane
|
|
|
|
## Why the coordinator must preserve the primary path
|
|
|
|
The current primary path already owns:
|
|
|
|
- deterministic bootstrap
|
|
- lead inbox/session continuity
|
|
- much of the team lifecycle
|
|
|
|
So the coordinator should wrap it, not replace it.
|
|
|
|
That means the architecture is:
|
|
|
|
- existing primary provisioning path remains the canonical team owner
|
|
- secondary lanes are additive sidecars
|
|
|
|
This is safer than trying to make every provider look like a fully independent team owner.
|
|
|
|
## In-Memory Tracking Model
|
|
|
|
The earlier draft said to replace `team -> run` maps.
|
|
That is too broad for V1.
|
|
|
|
## Safer V1 tracking decision
|
|
|
|
Keep:
|
|
|
|
- `provisioningRunByTeam`
|
|
- `aliveRunByTeam`
|
|
|
|
as the primary-lane run markers.
|
|
|
|
Add:
|
|
|
|
```ts
|
|
type SecondaryLaneRunRecord = {
|
|
laneId: string
|
|
providerId: TeamProviderId
|
|
memberName: string
|
|
runId: string
|
|
cwd?: string
|
|
}
|
|
```
|
|
|
|
and:
|
|
|
|
```ts
|
|
private readonly secondaryLaneRunsByTeam = new Map<string, Map<string, SecondaryLaneRunRecord>>()
|
|
```
|
|
|
|
Optionally also add:
|
|
|
|
```ts
|
|
private readonly secondaryLaneProgressByRunId = new Map<string, TeamProvisioningProgress>()
|
|
```
|
|
|
|
### Why this is safer
|
|
|
|
- most of the codebase already assumes one primary team run
|
|
- mixed lanes only need additive side-lane tracking
|
|
- stop/reconcile/restore can aggregate over the new side map without destabilizing every existing caller
|
|
|
|
## Create And Launch Flow
|
|
|
|
## Critical bootstrap invariant
|
|
|
|
The current primary bootstrap flow explicitly embeds the teammate roster into:
|
|
|
|
- deterministic bootstrap spec `members`
|
|
- primary run `expectedMembers`
|
|
- primary-lane member spawn tracking
|
|
|
|
So a mixed-lane implementation must not treat those structures as the full mixed-team roster.
|
|
|
|
🚨 V1 rule:
|
|
|
|
- secondary-lane members must be excluded from the primary bootstrap spec
|
|
- secondary-lane members must be excluded from the primary in-memory `expectedMembers`
|
|
- aggregate mixed-team progress must track the full desired roster separately
|
|
|
|
Otherwise the primary lane will wait forever for members that are intentionally being launched elsewhere.
|
|
|
|
## Step 1 - persist desired contracts
|
|
|
|
Before runtime launch:
|
|
|
|
- write `team.meta.json` with lead request and lead launch identity seed
|
|
- write `members.meta.json` with full roster and member desired overrides
|
|
|
|
### Rule
|
|
|
|
Canonical team artifacts must always describe the full roster.
|
|
No provider-specific adapter is allowed to "own" who belongs in the roster.
|
|
|
|
### Additional rule
|
|
|
|
That full roster must be persisted in:
|
|
|
|
- `members.meta.json`
|
|
- mixed-lane-aware `launch-state.json`
|
|
|
|
and **not** inferred from launch-time `config.json`, because the current launch flow intentionally strips teammates out of `config.json` before spawning the lead CLI.
|
|
|
|
## Step 2 - resolve member runtime identity
|
|
|
|
Resolve:
|
|
|
|
- lead desired contract
|
|
- each member desired contract
|
|
- resolved runtime identity for each member
|
|
|
|
This uses the same provider-aware resolution services already used for team launch, but now produces member-scoped output.
|
|
|
|
## Step 3 - partition lanes
|
|
|
|
Partition members into:
|
|
|
|
- primary-lane members
|
|
- secondary-lane members
|
|
|
|
### V1 partition policy
|
|
|
|
- lead is always primary
|
|
- primary-eligible members stay primary
|
|
- `OpenCode` members become one-member secondary lanes
|
|
|
|
### Bootstrap subset rule
|
|
|
|
After partitioning:
|
|
|
|
- `primaryBootstrapMembers = lead + primary-eligible teammates`
|
|
- `secondaryLaneMembers = secondary-required teammates`
|
|
|
|
Only `primaryBootstrapMembers` may feed:
|
|
|
|
- `buildDeterministicCreateBootstrapSpec(...)`
|
|
- `buildDeterministicLaunchBootstrapSpec(...)`
|
|
- primary-lane `expectedMembers`
|
|
- primary-lane `memberSpawnStatuses`
|
|
|
|
## Step 4 - launch primary lane first
|
|
|
|
Use the existing primary team bootstrap flow for:
|
|
|
|
- lead
|
|
- all primary-lane teammates
|
|
|
|
This preserves current session and deterministic bootstrap assumptions.
|
|
|
|
## Step 5 - launch secondary lanes after primary readiness
|
|
|
|
Once the primary lane reaches the point where it can truthfully own the team context:
|
|
|
|
- launch secondary lanes
|
|
- one side lane per member in V1
|
|
|
|
### Concurrency
|
|
|
|
Allow bounded parallelism only if provider-specific side-lane launches are proven safe.
|
|
Default to conservative small concurrency.
|
|
|
|
### Join semantics
|
|
|
|
Secondary lanes must behave like post-bootstrap teammate attachments, not hidden members inside the original primary bootstrap transaction.
|
|
|
|
That means:
|
|
|
|
- the primary lane becomes ready against its own subset
|
|
- secondary lanes attach afterward
|
|
- mixed-team aggregate progress remains launching until both:
|
|
- the primary lane is ready enough
|
|
- every planned secondary lane has either joined or failed
|
|
|
|
## Explicit V1 mixed-team shipping matrix
|
|
|
|
### Supported in V1
|
|
|
|
- `Anthropic lead + one OpenCode teammate`
|
|
- `Codex lead + one OpenCode teammate`
|
|
- `Gemini lead + one OpenCode teammate`
|
|
- all existing pure non-OpenCode teams
|
|
- all existing pure OpenCode teams
|
|
|
|
### Deferred after V1
|
|
|
|
- more than one `OpenCode` teammate inside the same mixed team
|
|
- `OpenCode lead + Codex teammate`
|
|
- `OpenCode lead + Anthropic teammate`
|
|
- `OpenCode lead + Gemini teammate`
|
|
|
|
Those cases require canonical team ownership on the adapter path and should be treated as a separate risk class.
|
|
|
|
## Step 6 - merge lane outcomes
|
|
|
|
Merge:
|
|
|
|
- primary lane member evidence
|
|
- secondary lane member evidence
|
|
|
|
into one `launch-state.json` snapshot.
|
|
|
|
### Guardrail
|
|
|
|
Aggregate team state must be derived from member states, never the other way around.
|
|
|
|
### Aggregate roster truth
|
|
|
|
The mixed-team aggregate snapshot should distinguish:
|
|
|
|
- full desired roster for the whole launch
|
|
- primary bootstrap expected members only
|
|
|
|
Without that distinction, UI and restore logic will confuse:
|
|
|
|
- "primary bootstrap is still waiting"
|
|
with
|
|
- "a side lane has not attached yet"
|
|
|
|
### Backward-safe field rule
|
|
|
|
Because current readers and UI already treat `expectedMembers` as the full expected teammate roster, the mixed schema should add a new explicit field for the primary subset rather than silently changing the meaning of `expectedMembers`.
|
|
|
|
## Relaunch And Restore
|
|
|
|
This remains one of the highest-risk parts.
|
|
|
|
## Relaunch source rules
|
|
|
|
When relaunching:
|
|
|
|
1. desired contract comes from `members.meta.json` + lead-level `team.meta.json`
|
|
2. previous resolved member/lane truth comes from `launch-state.json`
|
|
3. current global provider settings are fallback only when stored team/member truth is absent
|
|
|
|
### Why this matters
|
|
|
|
The current relaunch flow:
|
|
|
|
- stops the team
|
|
- replaces members
|
|
- launches again
|
|
|
|
So if we do not persist member lane truth explicitly, relaunch will drift back toward team-level assumptions.
|
|
|
|
## Restore rules
|
|
|
|
On restore or startup reconciliation:
|
|
|
|
- restore primary lane from primary persisted state
|
|
- restore or reconcile secondary lanes from per-member lane attribution in `launch-state.json`
|
|
- if a secondary lane cannot be restored, only that member is degraded or failed
|
|
|
|
### Snapshot precedence rule
|
|
|
|
If both bootstrap-derived state and persisted launch-state exist:
|
|
|
|
- do not choose only by `updatedAt`
|
|
- prefer the snapshot that preserves mixed-lane structural truth
|
|
- a newer but structurally poorer bootstrap snapshot must not overwrite richer lane-aware launch-state
|
|
|
|
### Explicit non-goal
|
|
|
|
Do not reinterpret a previously mixed team as a pure team just because side-lane state is missing.
|
|
|
|
## Stop And Cleanup
|
|
|
|
## Ordering decision
|
|
|
|
For stop/relaunch:
|
|
|
|
1. stop secondary lanes first
|
|
2. stop primary lane second
|
|
|
|
### Why this order
|
|
|
|
- prevents side-lane members from being left attached to a dead team owner
|
|
- reduces orphaned-runtime risk
|
|
|
|
## Cleanup obligations
|
|
|
|
`stopTeam(...)` must:
|
|
|
|
- stop all secondary lanes
|
|
- stop the primary lane
|
|
- clear additive side-lane tracking maps
|
|
- persist a reconciled launch snapshot
|
|
|
|
No stop path may claim success while a side-lane member is still live.
|
|
|
|
## Live Edit And Add-Member Boundary
|
|
|
|
This is another high-risk area that the earlier draft was too soft about.
|
|
|
|
Today:
|
|
|
|
- `Edit Team` uses `replaceMembers(...)`
|
|
- runtime-affecting edits on a live team rely on `restartMember(...)`
|
|
- `Add member` is a post-bootstrap attachment flow
|
|
|
|
Those semantics are not equivalent for mixed lanes.
|
|
|
|
### V1 policy
|
|
|
|
#### Allowed
|
|
|
|
- fresh mixed create
|
|
- mixed relaunch
|
|
- mixed stop/start
|
|
|
|
#### Explicitly blocked
|
|
|
|
- live `addMember` on a running mixed team in V1
|
|
- live `replaceMembers` on a running mixed team in V1
|
|
- live `removeMember` on a running mixed team in V1
|
|
- live editing an existing member from primary-lane ownership to secondary-lane ownership
|
|
- live editing an existing member from secondary-lane ownership back to primary-lane ownership
|
|
- treating a lane migration as a simple restart
|
|
- using the current lead Agent tool restart flow as if it were a safe secondary-lane restart/migration path
|
|
|
|
### Why this is the right boundary
|
|
|
|
`addMember(...)` is already conceptually a post-bootstrap attachment flow.
|
|
`restartMember(...)` is not. It assumes the same runtime ownership model still applies.
|
|
|
|
And for secondary lanes there is one more hard constraint:
|
|
|
|
- the current add/restart seam is lead-owned Agent tool spawning with only provider/model/effort overrides
|
|
- that seam is insufficient for side-lane runtime owners like `OpenCode`
|
|
|
|
So the safer V1 boundary is:
|
|
|
|
- ship fresh launch / relaunch / stop / startup recovery for mixed teams
|
|
- defer live roster mutation of running mixed teams
|
|
- defer live side-lane add-member and live lane migration to a later phase
|
|
|
|
## Renderer Truth
|
|
|
|
## V1 renderer scope
|
|
|
|
Do not redesign the team editor UI.
|
|
|
|
Do:
|
|
|
|
- show lead and members with truthful runtime identity
|
|
- show that some members are on secondary lanes when useful
|
|
- keep partial-failure attribution member-scoped
|
|
|
|
### Shipping rule
|
|
|
|
Mixed-team execution must not be enabled for real users until `TeamMemberSnapshot` and `TeamMemberResolver` can surface lane-aware truth.
|
|
Without that, runtime behavior and Team Detail diverge immediately.
|
|
|
|
## Required visible truth
|
|
|
|
- lead row must always exist
|
|
- member rows must not inherit another member's failure
|
|
- provider/runtime labels must be per member, not flattened from one team-wide owner
|
|
|
|
## Current lead bug tie-in
|
|
|
|
The `vector-room-7` missing lead issue proves one more rule:
|
|
|
|
- lead visibility must not depend on provider-specific config writing quirks
|
|
|
|
The canonical roster must remain team-owned, not adapter-owned.
|
|
|
|
## Scheduler Boundary
|
|
|
|
This part needed the strongest correction.
|
|
|
|
## Current scheduler reality
|
|
|
|
`ScheduledTaskExecutor` is a one-shot CLI job runner.
|
|
It is not a persistent team-runtime lifecycle owner.
|
|
|
|
Therefore this feature should **not** claim:
|
|
|
|
- mixed-team lane execution through the current schedule system
|
|
- schedule parity with the persistent mixed-team coordinator
|
|
|
|
## V1 schedule policy
|
|
|
|
- existing `ScheduleLaunchConfig` remains lead-level only
|
|
- current schedules remain independent one-shot prompt runs
|
|
- mixed-team lane coordination is explicitly out of scope for this feature
|
|
|
|
### Terminology clarification
|
|
|
|
This repository currently has multiple different "restore" concepts:
|
|
|
|
- `restoreTeam(...)` in `TeamDataService` is undelete of `config.deletedAt`
|
|
- `TeamBackupService.restoreIfNeeded()` is startup backup restoration
|
|
- startup reconciliation also reads persisted launch state
|
|
|
|
This feature plan uses:
|
|
|
|
- **relaunch** for stop + launch again with persisted team metadata
|
|
- **startup recovery** for recovering mixed-lane runtime truth from persisted files after app/runtime interruption
|
|
|
|
Keeping those concepts separate will reduce implementation mistakes.
|
|
|
|
## Future extension
|
|
|
|
If later the product adds a true "scheduled team relaunch / scheduled team run" feature, that feature must call the same `TeamRuntimeLaneCoordinator`.
|
|
|
|
But that is a different system than the current scheduler.
|
|
|
|
## Diagnostics And Observability
|
|
|
|
The coordinator must emit:
|
|
|
|
- member desired contract summary
|
|
- lane assignment summary
|
|
- per-lane prepare result
|
|
- per-member launch attribution
|
|
- aggregate launch snapshot revision
|
|
|
|
### Example diagnostics
|
|
|
|
- `member alice stays on primary lane (codex primary bootstrap)`
|
|
- `member tom routed to secondary lane opencode:tom`
|
|
- `member bob routed to secondary lane opencode:bob`
|
|
|
|
This is essential. Mixed teams will be too hard to debug without explicit lane attribution.
|
|
|
|
## Migration And Compatibility
|
|
|
|
## Read compatibility
|
|
|
|
Old teams remain valid:
|
|
|
|
- missing member `providerBackendId` -> inherit lead/default provider backend
|
|
- missing member `fastMode` -> `inherit`
|
|
- missing member lane attribution in `launch-state.json` -> assume primary-lane if the member is primary-eligible
|
|
|
|
### Important limit
|
|
|
|
That last fallback must be used only for clearly primary-eligible providers.
|
|
Do not silently infer `OpenCode` members back onto the primary lane.
|
|
|
|
## Mixed-Lane Degraded Fallback Policy
|
|
|
|
This needed a stricter statement after the code pass.
|
|
|
|
### Pure primary-lane teams
|
|
|
|
For existing non-mixed teams, the current degraded launch-roster fallback behavior can stay:
|
|
|
|
- `members.meta.json`
|
|
- inboxes + `config.json`
|
|
- `config.json`
|
|
|
|
because the primary lane already understands member `provider/model/effort` in that model.
|
|
|
|
### Persisted mixed-lane teams
|
|
|
|
For teams that already have mixed-lane evidence, the coordinator must treat these as authoritative inputs:
|
|
|
|
- `members.meta.json`
|
|
- `launch-state.json`
|
|
|
|
If those files are missing or inconsistent, the product must **not** silently rebuild a mixed-lane plan from inboxes/config fallback only.
|
|
|
|
### Required degraded behavior
|
|
|
|
If mixed-lane evidence is missing:
|
|
|
|
- recover only the canonical team summary and visible roster if possible
|
|
- mark runtime/lane reconstruction as degraded
|
|
- block or require a fresh relaunch/bootstrap for mixed-lane execution
|
|
|
|
This is safer than pretending inbox/config fallback can recover member backend/fast/lane truth when the current code proves it cannot.
|
|
|
|
## Write policy
|
|
|
|
All new mixed launches must persist:
|
|
|
|
- member desired overrides in `members.meta.json`
|
|
- member resolved lane attribution in `launch-state.json`
|
|
|
|
Do not remove old team-level fields during rollout.
|
|
|
|
### Existing relaunch compatibility we can reuse
|
|
|
|
The current relaunch path already reconstructs member `providerId/model/effort` from `members.meta.json` when relaunching a team.
|
|
|
|
So mixed-lane persistence work should focus mainly on adding:
|
|
|
|
- lane attribution
|
|
- member-level backend/fast metadata
|
|
- resolved launch identity where needed
|
|
|
|
and should not re-solve the already-working member `provider/model/effort` relaunch behavior from scratch.
|
|
|
|
## Backup And Restore Compatibility
|
|
|
|
Because backup already captures `team.meta.json` and `members.meta.json`, this feature should follow a simple compatibility rule:
|
|
|
|
- only additive field changes in those files during V1
|
|
- no rename or shape break of the existing root files
|
|
|
|
### Required tests
|
|
|
|
- backup a team with mixed-lane additive member metadata
|
|
- restore it
|
|
- verify `team.meta.json`, `members.meta.json`, and `launch-state.json` still reconstruct the same desired + resolved member truth
|
|
|
|
## Dangerous And Thin Places
|
|
|
|
### 1. Treating lane compatibility as model/effort math instead of runtime ownership policy
|
|
|
|
This would produce an elegant but wrong planner.
|
|
|
|
Guardrail:
|
|
|
|
- first ask which runtime owner is allowed
|
|
- only then talk about model/effort/backend specifics
|
|
|
|
### 2. Letting side-lane adapters write canonical roster artifacts
|
|
|
|
That caused the missing-lead bug class already.
|
|
|
|
Guardrail:
|
|
|
|
- canonical roster is always written by team-owned code
|
|
|
|
### 3. Replacing the primary run maps too early
|
|
|
|
That would destabilize a lot of lifecycle code unrelated to mixed teams.
|
|
|
|
Guardrail:
|
|
|
|
- keep primary maps
|
|
- add side-lane maps
|
|
|
|
### 4. Claiming schedule parity too early
|
|
|
|
This would create a misleading plan and likely bugs.
|
|
|
|
Guardrail:
|
|
|
|
- keep current scheduler explicitly out of scope
|
|
|
|
### 5. Allowing multi-member secondary lanes in V1
|
|
|
|
That creates grouping bugs before ownership is stable.
|
|
|
|
Guardrail:
|
|
|
|
- V1 side lanes are single-member only
|
|
|
|
### 6. Mixing primary bootstrap expectations with aggregate mixed-team progress
|
|
|
|
This would create stuck launches and misleading `"Members joining"` states.
|
|
|
|
Guardrail:
|
|
|
|
- primary in-memory `expectedMembers` is subset-only
|
|
- persisted launch-state `expectedMembers` stays the full expected roster unless a new explicit field is introduced for the bootstrap subset
|
|
- aggregate desired roster is tracked separately
|
|
|
|
### 7. Treating live lane migration as a simple restart
|
|
|
|
This would corrupt ownership and attribution.
|
|
|
|
Guardrail:
|
|
|
|
- block lane-migration edits in V1
|
|
- implement migration later as a separate coordinator-backed increment
|
|
|
|
### 8. Assuming primary-lane teammates already support member-level backend/fast
|
|
|
|
This would create hidden runtime drift between persisted desired state and actual spawn behavior.
|
|
|
|
Guardrail:
|
|
|
|
- document that primary-lane teammates inherit backend/fast from the lead/runtime owner in V1
|
|
- only secondary-lane planning may consume member-level backend/fast earlier
|
|
|
|
### 9. Passing non-owned members into a side-lane adapter input
|
|
|
|
This would cause provider-local flattening of failures back onto unrelated members.
|
|
|
|
Guardrail:
|
|
|
|
- adapter inputs must be lane-local subsets only
|
|
- add tests where one side-lane member fails and no other member inherits that failure
|
|
|
|
### 10. Trusting `config.json` as canonical mixed-team roster truth
|
|
|
|
The launch path intentionally normalizes `config.json` to lead-only.
|
|
|
|
Guardrail:
|
|
|
|
- `config.json` may remain CLI-facing and lead-centric
|
|
- mixed full-roster truth must come from `members.meta.json` and mixed-lane-aware `launch-state.json`
|
|
|
|
### 11. Reusing the current lead Agent tool add-member/restart seam for secondary lanes
|
|
|
|
That seam only carries provider/model/effort and assumes the lead owns the teammate spawn.
|
|
|
|
Guardrail:
|
|
|
|
- coordinator-managed secondary-lane attachment must bypass the generic lead Agent tool spawn-message path
|
|
- V1 must not claim secondary-lane restart/migration support through the current restart helper
|
|
|
|
### 12. Treating inbox/config fallback as authoritative mixed-lane recovery
|
|
|
|
That would silently erase member backend/fast/lane truth on relaunch/startup recovery.
|
|
|
|
Guardrail:
|
|
|
|
- inbox/config fallback may recover names and basic provider/model/effort only
|
|
- persisted mixed-lane teams require `members.meta.json` + `launch-state.json` for authoritative recovery
|
|
- if those are missing, degrade explicitly instead of inventing a mixed plan
|
|
|
|
### 13. Reusing primary-lane env/provider args for side lanes
|
|
|
|
Primary create/launch resolves env/auth/provider args once for the root request.
|
|
|
|
Guardrail:
|
|
|
|
- every secondary lane resolves its own env/auth/provider args
|
|
- do not inherit root request provider args into side-lane launches
|
|
|
|
### 14. Treating renderer parity as optional polish
|
|
|
|
Today `TeamMemberResolver` is built from `config + meta + inboxes`, not lane-aware launch-state truth.
|
|
|
|
Guardrail:
|
|
|
|
- lane-aware `TeamMemberSnapshot` and resolver parity must land before mixed support is enabled
|
|
- do not treat this as a post-shipping cosmetic cleanup
|
|
|
|
### 15. Adding mixed-lane fields without upgrading launch-state normalization
|
|
|
|
`normalizePersistedLaunchSnapshot(...)` reconstructs known fields and would otherwise drop new member-level lane/runtime data.
|
|
|
|
Guardrail:
|
|
|
|
- any mixed-lane schema change must ship with normalizer support
|
|
- schema upgrade is not complete until read-normalize-write roundtrips preserve the new fields
|
|
|
|
### 16. Quietly redefining `expectedMembers`
|
|
|
|
That field is already used widely for launch badges, summaries, provisioning presentation, and recovery.
|
|
|
|
Guardrail:
|
|
|
|
- do not change `expectedMembers` from "full expected roster" to "primary subset"
|
|
- add a new explicit field for bootstrap/primary expected members instead
|
|
|
|
### 17. Choosing bootstrap vs persisted launch snapshot by timestamp only
|
|
|
|
Mixed teams introduce a structurally richer launch-state than bootstrap-only recovery can express.
|
|
|
|
Guardrail:
|
|
|
|
- timestamp-only precedence is not enough once mixed-lane fields exist
|
|
- snapshot selection must prefer structurally richer mixed-lane truth over a newer but poorer bootstrap snapshot
|
|
|
|
### 18. Letting mixed-lane launch-state outgrow Team list / dashboard summary readers
|
|
|
|
Main runtime storage and summary readers currently have different size budgets.
|
|
|
|
Guardrail:
|
|
|
|
- mixed-lane rollout must define an explicit launch-state size budget for summary-safe operation
|
|
- preferred V1 answer is a compact summary projection written from canonical snapshot truth, not a blind increase of all reader limits
|
|
- if that compact projection does not exist, rollout must stay blocked until reader limits, performance, and summary parity are explicitly re-validated
|
|
|
|
### 19. Updating summary logic only in one of the two reader paths
|
|
|
|
Team summaries are read both in-process and through `team-fs-worker`.
|
|
|
|
The current worker path does **not** use `choosePreferredLaunchSnapshot(...)` and does **not** read bootstrap launch snapshots. It only reads raw persisted `launch-state.json`.
|
|
|
|
Guardrail:
|
|
|
|
- schema, precedence, and summary derivation changes must land in both paths together
|
|
- preferred V1 answer is a shared summary derivation contract or compact summary artifact consumed by both paths
|
|
- if parity cannot be guaranteed, mixed launch-state summary should stay dark rather than diverge between worker and fallback modes
|
|
|
|
### 20. Treating post-launch `config.json` runtime projection as authoritative mixed truth
|
|
|
|
The existing config projection writes only provider/model/effort and is intentionally lossy.
|
|
|
|
Guardrail:
|
|
|
|
- never reconstruct mixed-lane runtime truth from projected `config.json`
|
|
- keep config projection compatibility-focused and minimal unless the CLI itself requires more
|
|
|
|
### 21. Allowing live roster mutation on running mixed teams in V1
|
|
|
|
Current live add/remove/replace flows all route through lead-side messaging and primary-lane assumptions.
|
|
|
|
Guardrail:
|
|
|
|
- V1 mixed support should block live roster mutation on running mixed teams
|
|
- require stop/edit/relaunch until a coordinator-owned live mutation model exists
|
|
|
|
### 22. Treating provider-local `OpenCode` runtime stores as if they were already lane-scoped
|
|
|
|
The current `.opencode-runtime` namespace is team-scoped and single-active-run-scoped.
|
|
|
|
Guardrail:
|
|
|
|
- keep provider-local `OpenCode` launch-state/manifest/transaction files separate from global mixed-team `launch-state.json`
|
|
- do not multiplex multiple `OpenCode` side lanes into the same provider-local store namespace in V1
|
|
|
|
### 23. Assuming multiple `OpenCode` side lanes are possible without changing the bridge contract
|
|
|
|
The current `OpenCode` launch command takes one `selectedModel` for the whole provider-owned run.
|
|
|
|
Guardrail:
|
|
|
|
- V1 mixed support must reject drafts with more than one `OpenCode` teammate
|
|
- future multi-`OpenCode` support requires both lane-scoped runtime-store namespacing and a bridge/API seam that can represent lane-local model ownership truthfully
|
|
|
|
### 24. Reusing the legacy "expected teammates vs confirmed artifacts" summary fallback for mixed teams
|
|
|
|
Current Team list / dashboard fallback summary logic infers partial launch failure by comparing:
|
|
|
|
- expected teammate names from config/meta
|
|
- confirmed artifacts such as config members and inbox files
|
|
|
|
That heuristic is not lane-aware.
|
|
|
|
For mixed teams, a side-lane member can be legitimately absent from primary-lane artifacts during bootstrap or attachment without meaning "launch failed".
|
|
|
|
Guardrail:
|
|
|
|
- once a team is mixed-aware, summary fallback must not reuse the legacy artifact-count heuristic as if it were canonical launch truth
|
|
- mixed teams should prefer canonical lane-aware snapshot/projection truth
|
|
- if that truth is missing, summary should degrade to unknown/pending rather than inventing a partial failure
|
|
|
|
### 25. Reusing the current primary-run `persistLaunchStateSnapshot(...)` as the canonical mixed snapshot writer
|
|
|
|
Today `persistLaunchStateSnapshot(run)` writes `launch-state.json` from one `ProvisioningRun` and uses:
|
|
|
|
- `run.expectedMembers`
|
|
- `run.memberSpawnStatuses`
|
|
- `run.provisioningComplete`
|
|
|
|
That is safe for a single primary run, but unsafe for mixed teams because:
|
|
|
|
- primary `run.expectedMembers` is only the bootstrap subset
|
|
- a clean-success primary run currently clears persisted launch-state entirely
|
|
- aggregate mixed truth may still include pending or failed side lanes
|
|
|
|
Guardrail:
|
|
|
|
- mixed teams need an aggregate snapshot writer owned by the coordinator, not direct reuse of the current primary-run persistence helper as canonical truth
|
|
- primary-lane snapshot persistence may remain lane-local input, but it must not overwrite or clear the global mixed-team launch snapshot on its own
|
|
|
|
### 26. Letting `createPersistedLaunchSnapshot(...)` auto-fail not-yet-attached side-lane members
|
|
|
|
Current `createPersistedLaunchSnapshot(...)` upgrades members from `starting` to `failed_to_start` whenever:
|
|
|
|
- `launchPhase !== 'active'`
|
|
- the member never reached spawned/alive/confirmed signals
|
|
|
|
That is a good repair for single-lane crashed launches, but dangerous for mixed teams if:
|
|
|
|
- the primary lane flips out of `active`
|
|
- a planned side lane has not attached yet
|
|
- the aggregate launch is not actually terminal
|
|
|
|
Guardrail:
|
|
|
|
- aggregate mixed `launchPhase` must stay `active` until every planned side lane is terminal or explicitly removed from the plan
|
|
- primary-lane completion must not by itself trigger terminalization of still-planned side-lane members
|
|
- if V1 cannot enforce that invariant, mixed aggregate snapshot creation must use a separate terminalization policy instead of reusing the default helper unchanged
|
|
|
|
### 27. Reusing bootstrap-specific pending copy for mixed side-lane attachment
|
|
|
|
Current Team list / Team detail copy for `partial_pending` talks in bootstrap terms such as:
|
|
|
|
- teammate still joining
|
|
- runtime pending bootstrap
|
|
|
|
For mixed teams, `partial_pending` may instead mean:
|
|
|
|
- primary lane is already ready
|
|
- one side lane has not attached yet
|
|
- one side lane is reconciling provider-local runtime truth
|
|
|
|
Guardrail:
|
|
|
|
- mixed renderer states must not over-interpret aggregate `partial_pending` as bootstrap-only
|
|
- once a team is mixed-aware, pending copy should become lane-aware or neutral, for example "launch still reconciling" rather than "pending bootstrap"
|
|
|
|
## Rollout Plan
|
|
|
|
## Phase 0 - Additive contracts and persistence parity
|
|
|
|
**Commit boundary**: `feat(team-runtime): add additive mixed-lane contracts`
|
|
|
|
`🎯 10 🛡️ 10 🧠 4`
|
|
Roughly `420-750` lines
|
|
|
|
### Scope
|
|
|
|
- extend shared member runtime contracts with `providerBackendId` and `fastMode`
|
|
- extend `TeamMemberSnapshot` and persisted launch member contracts with lane-aware additive fields
|
|
- extend `members.meta.json` read/write shape
|
|
- extend launch-state member shape additively
|
|
- upgrade launch-state normalizer/read path so the new fields survive roundtrip
|
|
- add explicit mixed-safe roster fields instead of redefining `expectedMembers`
|
|
- define a mixed-safe launch-state size budget for summary readers
|
|
- choose a summary-safe read strategy for Team list / dashboard, preferably a compact summary projection derived from canonical snapshot precedence
|
|
- define aggregate mixed-team snapshot ownership so primary-lane persistence helpers cannot overwrite or clear canonical mixed truth
|
|
- no behavior change yet
|
|
|
|
### Tests
|
|
|
|
- backward-compatible meta read/write
|
|
- backward-compatible launch-state normalization
|
|
- launch-state roundtrip preserves new additive mixed-lane fields
|
|
- old readers still see `expectedMembers` as full expected roster
|
|
- summary readers continue to read mixed launch-state within the agreed size budget
|
|
- summary strategy tests prove worker and in-process readers agree even when bootstrap snapshot and persisted launch-state disagree on recency
|
|
- aggregate snapshot tests prove primary-lane clean-success does not clear pending mixed side-lane truth
|
|
- member contract serialization tests
|
|
- backup/restore compatibility for additive member runtime metadata
|
|
- explicit semantic tests proving primary-lane teammates still inherit backend/fast in V1
|
|
- `TeamMemberSnapshot` compatibility tests for sparse old teams
|
|
|
|
## Phase 1 - Coordinator shell plus resolver parity
|
|
|
|
**Commit boundary**: `feat(team-runtime): introduce mixed-lane coordinator shell`
|
|
|
|
`🎯 9 🛡️ 10 🧠 6`
|
|
Roughly `520-900` lines
|
|
|
|
### Scope
|
|
|
|
- add `TeamRuntimeLaneCoordinator`
|
|
- keep current primary launch path untouched behaviorally
|
|
- add side-lane tracking maps without enabling mixed behavior yet
|
|
- make `TeamMemberResolver` and related team-view snapshot assembly lane-aware for additive fields
|
|
- centralize Team summary derivation so `TeamConfigReader` and `team-fs-worker` consume the same mixed-summary contract instead of reimplementing snapshot choice independently
|
|
- introduce coordinator-owned aggregate launch-state assembly so lane-local progress feeds one canonical mixed snapshot
|
|
- primary-only teams remain identical
|
|
|
|
### Tests
|
|
|
|
- single-provider create/launch parity
|
|
- no regression in pure `OpenCode` gated path
|
|
- no regression in stop/reconcile for primary-only teams
|
|
- no regression in existing mixed primary-lane member `provider/model/effort` behavior
|
|
- team view remains stable for old teams with no mixed-lane fields
|
|
- lead synthesis still works when lane-aware fields are missing
|
|
- worker and in-process team summary paths derive the same launch summary from the same mixed snapshot
|
|
- worker and in-process paths agree when bootstrap snapshot is newer than persisted launch-state and when persisted launch-state is structurally richer than bootstrap snapshot
|
|
- mixed-aware teams do not fall back to the legacy artifact-count partial-failure heuristic when canonical lane-aware snapshot truth is missing
|
|
- lane-local primary snapshot updates do not clear or terminalize the aggregate mixed snapshot prematurely
|
|
|
|
## Phase 2 - Enable `OpenCode` single-member secondary lanes
|
|
|
|
**Commit boundary**: `feat(team-runtime): enable opencode secondary member lanes`
|
|
|
|
`🎯 9 🛡️ 9 🧠 8`
|
|
Roughly `650-1100` lines
|
|
|
|
### Scope
|
|
|
|
- remove team-wide `OpenCode` capture logic
|
|
- allow one `OpenCode` member to become a single-member secondary lane
|
|
- reject mixed drafts with more than one `OpenCode` teammate with a clear unsupported-in-v1 message
|
|
- exclude secondary-lane members from primary bootstrap spec and primary `expectedMembers`
|
|
- launch primary lane plus side lanes
|
|
- merge lane results into member-scoped launch-state
|
|
- keep aggregate mixed `launchPhase` active until all planned lanes are terminal, even if the primary lane finishes earlier
|
|
|
|
### Tests
|
|
|
|
- `Codex` lead + `OpenCode` teammate
|
|
- `Anthropic` lead + `OpenCode` teammate
|
|
- `Gemini` lead + `OpenCode` teammate
|
|
- multiple `OpenCode` teammates in one mixed team are rejected explicitly in V1
|
|
- primary lane does not wedge waiting for side-lane members
|
|
- one failing `OpenCode` side lane does not flatten failure onto other members
|
|
- mixed launch keeps `config.json` lead-only while `members.meta.json` retains the full desired roster
|
|
- every `OpenCode` side lane resolves its own env/auth/provider args instead of reusing the lead launch env
|
|
- global mixed-team `launch-state.json` and provider-local `.opencode-runtime/launch-state.json` remain separate and do not overwrite each other
|
|
- primary-lane clean-success does not clear aggregate mixed launch-state while a side lane is still pending
|
|
|
|
## Phase 3 - Lifecycle parity for mixed persistent teams
|
|
|
|
**Commit boundary**: `feat(team-runtime): add mixed-lane lifecycle parity`
|
|
|
|
`🎯 8 🛡️ 9 🧠 7`
|
|
Roughly `450-850` lines
|
|
|
|
### Scope
|
|
|
|
- mixed relaunch
|
|
- mixed restore/startup recovery
|
|
- mixed stop
|
|
- mixed reconcile
|
|
- explicit blocking for unsupported live lane-migration edits
|
|
- explicit degraded-state handling when persisted mixed-lane truth is missing and only inbox/config fallback is available
|
|
- explicit blocking for live side-lane add-member in V1
|
|
- mixed-safe snapshot precedence when bootstrap and persisted launch-state disagree
|
|
- mixed-safe summary precedence in both Team summary reader paths
|
|
- explicit blocking for live roster mutation on running mixed teams
|
|
|
|
### Tests
|
|
|
|
- create -> launch -> relaunch
|
|
- create -> launch -> stop
|
|
- startup recovery with stale secondary lane
|
|
- partial side-lane failure stays member-scoped
|
|
- live lane-migration edit is rejected with a clear message
|
|
- relaunch preserves primary-lane member `provider/model/effort` and does not invent member-level backend/fast behavior
|
|
- persisted mixed-lane recovery degrades explicitly when `members.meta.json` or lane-aware `launch-state.json` is missing
|
|
- live side-lane add-member attempt is rejected with a clear unsupported-in-v1 message
|
|
- richer persisted mixed launch-state wins over a newer bootstrap-only snapshot when both exist
|
|
- Team list / dashboard summary stays stable in both worker and in-process modes for the same mixed team
|
|
- live `addMember` / `replaceMembers` / `removeMember` on a running mixed team are rejected with clear stop-edit-relaunch guidance
|
|
|
|
## Phase 4 - Renderer truth and diagnostics polish
|
|
|
|
**Commit boundary**: `feat(team-runtime): surface mixed-lane member truth in ui`
|
|
|
|
`🎯 8 🛡️ 8 🧠 5`
|
|
Roughly `180-320` lines
|
|
|
|
### Scope
|
|
|
|
- member lane labels
|
|
- member-scoped failure copy
|
|
- mixed-aware pending/reconciling copy that does not pretend every pending state is "bootstrap pending"
|
|
- cleaner diagnostics and affordances on top of already-landed resolver parity
|
|
|
|
### Tests
|
|
|
|
- Team Detail shows lead reliably
|
|
- member rows show provider/runtime truth
|
|
- side-lane failure does not flatten team-wide
|
|
- Team list/detail pending copy stays truthful when the primary lane is ready but a side lane is still attaching or reconciling
|
|
|
|
## Future Phase - Live mixed-team roster mutation
|
|
|
|
Not part of the first mixed-team rollout.
|
|
|
|
Only start this after primary mixed launch/relaunch/recovery is stable.
|
|
|
|
### Scope
|
|
|
|
- add or remove members on a running mixed team
|
|
- replace member roster on a running mixed team
|
|
- restart a secondary-lane member through a coordinator-owned path
|
|
- explicit migration workflow for primary <-> secondary lane changes
|
|
|
|
### Why this is deferred
|
|
|
|
The current live add/remove/replace/restart seams are lead-Agent-tool-based or lead-message-based and do not carry enough runtime metadata for side-lane ownership. Shipping them in V1 would be materially riskier than fresh mixed launch support.
|
|
|
|
## Future Phase - `OpenCode` lane-scoped runtime namespace and multi-`OpenCode` mixed teams
|
|
|
|
Not part of the first mixed-team rollout.
|
|
|
|
Only start this after the single-`OpenCode`-teammate V1 is stable.
|
|
|
|
### Scope
|
|
|
|
- introduce lane-scoped provider-local runtime-store namespace, for example `.opencode-runtime/<laneId>/...`
|
|
- replace team-scoped single active-run assumptions in manifest/transaction/permission stores
|
|
- extend bridge/API contracts so `OpenCode` side-lane launches can represent lane-local model ownership truthfully
|
|
- only after that allow more than one `OpenCode` teammate inside the same mixed team
|
|
|
|
## Future Phase - Scheduled team runtime coordinator
|
|
|
|
Not part of this feature.
|
|
|
|
Only start this when the product adds a real scheduled team-runtime flow distinct from the current one-shot scheduler.
|
|
|
|
## Test Plan
|
|
|
|
## Main-process tests
|
|
|
|
- member desired-contract inheritance from lead defaults
|
|
- lane partition policy: primary-eligible vs secondary-required
|
|
- launch-state merge preserves per-member attribution
|
|
- side-lane run tracking does not break primary run tracking
|
|
|
|
## Integration tests
|
|
|
|
- mixed create writes full canonical roster
|
|
- mixed launch persists member lane attribution
|
|
- mixed relaunch uses persisted member truth, not current global defaults
|
|
- side-lane stop/reconcile only affects side-lane members
|
|
- mixed launch/relaunch never treats lead-only `config.json` as the canonical full roster source
|
|
- degraded inbox/config fallback for a mixed team does not silently reconstruct `OpenCode` back onto the primary lane
|
|
- team-view snapshots reflect lane-aware persisted truth instead of flattening back to config/meta-only labels
|
|
- Team list / dashboard summaries still read mixed launch-state within the supported size budget
|
|
- mixed drafts with more than one `OpenCode` teammate are rejected explicitly in V1
|
|
- global mixed-team `launch-state.json` and provider-local `.opencode-runtime/launch-state.json` remain disambiguated
|
|
- mixed Team list / dashboard summaries do not infer partial failure from missing side-lane artifacts while the team is still launching
|
|
|
|
## Renderer tests
|
|
|
|
- lead row remains visible even when old data is sparse
|
|
- mixed team rows show member-level runtime truth
|
|
- partial failure UI remains member-scoped
|
|
|
|
## Live Signoff
|
|
|
|
Required live signoff before rollout:
|
|
|
|
1. `Codex` lead + `OpenCode` teammate
|
|
2. `Anthropic` lead + `OpenCode` teammate
|
|
3. `Gemini` lead + `OpenCode` teammate
|
|
4. mixed team relaunch
|
|
5. mixed team stop and restart
|
|
6. startup recovery with an interrupted side lane
|
|
7. Team Detail truth matches persisted lane/member runtime identity for a mixed team
|
|
8. mixed draft with two `OpenCode` teammates is rejected with a clear unsupported-in-v1 message
|
|
|
|
## Final Recommendation
|
|
|
|
Implement mixed teams as:
|
|
|
|
- one canonical primary team bootstrap lane
|
|
- additive single-member secondary lanes where runtime ownership truly requires it
|
|
- in V1, at most one `OpenCode` secondary teammate per mixed team
|
|
- member-scoped persistence before behavior changes
|
|
- side-lane tracking added beside, not instead of, current primary run maps
|
|
- fresh mixed launch/relaunch/recovery first, with live side-lane attachment deferred
|
|
|
|
This is the most reliable and scalable path because it:
|
|
|
|
- matches the real code
|
|
- fixes the `vector-room-7` class of bugs directly
|
|
- keeps working bootstrap behavior intact
|
|
- gives us a clean extension point for future side-lane providers without over-engineering V1
|