Status and delivery follow the live overlay, not the roster primary - #1064
Conversation
A switched desk keeps its roster primary while active-harness.json names the live harness. Status advertised the roster field, and flotilla send passed that field into ResolveLiveDriver, so a generic node pane fell back to the stale driver and classified overlay chrome as errored. workspace.EffectiveSurface is the shared overlay-first seam. Status, CLI send, watch assessment, and dash routing all use it. Generic node + overlay codex + roster grok tests cover assess, send, and status JSON.
There was a problem hiding this comment.
2 issues found across 15 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="cmd/flotilla/main.go">
<violation number="1" location="cmd/flotilla/main.go:491">
P2: When `active-harness.json` contains a parseable but unregistered `surface` and the pane command is generic (`node`), this call makes `flotilla send` fail instead of falling back to the roster. Validate the overlay surface or treat unregistered values as torn before resolving the live driver.</violation>
</file>
<file name="cmd/flotilla/send_test.go">
<violation number="1" location="cmd/flotilla/send_test.go:35">
P3: TestResolveSendLiveDriverGenericNodeUsesOverlay duplicates the send-check block of TestGenericNodeAssessAndSendUseOverlayNotRoster in watch_test.go — same agent, overlay, cfg, inputs, and assertions. If the send-path coverage is meant to live in send_test.go, drop the duplicated send block from watch_test.go so the same scenario is asserted once.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| return fmt.Errorf("agent %q: read live command: %w", agentName, err) | ||
| } | ||
| drv, liveSurface, _, err := surface.ResolveLiveDriver(agent.Surface, pane, func(string) (string, error) { return liveCommand, nil }) | ||
| drv, liveSurface, err := resolveSendLiveDriver(cfg, agentName, pane, liveCommand) |
There was a problem hiding this comment.
P2: When active-harness.json contains a parseable but unregistered surface and the pane command is generic (node), this call makes flotilla send fail instead of falling back to the roster. Validate the overlay surface or treat unregistered values as torn before resolving the live driver.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At cmd/flotilla/main.go, line 491:
<comment>When `active-harness.json` contains a parseable but unregistered `surface` and the pane command is generic (`node`), this call makes `flotilla send` fail instead of falling back to the roster. Validate the overlay surface or treat unregistered values as torn before resolving the live driver.</comment>
<file context>
@@ -488,7 +488,7 @@ func cmdSend(args []string) error {
return fmt.Errorf("agent %q: read live command: %w", agentName, err)
}
- drv, liveSurface, _, err := surface.ResolveLiveDriver(agent.Surface, pane, func(string) (string, error) { return liveCommand, nil })
+ drv, liveSurface, err := resolveSendLiveDriver(cfg, agentName, pane, liveCommand)
if err != nil {
return fmt.Errorf("agent %q: %w", agentName, err)
</file context>
There was a problem hiding this comment.
FIX-landed at ca83f559b4d6a3212455f583974a04371eba9cde.
workspace.EffectiveSurface treats an unregistered overlay Surface as torn (surface.Registered) and fail-safes to the roster. Coverage: TestEffectiveSurfaceUnregisteredOverlayFallsBackToRoster, TestResolveSendLiveDriverUnregisteredOverlayFallsBackToRoster (generic node + overlay not-a-driver + roster grok → send driver grok, no error). Shared seam, not send-only.
|
|
||
| // --mirror and --no-mirror together is a clear error (caught right after flag parse, | ||
| // before any roster load or tmux delivery). | ||
| func TestResolveSendLiveDriverGenericNodeUsesOverlay(t *testing.T) { |
There was a problem hiding this comment.
P3: TestResolveSendLiveDriverGenericNodeUsesOverlay duplicates the send-check block of TestGenericNodeAssessAndSendUseOverlayNotRoster in watch_test.go — same agent, overlay, cfg, inputs, and assertions. If the send-path coverage is meant to live in send_test.go, drop the duplicated send block from watch_test.go so the same scenario is asserted once.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At cmd/flotilla/send_test.go, line 35:
<comment>TestResolveSendLiveDriverGenericNodeUsesOverlay duplicates the send-check block of TestGenericNodeAssessAndSendUseOverlayNotRoster in watch_test.go — same agent, overlay, cfg, inputs, and assertions. If the send-path coverage is meant to live in send_test.go, drop the duplicated send block from watch_test.go so the same scenario is asserted once.</comment>
<file context>
@@ -30,6 +32,19 @@ func TestShouldMirror(t *testing.T) {
// --mirror and --no-mirror together is a clear error (caught right after flag parse,
// before any roster load or tmux delivery).
+func TestResolveSendLiveDriverGenericNodeUsesOverlay(t *testing.T) {
+ root := t.TempDir()
+ writeAgentOverlay(t, root, "backend", `{"slot":"fallback-0","surface":"codex"}`)
</file context>
There was a problem hiding this comment.
FIX-landed at ca83f559b4d6a3212455f583974a04371eba9cde.
Watch coverage is TestGenericNodeAssessUsesOverlayNotRoster (assess only). Send coverage stays in cmd/flotilla/send_test.go (TestResolveSendLiveDriverGenericNodeUsesOverlay). Duplicate send-check block removed.
An overlay Surface that is not a registered driver is fail-safe to the roster at workspace.EffectiveSurface (shared seam for status, send, watch, dash). BuildBoard and buildStatusJSON take pre-resolved surfaces so they stay pure. Send coverage for unregistered overlay + generic node lives in send_test.go.
There was a problem hiding this comment.
1 issue found across 13 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="cmd/flotilla/status.go">
<violation number="1" location="cmd/flotilla/status.go:190">
P3: statusSurfaces and loadAgentSurfaces are identical helpers (iterate roster agents, map each to EffectiveSurface) added in two different call layers, and a third copy already exists in goals.agentSurfacesFromRoster. Extract one helper into internal/workspace (e.g. EffectiveSurfaces(cfg)) and call it from status, dash server, and goals so overlay-first resolution stays in one place.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| // statusSurfaces resolves overlay-first configured surfaces for every roster | ||
| // agent. The command layer (cmdStatus, notify) calls this so buildStatusJSON | ||
| // stays pure. | ||
| func statusSurfaces(cfg *roster.Config) map[string]string { |
There was a problem hiding this comment.
P3: statusSurfaces and loadAgentSurfaces are identical helpers (iterate roster agents, map each to EffectiveSurface) added in two different call layers, and a third copy already exists in goals.agentSurfacesFromRoster. Extract one helper into internal/workspace (e.g. EffectiveSurfaces(cfg)) and call it from status, dash server, and goals so overlay-first resolution stays in one place.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At cmd/flotilla/status.go, line 190:
<comment>statusSurfaces and loadAgentSurfaces are identical helpers (iterate roster agents, map each to EffectiveSurface) added in two different call layers, and a third copy already exists in goals.agentSurfacesFromRoster. Extract one helper into internal/workspace (e.g. EffectiveSurfaces(cfg)) and call it from status, dash server, and goals so overlay-first resolution stays in one place.</comment>
<file context>
@@ -179,9 +184,23 @@ func summarizeStatusItems(items []statusItem) utilization.Summary {
+// statusSurfaces resolves overlay-first configured surfaces for every roster
+// agent. The command layer (cmdStatus, notify) calls this so buildStatusJSON
+// stays pure.
+func statusSurfaces(cfg *roster.Config) map[string]string {
+ if cfg == nil {
+ return nil
</file context>
There was a problem hiding this comment.
FIX-landed at 8cbd684d3c75ef6d56b35b49d3935ad11a33047b.
workspace.EffectiveSurfaces([]NamedSurface) is the shared seam (no roster import). statusSurfaces, loadAgentSurfaces, and agentSurfacesFromRoster call it. Goals still lowercases keys and skips empty after that map. Overlay-first semantics unchanged. Generic-fixture coverage: TestEffectiveSurfacesGenericFixturesOverlayFirst.
statusSurfaces, loadAgentSurfaces, and agentSurfacesFromRoster all iterated roster agents into EffectiveSurface. workspace.EffectiveSurfaces takes name+roster-surface pairs with no roster import. Goals still lowercases keys and skips empty after the shared map. Overlay-first semantics unchanged.
There was a problem hiding this comment.
All reported issues were addressed across 5 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
agentSurfacesFromRoster ranged the EffectiveSurfaces map, so case-insensitive name collisions were map-iteration order. Fold cfg.Agents in roster order, look up raw[a.Name], then ToLower. Last roster duplicate wins stably. Generic-fixture test: Backend/grok then backend/codex → backend=codex.
Operator summary
Before
After a switch of the coding agent program in a pane (the harness — Grok, Codex, and so on), the live desk ran the overlay recorded in
active-harness.json, butflotilla statusand message delivery still treated the roster's declared primary as that program. Screen chrome from the overlay was fed to the wrong driver, so a clean idle overlay session could show aserrored.Change
Status, send, watch assessment, and the dash now resolve the overlay surface first (the active-harness file), and only fall back to the roster primary when that overlay is missing or unreadable.
After
A desk whose overlay is
codexwhile the roster still saysgrokis advertised and delivered ascodex. A genericnodepane falls back to the overlay driver, not the stale roster driver.Identifiers
c1984e96921880264181eefee30168cdbf9d6c48(agent/overlay-status-delivery)95b92359b3ff48d3901e391d0dd5a100952acc34(main)Summary
workspace.EffectiveSurfaceis the shared overlay-first seam.agentSurfacedelegates to it. Status JSON, CLI send (resolveSendLiveDriver), watch assessment, and dash board/control/goals pass that configured surface into display andResolveLiveDriver.A missing or torn overlay is fail-safe: routing stays on the roster primary so a live desk is never unroutable.
Tracks #1063.
Tests
Generic fixtures only (
backend/frontend/xo): rostergrok+ overlaycodex+ live commandnode→ driver and status surfacecodex. Coverage ininternal/workspace,cmd/flotillastatus/send/watch, and dash board tests.Summary by cubic
Makes
flotilla status, send, watch assessment, and dash routing follow the live overlay harness (active-harness.json) instead of the roster's declared primary, so a switched desk is advertised and delivered as the harness actually running. Fixes #1063.Bug Fixes
workspace.EffectiveSurface, shared as a collection throughworkspace.EffectiveSurfaces.nodepane now falls back to the overlay driver, not the stale roster driver.Written for commit de185b5. Summary will update on new commits.