@@ -214,16 +214,27 @@ def _claim_bucket(item: Mapping[str, Any], *, agent_id: str | None) -> int:
214214 return 2
215215
216216
217- def _monitor_lane_exclusion ( candidate : Mapping [str , Any ]) -> dict [str , Any ] | None :
218- if candidate . get ( "task_class" ) != TODO_TASK_CLASS_MONITOR :
219- return None
217+ def _candidate_readiness ( item : Mapping [str , Any ]) -> dict [str , Any ]:
218+ # Reuse the canonical read model; a planner does not resolve dependencies
219+ # from ownership, lineage, or the other candidates selected in this batch.
220220 return {
221- ** candidate ,
222- "selection_status" : "excluded_non_exploration_lane" ,
223- "exclusion_reason" : "continuous_monitor_does_not_consume_exploration_budget" ,
221+ "actionable_open" : todo_item_is_actionable_open (dict (item )),
222+ ** {key : item [key ] for key in ("status" , "resume_when" , "resume_ready" ) if key in item },
224223 }
225224
226225
226+ def _execution_exclusion (candidate : Mapping [str , Any ]) -> dict [str , Any ] | None :
227+ if candidate .get ("task_class" ) == TODO_TASK_CLASS_MONITOR :
228+ return {
229+ ** candidate ,
230+ "selection_status" : "excluded_non_exploration_lane" ,
231+ "exclusion_reason" : "continuous_monitor_does_not_consume_exploration_budget" ,
232+ }
233+ if not candidate ["actionable_open" ]:
234+ return {** candidate , "selection_status" : "not_actionable_open" }
235+ return None
236+
237+
227238def _lane_id (todo_id : str , * , index : int ) -> str :
228239 return f"branch_{ index } _{ todo_id .removeprefix ('todo_' )} "
229240
@@ -366,6 +377,7 @@ def build_explore_todo_branch_plan(
366377 task_class = todo_item_task_class (dict (item ))
367378 required_capabilities = _required_capabilities (item )
368379 candidate = {
380+ ** _candidate_readiness (item ),
369381 "todo_id" : todo_id ,
370382 "text" : _compact_text (item .get ("text" ) or item .get ("title" )),
371383 "priority" : item .get ("priority" ),
@@ -407,15 +419,16 @@ def build_explore_todo_branch_plan(
407419 )
408420 )
409421
410- monitor_lanes = [
422+ excluded_candidates = [
411423 exclusion
412424 for candidate in candidates
413- if (exclusion := _monitor_lane_exclusion (candidate )) is not None
425+ if (exclusion := _execution_exclusion (candidate )) is not None
414426 ]
415427 exploration_candidates = [
416428 candidate
417429 for candidate in candidates
418430 if candidate .get ("task_class" ) != TODO_TASK_CLASS_MONITOR
431+ if candidate ["actionable_open" ]
419432 ]
420433 schedulable = [
421434 candidate
@@ -552,7 +565,7 @@ def build_explore_todo_branch_plan(
552565 lane = "todo_branch_plan" ,
553566 )
554567 if not invalidated :
555- rejected = [* monitor_lanes , * dependency_rejections , * selection_rejections ]
568+ rejected = [* excluded_candidates , * dependency_rejections , * selection_rejections ]
556569 selected = valid_selected
557570 break
558571 new_invalidated_ids = {
@@ -562,7 +575,7 @@ def build_explore_todo_branch_plan(
562575 dependency_events .extend (events )
563576 invalidated_ids .update (new_invalidated_ids )
564577 if not new_invalidated_ids :
565- rejected = [* monitor_lanes , * dependency_rejections , * selection_rejections ]
578+ rejected = [* excluded_candidates , * dependency_rejections , * selection_rejections ]
566579 selected = valid_selected
567580 break
568581
@@ -602,6 +615,7 @@ def build_explore_todo_branch_plan(
602615 "exploration_candidate_count" : len (exploration_candidates ),
603616 "selected_count" : len (selected ),
604617 "selected_branches" : selected ,
618+ "rejected_candidate_count" : len (rejected ),
605619 "rejected_candidates" : rejected [: max (0 , normalized_width * 3 )],
606620 "resource_portfolio" : resource_portfolio_with_selection (resource_portfolio , selected ),
607621 "hazard_model" : {
@@ -642,6 +656,9 @@ def build_explore_todo_branch_plan(
642656 "registered goal's spawn_policy (spawn_allowed, max_children) to receive "
643657 "suggested commands; execution stays in the normal LoopX lifecycle."
644658 if gate ["state" ] == GATE_STATE_ANALYSIS_ONLY
659+ else "No actionable branch was selected. Inspect rejected candidates and their "
660+ "resume conditions; replan when readiness changes."
661+ if not selected
645662 else "Run the primary branch normally; hand selected speculative branches to side "
646663 "agents only after claiming/lease commands are accepted and workspace guards pass."
647664 if len (selected ) > 1
0 commit comments