Skip to content

[Bug]: Workflow between phases reports zero working agents — banner, panel footer and badge all read as finished #8720

Description

@ahmadhajji

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.

Area

packages/client-runtime, apps/web

Steps to reproduce

  1. Run a Workflow with at least two phases, e.g. phase('Find') with 3 agents, then phase('Verify') with 3 agents.
  2. Wait for every phase-1 member to settle, before the first phase-2 member spawns.
  3. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions