Skip to content

[Bug]: Orchestrator V2 shows a Claude workflow as a plain subagent; its phases and agents are dropped #15448

Description

@developerAkX

Before submitting

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

Area

apps/server, packages/contracts or packages/shared, apps/web

Steps to reproduce

  1. On a V2 nightly, in a Claude Code thread, ask the agent to run a Workflow with at least two phases. For example: phase('Warmup') with 2 agents, then phase('Sleep') with 44 agents that each run sleep 300.
  2. While it runs, open the thread's Lineage list, then open the workflow's row.
  3. Once it finishes, look at the row again under Previous agents.

Expected behavior

Lineage shows the run as a workflow, not as a subagent. #14871 lists "Subagents and background work you can see" as a V2 feature, and V1 showed this through 0.0.45 (#5219). At minimum:

  • the workflow's name and state;
  • its phases, and which phase is running;
  • each member agent with its label, phase, state, model and last tool;
  • a way to open a workflow and see that roster, the way a subagent row opens its transcript.

Actual behavior

V2 treats a workflow exactly like an Agent subagent:

  • Lineage: one subagent row, titled with the workflow's description. No phases, no member agents, no model.
  • Opening the row: the child thread contains one item, the whole workflow script (export const meta = {...}) shown as a message "Sent by another agent", and nothing else, for the entire run. A real subagent's child thread fills with its tool calls.
  • Status: the only sign of progress is a one-line status taken from task_progress.description (for example Rollout: rollout, areas, tickets, indexes · opus high). At the end the result is Dynamic workflow "…" completed.

From one of my threads (orchestration_v2_projection_subagents, read from a copy of statev2.sqlite; titles anonymized):

Lineage row task_type items in child thread
Workflow: resume docs write run (Features, Areas, Review…) local_workflow 2
Workflow: resume docs write run (second attempt) local_workflow 1
Workflow: apply decision across docs (Apply, Rollout) local_workflow 1
Workflow: finish rollout docs (Rollout) local_workflow 1
Rename a term across docs local_agent 198
Update phase-1 requirements page local_agent 242
Update phase-2 requirements page local_agent 327
Dry run of the docs local_agent 139
Fix docs consistency gaps local_agent 202

All nine rows look the same in Lineage. The four workflows ran 5 to 44 agents each, and none of those agents is visible anywhere in T3 Code.

Cause

Claude Code sends everything needed. Each workflow's task_started carries task_type: "local_workflow" and workflow_name. Each task_progress carries a workflow_progress array, one entry per phase and per agent:

{"type":"system","subtype":"task_progress","task_id":"wmca5k5dl","description":"Apply: features: … · opus high",
 "workflow_progress":[
  {"type":"workflow_phase","index":1,"title":"Apply"},
  {"type":"workflow_phase","index":2,"title":"Rollout"},
  {"type":"workflow_agent","index":1,"label":"feature-switches.md · opus high","phaseIndex":1,"phaseTitle":"Apply",
   "agentId":"a4073ffb714011049","model":"claude-opus-5-5[1m]","state":"progress","attempt":1,
   "lastToolName":"Bash","tokens":100021,"toolCalls":11,"promptPreview":"…"}
 ]}

A workflow's member agents never send messages on the parent's stream; their transcripts are written to ~/.claude/projects/<project>/<session>/subagents/workflows/wf_<id>/. So workflow_progress is the only live view T3 gets, and V2 drops it:

  • task_started: the handler ClaudeAdapterV2.ts#L5847-L5895 only separates local_bash. Every other task_type, local_workflow included, becomes an ordinary subagent node.
  • task_progress: the handler ClaudeAdapterV2.ts#L5900-L5918 keeps only description. workflow_progress isn't read anywhere in apps/server.
  • Contract: OrchestrationV2Subagent has no field for a task kind, workflow name, phases or member agents.
  • Client: projectedSubagentsToRuntime sets workflowName: null, phases: [], runHandles: null, phaseIndex: null and parentAgentId: null for every row. Those RuntimeSubagent fields are left over from V1 and never filled.

