[feature] wire cell-task wave chain + by-osmosis edge (sequencing S3)

Kind 2 (cell-task wave chain): a new cell-task under root-subtask UT_n
depends on every cell-task under every root-subtask in UT_n.dependency_ids
(the kind-1 wave-chain edges), so its branch carries the previous wave's
merged cell work. Re-derived from the root-subtask's deps, not the cell-task's
own dependency_ids (which also carry UX/product-fanout edges the by-osmosis
edge must not pick up). A root may fan to several cell-tasks (different cells),
so the previous wave's cell-task is a SET.

Kind 4 (by-osmosis): the first dev task (sequence 0) under a cell-task depends
on each predecessor cell-task's tail (max-sequence) dev task, so the new wave's
first branch carries the previous wave's fully-merged tail. Subsequent dev
tasks inherit the tail via kind 3 or the merged base.

Both wired from _create_subtask_from_inputs, dispatched on parent.team
(MAIN_PM -> kind 2; cell team -> kind 4). Pure helpers
(cell_task_wave_chain_depends_on, by_osmosis_tail_dev_tasks) unit-tested in
test_sequencing.py; TaskService methods integration-tested. Idempotent +
best-effort throughout (add_dependency dedupes; missing predecessors are
no-ops). Also fixes a latent mypy-tests gap (estimated_complexity required on
direct TaskCreateRequest calls in the S2 tests).
This commit is contained in:
Renn F
2026-06-28 04:22:27 +02:00
parent 12621a3608
commit 9927d248ea
5 changed files with 376 additions and 1 deletions
@@ -4884,6 +4884,25 @@ class Choreographer:
# dev_task_collision_edges orders by (priority, sequence) so re-runs
# only add edges (never flip an existing pair's order into a cycle).
await self.task.wire_sibling_collision_dag(parent_task_id)
# Multi-level sequencing — edge kinds 2 + 4: the cell-task wave chain
# and the by-osmosis dev-task edge. Dispatch on the parent's team: a
# MAIN_PM parent is a root-subtask, so the new task is a CELL-TASK →
# wire kind 2 (it depends on the previous wave's cell-tasks); a cell
# team parent is a cell-task, so the new task is a DEV TASK → wire kind
# 4 (its first dev task carries the previous wave's merged tail). kind 3
# (wire_sibling_collision_dag, above) runs for both. Idempotent +
# best-effort: missing predecessors / cell-tasks contribute no edge.
from roboco.foundation.identity import Team
parent_team = getattr(parent, "team", None)
if parent_team == Team.MAIN_PM.value:
await self.task.wire_cell_task_wave_chain(new_task.id)
elif parent_team in (
Team.BACKEND.value,
Team.FRONTEND.value,
Team.UX_UI.value,
):
await self.task.wire_by_osmosis_edge(new_task.id)
# Thread the parent's existing session links onto the
# new subtask so the assigned agent (dev/qa/doc) lands in the
# group chat the PM has already been talking in. Pre-gateway