feat(gateway): developers author the full rich plan, at parity with PMs

A dev's i_will_work_on stored a flat {text} plan and routed steps to
progress, so the dev leaf's Plan tab rendered empty (no approach,
sub_tasks, technical_considerations, risks) — zero audit/tracing on the
task that does the actual work.

i_will_work_on now captures the same rich plan a PM authors via
i_will_plan: plan(>=150) doubles as approach, steps become sub_tasks,
plus technical_considerations + risks (open_questions optional). A new
_dev_plan_gate enforces them on FRESH claims only (re-entry/recovery
short-circuit before it). set_plan gains a no-downgrade guard so a
flaked-then-recovered dev can't clobber its rich plan back to flat (the
actual mechanism behind the empty leaf).
This commit is contained in:
Renn F
2026-05-24 06:59:25 +02:00
parent a8056892b6
commit d49d1cdb37
12 changed files with 413 additions and 99 deletions
@@ -33,6 +33,21 @@ _STEPS = [
),
}
]
# Full parity: a fresh dev claim authors the same rich plan a PM does.
# These satisfy _dev_plan_gate (plan/approach >= 150 chars,
# technical_considerations, risks).
_GOOD_PLAN = (
"Append the timestamp HTML comment to the very bottom of README.md without "
"touching any other line, then commit it on the task branch and open a PR. "
"Verify the diff is a single-line addition before submitting for QA."
)
_GOOD_TC = ["Use a trailing newline so the comment sits on its own line."]
_GOOD_RISKS = [
{
"risk": "An accidental reformat of README.md balloons the diff.",
"mitigation": "Append only; assert the diff touches one line pre-commit.",
}
]
def _make_deps(**overrides: Any) -> ChoreographerDeps:
@@ -189,7 +204,14 @@ async def test_i_will_work_on_allows_when_earlier_sibling_terminal() -> None:
deps = _make_deps(task=task_svc)
c = Choreographer(deps)
env = await c.i_will_work_on(agent_id, target_id, steps=_STEPS)
env = await c.i_will_work_on(
agent_id,
target_id,
plan=_GOOD_PLAN,
steps=_STEPS,
technical_considerations=_GOOD_TC,
risks=_GOOD_RISKS,
)
assert env.error is None
task_svc.claim.assert_awaited_once_with(target_id, agent_id)
@@ -219,7 +241,14 @@ async def test_root_task_no_sequence_check() -> None:
deps = _make_deps(task=task_svc)
c = Choreographer(deps)
env = await c.i_will_work_on(agent_id, target_id, steps=_STEPS)
env = await c.i_will_work_on(
agent_id,
target_id,
plan=_GOOD_PLAN,
steps=_STEPS,
technical_considerations=_GOOD_TC,
risks=_GOOD_RISKS,
)
assert env.error is None
# Sequence check should not have queried siblings on a root task
task_svc.get_subtasks.assert_not_awaited()