loopx 0.4.8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (811) hide show
  1. loopx/__init__.py +5 -0
  2. loopx/agent_onboarding.py +654 -0
  3. loopx/agent_registry.py +112 -0
  4. loopx/ark_managed_agent_host.py +59 -0
  5. loopx/authority.py +805 -0
  6. loopx/benchmark.py +2875 -0
  7. loopx/benchmark_adapters/__init__.py +1 -0
  8. loopx/benchmark_adapters/agentissue.py +2644 -0
  9. loopx/benchmark_adapters/agents_last_exam.py +3998 -0
  10. loopx/benchmark_adapters/edgebench.py +322 -0
  11. loopx/benchmark_adapters/skillsbench.py +5978 -0
  12. loopx/benchmark_adapters/skillsbench_acp_failure_policy.py +143 -0
  13. loopx/benchmark_adapters/skillsbench_acp_process.py +31 -0
  14. loopx/benchmark_adapters/skillsbench_acp_relay.py +4832 -0
  15. loopx/benchmark_adapters/skillsbench_batch.py +124 -0
  16. loopx/benchmark_adapters/skillsbench_bridge_guard.py +209 -0
  17. loopx/benchmark_adapters/skillsbench_bridge_summary.py +203 -0
  18. loopx/benchmark_adapters/skillsbench_codex_goal_recovery.py +271 -0
  19. loopx/benchmark_adapters/skillsbench_codex_goal_trace.py +81 -0
  20. loopx/benchmark_adapters/skillsbench_codex_runtime.py +339 -0
  21. loopx/benchmark_adapters/skillsbench_dockerfile_runtime.py +467 -0
  22. loopx/benchmark_adapters/skillsbench_failure_signals.py +652 -0
  23. loopx/benchmark_adapters/skillsbench_proxy_runtime.py +327 -0
  24. loopx/benchmark_adapters/skillsbench_remote_bridge.py +402 -0
  25. loopx/benchmark_adapters/skillsbench_result_discovery.py +143 -0
  26. loopx/benchmark_adapters/skillsbench_runner_profile.py +436 -0
  27. loopx/benchmark_adapters/skillsbench_runner_source.py +99 -0
  28. loopx/benchmark_adapters/skillsbench_setup_preflight.py +771 -0
  29. loopx/benchmark_adapters/skillsbench_signals.py +15 -0
  30. loopx/benchmark_adapters/skillsbench_task_source.py +141 -0
  31. loopx/benchmark_adapters/skillsbench_turn_route.py +723 -0
  32. loopx/benchmark_adapters/skillsbench_turn_runtime.py +1069 -0
  33. loopx/benchmark_adapters/skillsbench_typed_repair.py +689 -0
  34. loopx/benchmark_adapters/skillsbench_uv_cache.py +111 -0
  35. loopx/benchmark_adapters/skillsbench_verifier_bootstrap.py +227 -0
  36. loopx/benchmark_adapters/skillsbench_verifier_cache.py +138 -0
  37. loopx/benchmark_adapters/terminal_bench.py +10078 -0
  38. loopx/benchmark_case_analysis.py +1276 -0
  39. loopx/benchmark_case_state.py +1079 -0
  40. loopx/benchmark_core/__init__.py +239 -0
  41. loopx/benchmark_core/adapter.py +84 -0
  42. loopx/benchmark_core/artifacts.py +517 -0
  43. loopx/benchmark_core/attempts.py +199 -0
  44. loopx/benchmark_core/container_exec.py +216 -0
  45. loopx/benchmark_core/io.py +68 -0
  46. loopx/benchmark_core/lifecycle.py +211 -0
  47. loopx/benchmark_core/loop_protocol.py +689 -0
  48. loopx/benchmark_core/observable_handles.py +348 -0
  49. loopx/benchmark_core/parity.py +256 -0
  50. loopx/benchmark_core/remote_closeout.py +482 -0
  51. loopx/benchmark_core/rounds.py +215 -0
  52. loopx/benchmark_core/route_profile.py +509 -0
  53. loopx/benchmark_core/run_permissions.py +206 -0
  54. loopx/benchmark_core/split_control.py +925 -0
  55. loopx/benchmark_core/turn_fidelity.py +326 -0
  56. loopx/benchmark_ledger.py +3793 -0
  57. loopx/benchmark_ledger_countability.py +372 -0
  58. loopx/benchmark_ledger_current.py +724 -0
  59. loopx/benchmark_trajectory.py +405 -0
  60. loopx/benchmarks/__init__.py +1 -0
  61. loopx/benchmarks/qualification/__init__.py +1 -0
  62. loopx/benchmarks/qualification/release_outcome_baseline.py +360 -0
  63. loopx/benchmarks/read_models/__init__.py +1 -0
  64. loopx/benchmarks/read_models/benchmark_attempt_accounting.py +53 -0
  65. loopx/benchmarks/read_models/benchmark_comparison.py +414 -0
  66. loopx/benchmarks/read_models/benchmark_event_timeline.py +113 -0
  67. loopx/benchmarks/read_models/benchmark_experiment_report.py +475 -0
  68. loopx/benchmarks/read_models/benchmark_learning_ledger.py +137 -0
  69. loopx/benchmarks/read_models/benchmark_lifecycle_contracts.py +228 -0
  70. loopx/benchmarks/read_models/benchmark_projection.py +723 -0
  71. loopx/benchmarks/read_models/benchmark_result.py +146 -0
  72. loopx/benchmarks/read_models/benchmark_run_execution_contract.py +116 -0
  73. loopx/benchmarks/read_models/benchmark_run_failure.py +157 -0
  74. loopx/benchmarks/read_models/benchmark_run_metrics.py +213 -0
  75. loopx/benchmarks/read_models/benchmark_run_post_execution.py +635 -0
  76. loopx/benchmarks/read_models/benchmark_run_pre_execution.py +541 -0
  77. loopx/benchmarks/read_models/benchmark_status_compaction.py +1255 -0
  78. loopx/benchmarks/read_models/benchmark_status_runner.py +780 -0
  79. loopx/benchmarks/read_models/goal_start_control_score.py +857 -0
  80. loopx/benchmarks/read_models/skillsbench_post_run_debug.py +746 -0
  81. loopx/benchmarks/read_models/skillsbench_verifier_attribution.py +269 -0
  82. loopx/bootstrap.py +1116 -0
  83. loopx/bootstrap_command_pack.py +2167 -0
  84. loopx/boundary_authority.py +199 -0
  85. loopx/canary/__init__.py +1 -0
  86. loopx/canary/maintainability_ratchet.py +800 -0
  87. loopx/canary/planner.py +1984 -0
  88. loopx/canary/premerge.py +1130 -0
  89. loopx/canary/qualification_profiles.py +309 -0
  90. loopx/canary/quality_surface_catalog.py +838 -0
  91. loopx/canary/release_profiles.py +51 -0
  92. loopx/canary/runner.py +1107 -0
  93. loopx/canary/smoke_health.py +581 -0
  94. loopx/canary/smoke_profiles.py +212 -0
  95. loopx/capabilities/__init__.py +0 -0
  96. loopx/capabilities/agent_turn_recall/__init__.py +17 -0
  97. loopx/capabilities/agent_turn_recall/cli.py +369 -0
  98. loopx/capabilities/agent_turn_recall/core.py +296 -0
  99. loopx/capabilities/auto_research/__init__.py +16 -0
  100. loopx/capabilities/auto_research/bootstrap_contract.py +157 -0
  101. loopx/capabilities/auto_research/cli.py +1468 -0
  102. loopx/capabilities/auto_research/core.py +11 -0
  103. loopx/capabilities/auto_research/defaults.py +79 -0
  104. loopx/capabilities/auto_research/demo_e2e.py +1848 -0
  105. loopx/capabilities/auto_research/demo_supervisor.py +186 -0
  106. loopx/capabilities/auto_research/evidence_packet.py +767 -0
  107. loopx/capabilities/auto_research/human_view.py +794 -0
  108. loopx/capabilities/auto_research/kernel.py +191 -0
  109. loopx/capabilities/auto_research/knn_demo_workspace.py +322 -0
  110. loopx/capabilities/auto_research/live_evidence.py +248 -0
  111. loopx/capabilities/auto_research/preset.py +176 -0
  112. loopx/capabilities/auto_research/research_state.py +1085 -0
  113. loopx/capabilities/auto_research/role_profiles.py +394 -0
  114. loopx/capabilities/auto_research/rollout_append.py +97 -0
  115. loopx/capabilities/auto_research/terminal_result_contract.py +422 -0
  116. loopx/capabilities/auto_research/terminal_result_projection.py +171 -0
  117. loopx/capabilities/auto_research/terminal_result_query.py +233 -0
  118. loopx/capabilities/auto_research/terminal_results.py +349 -0
  119. loopx/capabilities/auto_research/user_contract.py +190 -0
  120. loopx/capabilities/auto_research/worker_loop.py +163 -0
  121. loopx/capabilities/auto_research/worker_runtime.py +777 -0
  122. loopx/capabilities/auto_research/worker_skill/SKILL.md +343 -0
  123. loopx/capabilities/benchmark_toolkit/__init__.py +19 -0
  124. loopx/capabilities/benchmark_toolkit/integrity.py +387 -0
  125. loopx/capabilities/catalog.py +1875 -0
  126. loopx/capabilities/change_quality/__init__.py +19 -0
  127. loopx/capabilities/change_quality/cli.py +171 -0
  128. loopx/capabilities/change_quality/context.py +156 -0
  129. loopx/capabilities/change_quality/oracles.py +269 -0
  130. loopx/capabilities/change_quality/policy.py +34 -0
  131. loopx/capabilities/change_quality/receipt.py +482 -0
  132. loopx/capabilities/change_quality/result.py +493 -0
  133. loopx/capabilities/change_quality/scope.py +171 -0
  134. loopx/capabilities/change_quality/shadow.py +680 -0
  135. loopx/capabilities/content_ops/__init__.py +0 -0
  136. loopx/capabilities/content_ops/cli.py +649 -0
  137. loopx/capabilities/content_ops/connector_packets.py +164 -0
  138. loopx/capabilities/content_ops/item_lifecycle.py +1000 -0
  139. loopx/capabilities/content_ops/layout.py +451 -0
  140. loopx/capabilities/content_ops/markdown.py +456 -0
  141. loopx/capabilities/content_ops/schemas.py +51 -0
  142. loopx/capabilities/content_ops/social_browser_x.py +107 -0
  143. loopx/capabilities/content_ops/surface.py +1956 -0
  144. loopx/capabilities/content_ops/templates/layout-catalog-v0.json +72 -0
  145. loopx/capabilities/context_providers/__init__.py +36 -0
  146. loopx/capabilities/context_providers/base.py +189 -0
  147. loopx/capabilities/context_providers/factory.py +32 -0
  148. loopx/capabilities/context_providers/openviking.py +702 -0
  149. loopx/capabilities/context_providers/service_ownership.py +185 -0
  150. loopx/capabilities/decision_context/__init__.py +129 -0
  151. loopx/capabilities/decision_context/architecture.py +83 -0
  152. loopx/capabilities/decision_context/assembler.py +849 -0
  153. loopx/capabilities/decision_context/catalog_entry.py +195 -0
  154. loopx/capabilities/decision_context/cli.py +310 -0
  155. loopx/capabilities/decision_context/cursor_commit.py +535 -0
  156. loopx/capabilities/decision_context/outcome_feedback.py +352 -0
  157. loopx/capabilities/decision_context/packets.py +654 -0
  158. loopx/capabilities/decision_context/private_state.py +189 -0
  159. loopx/capabilities/decision_context/profile.py +453 -0
  160. loopx/capabilities/decision_context/providers.py +228 -0
  161. loopx/capabilities/decision_context/review_settlement.py +136 -0
  162. loopx/capabilities/decision_context/runtime.py +273 -0
  163. loopx/capabilities/decision_context/sources.py +415 -0
  164. loopx/capabilities/explore/__init__.py +1 -0
  165. loopx/capabilities/explore/activation.py +198 -0
  166. loopx/capabilities/explore/adaptive_replay_planner.py +221 -0
  167. loopx/capabilities/explore/child_replay_runtime.py +463 -0
  168. loopx/capabilities/explore/composition_frontier.py +291 -0
  169. loopx/capabilities/explore/counterfactual_runtime.py +578 -0
  170. loopx/capabilities/explore/episode_runtime.py +647 -0
  171. loopx/capabilities/explore/harness_checkpoint.py +171 -0
  172. loopx/capabilities/explore/harness_gate.py +115 -0
  173. loopx/capabilities/explore/harness_runtime.py +1124 -0
  174. loopx/capabilities/explore/replay_metrics.py +206 -0
  175. loopx/capabilities/explore/replay_runtime.py +1271 -0
  176. loopx/capabilities/explore/resource_portfolio.py +173 -0
  177. loopx/capabilities/explore/result_log.py +974 -0
  178. loopx/capabilities/explore/router_state.py +432 -0
  179. loopx/capabilities/explore/source_history_reconcile.py +255 -0
  180. loopx/capabilities/explore/speculative_scheduler.py +498 -0
  181. loopx/capabilities/explore/todo_branch_plan.py +650 -0
  182. loopx/capabilities/explore/todo_evidence.py +141 -0
  183. loopx/capabilities/explore/trace_runtime.py +284 -0
  184. loopx/capabilities/explore/worker_branch_plan.py +1257 -0
  185. loopx/capabilities/integration_branch/__init__.py +13 -0
  186. loopx/capabilities/integration_branch/cli.py +148 -0
  187. loopx/capabilities/integration_branch/core.py +916 -0
  188. loopx/capabilities/issue_fix/__init__.py +19 -0
  189. loopx/capabilities/issue_fix/acceptance_loop.py +1050 -0
  190. loopx/capabilities/issue_fix/candidate_evidence.py +503 -0
  191. loopx/capabilities/issue_fix/candidate_preflight.py +676 -0
  192. loopx/capabilities/issue_fix/cli.py +1822 -0
  193. loopx/capabilities/issue_fix/cli_input.py +87 -0
  194. loopx/capabilities/issue_fix/content_ops_cli.py +148 -0
  195. loopx/capabilities/issue_fix/discovered_issue_promotion.py +947 -0
  196. loopx/capabilities/issue_fix/explore_projection.py +710 -0
  197. loopx/capabilities/issue_fix/feasibility.py +542 -0
  198. loopx/capabilities/issue_fix/github_public.py +661 -0
  199. loopx/capabilities/issue_fix/intake_surface.py +832 -0
  200. loopx/capabilities/issue_fix/metadata_preview.py +218 -0
  201. loopx/capabilities/issue_fix/metrics_projection.py +1340 -0
  202. loopx/capabilities/issue_fix/metrics_supplement.py +634 -0
  203. loopx/capabilities/issue_fix/metrics_supplement_cli.py +127 -0
  204. loopx/capabilities/issue_fix/outcome_projection.py +1235 -0
  205. loopx/capabilities/issue_fix/periodic_report.py +189 -0
  206. loopx/capabilities/issue_fix/pr_description.py +418 -0
  207. loopx/capabilities/issue_fix/pr_gate_reconcile.py +496 -0
  208. loopx/capabilities/issue_fix/pr_gate_reconcile_cli.py +464 -0
  209. loopx/capabilities/issue_fix/pr_lifecycle.py +1327 -0
  210. loopx/capabilities/issue_fix/pr_lifecycle_rollout.py +85 -0
  211. loopx/capabilities/issue_fix/pr_monitor_materialization.py +257 -0
  212. loopx/capabilities/issue_fix/pr_review_ack.py +439 -0
  213. loopx/capabilities/issue_fix/provider_hooks.py +24 -0
  214. loopx/capabilities/issue_fix/repository_commit_evidence.py +186 -0
  215. loopx/capabilities/issue_fix/repository_context.py +457 -0
  216. loopx/capabilities/issue_fix/repository_memory.py +459 -0
  217. loopx/capabilities/issue_fix/repository_memory_provider.py +1454 -0
  218. loopx/capabilities/issue_fix/repository_snapshot.py +454 -0
  219. loopx/capabilities/issue_fix/reviewer_cli.py +917 -0
  220. loopx/capabilities/issue_fix/reviewer_notification.py +882 -0
  221. loopx/capabilities/issue_fix/reviewer_notification_drain.py +942 -0
  222. loopx/capabilities/issue_fix/reviewer_recommendation.py +1057 -0
  223. loopx/capabilities/issue_fix/reviewer_request.py +1282 -0
  224. loopx/capabilities/issue_fix/reward_memory.py +879 -0
  225. loopx/capabilities/issue_fix/workflow_plan.py +1286 -0
  226. loopx/capabilities/material_lifecycle/__init__.py +161 -0
  227. loopx/capabilities/material_lifecycle/_validation.py +183 -0
  228. loopx/capabilities/material_lifecycle/apply.py +672 -0
  229. loopx/capabilities/material_lifecycle/architecture.py +122 -0
  230. loopx/capabilities/material_lifecycle/cli.py +161 -0
  231. loopx/capabilities/material_lifecycle/decision_planning.py +470 -0
  232. loopx/capabilities/material_lifecycle/explore_execution.py +306 -0
  233. loopx/capabilities/material_lifecycle/intake.py +869 -0
  234. loopx/capabilities/material_lifecycle/inventory.py +147 -0
  235. loopx/capabilities/material_lifecycle/lifecycle.py +98 -0
  236. loopx/capabilities/material_lifecycle/preparation.py +147 -0
  237. loopx/capabilities/material_lifecycle/project_skill.py +83 -0
  238. loopx/capabilities/material_lifecycle/ranking.py +267 -0
  239. loopx/capabilities/material_lifecycle/readable_projection.py +500 -0
  240. loopx/capabilities/material_lifecycle/rebuild.py +480 -0
  241. loopx/capabilities/material_lifecycle/settlement.py +238 -0
  242. loopx/capabilities/periodic_report/__init__.py +71 -0
  243. loopx/capabilities/periodic_report/adapters.py +939 -0
  244. loopx/capabilities/periodic_report/archive.py +422 -0
  245. loopx/capabilities/periodic_report/bindings.py +705 -0
  246. loopx/capabilities/periodic_report/cli.py +277 -0
  247. loopx/capabilities/periodic_report/core.py +691 -0
  248. loopx/capabilities/periodic_report/extension_envelope.py +66 -0
  249. loopx/capabilities/periodic_report/presets.py +103 -0
  250. loopx/capabilities/periodic_report/profile.py +235 -0
  251. loopx/capabilities/periodic_report/project_progress.py +179 -0
  252. loopx/capabilities/periodic_report/triggers.py +452 -0
  253. loopx/capabilities/pr_review_queue/__init__.py +17 -0
  254. loopx/capabilities/pr_review_queue/core.py +506 -0
  255. loopx/capabilities/pr_review_queue/review_contract.py +506 -0
  256. loopx/capabilities/registry.py +192 -0
  257. loopx/capabilities/reward_memory/__init__.py +75 -0
  258. loopx/capabilities/reward_memory/application.py +819 -0
  259. loopx/capabilities/reward_memory/architecture.py +572 -0
  260. loopx/capabilities/reward_memory/candidate_review.py +511 -0
  261. loopx/capabilities/reward_memory/cli.py +469 -0
  262. loopx/capabilities/reward_memory/dogfood.py +574 -0
  263. loopx/capabilities/reward_memory/evaluation.py +296 -0
  264. loopx/capabilities/reward_memory/evaluation_fixtures.py +362 -0
  265. loopx/capabilities/reward_memory/experiment.py +567 -0
  266. loopx/capabilities/reward_memory/health.py +222 -0
  267. loopx/capabilities/reward_memory/ingestion.py +519 -0
  268. loopx/capabilities/reward_memory/registry.py +600 -0
  269. loopx/capabilities/reward_memory/runtime_hooks.py +312 -0
  270. loopx/capabilities/reward_memory/scoped_feedback.py +173 -0
  271. loopx/capabilities/semantic_preference/__init__.py +12 -0
  272. loopx/capabilities/semantic_preference/cli.py +189 -0
  273. loopx/capabilities/semantic_preference/contract.py +592 -0
  274. loopx/capabilities/semantic_preference/reward_memory.py +62 -0
  275. loopx/capabilities/value_connectors/__init__.py +1 -0
  276. loopx/capabilities/value_connectors/cli.py +401 -0
  277. loopx/capabilities/value_connectors/finance_extension_migration.py +108 -0
  278. loopx/capabilities/value_connectors/install_check.py +147 -0
  279. loopx/capabilities/value_connectors/planner.py +733 -0
  280. loopx/capabilities/value_connectors/source_map.py +446 -0
  281. loopx/claude_goal_baseline.py +138 -0
  282. loopx/claude_goal_mode/__init__.py +23 -0
  283. loopx/claude_goal_mode/hooks/goal_policy.py +212 -0
  284. loopx/claude_goal_mode/hooks/goal_state.py +139 -0
  285. loopx/claude_goal_mode/mcp/loopx_mcp.py +167 -0
  286. loopx/claude_goal_mode/scripts/connect.py +103 -0
  287. loopx/claude_goal_mode/scripts/goalmode_cmd.py +241 -0
  288. loopx/claude_goal_mode/scripts/install.py +328 -0
  289. loopx/claude_goal_mode/statusline/goal_status.py +97 -0
  290. loopx/cli.py +836 -0
  291. loopx/cli_commands/__init__.py +334 -0
  292. loopx/cli_commands/_host_thread.py +13 -0
  293. loopx/cli_commands/agentissue_runner_flow.py +447 -0
  294. loopx/cli_commands/agents_last_exam.py +160 -0
  295. loopx/cli_commands/agents_last_exam_baked_input.py +302 -0
  296. loopx/cli_commands/agents_last_exam_host_codex.py +374 -0
  297. loopx/cli_commands/agents_last_exam_launch_dry_run.py +372 -0
  298. loopx/cli_commands/agents_last_exam_local_plan.py +322 -0
  299. loopx/cli_commands/agents_last_exam_runner_source.py +352 -0
  300. loopx/cli_commands/agents_last_exam_task_material.py +335 -0
  301. loopx/cli_commands/agents_last_exam_validation_gate.py +236 -0
  302. loopx/cli_commands/benchmark_boundary.py +499 -0
  303. loopx/cli_commands/benchmark_dispatch.py +161 -0
  304. loopx/cli_commands/benchmark_release_outcome.py +123 -0
  305. loopx/cli_commands/benchmark_review_lifecycle.py +1275 -0
  306. loopx/cli_commands/benchmark_run_ledger.py +763 -0
  307. loopx/cli_commands/benchmark_run_ledger_case_analysis.py +249 -0
  308. loopx/cli_commands/benchmark_run_ledger_classification.py +45 -0
  309. loopx/cli_commands/benchmark_run_ledger_maintenance.py +486 -0
  310. loopx/cli_commands/benchmark_run_ledger_maintenance_registration.py +342 -0
  311. loopx/cli_commands/benchmark_run_ledger_maintenance_rendering.py +233 -0
  312. loopx/cli_commands/benchmark_run_ledger_parity.py +92 -0
  313. loopx/cli_commands/bootstrap_connect.py +238 -0
  314. loopx/cli_commands/canary.py +707 -0
  315. loopx/cli_commands/canary_release_qualification.py +79 -0
  316. loopx/cli_commands/capability.py +96 -0
  317. loopx/cli_commands/doctor.py +43 -0
  318. loopx/cli_commands/dreaming.py +143 -0
  319. loopx/cli_commands/edgebench.py +205 -0
  320. loopx/cli_commands/evidence_log.py +275 -0
  321. loopx/cli_commands/explore.py +989 -0
  322. loopx/cli_commands/explore_planning_commands.py +157 -0
  323. loopx/cli_commands/extension.py +271 -0
  324. loopx/cli_commands/first_run_report.py +73 -0
  325. loopx/cli_commands/goal_channel.py +656 -0
  326. loopx/cli_commands/handoff_mode.py +158 -0
  327. loopx/cli_commands/history.py +622 -0
  328. loopx/cli_commands/host_mode_plan.py +113 -0
  329. loopx/cli_commands/lark_inbox.py +431 -0
  330. loopx/cli_commands/lark_kanban.py +629 -0
  331. loopx/cli_commands/ml_experiment.py +321 -0
  332. loopx/cli_commands/multi_agent.py +211 -0
  333. loopx/cli_commands/opencode2_goal_worker.py +217 -0
  334. loopx/cli_commands/pr_review.py +167 -0
  335. loopx/cli_commands/presentation.py +218 -0
  336. loopx/cli_commands/preset.py +96 -0
  337. loopx/cli_commands/project.py +150 -0
  338. loopx/cli_commands/project_lifecycle.py +915 -0
  339. loopx/cli_commands/quota.py +859 -0
  340. loopx/cli_commands/quota_registration.py +241 -0
  341. loopx/cli_commands/quota_request.py +113 -0
  342. loopx/cli_commands/ready_score.py +110 -0
  343. loopx/cli_commands/registry_admin.py +975 -0
  344. loopx/cli_commands/registry_admin_configure.py +344 -0
  345. loopx/cli_commands/registry_admin_peer.py +84 -0
  346. loopx/cli_commands/registry_authority.py +218 -0
  347. loopx/cli_commands/review_batch.py +146 -0
  348. loopx/cli_commands/slash_commands.py +145 -0
  349. loopx/cli_commands/start_goal.py +251 -0
  350. loopx/cli_commands/starter.py +175 -0
  351. loopx/cli_commands/starter_bootstrap.py +179 -0
  352. loopx/cli_commands/starter_bootstrap_registration.py +198 -0
  353. loopx/cli_commands/starter_runtime_idle.py +107 -0
  354. loopx/cli_commands/starter_scheduler.py +207 -0
  355. loopx/cli_commands/starter_session_runtime.py +152 -0
  356. loopx/cli_commands/starter_visible_common.py +54 -0
  357. loopx/cli_commands/starter_visible_driver.py +161 -0
  358. loopx/cli_commands/starter_visible_pilot.py +278 -0
  359. loopx/cli_commands/status.py +867 -0
  360. loopx/cli_commands/status_registration.py +239 -0
  361. loopx/cli_commands/summary_all.py +222 -0
  362. loopx/cli_commands/support_control.py +809 -0
  363. loopx/cli_commands/support_control_registry.py +68 -0
  364. loopx/cli_commands/support_control_supervisor.py +289 -0
  365. loopx/cli_commands/task_lease.py +306 -0
  366. loopx/cli_commands/terminal_bench_adapter.py +717 -0
  367. loopx/cli_commands/terminal_bench_environment_result.py +1246 -0
  368. loopx/cli_commands/todo.py +940 -0
  369. loopx/cli_commands/todo_argument_validation.py +572 -0
  370. loopx/cli_commands/todo_event.py +114 -0
  371. loopx/cli_commands/turn.py +804 -0
  372. loopx/cli_commands/version.py +46 -0
  373. loopx/cli_commands/worker_bridge.py +659 -0
  374. loopx/cli_rollout.py +314 -0
  375. loopx/codex_cli_goal_tui.py +672 -0
  376. loopx/codex_cli_probe.py +1530 -0
  377. loopx/codex_cli_probe_markdown.py +935 -0
  378. loopx/codex_cli_runtime_probe.py +733 -0
  379. loopx/codex_cli_scheduler.py +564 -0
  380. loopx/codex_goal_baseline.py +620 -0
  381. loopx/configuration_catalog.py +617 -0
  382. loopx/configure_goal.py +1375 -0
  383. loopx/contract.py +996 -0
  384. loopx/control_plane/__init__.py +71 -0
  385. loopx/control_plane/agents/__init__.py +1 -0
  386. loopx/control_plane/agents/agent_lane_recommendation.py +516 -0
  387. loopx/control_plane/agents/agent_scope.py +1578 -0
  388. loopx/control_plane/agents/agent_scope_frontier.py +60 -0
  389. loopx/control_plane/agents/capability_gate.py +531 -0
  390. loopx/control_plane/agents/identity.py +140 -0
  391. loopx/control_plane/agents/legacy_migration.py +169 -0
  392. loopx/control_plane/agents/management_projection.py +658 -0
  393. loopx/control_plane/agents/material_frontier.py +608 -0
  394. loopx/control_plane/agents/material_handoff.py +156 -0
  395. loopx/control_plane/agents/multi_agent/__init__.py +1 -0
  396. loopx/control_plane/agents/multi_agent/codex_executable.py +207 -0
  397. loopx/control_plane/agents/multi_agent/collective_round_ledger.py +387 -0
  398. loopx/control_plane/agents/multi_agent/contract.py +474 -0
  399. loopx/control_plane/agents/multi_agent/recipe.py +110 -0
  400. loopx/control_plane/agents/multi_agent/role_successor.py +297 -0
  401. loopx/control_plane/agents/multi_agent/runtime_scripts.py +426 -0
  402. loopx/control_plane/agents/multi_agent/visible_launch_policy.py +149 -0
  403. loopx/control_plane/agents/multi_agent/visible_wake_scheduler.py +392 -0
  404. loopx/control_plane/agents/profile.py +216 -0
  405. loopx/control_plane/agents/runtime_model.py +73 -0
  406. loopx/control_plane/agents/subagent_activity.py +164 -0
  407. loopx/control_plane/agents/supervisor.py +544 -0
  408. loopx/control_plane/agents/supervisor_events.py +462 -0
  409. loopx/control_plane/agents/supervisor_inject.py +204 -0
  410. loopx/control_plane/agents/work_mode.py +56 -0
  411. loopx/control_plane/agents/workspace_guard.py +364 -0
  412. loopx/control_plane/effect_program.py +644 -0
  413. loopx/control_plane/goals/__init__.py +1 -0
  414. loopx/control_plane/goals/active_state_event_projection.py +103 -0
  415. loopx/control_plane/goals/active_state_metadata.py +47 -0
  416. loopx/control_plane/goals/active_state_sections.py +58 -0
  417. loopx/control_plane/goals/configure_goal_service.py +354 -0
  418. loopx/control_plane/goals/contract_health.py +132 -0
  419. loopx/control_plane/goals/dreaming.py +152 -0
  420. loopx/control_plane/goals/global_registry_health.py +199 -0
  421. loopx/control_plane/goals/global_registry_shadow.py +33 -0
  422. loopx/control_plane/goals/goal_channel.py +34 -0
  423. loopx/control_plane/goals/goal_channel_projection.py +560 -0
  424. loopx/control_plane/goals/goal_frontier/__init__.py +1917 -0
  425. loopx/control_plane/goals/goal_frontier/ack_policy.py +149 -0
  426. loopx/control_plane/goals/goal_frontier/outcome_continuity.py +437 -0
  427. loopx/control_plane/goals/goal_frontier/replan_rules.py +210 -0
  428. loopx/control_plane/goals/goal_frontier/semantic_history.py +314 -0
  429. loopx/control_plane/goals/goal_frontier/terminal.py +180 -0
  430. loopx/control_plane/goals/goal_vision.py +443 -0
  431. loopx/control_plane/goals/goal_vision_policy.py +36 -0
  432. loopx/control_plane/goals/goal_vision_state.py +62 -0
  433. loopx/control_plane/goals/goal_vision_wait.py +290 -0
  434. loopx/control_plane/goals/path_resolution.py +20 -0
  435. loopx/control_plane/goals/start_contract.py +206 -0
  436. loopx/control_plane/goals/vision_checkpoint.py +92 -0
  437. loopx/control_plane/handoff/__init__.py +1 -0
  438. loopx/control_plane/handoff/cross_runtime_impl_review.py +311 -0
  439. loopx/control_plane/handoff/delivery_contract.py +161 -0
  440. loopx/control_plane/handoff/handoff_runs.py +71 -0
  441. loopx/control_plane/handoff/project_handoff.py +155 -0
  442. loopx/control_plane/handoff/review_batch.py +463 -0
  443. loopx/control_plane/handoff/review_packet_context.py +216 -0
  444. loopx/control_plane/heartbeat/agent.py +173 -0
  445. loopx/control_plane/heartbeat/budget.py +66 -0
  446. loopx/control_plane/heartbeat/builder.py +501 -0
  447. loopx/control_plane/heartbeat/host.py +64 -0
  448. loopx/control_plane/heartbeat/rules.py +68 -0
  449. loopx/control_plane/heartbeat/task_body.py +759 -0
  450. loopx/control_plane/heartbeat/visible_goal.py +86 -0
  451. loopx/control_plane/projects/__init__.py +1 -0
  452. loopx/control_plane/projects/contract.py +25 -0
  453. loopx/control_plane/projects/registry.py +663 -0
  454. loopx/control_plane/quota/__init__.py +1 -0
  455. loopx/control_plane/quota/cli_projection.py +704 -0
  456. loopx/control_plane/quota/decision_summary.py +431 -0
  457. loopx/control_plane/quota/effect_program.py +152 -0
  458. loopx/control_plane/quota/error_codes.py +19 -0
  459. loopx/control_plane/quota/goal_boundary.py +464 -0
  460. loopx/control_plane/quota/heartbeat_receipt.py +277 -0
  461. loopx/control_plane/quota/heartbeat_recommendation.py +718 -0
  462. loopx/control_plane/quota/host_poll_receipts.py +162 -0
  463. loopx/control_plane/quota/live_decision.py +142 -0
  464. loopx/control_plane/quota/monitor_poll.py +786 -0
  465. loopx/control_plane/quota/policy_constants.py +40 -0
  466. loopx/control_plane/quota/projection_repair.py +262 -0
  467. loopx/control_plane/quota/recent_runs.py +210 -0
  468. loopx/control_plane/quota/scheduler_ack.py +490 -0
  469. loopx/control_plane/quota/selected_todo_projection.py +139 -0
  470. loopx/control_plane/quota/settlement.py +437 -0
  471. loopx/control_plane/quota/settlement_cli.py +246 -0
  472. loopx/control_plane/quota/settlement_validation.py +64 -0
  473. loopx/control_plane/quota/settlement_workspace_causality.py +180 -0
  474. loopx/control_plane/quota/should_run.py +249 -0
  475. loopx/control_plane/quota/should_run_packet.py +1165 -0
  476. loopx/control_plane/quota/should_run_prepare.py +675 -0
  477. loopx/control_plane/quota/slot_accounting.py +1123 -0
  478. loopx/control_plane/quota/spend_sources.py +11 -0
  479. loopx/control_plane/quota/stall_repair.py +397 -0
  480. loopx/control_plane/quota/states.py +29 -0
  481. loopx/control_plane/quota/task_orchestration.py +448 -0
  482. loopx/control_plane/quota/task_orchestration_admission.py +497 -0
  483. loopx/control_plane/quota/turn_envelope.py +889 -0
  484. loopx/control_plane/quota/usage_summary.py +140 -0
  485. loopx/control_plane/reward_memory.py +43 -0
  486. loopx/control_plane/runtime/__init__.py +2 -0
  487. loopx/control_plane/runtime/active_user_assisted_pilot.py +275 -0
  488. loopx/control_plane/runtime/agent_scoped_evidence_log.py +435 -0
  489. loopx/control_plane/runtime/decision_freshness.py +203 -0
  490. loopx/control_plane/runtime/event_ledger.py +197 -0
  491. loopx/control_plane/runtime/event_store_migration_bridge.py +196 -0
  492. loopx/control_plane/runtime/goal_project_route.py +70 -0
  493. loopx/control_plane/runtime/local_state_write_correctness.py +242 -0
  494. loopx/control_plane/runtime/promotion_readiness.py +152 -0
  495. loopx/control_plane/runtime/public_safety.py +120 -0
  496. loopx/control_plane/runtime/run_artifacts.py +78 -0
  497. loopx/control_plane/runtime/run_compaction.py +397 -0
  498. loopx/control_plane/runtime/run_context_retention.py +241 -0
  499. loopx/control_plane/runtime/run_history.py +132 -0
  500. loopx/control_plane/runtime/run_index_duplicates.py +205 -0
  501. loopx/control_plane/runtime/run_index_rebuild.py +263 -0
  502. loopx/control_plane/runtime/run_ingest_health.py +336 -0
  503. loopx/control_plane/runtime/runtime_projection_route.py +624 -0
  504. loopx/control_plane/runtime/runtime_projection_writer.py +98 -0
  505. loopx/control_plane/runtime/session_runtime.py +339 -0
  506. loopx/control_plane/runtime/shared_runtime_material_projection.py +332 -0
  507. loopx/control_plane/runtime/shared_runtime_refresh_projection.py +183 -0
  508. loopx/control_plane/runtime/stale_latest_run.py +90 -0
  509. loopx/control_plane/runtime/status_classifications.py +49 -0
  510. loopx/control_plane/runtime/status_projection_cache.py +235 -0
  511. loopx/control_plane/runtime/stride_observation.py +144 -0
  512. loopx/control_plane/runtime/time.py +39 -0
  513. loopx/control_plane/runtime/trajectory_hygiene.py +149 -0
  514. loopx/control_plane/runtime/validation_command.py +69 -0
  515. loopx/control_plane/scheduler/__init__.py +1 -0
  516. loopx/control_plane/scheduler/ack.py +329 -0
  517. loopx/control_plane/scheduler/arbitration.py +188 -0
  518. loopx/control_plane/scheduler/automation_liveness.py +183 -0
  519. loopx/control_plane/scheduler/execution_context.py +555 -0
  520. loopx/control_plane/scheduler/external_evidence_observation.py +428 -0
  521. loopx/control_plane/scheduler/monitor_display.py +143 -0
  522. loopx/control_plane/scheduler/monitor_poll_policy.py +161 -0
  523. loopx/control_plane/scheduler/monitor_poll_writeback.py +351 -0
  524. loopx/control_plane/scheduler/monitor_target.py +64 -0
  525. loopx/control_plane/scheduler/monitor_todo.py +146 -0
  526. loopx/control_plane/scheduler/monitor_wait.py +237 -0
  527. loopx/control_plane/scheduler/scheduler_hint.py +1284 -0
  528. loopx/control_plane/scheduler/state.py +354 -0
  529. loopx/control_plane/scheduler/state_transition_rules.py +179 -0
  530. loopx/control_plane/scheduler/time.py +10 -0
  531. loopx/control_plane/settlement_driver.py +293 -0
  532. loopx/control_plane/status/__init__.py +6 -0
  533. loopx/control_plane/status/active_state_projection.py +105 -0
  534. loopx/control_plane/status/agent_lane_projection.py +375 -0
  535. loopx/control_plane/status/attention_projection.py +74 -0
  536. loopx/control_plane/status/autonomous_replan_projection.py +103 -0
  537. loopx/control_plane/status/collection.py +140 -0
  538. loopx/control_plane/status/contract_projection.py +31 -0
  539. loopx/control_plane/status/dreaming_projection.py +52 -0
  540. loopx/control_plane/status/goal_attention_projection.py +157 -0
  541. loopx/control_plane/status/lifecycle_projection.py +110 -0
  542. loopx/control_plane/status/monitor_display_projection.py +69 -0
  543. loopx/control_plane/status/registry_health_projection.py +75 -0
  544. loopx/control_plane/status/run_projection.py +70 -0
  545. loopx/control_plane/status/runtime_summaries.py +161 -0
  546. loopx/control_plane/testing/__init__.py +1 -0
  547. loopx/control_plane/testing/actual_default_model_behavior_portfolio.py +1371 -0
  548. loopx/control_plane/testing/canary_harness.py +182 -0
  549. loopx/control_plane/testing/capability_monitor_repair_tool_behavior.py +674 -0
  550. loopx/control_plane/testing/cli_output_budget.py +807 -0
  551. loopx/control_plane/testing/cli_output_differential.py +250 -0
  552. loopx/control_plane/testing/cli_output_semantics.py +87 -0
  553. loopx/control_plane/testing/control_plane_composition_scenarios.py +225 -0
  554. loopx/control_plane/testing/decision_replay.py +268 -0
  555. loopx/control_plane/testing/doubao_model_behavior_actor.py +559 -0
  556. loopx/control_plane/testing/model_behavior_corpus.py +344 -0
  557. loopx/control_plane/testing/model_behavior_qualification.py +769 -0
  558. loopx/control_plane/testing/model_behavior_retained_cases.py +235 -0
  559. loopx/control_plane/testing/model_tool_behavior.py +536 -0
  560. loopx/control_plane/testing/onboarding_model_behavior_qualification.py +642 -0
  561. loopx/control_plane/testing/quota_fixtures.py +208 -0
  562. loopx/control_plane/testing/quota_should_run_parity.py +57 -0
  563. loopx/control_plane/testing/release_commit_qualification.py +671 -0
  564. loopx/control_plane/testing/replan_semantic_action_behavior.py +1302 -0
  565. loopx/control_plane/testing/scoped_gate_successor_tool_behavior.py +527 -0
  566. loopx/control_plane/testing/selected_todo_tool_behavior.py +1002 -0
  567. loopx/control_plane/testing/terminal_settlement_tool_behavior.py +656 -0
  568. loopx/control_plane/todos/__init__.py +1 -0
  569. loopx/control_plane/todos/active_state_editing.py +296 -0
  570. loopx/control_plane/todos/active_state_todo_parser.py +138 -0
  571. loopx/control_plane/todos/active_state_todos.py +175 -0
  572. loopx/control_plane/todos/addition.py +103 -0
  573. loopx/control_plane/todos/claim_visibility.py +253 -0
  574. loopx/control_plane/todos/completed_archive.py +139 -0
  575. loopx/control_plane/todos/completion_fence.py +49 -0
  576. loopx/control_plane/todos/completion_policy.py +153 -0
  577. loopx/control_plane/todos/completion_validation.py +248 -0
  578. loopx/control_plane/todos/completion_validation_accountability.py +27 -0
  579. loopx/control_plane/todos/completion_validation_projection.py +57 -0
  580. loopx/control_plane/todos/contract.py +1476 -0
  581. loopx/control_plane/todos/decision_scope.py +554 -0
  582. loopx/control_plane/todos/deferred_resume.py +546 -0
  583. loopx/control_plane/todos/durable_completion.py +201 -0
  584. loopx/control_plane/todos/event_writeback.py +484 -0
  585. loopx/control_plane/todos/frontier_deadline.py +132 -0
  586. loopx/control_plane/todos/handoff_gate.py +283 -0
  587. loopx/control_plane/todos/handoff_mode.py +444 -0
  588. loopx/control_plane/todos/handoff_note.py +202 -0
  589. loopx/control_plane/todos/line_update.py +361 -0
  590. loopx/control_plane/todos/list_projection.py +205 -0
  591. loopx/control_plane/todos/markdown.py +199 -0
  592. loopx/control_plane/todos/monitor_metadata.py +88 -0
  593. loopx/control_plane/todos/mutation_authority.py +299 -0
  594. loopx/control_plane/todos/projection.py +655 -0
  595. loopx/control_plane/todos/quota_summary.py +1138 -0
  596. loopx/control_plane/todos/route_continuation.py +267 -0
  597. loopx/control_plane/todos/succession_warning.py +174 -0
  598. loopx/control_plane/todos/summary_item.py +223 -0
  599. loopx/control_plane/todos/text.py +30 -0
  600. loopx/control_plane/todos/todo_index.py +226 -0
  601. loopx/control_plane/todos/todo_summary.py +1458 -0
  602. loopx/control_plane/todos/unblock_resume.py +326 -0
  603. loopx/control_plane/todos/user_gate.py +263 -0
  604. loopx/control_plane/todos/write_hint.py +63 -0
  605. loopx/control_plane/todos/write_policy.py +135 -0
  606. loopx/control_plane/turn_driver/__init__.py +85 -0
  607. loopx/control_plane/turn_driver/codex_cli.py +502 -0
  608. loopx/control_plane/turn_driver/driver.py +355 -0
  609. loopx/control_plane/turn_driver/executor.py +1468 -0
  610. loopx/control_plane/turn_driver/loop_controller.py +669 -0
  611. loopx/control_plane/turn_driver/settlement.py +318 -0
  612. loopx/control_plane/turn_driver/transaction.py +375 -0
  613. loopx/control_plane/work_items/__init__.py +1 -0
  614. loopx/control_plane/work_items/attention_fields.py +56 -0
  615. loopx/control_plane/work_items/attention_item.py +77 -0
  616. loopx/control_plane/work_items/attention_queue.py +322 -0
  617. loopx/control_plane/work_items/attention_routing.py +213 -0
  618. loopx/control_plane/work_items/autonomous_candidates.py +135 -0
  619. loopx/control_plane/work_items/autonomous_replan_ack.py +276 -0
  620. loopx/control_plane/work_items/autonomous_replan_obligation.py +786 -0
  621. loopx/control_plane/work_items/backlog_hygiene.py +59 -0
  622. loopx/control_plane/work_items/capability_monitor_fallback.py +221 -0
  623. loopx/control_plane/work_items/delivery_batch_scale.py +66 -0
  624. loopx/control_plane/work_items/delivery_outcome.py +152 -0
  625. loopx/control_plane/work_items/delivery_signals.py +113 -0
  626. loopx/control_plane/work_items/execution_obligation.py +235 -0
  627. loopx/control_plane/work_items/goal_route_hint.py +320 -0
  628. loopx/control_plane/work_items/interaction_contract.py +1540 -0
  629. loopx/control_plane/work_items/issue_meta_surface.py +159 -0
  630. loopx/control_plane/work_items/lifecycle.py +139 -0
  631. loopx/control_plane/work_items/operator_inbox.py +266 -0
  632. loopx/control_plane/work_items/outcome_followthrough.py +69 -0
  633. loopx/control_plane/work_items/primary_action.py +326 -0
  634. loopx/control_plane/work_items/progress_observation.py +630 -0
  635. loopx/control_plane/work_items/project_asset.py +675 -0
  636. loopx/control_plane/work_items/repair_delta.py +693 -0
  637. loopx/control_plane/work_items/runtime_capability_reentry.py +168 -0
  638. loopx/control_plane/work_items/semantic_replan_writeback.py +177 -0
  639. loopx/control_plane/work_items/status_contract.py +49 -0
  640. loopx/control_plane/work_items/task_graph.py +1046 -0
  641. loopx/control_plane/work_items/task_lease.py +1254 -0
  642. loopx/control_plane/work_items/task_lease_settlement.py +422 -0
  643. loopx/control_plane/work_items/work_lane.py +510 -0
  644. loopx/control_plane/work_items/work_lane_context.py +161 -0
  645. loopx/demo.py +247 -0
  646. loopx/diagnose.py +633 -0
  647. loopx/doctor.py +1251 -0
  648. loopx/domain_packs/__init__.py +1 -0
  649. loopx/domain_packs/issue_fix.py +571 -0
  650. loopx/domain_packs/ml_experiment.py +854 -0
  651. loopx/domain_state.py +137 -0
  652. loopx/dreaming.py +706 -0
  653. loopx/entrypoint.py +16 -0
  654. loopx/event_sourced_state.py +981 -0
  655. loopx/execution_profile.py +286 -0
  656. loopx/experiments/__init__.py +1 -0
  657. loopx/experiments/planner_worker/__init__.py +1 -0
  658. loopx/experiments/planner_worker/contract.py +523 -0
  659. loopx/experiments/planner_worker/runtime.py +391 -0
  660. loopx/experiments/planner_worker/traex.py +461 -0
  661. loopx/explore_graph.py +11 -0
  662. loopx/extensions/__init__.py +1 -0
  663. loopx/extensions/bundled.py +28 -0
  664. loopx/extensions/execution_envelope.py +126 -0
  665. loopx/extensions/lark/__init__.py +11 -0
  666. loopx/extensions/lark/event_collector.py +478 -0
  667. loopx/extensions/lark/event_collector_runtime.py +506 -0
  668. loopx/extensions/lark/event_inbox.py +454 -0
  669. loopx/extensions/lark/extension.toml +88 -0
  670. loopx/extensions/lark/goal_channel.py +44 -0
  671. loopx/extensions/lark/goal_channel_contracts.py +388 -0
  672. loopx/extensions/lark/goal_channel_lifecycle.py +218 -0
  673. loopx/extensions/lark/goal_channel_runtime.py +792 -0
  674. loopx/extensions/lark/goal_channel_setup.py +805 -0
  675. loopx/extensions/lark/goal_channel_targets.py +215 -0
  676. loopx/extensions/lark/goal_channel_transport.py +281 -0
  677. loopx/extensions/lark/inbox_reactions.py +650 -0
  678. loopx/extensions/lark/inbox_reply.py +430 -0
  679. loopx/extensions/lark/presentation/__init__.py +11 -0
  680. loopx/extensions/lark/presentation/explore_results.py +2276 -0
  681. loopx/extensions/lark/presentation/explore_singleflight.py +127 -0
  682. loopx/extensions/lark/presentation/explore_source_guard.py +121 -0
  683. loopx/extensions/lark/presentation/explore_stage_document.py +703 -0
  684. loopx/extensions/lark/presentation/explore_visual_integrity.py +122 -0
  685. loopx/extensions/lark/presentation/explore_visual_readback.py +452 -0
  686. loopx/extensions/lark/presentation/explore_visual_styles.py +156 -0
  687. loopx/extensions/lark/presentation/issue_fix_surface.py +612 -0
  688. loopx/extensions/lark/presentation/kanban.py +2791 -0
  689. loopx/extensions/lark/presentation/message_card.py +112 -0
  690. loopx/extensions/lark/presentation/periodic_report.py +261 -0
  691. loopx/extensions/lark/presentation/projection_rows.py +600 -0
  692. loopx/extensions/lark/presentation/record_io.py +95 -0
  693. loopx/extensions/lark/presentation/sync_receipt.py +145 -0
  694. loopx/extensions/lark/private_json.py +40 -0
  695. loopx/extensions/lark/provider.py +86 -0
  696. loopx/extensions/lark/reviewer_notification.py +604 -0
  697. loopx/extensions/manifest.py +385 -0
  698. loopx/extensions/openviking_periodic_report/__init__.py +17 -0
  699. loopx/extensions/openviking_periodic_report/activation.py +173 -0
  700. loopx/extensions/openviking_periodic_report/extension.toml +17 -0
  701. loopx/extensions/openviking_periodic_report/provider.py +355 -0
  702. loopx/extensions/openviking_periodic_report/sink.py +117 -0
  703. loopx/extensions/openviking_semantic_preference/__init__.py +5 -0
  704. loopx/extensions/openviking_semantic_preference/extension.toml +16 -0
  705. loopx/extensions/openviking_semantic_preference/history_export.py +484 -0
  706. loopx/extensions/openviking_semantic_preference/project_peer.py +68 -0
  707. loopx/extensions/openviking_semantic_preference/provider.py +312 -0
  708. loopx/extensions/presentation.py +979 -0
  709. loopx/extensions/process_runtime.py +204 -0
  710. loopx/extensions/readiness.py +168 -0
  711. loopx/extensions/runtime.py +931 -0
  712. loopx/extensions/scaffold.py +335 -0
  713. loopx/feedback.py +581 -0
  714. loopx/file_lock.py +382 -0
  715. loopx/global_registry.py +842 -0
  716. loopx/global_risks.py +970 -0
  717. loopx/global_todos.py +568 -0
  718. loopx/handoff_budget.py +28 -0
  719. loopx/heartbeat_prequota.py +80 -0
  720. loopx/heartbeat_prompt.py +159 -0
  721. loopx/help_surface.py +516 -0
  722. loopx/history.py +1507 -0
  723. loopx/host_loop_activation.py +1311 -0
  724. loopx/host_mode_planner.py +991 -0
  725. loopx/install_contract.py +1 -0
  726. loopx/interface_budget.py +196 -0
  727. loopx/long_task_cadence.py +208 -0
  728. loopx/materials.py +185 -0
  729. loopx/ml_experiment.py +3 -0
  730. loopx/onboarding.py +214 -0
  731. loopx/opencode2_goal_mode/README.md +81 -0
  732. loopx/opencode2_goal_mode/__init__.py +9 -0
  733. loopx/opencode2_goal_mode/opencode2-goal-worker.mjs +1018 -0
  734. loopx/opencode_goal_mode/README.md +99 -0
  735. loopx/opencode_goal_mode/__init__.py +13 -0
  736. loopx/opencode_goal_mode/goal-bridge-runtime.mjs +858 -0
  737. loopx/opencode_goal_mode/loopx-goal.js +8 -0
  738. loopx/operator_gate.py +420 -0
  739. loopx/orchestration.py +127 -0
  740. loopx/paths.py +59 -0
  741. loopx/pi_goal_mode/README.md +67 -0
  742. loopx/pi_goal_mode/__init__.py +13 -0
  743. loopx/pi_goal_mode/loopx-goal.ts +254 -0
  744. loopx/pi_goal_mode/pi-goal-loop-runtime.mjs +574 -0
  745. loopx/pr_review.py +1206 -0
  746. loopx/presentation/__init__.py +1 -0
  747. loopx/presentation/explore_views.py +1334 -0
  748. loopx/presentation/markdown.py +61 -0
  749. loopx/presentation/projection_source_reconcile.py +140 -0
  750. loopx/presentation/public_safety.py +42 -0
  751. loopx/presentation/renderers/__init__.py +17 -0
  752. loopx/presentation/renderers/goal_channel_html.py +269 -0
  753. loopx/presentation/renderers/periodic_report_html.py +786 -0
  754. loopx/presentation/renderers/periodic_report_markdown.py +184 -0
  755. loopx/presentation/renderers/quota_event_markdown.py +116 -0
  756. loopx/presentation/renderers/quota_markdown.py +1112 -0
  757. loopx/presentation/renderers/status_markdown.py +1570 -0
  758. loopx/presentation/renderers/trajectory_hygiene_markdown.py +39 -0
  759. loopx/presentation/renderers/turn_envelope_markdown.py +33 -0
  760. loopx/presentation/sinks/__init__.py +5 -0
  761. loopx/presentation/sinks/openviking_periodic_report.py +7 -0
  762. loopx/presentation/static_site.py +691 -0
  763. loopx/presets.py +369 -0
  764. loopx/project_alias.py +217 -0
  765. loopx/project_map.py +589 -0
  766. loopx/project_prompt.py +1153 -0
  767. loopx/project_skill_cli.py +125 -0
  768. loopx/project_skill_delivery.py +470 -0
  769. loopx/project_uninstall.py +462 -0
  770. loopx/promotion_gate.py +197 -0
  771. loopx/quota.py +1197 -0
  772. loopx/ready_score.py +413 -0
  773. loopx/registry.py +621 -0
  774. loopx/registry_writability.py +64 -0
  775. loopx/release_candidate.py +148 -0
  776. loopx/release_manifest.py +316 -0
  777. loopx/repository_identity.py +100 -0
  778. loopx/review_packet.py +1024 -0
  779. loopx/rollout_event_log.py +505 -0
  780. loopx/runtime.py +112 -0
  781. loopx/self_update.py +750 -0
  782. loopx/session_runtime.py +418 -0
  783. loopx/skill_install_readback.py +500 -0
  784. loopx/slash_command_install.py +1393 -0
  785. loopx/slash_commands.py +264 -0
  786. loopx/state_backup.py +573 -0
  787. loopx/state_migration.py +350 -0
  788. loopx/state_projection.py +809 -0
  789. loopx/state_refresh.py +1416 -0
  790. loopx/status.py +1383 -0
  791. loopx/status_server.py +935 -0
  792. loopx/summary_all.py +725 -0
  793. loopx/terminal_bench_agent.py +2056 -0
  794. loopx/thread_agent_binding.py +408 -0
  795. loopx/todo_followups.py +168 -0
  796. loopx/todo_suggestion_prompt.py +204 -0
  797. loopx/todos.py +2229 -0
  798. loopx/turn_identity.py +17 -0
  799. loopx/upgrade.py +1083 -0
  800. loopx/visible_governance.py +667 -0
  801. loopx/visible_multi_agent_launcher.py +1253 -0
  802. loopx/visible_multi_agent_tmux.py +429 -0
  803. loopx/worker_bridge.py +1574 -0
  804. loopx-0.4.8.dist-info/METADATA +708 -0
  805. loopx-0.4.8.dist-info/RECORD +811 -0
  806. loopx-0.4.8.dist-info/WHEEL +5 -0
  807. loopx-0.4.8.dist-info/entry_points.txt +5 -0
  808. loopx-0.4.8.dist-info/licenses/LICENSE +202 -0
  809. loopx-0.4.8.dist-info/licenses/LICENSE-MIT +21 -0
  810. loopx-0.4.8.dist-info/licenses/NOTICE +6 -0
  811. loopx-0.4.8.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1578 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from dataclasses import dataclass
