Before submitting
Area
packages/client-runtime, apps/web
Steps to reproduce
- Run a
Workflow with at least two phases, e.g. phase('Find') with 3 agents, then phase('Verify') with 3 agents.
- Wait for every phase-1 member to settle, before the first phase-2 member spawns.
- Read the three aggregate liveness surfaces during that gap: the composer background-work banner, the Agents panel footer, and the agents right-panel badge.
Unit repro without a provider: call deriveAgentPanelModel with a roster of one kind: "workflow" coordinator at status: "running" plus three members carrying parentAgentId, all at a settled status. It returns liveCount: 0.
Expected behavior
A workflow that is between phases is still running. The coordinator is executing the script body — collecting the previous phase's results, filtering, deciding what to fan out next — and will spawn more agents. The aggregate surfaces should keep saying so, ideally with the phase position (Workflow · phase 2 of 4) rather than an agent count that happens to be zero at that instant.
Actual behavior
Every aggregate count drops to zero in the gap, so the run reads as finished:
- The composer banner degrades from "3 agents working" to the generic "Background work" (
apps/web/src/components/ChatView.tsx:4812).
- The Agents panel footer's "● N working" chip disappears entirely — it is gated on
runningCount + waitingCount > 0 (apps/web/src/components/AgentsPanel.tsx:571) — leaving only "3 settled".
- The agents right-panel badge count goes to 0 (
apps/web/src/components/ChatView.tsx:6874).
Then phase 2 spawns and everything comes back. On a workflow with several phases the UI cycles through looking-finished once per phase boundary.
The per-phase rows themselves are fine: AgentsPanel.tsx:360 renders {phase.activeCount} active · {phase.settledCount} done and a done checkmark per phase, and AgentPanelWorkflowGroup.phases carries the structure. The phase model is there — only the rollup that everything else reads is wrong.
Root cause: packages/client-runtime/src/state/subagentRuntime.ts:833
if (agent.kind === "workflow" && (members.get(agent.id) ?? []).length > 0) continue;
The coordinator is skipped from every status bucket whenever it has at least one member. Between phases it has members — all of them settled — so it is skipped, and liveCount = runningCount + waitingCount is 0 even though the coordinator's own status is running.
This is a regression from the fix for #5807. That issue was real: the coordinator was being counted as an extra working agent. But note what the superseded PR #5808 said about its own version of the fix:
A memberless coordinator still counts, so a workflow that has not spawned its first member — or is between phases — does not read as zero agents.
The PR that actually merged, #6672 (2026-08-15), hoisted a single continue to the top of the tally loop as a "strict superset" of #5808. It is a superset for the overcount, but it drops the between-phases case #5808 was guarding: the guard keys on members.length > 0, and between phases the member list is non-empty, just entirely settled.
Side effect of the same continue: the bucket-sum invariant idleCount + runningCount + waitingCount + settledCount === roster.length that #5808 preserved is now short by the number of workflow coordinators that have members.
Proposed fix
Sketch.
Distinguish "coordinator is a container for work happening in its members" from "coordinator is the only thing still running". Skip the coordinator only when at least one member is itself live:
const workflowMembers = members.get(agent.id) ?? [];
const hasLiveMember = workflowMembers.some(
(m) => m.status === "running" || m.status === "pending" || m.status === "waiting",
);
if (agent.kind === "workflow" && hasLiveMember) continue;
That keeps #5807 fixed — while members are working the coordinator is not double-counted — and makes a between-phases workflow count as exactly one live unit instead of zero. Token aggregation should keep its existing members.length === 0 rule, since that guards double-counting rather than liveness.
Better still, expose the phase position the panel already computes so the banner can read Workflow · phase 2 of 4 instead of an agent count. A count is the wrong primitive for a workflow: it is legitimately zero at every phase boundary.
Impact
Misleading UI — the user reads a run as finished and walks away, or interrupts it. The banner is also the only visible Stop affordance for background work (per #5807), so its accuracy is what people act on.
Related
Version or commit
main @ 1f8ed54
Before submitting
Area
packages/client-runtime, apps/web
Steps to reproduce
Workflowwith at least two phases, e.g.phase('Find')with 3 agents, thenphase('Verify')with 3 agents.Unit repro without a provider: call
deriveAgentPanelModelwith a roster of onekind: "workflow"coordinator atstatus: "running"plus three members carryingparentAgentId, all at a settled status. It returnsliveCount: 0.Expected behavior
A workflow that is between phases is still running. The coordinator is executing the script body — collecting the previous phase's results, filtering, deciding what to fan out next — and will spawn more agents. The aggregate surfaces should keep saying so, ideally with the phase position (
Workflow · phase 2 of 4) rather than an agent count that happens to be zero at that instant.Actual behavior
Every aggregate count drops to zero in the gap, so the run reads as finished:
apps/web/src/components/ChatView.tsx:4812).runningCount + waitingCount > 0(apps/web/src/components/AgentsPanel.tsx:571) — leaving only "3 settled".apps/web/src/components/ChatView.tsx:6874).Then phase 2 spawns and everything comes back. On a workflow with several phases the UI cycles through looking-finished once per phase boundary.
The per-phase rows themselves are fine:
AgentsPanel.tsx:360renders{phase.activeCount} active · {phase.settledCount} doneand adonecheckmark per phase, andAgentPanelWorkflowGroup.phasescarries the structure. The phase model is there — only the rollup that everything else reads is wrong.Root cause:
packages/client-runtime/src/state/subagentRuntime.ts:833The coordinator is skipped from every status bucket whenever it has at least one member. Between phases it has members — all of them settled — so it is skipped, and
liveCount = runningCount + waitingCountis 0 even though the coordinator's ownstatusisrunning.This is a regression from the fix for #5807. That issue was real: the coordinator was being counted as an extra working agent. But note what the superseded PR #5808 said about its own version of the fix:
The PR that actually merged, #6672 (2026-08-15), hoisted a single
continueto the top of the tally loop as a "strict superset" of #5808. It is a superset for the overcount, but it drops the between-phases case #5808 was guarding: the guard keys onmembers.length > 0, and between phases the member list is non-empty, just entirely settled.Side effect of the same
continue: the bucket-sum invariantidleCount + runningCount + waitingCount + settledCount === roster.lengththat #5808 preserved is now short by the number of workflow coordinators that have members.Proposed fix
Sketch.
Distinguish "coordinator is a container for work happening in its members" from "coordinator is the only thing still running". Skip the coordinator only when at least one member is itself live:
That keeps #5807 fixed — while members are working the coordinator is not double-counted — and makes a between-phases workflow count as exactly one live unit instead of zero. Token aggregation should keep its existing
members.length === 0rule, since that guards double-counting rather than liveness.Better still, expose the phase position the panel already computes so the banner can read
Workflow · phase 2 of 4instead of an agent count. A count is the wrong primitive for a workflow: it is legitimately zero at every phase boundary.Impact
Misleading UI — the user reads a run as finished and walks away, or interrupts it. The banner is also the only visible Stop affordance for background work (per #5807), so its accuracy is what people act on.
Related
Version or commit
main @ 1f8ed54