You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
delegate_task: group delegated children into one Lineage workflow node with phases, across providers
#22852
Let delegate_task attach a child task to a named workflow group and a phase, so that an orchestrating thread's delegated work renders in Lineage as one workflow node with phases, the way a Claude Code Workflow run does today. It should work across providers and across turns and restarts.
Problem
I maintain an orchestrator skill (a "team lead") that drives one feature through roles: analyst, architect, planner, executor, reviewer, QA, acceptance, finalizer. The roles run in waves, and the orchestrator keeps durable state on disk between waves. One feature runs for days, survives session restarts, and mixes providers: some roles run on Claude, others on Codex or opencode.
Claude Code Workflow runs look great in Lineage: one node, declared phases, and agents that appear under their phase as they start. I would like the orchestrator's whole feature to look like that. Today it can't:
A native Claude Workflow lasts one run in one session. The orchestrator has to stop for user questions, integration barriers and next-day resumes, so every wave becomes a separate tree with a single phase.
Codex and opencode children cannot join a Claude Workflow at all.
delegate_task has no way to say "this child belongs to group X, phase Y", so delegated children render as a flat list.
What T3 already has (from reading the 0.1.1 desktop bundle, so names may differ in source)
The data model and the UI are nearly ready. What's missing is an app-side way in.
OrchestrationV2Subagent.workflow (OrchestrationV2SubagentWorkflow) has name, runHandles, phases[] and agents[]. Each agent carries index, label, state, phaseIndex, phaseTitle, attempt, childThreadId, model and metrics.
Only ClaudeAdapterV2 fills it, from task_started (task_type: "local_workflow", workflow_name) and task_progress.workflow_progress (workflow_phase / workflow_agent entries), merged by mergeClaudeWorkflowProgress.
The client renders phases only for rows of kind: "workflow", meaning subagents with workflow set. Every other subagent becomes a flat kind: "subagent" row.
packages/contracts/providerRuntime.ts already has provider-neutral linkage fields (workflowName, phases, phaseIndex, phaseTitle, agentIndex, parentAgentId, attempt). No adapter other than Claude populates the phase ones.
Proposal
1. Optional workflow parameter on delegate_task
{
"task": "...",
"title": "architect: API contract",
"workflow": {
"id": "feature-0042", // stable group key, scoped to the parent thread"name": "team-lead · billing-export", // node name; last non-empty value wins"phase": { "index": 1, "title": "Architect" },
"phases": [ // optional up-front declaration, merged by index
{ "index": 0, "title": "Analyst" },
{ "index": 1, "title": "Architect" },
{ "index": 2, "title": "Tasks" },
{ "index": 3, "title": "Acceptance" }
]
}
}
Semantics:
Grouping. Children of the same parent thread with the same workflow.id belong to one synthetic app_owned coordinator subagent whose workflow is populated. The first such call creates the node and later calls attach to it.
Agents. Each delegated task becomes one entry in workflow.agents. index is assigned in order, label comes from title, phaseIndex/phaseTitle from phase, and model, childThreadId, metrics and timestamps from the task. Status maps as it does today: pending → queued, running/waiting → running, completed → completed, and failed/cancelled/interrupted → failed.
Retries. A new delegate_task with the same group, the same phase and the same title (or an explicit workflow.agentKey) increments attempt on the existing entry instead of adding a row. This mirrors mergeClaudeWorkflowProgress.
Providers. Grouping ignores the provider, so Claude, Codex and opencode children can sit in one tree.
Lifetime. The group lives in orchestration state with its tasks. It survives session and server restarts, and any later turn of the same parent thread can extend it.
Node status. Derived from members by default: running while any member is active, otherwise completed, or failed if a member failed.
2. Optional companion tools
workflow_update { id, name?, phases?, status?: "waiting" | "completed" | "cancelled", progress? } renames the group, declares phases, shows "waiting for the user" between waves, or closes the group explicitly.
workflow_status { id } returns a snapshot of the whole group, like task_status for a tree.
Part 1 alone covers the core need. Part 2 avoids a group looking finished between waves.
3. Later, optional
Native adapters (Codex, opencode, Pi) could fill the same fields from provider telemetry when it exists. #17564 is one such case for Pi.
UI
No new components should be needed: an app_owned coordinator with workflow set already renders as a workflow node. Nice to have:
a provider or model badge per agent row, since one tree can now mix providers;
clicking an agent opens its childThreadId, as delegated tasks do now;
Stop on the node cancels the group's active tasks through the existing task_cancel path.
Edge cases
workflow.id is unique per parent thread, so the same id in two threads means two groups.
Tasks without workflow behave exactly as today.
A missing phase puts the agent in phase 0, titled from phases or "Phase 1", which matches the current client fallback.
A task cancelled before it starts stays in the tree as failed or cancelled; it does not disappear.
When two titles are declared for the same phase index, the last one wins.
isolation: "worktree" children group the same way.
Acceptance criteria
Three delegate_task calls with one workflow.id and two phases, one child on Claude and two on Codex, render as one Lineage node with two phases and three agents. Agents appear as they start, and their statuses update live.
After restarting T3, a new child with the same workflow.id in a later turn of the same thread joins the existing node.
Re-delegating an agent with the same key bumps attempt instead of duplicating the row.
Tasks without workflow look exactly as they do now.
Orchestrators built on T3's cross-provider delegation (team leads, review pipelines, multi-step migrations) could show one coherent tree per body of work, instead of flat children next to per-wave fragments. The model and the renderer already exist; the only missing piece is an app-side way to populate them.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Summary
Let
delegate_taskattach a child task to a named workflow group and a phase, so that an orchestrating thread's delegated work renders in Lineage as one workflow node with phases, the way a Claude Code Workflow run does today. It should work across providers and across turns and restarts.Problem
I maintain an orchestrator skill (a "team lead") that drives one feature through roles: analyst, architect, planner, executor, reviewer, QA, acceptance, finalizer. The roles run in waves, and the orchestrator keeps durable state on disk between waves. One feature runs for days, survives session restarts, and mixes providers: some roles run on Claude, others on Codex or opencode.
Claude Code Workflow runs look great in Lineage: one node, declared phases, and agents that appear under their phase as they start. I would like the orchestrator's whole feature to look like that. Today it can't:
delegate_taskhas no way to say "this child belongs to group X, phase Y", so delegated children render as a flat list.What T3 already has (from reading the 0.1.1 desktop bundle, so names may differ in source)
The data model and the UI are nearly ready. What's missing is an app-side way in.
OrchestrationV2Subagent.workflow(OrchestrationV2SubagentWorkflow) hasname,runHandles,phases[]andagents[]. Each agent carriesindex,label,state,phaseIndex,phaseTitle,attempt,childThreadId,modeland metrics.ClaudeAdapterV2fills it, fromtask_started(task_type: "local_workflow",workflow_name) andtask_progress.workflow_progress(workflow_phase/workflow_agententries), merged bymergeClaudeWorkflowProgress.kind: "workflow", meaning subagents withworkflowset. Every other subagent becomes a flatkind: "subagent"row.packages/contracts/providerRuntime.tsalready has provider-neutral linkage fields (workflowName,phases,phaseIndex,phaseTitle,agentIndex,parentAgentId,attempt). No adapter other than Claude populates the phase ones.Proposal
1. Optional
workflowparameter ondelegate_task{ "task": "...", "title": "architect: API contract", "workflow": { "id": "feature-0042", // stable group key, scoped to the parent thread "name": "team-lead · billing-export", // node name; last non-empty value wins "phase": { "index": 1, "title": "Architect" }, "phases": [ // optional up-front declaration, merged by index { "index": 0, "title": "Analyst" }, { "index": 1, "title": "Architect" }, { "index": 2, "title": "Tasks" }, { "index": 3, "title": "Acceptance" } ] } }Semantics:
workflow.idbelong to one syntheticapp_ownedcoordinator subagent whoseworkflowis populated. The first such call creates the node and later calls attach to it.workflow.agents.indexis assigned in order,labelcomes fromtitle,phaseIndex/phaseTitlefromphase, andmodel,childThreadId, metrics and timestamps from the task. Status maps as it does today:pending→queued,running/waiting→running,completed→completed, andfailed/cancelled/interrupted→failed.delegate_taskwith the same group, the same phase and the sametitle(or an explicitworkflow.agentKey) incrementsattempton the existing entry instead of adding a row. This mirrorsmergeClaudeWorkflowProgress.runningwhile any member is active, otherwisecompleted, orfailedif a member failed.2. Optional companion tools
workflow_update { id, name?, phases?, status?: "waiting" | "completed" | "cancelled", progress? }renames the group, declares phases, shows "waiting for the user" between waves, or closes the group explicitly.workflow_status { id }returns a snapshot of the whole group, liketask_statusfor a tree.Part 1 alone covers the core need. Part 2 avoids a group looking finished between waves.
3. Later, optional
Native adapters (Codex, opencode, Pi) could fill the same fields from provider telemetry when it exists. #17564 is one such case for Pi.
UI
No new components should be needed: an
app_ownedcoordinator withworkflowset already renders as a workflow node. Nice to have:childThreadId, as delegated tasks do now;task_cancelpath.Edge cases
workflow.idis unique per parent thread, so the same id in two threads means two groups.workflowbehave exactly as today.phaseputs the agent in phase 0, titled fromphasesor "Phase 1", which matches the current client fallback.isolation: "worktree"children group the same way.Acceptance criteria
delegate_taskcalls with oneworkflow.idand two phases, one child on Claude and two on Codex, render as one Lineage node with two phases and three agents. Agents appear as they start, and their statuses update live.workflow.idin a later turn of the same thread joins the existing node.attemptinstead of duplicating the row.workflowlook exactly as they do now.Related
Why
Orchestrators built on T3's cross-provider delegation (team leads, review pipelines, multi-step migrations) could show one coherent tree per body of work, instead of flat children next to per-wave fragments. The model and the renderer already exist; the only missing piece is an app-side way to populate them.
All reactions