5
+ from typing import Any
6
+
7
+ from .agent_scope_frontier import (
8
+ AGENT_LANE_FRONTIER_HINT_SCHEMA_VERSION,
9
+ AgentLaneFrontierHintDecision,
10
+ AgentScopeFrontierAction,
11
+ agent_scope_frontier_action as _agent_scope_frontier_action,
12
+ build_agent_scope_frontier_payload,
13
+ )
14
+ from ..todos.decision_scope import todo_gate_relation, todo_gate_relation_blocks_agent
15
+ from ..work_items.work_lane import (
16
+ work_lane_contract_is_due_monitor_attempt,
17
+ work_lane_contract_requires_current_agent_attempt,
18
+ )
19
+ from ..todos.contract import (
20
+ TODO_STATUS_OPEN,
21
+ TODO_TASK_CLASS_ADVANCEMENT,
22
+ TODO_TASK_CLASS_MONITOR,
23
+ normalize_todo_blocks_agent,
24
+ normalize_todo_bound_agent,
25
+ normalize_todo_claimed_by,
26
+ normalize_todo_excluded_agents,
27
+ normalize_todo_global_gate,
28
+ normalize_todo_id,
29
+ normalize_todo_status,
30
+ normalize_todo_task_class,
31
+ )
32
+ from ..todos.handoff_gate import HandoffGateState
33
+ from ..todos.deferred_resume import todo_summary_blocked_successor_items
34
+ from ..todos.projection import (
35
+ todo_item_claimed_by_agent_or_unclaimed,
36
+ todo_item_excludes_agent,
37
+ todo_item_is_actionable_open,
38
+ todo_item_is_deferred,
39
+ todo_item_task_class,
40
+ todo_projection_sort_key,
41
+ )
42
+ from ..todos.summary_item import compact_todo_summary_item
43
+ from ..todos.user_gate import (
44
+ open_todo_count as _open_todo_count,
45
+ open_user_gate_todo_items as _open_user_gate_todo_items,
46
+ )
47
+
48
+ _ACTION_SCOPE_STOPWORDS = {
49
+ "a",
50
+ "an",
51
+ "and",
52
+ "approve",
53
+ "approved",
54
+ "approval",
55
+ "before",
56
+ "blocked",
57
+ "continue",
58
+ "decide",
59
+ "decision",
60
+ "for",
61
+ "from",
62
+ "gate",
63
+ "gated",
64
+ "goal",
65
+ "harness",
66
+ "internal",
67
+ "material",
68
+ "next",
69
+ "open",
70
+ "owner",
71
+ "p0",
72
+ "p1",
73
+ "p2",
74
+ "p3",
75
+ "provide",
76
+ "read",
77
+ "registered",
78
+ "safe",
79
+ "should",
80
+ "sync",
81
+ "the",
82
+ "todo",
83
+ "until",
84
+ "user",
85
+ "whether",
86
+ "with",
87
+ }
88
+
89
+ AGENT_TASK_SCOPE = "goal_all_read_claimed_run_global_read_v0"
90
+
91
+
92
+ def _agent_identity_has_scoped_lane(agent_identity: dict[str, Any] | None) -> bool:
93
+ return bool(
94
+ isinstance(agent_identity, dict)
95
+ and normalize_todo_claimed_by(agent_identity.get("agent_id"))
96
+ )
97
+
98
+
99
+ def _attach_agent_identity_contracts(
100
+ *,
101
+ payload: dict[str, Any],
102
+ agent_identity: dict[str, Any] | None,
103
+ ) -> dict[str, Any]:
104
+ if not agent_identity:
105
+ return payload
106
+ payload["agent_identity"] = agent_identity
107
+ payload["task_scope"] = AGENT_TASK_SCOPE
108
+ return payload
109
+
110
+
111
+ def _todo_task_class(item: dict[str, Any]) -> str:
112
+ return todo_item_task_class(item)
113
+
114
+
115
+ def _todo_projection_sort_key(item: dict[str, Any]) -> tuple[int, int, int, str]:
116
+ return todo_projection_sort_key(item)
117
+
118
+
119
+ def _monitor_debt_projection_sort_key(item: dict[str, Any]) -> tuple[Any, ...]:
120
+ priority, *remainder = _todo_projection_sort_key(item)
121
+ advancement_first = 0 if _todo_task_class(item) == TODO_TASK_CLASS_ADVANCEMENT else 1
122
+ return (priority, advancement_first, *remainder)
123
+
124
+
125
+ def _todo_item_is_actionable_open(item: dict[str, Any]) -> bool:
126
+ return todo_item_is_actionable_open(item)
127
+
128
+
129
+ def agent_scope_item_claimed_by(item: dict[str, Any]) -> str | None:
130
+ return normalize_todo_claimed_by(item.get("claimed_by"))
131
+
132
+
133
+ def agent_scope_item_blocks_agent(item: dict[str, Any]) -> str | None:
134
+ return normalize_todo_blocks_agent(item.get("blocks_agent"))
135
+
136
+
137
+ def agent_scope_item_bound_agent(item: dict[str, Any]) -> str | None:
138
+ return normalize_todo_bound_agent(item.get("bound_agent"))
139
+
140
+
141
+ def agent_scope_item_excluded_agents(item: dict[str, Any]) -> list[str]:
142
+ return normalize_todo_excluded_agents(item.get("excluded_agents"))
143
+
144
+
145
+ def agent_scope_item_claimed_by_agent_or_unclaimed(
146
+ item: dict[str, Any],
147
+ *,
148
+ agent_id: str | None,
149
+ ) -> bool:
150
+ return todo_item_claimed_by_agent_or_unclaimed(item, agent_id=agent_id)
151
+
152
+
153
+ def agent_scope_item_matches_agent_or_unclaimed(
154
+ item: dict[str, Any],
155
+ *,
156
+ agent_id: str | None,
157
+ ) -> bool:
158
+ normalized_agent_id = normalize_todo_claimed_by(agent_id)
159
+ if not normalized_agent_id:
160
+ return True
161
+ if todo_item_excludes_agent(item, agent_id=normalized_agent_id):
162
+ return False
163
+ return agent_scope_item_claimed_by_agent_or_unclaimed(
164
+ item,
165
+ agent_id=normalized_agent_id,
166
+ )
167
+
168
+
169
+ def agent_scope_count_advancement_items(
170
+ items: Any,
171
+ *,
172
+ claimed_by: str | None = None,
173
+ ) -> int:
174
+ if not isinstance(items, list):
175
+ return 0
176
+ normalized_claimed_by = (
177
+ "__unclaimed__"
178
+ if claimed_by == "__unclaimed__"
179
+ else normalize_todo_claimed_by(claimed_by)
180
+ if claimed_by is not None
181
+ else None
182
+ )
183
+ count = 0
184
+ for item in items:
185
+ if not isinstance(item, dict):
186
+ continue
187
+ if not _todo_item_is_actionable_open(item):
188
+ continue
189
+ if _todo_task_class(item) != TODO_TASK_CLASS_ADVANCEMENT:
190
+ continue
191
+ item_claimed_by = agent_scope_item_claimed_by(item)
192
+ if normalized_claimed_by == "__unclaimed__":
193
+ if item_claimed_by:
194
+ continue
195
+ elif normalized_claimed_by is not None and item_claimed_by != normalized_claimed_by:
196
+ continue
197
+ count += 1
198
+ return count
199
+
200
+
201
+ def _protocol_action_text(value: Any, *, limit: int = 220) -> str | None:
202
+ if value is None:
203
+ return None
204
+ text = " ".join(str(value).strip().split())
205
+ if not text:
206
+ return None
207
+ if len(text) <= limit:
208
+ return text
209
+ return text[: max(0, limit - 3)].rstrip() + "..."
210
+
211
+
212
+ def _action_scope_tokens_from_text(text: str) -> set[str]:
213
+ return {
214
+ token
215
+ for token in re.findall(
216
+ r"[a-z0-9]+",
217
+ text.lower().replace("_", " ").replace("-", " "),
218
+ )
219
+ if len(token) > 1 and token not in _ACTION_SCOPE_STOPWORDS
220
+ }
221
+
222
+
223
+ def _todo_action_kind_tokens(item: dict[str, Any]) -> set[str]:
224
+ return _action_scope_tokens_from_text(str(item.get("action_kind") or ""))
225
+
226
+
227
+ def _todo_action_scope_tokens(item: dict[str, Any]) -> set[str]:
228
+ text = " ".join(
229
+ str(value or "")
230
+ for value in (item.get("action_kind"), item.get("title"), item.get("text"))
231
+ if str(value or "").strip()
232
+ )
233
+ return _action_scope_tokens_from_text(text)
234
+
235
+
236
+ def _todo_gate_relation(gate: dict[str, Any], agent_item: dict[str, Any]) -> dict[str, Any] | None:
237
+ return todo_gate_relation(gate, agent_item)
238
+
239
+
240
+ def _user_gate_blocks_agent_item(gate: dict[str, Any], agent_item: dict[str, Any]) -> bool:
241
+ if normalize_todo_global_gate(gate.get("global_gate")):
242
+ return True
243
+ relation = _todo_gate_relation(gate, agent_item)
244
+ if relation:
245
+ return todo_gate_relation_blocks_agent(relation)
246
+
247
+ gate_action_tokens = _todo_action_kind_tokens(gate)
248
+ agent_action_tokens = _todo_action_kind_tokens(agent_item)
249
+ if gate_action_tokens and agent_action_tokens:
250
+ return bool(gate_action_tokens & agent_action_tokens)
251
+ if agent_action_tokens:
252
+ return False
253
+
254
+ gate_tokens = _todo_action_scope_tokens(gate)
255
+ agent_tokens = _todo_action_scope_tokens(agent_item)
256
+ if not gate_tokens or not agent_tokens:
257
+ return False
258
+ if gate_action_tokens:
259
+ return len(gate_action_tokens & agent_tokens) >= 2
260
+ return len(gate_tokens & agent_tokens) >= 3
261
+
262
+
263
+ def _todo_item_claimed_by_agent_or_unclaimed(item: dict[str, Any], *, agent_id: str) -> bool:
264
+ return agent_scope_item_claimed_by_agent_or_unclaimed(item, agent_id=agent_id)
265
+
266
+
267
+ def _agent_scope_selectable_todo_item(
268
+ item: dict[str, Any],
269
+ *,
270
+ agent_identity: dict[str, Any] | None,
271
+ ) -> bool:
272
+ if not isinstance(agent_identity, dict):
273
+ return True
274
+ agent_id = normalize_todo_claimed_by(agent_identity.get("agent_id"))
275
+ if not agent_id:
276
+ return True
277
+ if todo_item_excludes_agent(item, agent_id=agent_id):
278
+ return False
279
+ return _todo_item_claimed_by_agent_or_unclaimed(item, agent_id=agent_id)
280
+
281
+
282
+ def _agent_scope_filter_user_gate_items(
283
+ open_items: list[dict[str, Any]],
284
+ *,
285
+ agent_identity: dict[str, Any] | None,
286
+ ) -> tuple[list[dict[str, Any]], list[dict[str, Any]], dict[str, Any] | None]:
287
+ if not isinstance(agent_identity, dict):
288
+ return open_items, [], None
289
+ agent_id = normalize_todo_claimed_by(agent_identity.get("agent_id"))
290
+ if not agent_id:
291
+ return open_items, [], None
292
+
293
+ current_agent_items: list[dict[str, Any]] = []
294
+ other_agent_scoped_items: list[dict[str, Any]] = []
295
+ for item in open_items:
296
+ if normalize_todo_global_gate(item.get("global_gate")):
297
+ current_agent_items.append(item)
298
+ continue
299
+ blocks_agent = agent_scope_item_blocks_agent(item)
300
+ if blocks_agent:
301
+ if blocks_agent != agent_id:
302
+ other_agent_scoped_items.append(item)
303
+ continue
304
+ current_agent_items.append(item)
305
+ continue
306
+ claimed_by = agent_scope_item_claimed_by(item)
307
+ if claimed_by and claimed_by != agent_id:
308
+ other_agent_scoped_items.append(item)
309
+ continue
310
+ current_agent_items.append(item)
311
+ if not other_agent_scoped_items:
312
+ return open_items, [], None
313
+
314
+ return (
315
+ current_agent_items,
316
+ other_agent_scoped_items,
317
+ {
318
+ "schema_version": "agent_scoped_user_gate_filter_v0",
319
+ "agent_id": agent_id,
320
+ "policy": (
321
+ "user todos scoped to another agent by blocks_agent or claimed_by "
322
+ "remain visible but do not block this agent's quota lane"
323
+ ),
324
+ "current_agent_blocking_open_count": len(current_agent_items),
325
+ "other_agent_scoped_open_count": len(other_agent_scoped_items),
326
+ },
327
+ )
328
+
329
+
330
+ def _agent_scope_filter_user_action_items(
331
+ open_items: list[dict[str, Any]],
332
+ *,
333
+ agent_identity: dict[str, Any] | None,
334
+ ) -> tuple[list[dict[str, Any]], list[dict[str, Any]], dict[str, Any] | None]:
335
+ if not isinstance(agent_identity, dict):
336
+ return open_items, [], None
337
+ agent_id = normalize_todo_claimed_by(agent_identity.get("agent_id"))
338
+ if not agent_id:
339
+ return open_items, [], None
340
+
341
+ current_agent_items: list[dict[str, Any]] = []
342
+ other_agent_items: list[dict[str, Any]] = []
343
+ for item in open_items:
344
+ # claimed_by is a read-only compatibility fallback for user actions
345
+ # written before bound_agent became a first-class routing relation.
346
+ bound_agent = agent_scope_item_bound_agent(item) or agent_scope_item_claimed_by(item)
347
+ if bound_agent and bound_agent != agent_id:
348
+ other_agent_items.append(item)
349
+ continue
350
+ current_agent_items.append(item)
351
+ if not other_agent_items:
352
+ return open_items, [], None
353
+ return (
354
+ current_agent_items,
355
+ other_agent_items,
356
+ {
357
+ "schema_version": "agent_scoped_user_action_filter_v0",
358
+ "agent_id": agent_id,
359
+ "policy": (
360
+ "user actions bound to another agent remain diagnostic-only and "
361
+ "must not enter this agent's reminder channel"
362
+ ),
363
+ "current_agent_user_action_open_count": len(current_agent_items),
364
+ "other_agent_bound_user_action_open_count": len(other_agent_items),
365
+ },
366
+ )
367
+
368
+
369
+ def _scoped_user_gate_fallback(
370
+ user_todo_summary: dict[str, Any] | None,
371
+ agent_todo_summary: dict[str, Any] | None,
372
+ *,
373
+ capability_gate: dict[str, Any] | None = None,
374
+ allow_unrelated_gate: bool = False,
375
+ monitor_debt_backoff_active: bool = False,
376
+ ) -> dict[str, Any] | None:
377
+ gates = _open_user_gate_todo_items(user_todo_summary)
378
+ if not gates or not isinstance(agent_todo_summary, dict):
379
+ return None
380
+
381
+ due_monitor_candidates = (
382
+ agent_todo_summary.get("monitor_due_items")
383
+ if isinstance(agent_todo_summary.get("monitor_due_items"), list)
384
+ else []
385
+ )
386
+ ready_deferred_candidates = (
387
+ agent_todo_summary.get("deferred_resume_candidates")
388
+ if isinstance(agent_todo_summary.get("deferred_resume_candidates"), list)
389
+ else []
390
+ )
391
+
392
+ # An empty capability projection is authoritative for advancement work.
393
+ # Due monitors are projected separately from advancement candidates and are
394
+ # already current-agent scoped, due, and capability-runnable here.
395
+ capability_candidates = (
396
+ capability_gate.get("runnable_candidates")
397
+ if isinstance(capability_gate, dict)
398
+ else None
399
+ )
400
+ if isinstance(capability_candidates, list):
401
+ executable_items = [
402
+ *due_monitor_candidates,
403
+ *capability_candidates,
404
+ *ready_deferred_candidates,
405
+ ]
406
+ else:
407
+ advancement_items = (
408
+ agent_todo_summary.get("executable_backlog_items")
409
+ if isinstance(agent_todo_summary.get("executable_backlog_items"), list)
410
+ else agent_todo_summary.get("first_executable_items")
411
+ if isinstance(agent_todo_summary.get("first_executable_items"), list)
412
+ else []
413
+ )
414
+ executable_items = [
415
+ *due_monitor_candidates,
416
+ *advancement_items,
417
+ *ready_deferred_candidates,
418
+ ]
419
+ executable_items = [item for item in executable_items if isinstance(item, dict)]
420
+ deduped_executable_items: list[dict[str, Any]] = []
421
+ seen_todo_ids: set[str] = set()
422
+ for item in executable_items:
423
+ todo_id = normalize_todo_id(item.get("todo_id"))
424
+ if todo_id and todo_id in seen_todo_ids:
425
+ continue
426
+ if todo_id:
427
+ seen_todo_ids.add(todo_id)
428
+ deduped_executable_items.append(item)
429
+ executable_items = sorted(
430
+ deduped_executable_items,
431
+ key=(
432
+ _monitor_debt_projection_sort_key
433
+ if monitor_debt_backoff_active
434
+ else _todo_projection_sort_key
435
+ ),
436
+ )
437
+ claim_scope = (
438
+ agent_todo_summary.get("claim_scope")
439
+ if isinstance(agent_todo_summary.get("claim_scope"), dict)
440
+ else None
441
+ )
442
+ if claim_scope:
443
+ agent_id = normalize_todo_claimed_by(claim_scope.get("agent_id"))
444
+ executable_items = [
445
+ item
446
+ for item in executable_items
447
+ if agent_scope_item_claimed_by_agent_or_unclaimed(item, agent_id=agent_id)
448
+ ]
449
+ blocked_items: list[dict[str, Any]] = []
450
+ selected: dict[str, Any] | None = None
451
+ blocking_gate: dict[str, Any] | None = None
452
+ for item in executable_items:
453
+ matching_gate = next(
454
+ (gate for gate in gates if _user_gate_blocks_agent_item(gate, item)),
455
+ None,
456
+ )
457
+ if matching_gate:
458
+ blocking_gate = blocking_gate or matching_gate
459
+ text = str(item.get("text") or "").strip()
460
+ blocked_item = compact_todo_summary_item(item, text=text)
461
+ relation = _todo_gate_relation(matching_gate, item)
462
+ if relation:
463
+ blocked_item["todo_gate_relation"] = relation
464
+ blocked_items.append(blocked_item)
465
+ continue
466
+ if selected is None:
467
+ selected = item
468
+
469
+ if selected is None:
470
+ return None
471
+ if not blocking_gate and not allow_unrelated_gate:
472
+ return None
473
+
474
+ selected_text = str(selected.get("text") or "").strip()
475
+ gate_to_surface = blocking_gate or gates[0]
476
+ selected_item = compact_todo_summary_item(selected, text=selected_text)
477
+ selected_is_deferred_replan = (
478
+ todo_item_is_deferred(selected) and selected.get("resume_ready") is True
479
+ )
480
+ if selected_is_deferred_replan:
481
+ selected_item["fallback_kind"] = "deferred_successor_replan"
482
+ selected_relation = _todo_gate_relation(gate_to_surface, selected)
483
+ if selected_relation:
484
+ selected_item["todo_gate_relation"] = selected_relation
485
+ gate_text = str(gate_to_surface.get("text") or "").strip()
486
+ reason = (
487
+ "an open user_gate blocks a scoped agent todo, but a non-dependent "
488
+ "executable fallback remains available"
489
+ if blocking_gate
490
+ else (
491
+ "an open user_gate blocks a different action scope, but the ready "
492
+ "deferred successor can be replanned independently"
493
+ if selected_is_deferred_replan
494
+ else "an open user_gate blocks a different action scope, but the selected "
495
+ "executable agent todo is non-dependent and safe to advance"
496
+ )
497
+ )
498
+ return {
499
+ "schema_version": "scoped_user_gate_fallback_v0",
500
+ "kind": "scoped_user_gate_fallback",
501
+ "notify_user": True,
502
+ "requires_user_action": True,
503
+ "reason": reason,
504
+ "blocked_user_gate": compact_todo_summary_item(gate_to_surface, text=gate_text),
505
+ "blocked_agent_items": blocked_items[:3],
506
+ "selected_executable": selected_item,
507
+ "recommended_action": (
508
+ "Notify the user about the scoped gate; then reopen, supersede, or "
509
+ "record a no-follow-up rationale for the selected ready deferred "
510
+ "successor, spending only after validated writeback."
511
+ if selected_is_deferred_replan
512
+ else "Notify the user about the scoped gate; then execute the selected "
513
+ "non-gated fallback and spend only after validated writeback."
514
+ ),
515
+ }
516
+
517
+
518
+ def _first_executable_todo_text(agent_todo_summary: dict[str, Any] | None) -> str | None:
519
+ if not isinstance(agent_todo_summary, dict):
520
+ return None
521
+ items = (
522
+ agent_todo_summary.get("first_executable_items")
523
+ if isinstance(agent_todo_summary.get("first_executable_items"), list)
524
+ else []
525
+ )
526
+ for item in items:
527
+ if not isinstance(item, dict):
528
+ continue
529
+ if not _todo_item_is_actionable_open(item):
530
+ continue
531
+ if _todo_task_class(item) != TODO_TASK_CLASS_ADVANCEMENT:
532
+ continue
533
+ text = _protocol_action_text(item.get("text"), limit=320)
534
+ if text:
535
+ return text
536
+ return None
537
+
538
+
539
+ def _first_monitor_todo_text(agent_todo_summary: dict[str, Any] | None) -> str | None:
540
+ if not isinstance(agent_todo_summary, dict):
541
+ return None
542
+ for key in ("monitor_due_items", "monitor_open_items"):
543
+ items = agent_todo_summary.get(key) if isinstance(agent_todo_summary.get(key), list) else []
544
+ for item in items:
545
+ if not isinstance(item, dict):
546
+ continue
547
+ if not _todo_item_is_actionable_open(item):
548
+ continue
549
+ if _todo_task_class(item) != TODO_TASK_CLASS_MONITOR:
550
+ continue
551
+ text = _protocol_action_text(item.get("text"), limit=320)
552
+ if text:
553
+ return text
554
+ return None
555
+
556
+
557
+ def _agent_scoped_user_todo_override(
558
+ *,
559
+ state: str,
560
+ item: dict[str, Any],
561
+ user_todo_summary: dict[str, Any] | None,
562
+ agent_todo_summary: dict[str, Any] | None,
563
+ agent_identity: dict[str, Any] | None,
564
+ ) -> dict[str, Any] | None:
565
+ if state != "operator_gate":
566
+ return None
567
+ if not isinstance(agent_identity, dict):
568
+ return None
569
+ if not isinstance(user_todo_summary, dict):
570
+ return None
571
+ # A non-blocking user_action may still need a reminder without preserving
572
+ # an operator gate inherited from a different agent's user_gate.
573
+ if _open_user_gate_todo_items(user_todo_summary):
574
+ return None
575
+ other_gate_count = _open_todo_count(
576
+ {"open_count": user_todo_summary.get("other_agent_scoped_open_count")}
577
+ )
578
+ if item.get("operator_question") or item.get("missing_gates"):
579
+ return None
580
+ selected_action = _first_executable_todo_text(agent_todo_summary)
581
+ if not selected_action:
582
+ selected_action = _first_monitor_todo_text(agent_todo_summary)
583
+ agent_id = normalize_todo_claimed_by(agent_identity.get("agent_id"))
584
+ if other_gate_count > 0:
585
+ if not selected_action:
586
+ return None
587
+ return {
588
+ "schema_version": "agent_scoped_user_gate_override_v0",
589
+ "kind": "agent_scoped_user_gate_override",
590
+ "agent_id": agent_id,
591
+ "from_state": "operator_gate",
592
+ "to_state": "eligible",
593
+ "other_agent_scoped_open_count": other_gate_count,
594
+ "selected_action": selected_action,
595
+ "reason": (
596
+ "the open user gate is scoped to another agent via blocks_agent or "
597
+ "claimed_by; this agent still has an executable in-scope todo"
598
+ ),
599
+ }
600
+ other_action_count = _open_todo_count(
601
+ {
602
+ "open_count": user_todo_summary.get(
603
+ "other_agent_bound_user_action_open_count"
604
+ )
605
+ }
606
+ )
607
+ if other_action_count <= 0:
608
+ return None
609
+ result: dict[str, Any] = {
610
+ "schema_version": "agent_scoped_user_action_override_v0",
611
+ "kind": "agent_scoped_user_action_override",
612
+ "agent_id": agent_id,
613
+ "from_state": "operator_gate",
614
+ "to_state": "eligible" if selected_action else "waiting",
615
+ "other_agent_bound_user_action_open_count": other_action_count,
616
+ "reason": (
617
+ "the only open user actions are bound to another agent; they remain "
618
+ "diagnostic-only for this lane and cannot create an operator gate"
619
+ ),
620
+ "quota_patch": {
621
+ "safe_bypass_allowed": False,
622
+ "safe_bypass_kind": None,
623
+ "blocked_action_scope": None,
624
+ },
625
+ "item_patch": {
626
+ "status": "active_state_agent_todo" if selected_action else "agent_scope_wait",
627
+ "waiting_on": "codex" if selected_action else "agent_scope",
628
+ "recommended_action": (
629
+ selected_action or "wait for work or a user action bound to this agent"
630
+ ),
631
+ },
632
+ }
633
+ if selected_action:
634
+ result["selected_action"] = selected_action
635
+ return result
636
+
637
+
638
+ def _count_advancement_items(items: Any, *, claimed_by: str | None = None) -> int:
639
+ return agent_scope_count_advancement_items(items, claimed_by=claimed_by)
640
+
641
+
642
+ def _selectable_unclaimed_advancement_count(
643
+ agent_todo_summary: dict[str, Any],
644
+ ) -> int:
645
+ executable_items = agent_todo_summary.get("executable_backlog_items")
646
+ source_items = (
647
+ executable_items
648
+ if isinstance(executable_items, list)
649
+ else agent_todo_summary.get("unclaimed_priority_open_items")
650
+ )
651
+ return _count_advancement_items(source_items, claimed_by="__unclaimed__")
652
+
653
+
654
+ def _first_compact_todo_id(items: Any) -> str | None:
655
+ if not isinstance(items, list):
656
+ return None
657
+ for item in items:
658
+ if not isinstance(item, dict):
659
+ continue
660
+ todo_id = normalize_todo_id(item.get("todo_id"))
661
+ if todo_id:
662
+ return todo_id
663
+ return None
664
+
665
+
666
+ def _agent_lane_frontier_hint(
667
+ *,
668
+ goal_id: str,
669
+ agent_identity: dict[str, Any] | None,
670
+ agent_todo_summary: dict[str, Any] | None,
671
+ agent_lane_next_action: dict[str, Any] | None,
672
+ agent_scope_frontier: dict[str, Any] | None,
673
+ work_lane_contract: dict[str, Any] | None,
674
+ ) -> dict[str, Any] | None:
675
+ if not _agent_identity_has_scoped_lane(agent_identity):
676
+ return None
677
+ agent_id = normalize_todo_claimed_by(agent_identity.get("agent_id"))
678
+ if not agent_id:
679
+ return None
680
+
681
+ def build_hint(
682
+ decision: AgentLaneFrontierHintDecision,
683
+ *,
684
+ source: str,
685
+ reason_code: str,
686
+ target_todo_id: str | None = None,
687
+ quiet_noop_allowed: bool,
688
+ next_cli_action: str | None = None,
689
+ ) -> dict[str, Any]:
690
+ hint: dict[str, Any] = {
691
+ "schema_version": AGENT_LANE_FRONTIER_HINT_SCHEMA_VERSION,
692
+ "decision": decision.value,
693
+ "agent_id": agent_id,
694
+ "agent_model": "peer_v1",
695
+ "source": source,
696
+ "reason_code": reason_code,
697
+ "quiet_noop_allowed": quiet_noop_allowed,
698
+ "uses_structured_frontier": True,
699
+ }
700
+ if target_todo_id:
701
+ hint["target_todo_id"] = target_todo_id
702
+ if next_cli_action:
703
+ hint["next_cli_action"] = next_cli_action
704
+ return hint
705
+
706
+ if isinstance(agent_lane_next_action, dict):
707
+ selected_by = str(agent_lane_next_action.get("selected_by") or "")
708
+ claim_required = agent_lane_next_action.get("claim_required_before_work") is True
709
+ target_todo_id = normalize_todo_id(agent_lane_next_action.get("todo_id"))
710
+ if selected_by == "unclaimed_todo" or claim_required:
711
+ action = None
712
+ if target_todo_id:
713
+ action = (
714
+ f"loopx todo claim --goal-id {goal_id} --todo-id {target_todo_id} "
715
+ f"--claimed-by {agent_id} --agent-id {agent_id}"
716
+ )
717
+ return build_hint(
718
+ AgentLaneFrontierHintDecision.CLAIM_UNOWNED_IN_SCOPE,
719
+ source="agent_lane_next_action",
720
+ reason_code="unclaimed_advancement_selected",
721
+ target_todo_id=target_todo_id,
722
+ quiet_noop_allowed=False,
723
+ next_cli_action=action,
724
+ )
725
+
726
+ if work_lane_contract_is_due_monitor_attempt(work_lane_contract):
727
+ return None
728
+ if work_lane_contract_requires_current_agent_attempt(work_lane_contract):
729
+ return None
730
+
731
+ frontier = agent_scope_frontier if isinstance(agent_scope_frontier, dict) else {}
732
+ frontier_action = _agent_scope_frontier_action(frontier.get("action"))
733
+ if frontier_action == AgentScopeFrontierAction.SUCCESSOR_REPLAN_REQUIRED:
734
+ cleared_todo_id = _first_compact_todo_id(
735
+ frontier.get("cleared_without_successor_handoff_gates")
736
+ )
737
+ if cleared_todo_id:
738
+ return build_hint(
739
+ AgentLaneFrontierHintDecision.RECORD_NO_FOLLOWUP,
740
+ source="agent_scope_frontier",
741
+ reason_code="cleared_handoff_without_successor",
742
+ target_todo_id=cleared_todo_id,
743
+ quiet_noop_allowed=False,
744
+ next_cli_action=(
745
+ f"loopx todo complete --goal-id {goal_id} --todo-id {cleared_todo_id} "
746
+ f"--agent-id {agent_id} --no-follow-up "
747
+ "--evidence '<public-safe rationale>'"
748
+ ),
749
+ )
750
+ monitor_candidates = (
751
+ frontier.get("monitor_blocked_resume_candidates")
752
+ if isinstance(frontier.get("monitor_blocked_resume_candidates"), list)
753
+ else []
754
+ )
755
+ monitor_candidate = (
756
+ monitor_candidates[0]
757
+ if monitor_candidates and isinstance(monitor_candidates[0], dict)
758
+ else {}
759
+ )
760
+ monitor_blocker_id = normalize_todo_id(
761
+ monitor_candidate.get("blocking_monitor_todo_id")
762
+ )
763
+ if monitor_candidate:
764
+ target_todo_id = normalize_todo_id(monitor_candidate.get("todo_id"))
765
+ return build_hint(
766
+ AgentLaneFrontierHintDecision.ADD_NEXT_ADVANCEMENT,
767
+ source="agent_scope_frontier",
768
+ reason_code="resume_blocked_by_open_monitor",
769
+ target_todo_id=target_todo_id or monitor_blocker_id,
770
+ quiet_noop_allowed=False,
771
+ next_cli_action=(
772
+ f"loopx todo complete --goal-id {goal_id} --todo-id {monitor_blocker_id} "
773
+ f"--agent-id {agent_id} --evidence '<validated gate evidence>'"
774
+ if monitor_blocker_id
775
+ else None
776
+ ),
777
+ )
778
+ deferred_todo_id = _first_compact_todo_id(frontier.get("deferred_resume_candidates"))
779
+ route_todo_id = _first_compact_todo_id(
780
+ frontier.get("route_continuation_replan_candidates")
781
+ )
782
+ route_candidates = (
783
+ frontier.get("route_continuation_replan_candidates")
784
+ if isinstance(frontier.get("route_continuation_replan_candidates"), list)
785
+ else []
786
+ )
787
+ route_candidate = (
788
+ route_candidates[0]
789
+ if route_candidates and isinstance(route_candidates[0], dict)
790
+ else {}
791
+ )
792
+ route_target = (
793
+ route_todo_id
794
+ or normalize_todo_id(route_candidate.get("route_id"))
795
+ or normalize_todo_id(route_candidate.get("route_key"))
796
+ )
797
+ if route_candidate:
798
+ return build_hint(
799
+ AgentLaneFrontierHintDecision.ADD_NEXT_ADVANCEMENT,
800
+ source="agent_scope_frontier",
801
+ reason_code="route_continuation_replan_required",
802
+ target_todo_id=route_target,
803
+ quiet_noop_allowed=False,
804
+ next_cli_action=(
805
+ f"loopx todo add --goal-id {goal_id} --role agent "
806
+ "--text '<public-safe route continuation advancement todo>'"
807
+ ),
808
+ )
809
+ return build_hint(
810
+ AgentLaneFrontierHintDecision.ADD_NEXT_ADVANCEMENT,
811
+ source="agent_scope_frontier",
812
+ reason_code="successor_replan_required",
813
+ target_todo_id=deferred_todo_id,
814
+ quiet_noop_allowed=False,
815
+ )
816
+
817
+ if frontier_action in {
818
+ AgentScopeFrontierAction.AGENT_SCOPE_WAIT,
819
+ AgentScopeFrontierAction.REASSIGNMENT_REQUIRED,
820
+ }:
821
+ blocked_successor_todo_id = _first_compact_todo_id(
822
+ frontier.get("blocked_successor_wait_candidates")
823
+ )
824
+ if blocked_successor_todo_id:
825
+ return build_hint(
826
+ AgentLaneFrontierHintDecision.QUIET_NOOP_BLOCKER,
827
+ source="agent_scope_frontier",
828
+ reason_code="blocked_successor_resume_pending",
829
+ target_todo_id=blocked_successor_todo_id,
830
+ quiet_noop_allowed=True,
831
+ )
832
+ blocker_todo_id = _first_compact_todo_id(frontier.get("blocking_handoff_gates"))
833
+ if not blocker_todo_id:
834
+ blocker_todo_id = _first_compact_todo_id(frontier.get("other_agent_claimed_items"))
835
+ if blocker_todo_id:
836
+ return build_hint(
837
+ AgentLaneFrontierHintDecision.QUIET_NOOP_BLOCKER,
838
+ source="agent_scope_frontier",
839
+ reason_code="blocked_by_other_agent_frontier",
840
+ target_todo_id=blocker_todo_id,
841
+ quiet_noop_allowed=True,
842
+ )
843
+ return build_hint(
844
+ AgentLaneFrontierHintDecision.ADD_NEXT_ADVANCEMENT,
845
+ source="agent_scope_frontier",
846
+ reason_code="no_current_agent_advancement_todo",
847
+ quiet_noop_allowed=True,
848
+ )
849
+
850
+ if frontier_action == AgentScopeFrontierAction.AGENT_SCOPE_EXHAUSTED:
851
+ return build_hint(
852
+ AgentLaneFrontierHintDecision.ADD_NEXT_ADVANCEMENT,
853
+ source="agent_scope_frontier",
854
+ reason_code="agent_scope_exhausted",
855
+ quiet_noop_allowed=True,
856
+ )
857
+
858
+ if not isinstance(agent_todo_summary, dict):
859
+ return None
860
+ current_advancement_count = int(
861
+ agent_todo_summary.get("current_agent_claimed_advancement_count") or 0
862
+ )
863
+ current_monitor_count = int(agent_todo_summary.get("current_agent_claimed_monitor_count") or 0)
864
+ unclaimed_count = _selectable_unclaimed_advancement_count(agent_todo_summary)
865
+ lane = str(work_lane_contract.get("lane") or "") if isinstance(work_lane_contract, dict) else ""
866
+ if current_advancement_count == 0 and unclaimed_count == 0 and current_monitor_count > 0:
867
+ return build_hint(
868
+ AgentLaneFrontierHintDecision.QUIET_NOOP_BLOCKER,
869
+ source="agent_todo_summary",
870
+ reason_code="only_current_agent_monitor_work_remains",
871
+ quiet_noop_allowed=lane != TODO_TASK_CLASS_ADVANCEMENT,
872
+ )
873
+ return None
874
+
875
+
876
+ def _agent_scope_deferred_resume_candidates(
877
+ agent_todo_summary: dict[str, Any],
878
+ *,
879
+ agent_id: str,
880
+ ) -> list[dict[str, Any]]:
881
+ candidates: list[dict[str, Any]] = []
882
+ for key in (
883
+ "current_agent_deferred_resume_candidates",
884
+ "unclaimed_deferred_resume_candidates",
885
+ ):
886
+ value = agent_todo_summary.get(key)
887
+ if isinstance(value, list):
888
+ candidates.extend(item for item in value if isinstance(item, dict))
889
+ if not candidates:
890
+ value = agent_todo_summary.get("deferred_resume_candidates")
891
+ if isinstance(value, list):
892
+ for item in value:
893
+ if not isinstance(item, dict):
894
+ continue
895
+ if not agent_scope_item_claimed_by_agent_or_unclaimed(
896
+ item,
897
+ agent_id=agent_id,
898
+ ):
899
+ continue
900
+ candidates.append(item)
901
+
902
+ unique: list[dict[str, Any]] = []
903
+ seen: set[str] = set()
904
+ for item in candidates:
905
+ if todo_item_excludes_agent(item, agent_id=agent_id):
906
+ continue
907
+ if item.get("resume_ready") is not True:
908
+ continue
909
+ if not todo_item_is_deferred(item):
910
+ continue
911
+ identity = str(item.get("todo_id") or item.get("index") or item.get("text") or "")
912
+ if identity in seen:
913
+ continue
914
+ seen.add(identity)
915
+ unique.append(compact_todo_summary_item(item, text=str(item.get("text") or "").strip()))
916
+ return sorted(unique, key=_todo_projection_sort_key)
917
+
918
+
919
+ def _agent_scope_monitor_blocked_resume_candidates(
920
+ agent_todo_summary: dict[str, Any] | None,
921
+ *,
922
+ agent_id: str | None,
923
+ ) -> list[dict[str, Any]]:
924
+ if not isinstance(agent_todo_summary, dict):
925
+ return []
926
+ candidates: list[dict[str, Any]] = []
927
+ if agent_id:
928
+ for key in (
929
+ "current_agent_monitor_blocked_resume_candidates",
930
+ "unclaimed_monitor_blocked_resume_candidates",
931
+ ):
932
+ value = agent_todo_summary.get(key)
933
+ if isinstance(value, list):
934
+ candidates.extend(item for item in value if isinstance(item, dict))
935
+ else:
936
+ value = agent_todo_summary.get("monitor_blocked_resume_candidates")
937
+ if isinstance(value, list):
938
+ candidates.extend(item for item in value if isinstance(item, dict))
939
+
940
+ unique: list[dict[str, Any]] = []
941
+ seen: set[str] = set()
942
+ for item in candidates:
943
+ if todo_item_excludes_agent(item, agent_id=agent_id):
944
+ continue
945
+ if _todo_task_class(item) != TODO_TASK_CLASS_ADVANCEMENT:
946
+ continue
947
+ if item.get("resume_ready") is not False:
948
+ continue
949
+ condition = item.get("resume_condition") if isinstance(item.get("resume_condition"), dict) else {}
950
+ if normalize_todo_status(condition.get("target_status")) != TODO_STATUS_OPEN:
951
+ continue
952
+ target_todo_id = normalize_todo_id(
953
+ item.get("blocking_monitor_todo_id")
954
+ or condition.get("target_todo_id")
955
+ or condition.get("target")
956
+ )
957
+ target_task_class = normalize_todo_task_class(
958
+ condition.get("target_task_class"),
959
+ text="",
960
+ )
961
+ if target_task_class != TODO_TASK_CLASS_MONITOR and not target_todo_id:
962
+ continue
963
+ identity = str(item.get("todo_id") or item.get("index") or item.get("text") or "")
964
+ if identity in seen:
965
+ continue
966
+ seen.add(identity)
967
+ compact = compact_todo_summary_item(item, text=str(item.get("text") or "").strip())
968
+ if target_todo_id:
969
+ compact["blocking_monitor_todo_id"] = target_todo_id
970
+ unique.append(compact)
971
+ return sorted(unique, key=_todo_projection_sort_key)
972
+
973
+
974
+ def _agent_scope_cleared_without_successor_handoff_gates(
975
+ agent_todo_summary: dict[str, Any],
976
+ *,
977
+ agent_id: str,
978
+ ) -> list[dict[str, Any]]:
979
+ unique = _agent_scope_handoff_gates_by_state(
980
+ agent_todo_summary,
981
+ agent_id=agent_id,
982
+ gate_state=HandoffGateState.CLEARED_WITHOUT_SUCCESSOR.value,
983
+ preferred_key="current_agent_cleared_without_successor_handoff_gates",
984
+ )
985
+ return sorted(
986
+ unique,
987
+ key=lambda item: (
988
+ -int(item.get("index") or 0),
989
+ str(item.get("todo_id") or ""),
990
+ ),
991
+ )
992
+
993
+
994
+ def _agent_scope_handoff_gates_by_state(
995
+ agent_todo_summary: dict[str, Any],
996
+ *,
997
+ agent_id: str | None,
998
+ gate_state: str,
999
+ preferred_key: str,
1000
+ ) -> list[dict[str, Any]]:
1001
+ if not agent_id:
1002
+ return []
1003
+ gates = agent_todo_summary.get(preferred_key)
1004
+ if not isinstance(gates, list):
1005
+ gates = agent_todo_summary.get("handoff_gates")
1006
+ if not isinstance(gates, list):
1007
+ return []
1008
+ selected: list[dict[str, Any]] = []
1009
+ seen: set[str] = set()
1010
+ for item in gates:
1011
+ if not isinstance(item, dict):
1012
+ continue
1013
+ if agent_id not in agent_scope_item_excluded_agents(item):
1014
+ continue
1015
+ if item.get("gate_state") != gate_state:
1016
+ continue
1017
+ identity = str(item.get("todo_id") or item.get("index") or item.get("text") or "")
1018
+ if identity in seen:
1019
+ continue
1020
+ seen.add(identity)
1021
+ compact = dict(item)
1022
+ compact["text"] = str(item.get("text") or "").strip()
1023
+ selected.append(compact)
1024
+ return selected
1025
+
1026
+
1027
+ def agent_scope_blocking_handoff_gates(
1028
+ agent_todo_summary: dict[str, Any],
1029
+ *,
1030
+ agent_id: str | None,
1031
+ ) -> list[dict[str, Any]]:
1032
+ selected = _agent_scope_handoff_gates_by_state(
1033
+ agent_todo_summary,
1034
+ agent_id=agent_id,
1035
+ gate_state=HandoffGateState.BLOCKING.value,
1036
+ preferred_key="current_agent_handoff_gates",
1037
+ )
1038
+ return sorted(selected, key=_todo_projection_sort_key)
1039
+
1040
+
1041
+ def _agent_scope_blocking_handoff_gates(
1042
+ agent_todo_summary: dict[str, Any],
1043
+ *,
1044
+ agent_id: str,
1045
+ ) -> list[dict[str, Any]]:
1046
+ return agent_scope_blocking_handoff_gates(agent_todo_summary, agent_id=agent_id)
1047
+
1048
+
1049
+ def _route_continuation_candidate_matches_agent(
1050
+ item: dict[str, Any],
1051
+ *,
1052
+ agent_id: str,
1053
+ ) -> bool:
1054
+ return agent_scope_item_matches_agent_or_unclaimed(item, agent_id=agent_id)
1055
+
1056
+
1057
+ def _agent_scope_route_continuation_replan_candidates(
1058
+ agent_todo_summary: dict[str, Any],
1059
+ *,
1060
+ agent_id: str,
1061
+ ) -> list[dict[str, Any]]:
1062
+ candidates: list[dict[str, Any]] = []
1063
+ for key in (
1064
+ "current_agent_route_continuation_replan_candidates",
1065
+ "unclaimed_route_continuation_replan_candidates",
1066
+ ):
1067
+ value = agent_todo_summary.get(key)
1068
+ if isinstance(value, list):
1069
+ candidates.extend(item for item in value if isinstance(item, dict))
1070
+ if not candidates:
1071
+ value = agent_todo_summary.get("route_continuation_replan_candidates")
1072
+ if isinstance(value, list):
1073
+ for item in value:
1074
+ if not isinstance(item, dict):
1075
+ continue
1076
+ if not _route_continuation_candidate_matches_agent(item, agent_id=agent_id):
1077
+ continue
1078
+ candidates.append(item)
1079
+
1080
+ unique: list[dict[str, Any]] = []
1081
+ seen: set[str] = set()
1082
+ for item in candidates:
1083
+ if not _route_continuation_candidate_matches_agent(
1084
+ item,
1085
+ agent_id=agent_id,
1086
+ ):
1087
+ continue
1088
+ if item.get("route_continuation_replan_required") is False:
1089
+ continue
1090
+ identity = str(
1091
+ item.get("todo_id")
1092
+ or item.get("route_id")
1093
+ or item.get("route_key")
1094
+ or item.get("index")
1095
+ or item.get("text")
1096
+ or ""
1097
+ )
1098
+ if not identity or identity in seen:
1099
+ continue
1100
+ seen.add(identity)
1101
+ compact = compact_todo_summary_item(item, text=str(item.get("text") or "").strip())
1102
+ compact["route_continuation_replan_required"] = True
1103
+ if item.get("route_continuation_reason") is not None:
1104
+ compact["route_continuation_reason"] = item.get("route_continuation_reason")
1105
+ if item.get("route_id") is not None:
1106
+ compact["route_id"] = item.get("route_id")
1107
+ if item.get("route_key") is not None:
1108
+ compact["route_key"] = item.get("route_key")
1109
+ unique.append(compact)
1110
+ return sorted(unique, key=_todo_projection_sort_key)
1111
+
1112
+
1113
+ @dataclass(frozen=True)
1114
+ class _AgentScopeNoCandidateContext:
1115
+ agent_id: str
1116
+ summary: dict[str, Any]
1117
+ current_agent_count: int
1118
+ unclaimed_count: int
1119
+ has_advancement_contract: bool
1120
+
1121
+ @property
1122
+ def candidate_counts(self) -> dict[str, int]:
1123
+ return {
1124
+ "current_agent_claimed_advancement_count": self.current_agent_count,
1125
+ "unclaimed_advancement_count": self.unclaimed_count,
1126
+ }
1127
+
1128
+
1129
+ def _selected_candidate_priority_frontier(
1130
+ *,
1131
+ agent_id: str,
1132
+ summary: dict[str, Any],
1133
+ selected: dict[str, Any],
1134
+ ) -> dict[str, Any] | None:
1135
+ candidates = _agent_scope_deferred_resume_candidates(summary, agent_id=agent_id)
1136
+ if not candidates:
1137
+ return None
1138
+ first = candidates[0]
1139
+ if _todo_projection_sort_key(first)[0] >= _todo_projection_sort_key(selected)[0]:
1140
+ return None
1141
+ candidate_todo_id = str(first.get("todo_id") or "").strip() or "<todo_id>"
1142
+ selected_todo_id = str(selected.get("todo_id") or "").strip() or "<open_todo_id>"
1143
+ return build_agent_scope_frontier_payload(
1144
+ agent_id=agent_id,
1145
+ action=AgentScopeFrontierAction.SUCCESSOR_REPLAN_REQUIRED,
1146
+ quiet_noop_allowed=False,
1147
+ spend_policy="spend once after validated priority successor replan/todo writeback",
1148
+ reason=(
1149
+ f"ready deferred successor {candidate_todo_id} has higher priority than "
1150
+ f"selected open advancement {selected_todo_id}; its lifecycle must be "
1151
+ "resolved before lower-priority delivery"
1152
+ ),
1153
+ recommended_action=(
1154
+ "Run a bounded successor replan before lower-priority delivery: reopen, "
1155
+ f"supersede, or record a no-follow-up rationale for {candidate_todo_id}."
1156
+ ),
1157
+ requires_replan=True,
1158
+ candidate_counts={
1159
+ "deferred_resume_candidate_count": len(candidates),
1160
+ "preempted_open_candidate_count": 1,
1161
+ },
1162
+ extra_fields={
1163
+ "deferred_resume_candidates": candidates[:3],
1164
+ "preempted_open_candidate": compact_todo_summary_item(
1165
+ selected,
1166
+ text=str(selected.get("text") or "").strip(),
1167
+ ),
1168
+ "priority_preemption": True,
1169
+ },
1170
+ )
1171
+
1172
+
1173
+ def _agent_scope_advancement_counts(
1174
+ summary: dict[str, Any],
1175
+ *,
1176
+ agent_id: str,
1177
+ ) -> tuple[int, int]:
1178
+ current = max(
1179
+ int(summary.get("current_agent_claimed_advancement_count") or 0),
1180
+ _count_advancement_items(
1181
+ summary.get("current_agent_claimed_advancement_items"),
1182
+ claimed_by=agent_id,
1183
+ ),
1184
+ )
1185
+ unclaimed = _selectable_unclaimed_advancement_count(summary)
1186
+ executable_items = summary.get("executable_backlog_items")
1187
+ if isinstance(executable_items, list):
1188
+ current = max(
1189
+ current,
1190
+ _count_advancement_items(executable_items, claimed_by=agent_id),
1191
+ )
1192
+ unclaimed = _count_advancement_items(
1193
+ executable_items,
1194
+ claimed_by="__unclaimed__",
1195
+ )
1196
+ return current, unclaimed
1197
+
1198
+
1199
+ def _monitor_blocked_resume_frontier(
1200
+ context: _AgentScopeNoCandidateContext,
1201
+ ) -> dict[str, Any] | None:
1202
+ candidates = _agent_scope_monitor_blocked_resume_candidates(
1203
+ context.summary,
1204
+ agent_id=context.agent_id,
1205
+ )
1206
+ if not candidates:
1207
+ return None
1208
+ first = candidates[0]
1209
+ candidate_id = str(first.get("todo_id") or "").strip() or "<todo_id>"
1210
+ monitor_id = (
1211
+ str(first.get("blocking_monitor_todo_id") or "").strip()
1212
+ or "<monitor_todo_id>"
1213
+ )
1214
+ return build_agent_scope_frontier_payload(
1215
+ agent_id=context.agent_id,
1216
+ action=AgentScopeFrontierAction.SUCCESSOR_REPLAN_REQUIRED,
1217
+ quiet_noop_allowed=False,
1218
+ spend_policy="spend once after validated standing-monitor gate repair/todo writeback",
1219
+ reason=(
1220
+ f"current agent {context.agent_id} has advancement todo {candidate_id} gated "
1221
+ f"by open continuous_monitor {monitor_id}; a standing monitor cannot be the "
1222
+ "todo_done prerequisite for autonomous continuation"
1223
+ ),
1224
+ recommended_action=(
1225
+ "Run a bounded gate-model repair before quiet wait: close/supersede "
1226
+ f"{monitor_id} after validated evidence, or rewrite {candidate_id} to use "
1227
+ "a non-blocking monitor contract before delivery."
1228
+ ),
1229
+ requires_replan=True,
1230
+ candidate_counts={
1231
+ **context.candidate_counts,
1232
+ "monitor_blocked_resume_candidate_count": len(candidates),
1233
+ },
1234
+ extra_fields={"monitor_blocked_resume_candidates": candidates[:3]},
1235
+ )
1236
+
1237
+
1238
+ def _deferred_resume_frontier(
1239
+ context: _AgentScopeNoCandidateContext,
1240
+ ) -> dict[str, Any] | None:
1241
+ candidates = _agent_scope_deferred_resume_candidates(
1242
+ context.summary,
1243
+ agent_id=context.agent_id,
1244
+ )
1245
+ if not candidates:
1246
+ return None
1247
+ candidate_id = str(candidates[0].get("todo_id") or "").strip() or "<todo_id>"
1248
+ return build_agent_scope_frontier_payload(
1249
+ agent_id=context.agent_id,
1250
+ action=AgentScopeFrontierAction.SUCCESSOR_REPLAN_REQUIRED,
1251
+ quiet_noop_allowed=False,
1252
+ spend_policy="spend once after validated successor replan/todo writeback",
1253
+ reason=(
1254
+ f"current agent {context.agent_id} has no open current/unclaimed advancement "
1255
+ "candidate, but a deferred successor resume condition is satisfied"
1256
+ ),
1257
+ recommended_action=(
1258
+ "Run a bounded successor replan before delivery: reopen, supersede, or "
1259
+ f"record a no-follow-up rationale for {candidate_id}."
1260
+ ),
1261
+ requires_replan=True,
1262
+ candidate_counts={
1263
+ **context.candidate_counts,
1264
+ "deferred_resume_candidate_count": len(candidates),
1265
+ },
1266
+ extra_fields={"deferred_resume_candidates": candidates[:3]},
1267
+ )
1268
+
1269
+
1270
+ def _blocked_successor_wait_frontier(
1271
+ context: _AgentScopeNoCandidateContext,
1272
+ ) -> dict[str, Any] | None:
1273
+ candidates = todo_summary_blocked_successor_items(
1274
+ context.summary,
1275
+ agent_id=context.agent_id,
1276
+ )
1277
+ if not candidates:
1278
+ return None
1279
+ first = candidates[0]
1280
+ candidate_id = str(first.get("todo_id") or "").strip() or "<todo_id>"
1281
+ resume_when = str(first.get("resume_when") or "").strip()
1282
+ return build_agent_scope_frontier_payload(
1283
+ agent_id=context.agent_id,
1284
+ action=AgentScopeFrontierAction.AGENT_SCOPE_WAIT,
1285
+ quiet_noop_allowed=True,
1286
+ spend_policy="no quota spend while an exact successor resume condition is pending",
1287
+ reason=(
1288
+ f"current agent {context.agent_id} has exact blocked successor {candidate_id} "
1289
+ f"waiting on {resume_when}; another successor replan would duplicate the "
1290
+ "existing route"
1291
+ ),
1292
+ recommended_action=(
1293
+ f"Keep {context.agent_id} active but quiet until {resume_when} becomes ready; "
1294
+ "then resume ordinary todo/deferred-successor routing automatically."
1295
+ ),
1296
+ requires_replan=False,
1297
+ candidate_counts={
1298
+ **context.candidate_counts,
1299
+ "blocked_successor_wait_count": len(candidates),
1300
+ },
1301
+ extra_fields={"blocked_successor_wait_candidates": candidates[:3]},
1302
+ )
1303
+
1304
+
1305
+ def _route_continuation_frontier(
1306
+ context: _AgentScopeNoCandidateContext,
1307
+ ) -> dict[str, Any] | None:
1308
+ candidates = _agent_scope_route_continuation_replan_candidates(
1309
+ context.summary,
1310
+ agent_id=context.agent_id,
1311
+ )
1312
+ if not candidates:
1313
+ return None
1314
+ first = candidates[0]
1315
+ route_label = (
1316
+ str(
1317
+ first.get("route_id")
1318
+ or first.get("route_key")
1319
+ or first.get("todo_id")
1320
+ or ""
1321
+ ).strip()
1322
+ or "<route>"
1323
+ )
1324
+ return build_agent_scope_frontier_payload(
1325
+ agent_id=context.agent_id,
1326
+ action=AgentScopeFrontierAction.SUCCESSOR_REPLAN_REQUIRED,
1327
+ quiet_noop_allowed=False,
1328
+ spend_policy="spend once after validated route continuation replan/todo writeback",
1329
+ reason=(
1330
+ f"current agent {context.agent_id} has no open current/unclaimed advancement "
1331
+ "candidate, but the route continuation projection requires a successor "
1332
+ f"replan for {route_label}"
1333
+ ),
1334
+ recommended_action=(
1335
+ "Run a bounded route continuation replan before quiet wait: create or claim "
1336
+ f"the next concrete {context.agent_id}/unclaimed advancement todo for "
1337
+ f"{route_label}, or record an explicit no-follow-up rationale."
1338
+ ),
1339
+ requires_replan=True,
1340
+ candidate_counts={
1341
+ **context.candidate_counts,
1342
+ "route_continuation_replan_candidate_count": len(candidates),
1343
+ },
1344
+ extra_fields={"route_continuation_replan_candidates": candidates[:3]},
1345
+ )
1346
+
1347
+
1348
+ def _blocking_handoff_frontier(
1349
+ context: _AgentScopeNoCandidateContext,
1350
+ ) -> dict[str, Any] | None:
1351
+ gates = _agent_scope_blocking_handoff_gates(
1352
+ context.summary,
1353
+ agent_id=context.agent_id,
1354
+ )
1355
+ if not gates:
1356
+ return None
1357
+ claimants = sorted(
1358
+ {
1359
+ claimed_by
1360
+ for item in gates
1361
+ for claimed_by in [agent_scope_item_claimed_by(item)]
1362
+ if claimed_by
1363
+ }
1364
+ )
1365
+ owner = ", ".join(claimants) or "the owning agent"
1366
+ return build_agent_scope_frontier_payload(
1367
+ agent_id=context.agent_id,
1368
+ action=AgentScopeFrontierAction.AGENT_SCOPE_WAIT,
1369
+ quiet_noop_allowed=True,
1370
+ spend_policy="no quota spend while the current agent has no in-scope runnable candidate",
1371
+ reason=(
1372
+ f"current agent {context.agent_id} has no current/unclaimed advancement "
1373
+ f"candidate; blocking handoff work is claimed by {owner}"
1374
+ ),
1375
+ recommended_action=(
1376
+ f"Keep {context.agent_id} active but quiet: wait for {owner} to finish the "
1377
+ "blocking handoff, reassign it, or create a concrete current-agent/"
1378
+ "unclaimed advancement todo before delivery."
1379
+ ),
1380
+ candidate_counts={
1381
+ **context.candidate_counts,
1382
+ "blocking_handoff_gate_count": len(gates),
1383
+ },
1384
+ extra_fields={
1385
+ "other_claimants": claimants,
1386
+ "blocking_handoff_claimants": claimants,
1387
+ "blocking_handoff_gates": gates[:3],
1388
+ },
1389
+ )
1390
+
1391
+
1392
+ def _cleared_handoff_frontier(
1393
+ context: _AgentScopeNoCandidateContext,
1394
+ ) -> dict[str, Any] | None:
1395
+ gates = _agent_scope_cleared_without_successor_handoff_gates(
1396
+ context.summary,
1397
+ agent_id=context.agent_id,
1398
+ )
1399
+ if not gates:
1400
+ return None
1401
+ first = gates[0]
1402
+ blocker_id = str(first.get("todo_id") or "").strip() or "<todo_id>"
1403
+ unblocks_id = normalize_todo_id(first.get("unblocks_todo_id"))
1404
+ target_text = f" for unblocked todo {unblocks_id}" if unblocks_id else ""
1405
+ return build_agent_scope_frontier_payload(
1406
+ agent_id=context.agent_id,
1407
+ action=AgentScopeFrontierAction.SUCCESSOR_REPLAN_REQUIRED,
1408
+ quiet_noop_allowed=False,
1409
+ spend_policy="spend once after validated successor replan/todo writeback",
1410
+ reason=(
1411
+ f"current agent {context.agent_id} has no open current/unclaimed advancement "
1412
+ f"candidate, but blocking handoff {blocker_id}{target_text} is already done "
1413
+ "without a projected successor"
1414
+ ),
1415
+ recommended_action=(
1416
+ "Run a bounded successor replan before quiet wait: reopen or supersede "
1417
+ f"{blocker_id} into a concrete {context.agent_id}/unclaimed advancement "
1418
+ "todo, or record an explicit no-follow-up rationale."
1419
+ ),
1420
+ requires_replan=True,
1421
+ candidate_counts={
1422
+ **context.candidate_counts,
1423
+ "cleared_without_successor_handoff_count": len(gates),
1424
+ },
1425
+ extra_fields={"cleared_without_successor_handoff_gates": gates[:3]},
1426
+ )
1427
+
1428
+
1429
+ def _other_agent_or_exhausted_frontier(
1430
+ context: _AgentScopeNoCandidateContext,
1431
+ ) -> dict[str, Any] | None:
1432
+ claimed_items = context.summary.get("claimed_advancement_open_items")
1433
+ claimed_items = claimed_items if isinstance(claimed_items, list) else []
1434
+ other_items = [
1435
+ item
1436
+ for item in claimed_items
1437
+ if isinstance(item, dict)
1438
+ and _todo_item_is_actionable_open(item)
1439
+ and _todo_task_class(item) == TODO_TASK_CLASS_ADVANCEMENT
1440
+ and agent_scope_item_claimed_by(item) not in {None, "", context.agent_id}
1441
+ ]
1442
+ if not context.has_advancement_contract and not other_items:
1443
+ return None
1444
+ other_claimants = sorted(
1445
+ {
1446
+ claimed_by
1447
+ for item in other_items
1448
+ for claimed_by in [agent_scope_item_claimed_by(item)]
1449
+ if claimed_by
1450
+ }
1451
+ )
1452
+ blocking_claimants = sorted(
1453
+ {
1454
+ claimed_by
1455
+ for item in other_items
1456
+ if context.agent_id in agent_scope_item_excluded_agents(item)
1457
+ for claimed_by in [agent_scope_item_claimed_by(item)]
1458
+ if claimed_by
1459
+ }
1460
+ )
1461
+ if blocking_claimants:
1462
+ action = AgentScopeFrontierAction.AGENT_SCOPE_WAIT
1463
+ owner = ", ".join(blocking_claimants)
1464
+ reason = (
1465
+ f"current agent {context.agent_id} has no current/unclaimed advancement "
1466
+ f"candidate; blocking handoff work is claimed by {owner}"
1467
+ )
1468
+ recommended_action = (
1469
+ f"Keep {context.agent_id} active but quiet: wait for {owner} to finish the "
1470
+ "blocking handoff, reassign it, or create a concrete current-agent/"
1471
+ "unclaimed advancement todo before delivery."
1472
+ )
1473
+ elif other_items:
1474
+ action = AgentScopeFrontierAction.REASSIGNMENT_REQUIRED
1475
+ owner = ", ".join(other_claimants) or "the owning agent"
1476
+ reason = (
1477
+ f"current agent {context.agent_id} has no current/unclaimed advancement "
1478
+ f"candidate; visible advancement work is claimed by {owner}"
1479
+ )
1480
+ recommended_action = (
1481
+ f"Keep {context.agent_id} active but quiet: wait for {owner} to finish, "
1482
+ "reassign, or create a concrete current-agent/unclaimed advancement todo "
1483
+ "before delivery."
1484
+ )
1485
+ else:
1486
+ action = AgentScopeFrontierAction.AGENT_SCOPE_EXHAUSTED
1487
+ reason = (
1488
+ f"current agent {context.agent_id} has no projected current/unclaimed "
1489
+ "advancement candidate despite a goal-level advancement lane"
1490
+ )
1491
+ recommended_action = (
1492
+ f"Keep {context.agent_id} active but quiet until LoopX projects a concrete "
1493
+ "current-agent or unclaimed advancement todo, or another peer transfers work."
1494
+ )
1495
+ claim_scope = context.summary.get("claim_scope")
1496
+ claim_scope = claim_scope if isinstance(claim_scope, dict) else {}
1497
+ return build_agent_scope_frontier_payload(
1498
+ agent_id=context.agent_id,
1499
+ action=action,
1500
+ quiet_noop_allowed=True,
1501
+ spend_policy="no quota spend while the current agent has no in-scope runnable candidate",
1502
+ reason=reason,
1503
+ recommended_action=recommended_action,
1504
+ candidate_counts={
1505
+ **context.candidate_counts,
1506
+ "other_agent_claimed_advancement_count": len(other_items),
1507
+ "other_agent_claimed_open_count": int(
1508
+ claim_scope.get("other_agent_claimed_open_count") or 0
1509
+ ),
1510
+ },
1511
+ extra_fields={
1512
+ "other_claimants": other_claimants,
1513
+ "blocking_handoff_claimants": blocking_claimants,
1514
+ "other_agent_claimed_items": [
1515
+ compact_todo_summary_item(item, text=str(item.get("text") or "").strip())
1516
+ for item in other_items[:3]
1517
+ ],
1518
+ },
1519
+ )
1520
+
1521
+
1522
+ _AGENT_SCOPE_NO_CANDIDATE_RULES = (
1523
+ _monitor_blocked_resume_frontier,
1524
+ _deferred_resume_frontier,
1525
+ _blocked_successor_wait_frontier,
1526
+ _route_continuation_frontier,
1527
+ _blocking_handoff_frontier,
1528
+ _cleared_handoff_frontier,
1529
+ _other_agent_or_exhausted_frontier,
1530
+ )
1531
+
1532
+
1533
+ def _agent_scope_no_candidate_frontier(
1534
+ *,
1535
+ agent_identity: dict[str, Any] | None,
1536
+ agent_todo_summary: dict[str, Any] | None,
1537
+ agent_lane_next_action: dict[str, Any] | None,
1538
+ work_lane_contract: dict[str, Any] | None,
1539
+ candidate_should_run: bool,
1540
+ ) -> dict[str, Any] | None:
1541
+ if not candidate_should_run or not _agent_identity_has_scoped_lane(agent_identity):
1542
+ return None
1543
+ agent_id = normalize_todo_claimed_by(agent_identity.get("agent_id"))
1544
+ if not agent_id or not isinstance(agent_todo_summary, dict):
1545
+ return None
1546
+ if work_lane_contract_is_due_monitor_attempt(work_lane_contract):
1547
+ return None
1548
+ if isinstance(agent_lane_next_action, dict):
1549
+ return _selected_candidate_priority_frontier(
1550
+ agent_id=agent_id,
1551
+ summary=agent_todo_summary,
1552
+ selected=agent_lane_next_action,
1553
+ )
1554
+ if work_lane_contract_requires_current_agent_attempt(work_lane_contract):
1555
+ return None
1556
+
1557
+ current_count, unclaimed_count = _agent_scope_advancement_counts(
1558
+ agent_todo_summary,
1559
+ agent_id=agent_id,
1560
+ )
1561
+ if current_count > 0 or unclaimed_count > 0:
1562
+ return None
1563
+ context = _AgentScopeNoCandidateContext(
1564
+ agent_id=agent_id,
1565
+ summary=agent_todo_summary,
1566
+ current_agent_count=current_count,
1567
+ unclaimed_count=unclaimed_count,
1568
+ has_advancement_contract=bool(
1569
+ isinstance(work_lane_contract, dict)
1570
+ and work_lane_contract.get("lane") == TODO_TASK_CLASS_ADVANCEMENT
1571
+ and work_lane_contract.get("must_attempt_work") is True
1572
+ ),
1573
+ )
1574
+ for rule in _AGENT_SCOPE_NO_CANDIDATE_RULES:
1575
+ frontier = rule(context)
1576
+ if frontier is not None:
1577
+ return frontier
1578
+ return None