Bug B from the 2026-05-09 smoke run. main-pm called
delegate(assigned_to='be-pm', task_type='code'). The chain validator
let it through (be-pm IS in main-pm's allowed targets), the schema
let it through (task_type='code' is a valid enum value), and the
subtask got created mis-typed. Task 0 made it cosmetically work
because PMs can now plan code-typed parents — but the model is
wrong: a Cell PM owns the PLANNING of the slice; the code execution
is what they delegate to devs.
New gate in _delegate_static_guards: when assignee is a Cell PM
(be-pm/fe-pm/ux-pm), task_type MUST be 'planning'. Returns
invalid_state with a remediate hint pointing at the right type.
Devs are unrestricted (could be code OR documentation, depending
on the slice).
Tests: 3139 passing (+ 2 regression tests pinning the rule), 100%
coverage, ruff clean.
Two coupled fixes from the 2026-05-08 smoke-test trace:
1. pm_cannot_execute_code is now scoped to i_will_work_on (the
EXECUTION verb) only. Pre-fix it also fired on i_will_plan, which
deadlocked any code-typed parent: cell_pm couldn't plan, so couldn't
transition parent to in_progress, so couldn't delegate. PMs PLAN
code-typed parents and DELEGATE the work — that's exactly the verb
we were blocking.
2. delegate.task_type is now REQUIRED at both the HTTP boundary
(DelegateRequest) and the choreographer dataclass (DelegateInputs).
The pre-fix default of 'code' silently changed semantics whenever a
caller forgot the field — main-pm's call in the smoke trace omitted
it, schema defaulted to 'code', and the cell PM downstream was
wedged. Also drops the choreographer's task.task_type fallback
(the DB column is NOT NULL anyway).
Plus middleware coverage tests for the parallel ServiceError →
4xx handler hierarchy added in the prior session, restoring 100%
coverage across the touched files.
Tests: 3101 passing, 100% coverage, ruff clean.
Smoke 2026-05-04 captured the cycle the prior 63d0adf fix didn't close:
- Spawn 1: i_will_plan succeeds, task pending → claimed → in_progress.
- Agent goes idle (LLM thinking, container exits, respawned).
- Spawn 2: i_will_plan called again. _i_will_plan_preflight rejects
'task in in_progress, expected pending'. Agent has no recovery path.
- Heartbeat eventually goes stale, reaper drops claim back to pending,
spawn 3 fires, loop repeats indefinitely.
Fix: when a respawned PM/dev re-enters the verb on a task they already
own in claimed/in_progress, return OK with current state and refresh
the heartbeat instead of rejecting. The verb is now genuinely
idempotent for the caller, which matches what 'I will plan' should
mean — record intent + advance state, regardless of how many times
the agent says it. Different-caller contention still rejects.
Refactored i_will_work_on's pending branch into _i_will_work_on_pending
helper to satisfy PLR0911 after the new branch raised return count.
Pin the smoke-2026-05-03 bugs that motivated 63d0adf:
- pre-assigned-and-pending: claim must still fire (was skipped when
task.assigned_to already matched pm_agent_id)
- start-returns-None: must surface invalid_state (was silently returning
Envelope.ok with fabricated status='in_progress')
Service signatures are (task_id, agent_id, ...) but choreographer was
calling (agent_id, task_id). Production claim path silently returned
None; unit tests pinned the buggy order so the bug was invisible. Swap
all 7 call sites and update test assertions. Add a regression pin
that locks in the correct order.
PARENT_NOT_CLAIMED: Choreographer.delegate now enforces that the parent
task is in_progress AND assigned to the calling PM before allowing
subtask creation. Pre-gateway this was implicit (orchestrator only
spawned PMs after they claimed their parent); the gateway exposes
delegate as a first-class verb so the gate must be explicit.
SUBTASK_CAP: hard-blocks delegation when the parent already has 12
subtasks. Pre-gateway never had this cap because PMs naturally never
created more than a handful per spawn cycle; with delegate as a verb
agents can loop, so a cap is needed.
The _delegate_guard helper was split into _delegate_role_guards,
_delegate_static_guards, and _delegate_lifecycle_guards to keep each
piece below the PLR0911 return-count threshold and make the layered
gating explicit.
Pre-gateway reference: implicit in roboco/runtime/orchestrator.py
spawn flow; restored here as explicit server-side enforcement.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Ports five pre-gateway predicates that were dropped when the gateway
displaced the MCP claim handler. Predicates restored from
roboco/mcp/tasks/handlers/_helpers.py:124-204 and
roboco/mcp/tasks/handlers/claim.py:121-180 at commit 254cc93:
- SEQUENCE_ORDER_VIOLATION: a sibling task with sequence < N must be
in completed/cancelled before sibling N can be claimed.
- ALREADY_ACTIVE: agent cannot claim while owning an in_progress /
claimed / verifying task other than the one being resumed.
- PAUSED_TASKS_EXIST: agent cannot claim while paused tasks exist.
- PM_CANNOT_EXECUTE_CODE: cell_pm/main_pm cannot claim task_type=code.
- ROLE_TYPED_CLAIM: developer claim is restricted to
code/research/design; qa/documenter must use claim_review /
claim_doc_task.
All five guards run inside Choreographer._run_claim_guards before
i_will_work_on / i_will_plan / claim_review / claim_doc_task mutate
state. Skip flags isolate guards that don't apply to a verb (e.g.,
PM-code skipped on QA verb, role-typed skipped on PM verb).
The guards live in roboco/services/gateway/claim_guards.py so
choreographer.py stays focused on orchestration.
Existing tests updated to provide the new mock primings; the
permissive auto-mock behavior they relied on no longer applies.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* api/schemas/v2/flow.py: IWillPlanRequest, DelegateRequest,
SubmitUpRequest with min_length=1 validators where appropriate.
* api/routes/v2/flow_cell_pm.py: give_me_work routes to
pm_give_me_work; new endpoints i_will_plan, delegate, submit_up.
* api/routes/v2/flow_main_pm.py: new endpoints give_me_work,
i_will_plan, delegate.
* mcp/flow_server.py: Python wrappers for i_will_plan, delegate,
submit_up registered in _TOOLS so manifest-scoped agents can call
them.
* tests/unit/gateway/test_choreographer_pm_extras.py: 22 tests
covering happy + reject paths for each new verb plus i_am_idle's
auto-pause behavior.
* tests/unit/api/routes/v2/test_flow_cell_pm.py +
test_flow_main_pm.py: route-level tests for the new endpoints.
Test count: 352 → 381 (+29). make quality-fast green.