The V1 path (#5219: ClaudeAdapter, ProviderRuntimeIngestion, workflow folding in subagentRuntime) was removed in #2829 with no V2 replacement. The "view workflow script" endpoint (orchestration-v2/workflowScriptQuery.ts) also survives, but no client gets a scriptPath to call it with.

Possible fix

  1. Add an optional, size-capped workflow field to OrchestrationV2Subagent: name, phases, and per-agent label, phase, state, model, last tool and counts. Leave out promptPreview to keep WebSocket payloads small.
  2. In ClaudeAdapterV2, mark local_workflow tasks as workflows and update that field from task_progress.workflow_progress.
  3. In Lineage and the child view, show a workflow as a workflow: a kind marker on the row, the running phase, and the member-agent roster instead of the script shown as a message.

Impact

Major degradation or frequent failure

People who use workflows can't tell from T3 Code whether a long run (often 30+ agents over hours) is progressing, stuck or dead. I ended up writing a terminal script that reads the workflow's journal to see what it was doing.

Version or commit

T3 Code (Nightly) desktop, V2, built from main at 65731f9

Environment

macOS 27, Claude Code 2.1.289, Claude Opus 5.5 driving Workflow runs with Opus and Sonnet 5.5 member agents.

Related

Workaround

Read ~/.claude/projects/<project>/<session>/subagents/workflows/wf_<id>/journal.jsonl and the agent-*.jsonl transcripts next to it from a terminal.


Investigated with Claude Code (Claude Opus 5.5) in T3 Code.

Activity

  1. juliusmarminge commented on Oct 4, 2026

    @juliusmarminge
    Member

    Note

    Grok responding on behalf of Julius.

    Triage

    Thanks @developerAkX for the thorough write-up and the projection table. I confirmed it on current main (737993303d). It's related to #15429 (subagent token usage) and #15214 (subagent effort), since they share the same gap in OrchestrationV2Subagent, but this one is about workflow structure: phases and member agents.

    What I found

    A Claude local_workflow is ingested as an ordinary subagent:

    • isClaudeNonSubagentTask only treats local_bash as non-subagent work, so task_started for local_workflow creates a kind: "subagent" node titled with the description.
    • A model is only remembered for an Agent tool call. A Workflow launch never goes through rememberClaudeSubagentLaunch, so the row has no model.
    • The child thread's opening message is task_started.prompt, written as an agent-authored message. For a workflow that's the script, and nothing else is appended because member agents don't emit on the parent stream.
    • task_progress keeps description and ignores workflow_progress, which isn't read anywhere under apps/ or packages/. task_notification keeps summary, which is the Dynamic workflow "…" completed result.
    • OrchestrationV2Subagent has no task kind, workflow name, phases or members, and projectedSubagentsToRuntime always fills those RuntimeSubagent fields with null / [].
    • orchestration.getWorkflowScript still exists and the client has a query atom for it, but runHandles.scriptPath is never filled, so nothing can reach it.

    The V1 workflow fold went away with Orchestrator V2 (#2829), and #8720 was closed expecting V2 to cover between-phase visibility. I didn't replay a live Claude workflow, but given the payloads in your report, this code path produces exactly the Lineage row, script-only child thread and one-line status you described.

    Likely fix area

    How much of the V1 roster to bring back is a product call. The live data is task_progress.workflow_progress; member transcripts only exist on disk. Some options:

    • A size-capped optional workflow field on OrchestrationV2Subagent (name, phases, per-agent label/phase/state/model/last tool/counts, without promptPreview), filled by ClaudeAdapterV2 from local_workflow tasks and workflow_progress, as you suggested.
    • Lineage and the child view rendering a workflow as a workflow, with the roster instead of the script message.
    • Filling runHandles.scriptPath so the existing script endpoint becomes reachable. That's complementary, not a replacement for the roster.

    This could land independently of the usage (#15429) and effort (#15214) work on the same record.

    A maintainer will decide on the fix direction.

  2. added
    bugSomething is broken or behaving incorrectly.
    via-triageFiled through npx t3 triage
    on Oct 4, 2026
  3. developerAkX commented on Oct 4, 2026

    @developerAkX
    Author

    Thanks for confirming it, @juliusmarminge.

    Reproduced on a fresh V2 thread. I ran a test workflow on the nightly, built from main at 65731f986b: 2 Warmup agents, 44 Sleep agents (Sonnet 5.5, low effort, each running sleep 300) and 1 Summary agent. While 12 sleepers were running:

    • Lineage showed one subagent row: "Check how T3 Co… Running", with a one-line status (Warmup: warmup 2). It showed no phases and no member agents.
    • Opening the row showed the whole workflow script (export const meta = {...}) as a message "Sent by another agent", followed only by "Thinking".
    • Claude sent task_progress frames with a full workflow_progress roster during that time; for example, Sleep: sleeper 08 at 03:29:37Z. All of it was dropped.

    I'll add the screenshots in a follow-up comment.

    Direction: here's my vote, to make the product call easier.

    1. Server and contract first. Add an optional, size-capped workflow field to OrchestrationV2Subagent, filled by ClaudeAdapterV2 from local_workflow tasks and task_progress.workflow_progress. It would carry the name, phases, and per-agent label, phase, state, model, last tool and counts, without promptPreview. Also fill runHandles.scriptPath, so the existing script endpoint can be reached.
    2. Then Lineage. Mark a workflow row as a workflow, show the running phase on it, and when it's opened show the member list instead of the script message.

    Step 1 changes nothing visible on its own, so it could be reviewed and land separately from the UI decision.

    One more small finding: V2 mistakes a workflow launch for a resumed subagent. A Workflow tool call is an ordinary tool call, so it lands in context.toolCalls, unlike an Agent launch. When the workflow's task_started arrives, recoverResumedClaudeSubagent reads "a task_started under a tracked tool call" as a SendMessage resume. It then runs subagentLaunchToolUseId, which reads the CLI's session storage for a launch record that doesn't exist. The provider log shows a subagent.lookup on every workflow launch. It returns null, so nothing breaks, but it's wasted I/O on each launch, and it shows the adapter has no notion of a workflow task. Skipping the lookup for task_type: "local_workflow" would fix it.

    Overlap with #12598. I've since found #12598 ("show Claude workflow phases and members in Lineage"), which already covers most of this on V2: a workflow field on the subagent record, workflow_progress parsing in ClaudeAdapterV2, runHandles.scriptPath, and a Lineage workflow row. It's currently marked conflicting with main, and it still calls recoverResumedClaudeSubagent for workflow launches, so the finding above applies there too.

    Questions:

    1. Is anyone already working on this, beyond feat(web): show Claude workflow phases and members in Lineage #12598?
    2. Can I take it on? If feat(web): show Claude workflow phases and members in Lineage #12598 is the intended path, I'm happy to help it land instead of starting over. I could record the live-workflow run you asked for there (phase expansion, member navigation and the running timer, against the base version), or send the local_workflow lookup fix as a small separate PR. If you'd rather have a fresh PR for step 1, I can do that too.
  4. bman654 commented on Oct 5, 2026

    @bman654

    Can confirm. This is how a workflow shows in the main thread. The lineage entry is the currently running agent task(s) within the workflow:

    Image

    clicking on it:

    Image

    And the Lineage on the 2nd screen shows the previous agent task(s) from the workflow. I can click through each to go backwards, but see nothing useful on any of them.

    The v1 workflow overview that listed all the tasks and their status would be an improvement.

  5. apex-woot commented on Oct 9, 2026

    @apex-woot

    Still happens for me on T3 Code (Nightly) 0.0.46-nightly.20261009.2861, Claude Code 2.1.294, macOS 27.0.1.

    I ran two Workflow runs one after another in the same Claude thread. The first had 3 agents doing three review rounds in sequence. The second was a pipeline() over 3 items, each going through 5 stages (explore, sceptic, contracts, implement, review), with the items running in parallel, so 15 agents.

    In the thread I only saw two subagent rows. Nothing showed the 18 agents, or even that the second run had 15 of them. I only got the counts from the completion notifications (agent_count 3 and 15) and from each run's journal.jsonl.

    I think the per-agent roster matters even more for pipeline() runs. Items are in different stages at the same time, so one "current phase" status line can't really describe what's going on. Showing each agent's phase and state, like proposed above, would cover it.

  6. GoatXYZ commented on Oct 10, 2026

    @GoatXYZ

    Astral Goat posting on behalf of GoatXYZ.

    Another production workflow reproduction on the October 10 nightly

    I hit this with a real implementation workflow today. The UI showed one agent for the workflow, but the retained Claude records show 17 executed member agents across three phases: eight builders, eight reviewers, and one integration agent. All 17 reached a terminal result. The work ran; its phase/member structure and per-member progress were missing from T3.

    There is a version distinction: the run was on 0.0.46-nightly.20261010.2908, based on the updater/startup history and versioned renderer telemetry. I updated to 0.0.46-nightly.20261010.2935 afterward. The current install is confirmed from its bundle metadata, and its adapter/runtime still have the same workflow metadata gap. I have not rerun the workflow on 2935.

    Environment: macOS 27.0.1, Claude Code 2.1.296, Orchestrator V2, Claude Opus 5.5 parent. The workflow ran from 16:06:18 to 16:40:49 UTC on October 10, about 34 minutes 31 seconds.

    What the retained records show

    Evidence Result
    Native workflow progress 476 task_progress messages for the workflow task; 101 included workflow_progress
    Native roster 3 phase rows and 17 distinct member agent IDs across the run; the final roster marks all 17 done
    Workflow journal 17 spawns and 17 results, with matching member transcripts
    T3 parent projection 1 workflow wrapper stored as kind: subagent, with model: null and no phase/member structure
    T3 workflow child 1 timeline item: the 34,984-character workflow JavaScript, represented as an agent-authored user_message
    Member conversations in T3 0 nested subagent records beneath that wrapper
    Same-thread ordinary agents Three earlier native Agent children have 71, 81, and 51 timeline items, including tool calls and assistant messages

    That last comparison matters: this conversation successfully ingested ordinary native subagents, while the workflow's members remained absent. “One agent” refers to this workflow's single wrapper, not to the whole conversation; the parent has four native subagent rows in total. All 17 final roster entries identify claude-haiku-5-5, independently corroborated by their member transcripts.

    The final workflow projection contains prompt, title, model, status, progress, result, identity fields, and timestamps. It has no workflow name, phases, member roster, member usage, or member model selection. A current read through T3's own thread API returns the same script-only child with itemCount: 1 and runCount: 0, so reopening the completed history has not recovered the roster.

    Two useful details for regression coverage

    The roster is present from launch, but member identity arrives incrementally. The first structured progress frame contains eight member rows and only one assigned agentId; the next frame contains all eight IDs. A parser needs to retain indexed entries while IDs are pending and enrich them when the IDs arrive, without duplicating the members.

    Build and Review also overlap: each review starts when its paired builder finishes. The first review began at 16:08:28Z while other builders continued until 16:14:09Z; integration started after all reviews ended. This is a productive pipeline case where one current-phase label loses useful information.

    The final structured frame contains all 17 member IDs and terminal states. The raw provider log marks none of the 101 structured frames as truncated. This gives a bounded, completed-run case for checking initial identity, phase/member updates, terminal state, and historical persistence.

    Where the metadata is lost in this build

    The October 10 execution tag resolves to 3e94afa7c26734f525a7d8a9cdb47896e8618cf8. In that source:

    The currently installed 2935 bundle identifies its commit as 98beed1a226c. Its bundled adapter and runtime also lack that workflow parsing/mapping. This is source and bundle verification, not a new live reproduction on 2935.

    One additional discrepancy to keep separate

    Claude delivered a successful native task_notification with a nonempty completion summary. The wrapper projection retains that result, but its child still has only the launch script. The adapter source appears to have a path that should append a child assistant summary. The retained provider log contains native protocol events, not the canonical event stream, so these records do not establish whether that child-summary event was emitted, rejected, or otherwise lost. I would keep this as a separate trace question rather than attribute it to the roster parser without evidence.

    This run completed and woke the parent for follow-up work. It supports #15448's observability/persistence problem, not a reproduction of #16697's silent mid-run closure.

    What would make this case pass

    The same input should retain a workflow identity with three phases and 17 stable members, preserve the member records as IDs arrive, and expose their states and models during the run and in completed history. Opening the workflow should lead to its roster, with the source available as a separate script view. The three ordinary Agent children should continue to behave as they do now.

    The useful regression input here is the captured roster sequence, including the initial missing IDs and final all-done roster. I have prepared an allowlisted, pseudonymized 103-frame fixture locally: launch, all 101 roster updates, and completion. It omits the 375 progress-only messages, private prompts, source code, paths, and identifying descriptions. Timestamps are relative and some required SDK fields are omitted, so it needs a small harness before adapter replay. It has not been replayed against either proposed fix, and no raw conversation or provider log is included in this comment.

    The one-row UI behavior is my observation; the counts above come from read-only log, journal, transcript, database, and T3 API checks. No new visual replay was performed during this investigation.

  7. juliusmarminge commented on Oct 11, 2026

    @juliusmarminge
    Member

    Note

    Grok responding on behalf of Julius.

    Fixed by #12598, which just landed on main. Lineage now expands Claude workflows into phase sections with member rows, status, counts and elapsed time, and each member opens its own chat with its prompt and final answer. It'll ship in the next nightly. If anything still looks off after updating, please open a new issue with the details.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething is broken or behaving incorrectly.via-triageFiled through npx t3 triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions