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,1458 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ import re
5
+ from typing import Any, Callable, Optional
6
+
7
+ from .contract import (
8
+ TODO_RESUME_KIND_CAPACITY_AVAILABLE,
9
+ TODO_RESUME_KIND_PR_MERGED,
10
+ TODO_RESUME_KIND_TODO_DONE,
11
+ TODO_STATUS_DONE,
12
+ TODO_STATUS_OPEN,
13
+ TODO_TASK_CLASS_ADVANCEMENT,
14
+ TODO_TASK_CLASS_BLOCKER,
15
+ TODO_TASK_CLASS_MONITOR,
16
+ TODO_TASK_CLASS_USER_ACTION,
17
+ build_todo_id,
18
+ normalize_required_capabilities,
19
+ normalize_required_write_scopes,
20
+ normalize_explore_result_node_refs,
21
+ normalize_target_capabilities,
22
+ normalize_todo_action_kind,
23
+ normalize_todo_capability_binding_ref,
24
+ normalize_todo_task_repository,
25
+ normalize_todo_blocks_agent,
26
+ normalize_todo_bound_agent,
27
+ normalize_todo_claimed_by,
28
+ normalize_todo_continuation_policy,
29
+ normalize_todo_decision_outcome,
30
+ normalize_todo_decision_scope,
31
+ normalize_todo_decision_scope_outcomes,
32
+ normalize_todo_excluded_agents,
33
+ normalize_todo_global_gate,
34
+ normalize_todo_goal_bound,
35
+ normalize_todo_id,
36
+ normalize_todo_id_list,
37
+ normalize_todo_no_followup,
38
+ normalize_removed_todo_continuation_policy,
39
+ normalize_todo_required_decision_scopes,
40
+ normalize_todo_resume_when,
41
+ normalize_todo_status,
42
+ normalize_todo_task_domain,
43
+ normalize_todo_task_class,
44
+ todo_done_for_status,
45
+ )
46
+ from .completion_validation_projection import project_completion_validation_authority
47
+ from .handoff_gate import build_todo_handoff_gate_states
48
+ from .handoff_note import attach_todo_handoff_note
49
+ from .projection import (
50
+ todo_claimed_visibility_items as projection_todo_claimed_visibility_items,
51
+ todo_item_is_actionable_open as projection_todo_item_is_actionable_open,
52
+ todo_item_is_deferred as projection_todo_item_is_deferred,
53
+ todo_item_is_due_monitor as projection_todo_item_is_due_monitor,
54
+ todo_item_is_watch_only_monitor as projection_todo_item_is_watch_only_monitor,
55
+ todo_item_missing_monitor_schedule as projection_todo_item_missing_monitor_schedule,
56
+ todo_item_task_class as projection_todo_item_task_class,
57
+ todo_item_next_due_at as projection_todo_item_next_due_at,
58
+ todo_item_expires_at as projection_todo_item_expires_at,
59
+ todo_priority_parts as projection_todo_priority_parts,
60
+ todo_priority_rank as projection_todo_priority_rank,
61
+ todo_projection_sort_key as projection_todo_projection_sort_key,
62
+ )
63
+ from ..work_items.project_asset import build_project_asset_todo_summary
64
+ from .user_gate import open_user_gate_todo_items
65
+
66
+
67
+ MAX_STATUS_TODOS_PER_ROLE = 12
68
+ MAX_PROJECT_ASSET_TODO_ITEMS = 3
69
+ MAX_PROJECT_ASSET_TODO_BACKLOG_ITEMS = 8
70
+ MAX_TODO_VISIBILITY_LANE_ITEMS = 16
71
+ MAX_DEFERRED_TODO_VISIBILITY_ITEMS = 8
72
+ MAX_MONITOR_DUE_ITEMS = 1
73
+ MAX_DEPENDENCY_BLOCKERS = 4
74
+ MAX_COMPLETED_SUCCESSION_WARNING_ITEMS = 5
75
+ MAX_RECENT_COMPLETED_ADVANCEMENT_ITEMS = MAX_TODO_VISIBILITY_LANE_ITEMS
76
+
77
+ TODO_ITEM_SCHEMA_VERSION = "todo_item_v0"
78
+ TODO_SUCCESSION_WARNING_SCHEMA_VERSION = "todo_succession_warning_v0"
79
+ TODO_SOURCE_PROOF_SCHEMA_VERSION = "todo_source_proof_v0"
80
+ TODO_CLOSURE_INTENT_SCHEMA_VERSION = "todo_closure_intent_v0"
81
+ TODO_TERMINAL_CLOSURE_PROOF_SCHEMA_VERSION = "todo_terminal_closure_proof_v0"
82
+ TASK_ORCHESTRATION_AUTHORITY_SCHEMA_VERSION = "task_orchestration_authority_v0"
83
+ TODO_ARCHIVE_STATE_ACTIVE = "active"
84
+ AttentionItemBuilder = Callable[..., dict[str, Any]]
85
+ GoalLifecycleFields = Callable[[dict[str, Any], Optional[dict[str, Any]]], dict[str, Any]]
86
+ PublicSafeText = Callable[..., Optional[str]]
87
+ TodoOpenCount = Callable[[Optional[dict[str, Any]]], int]
88
+ FirstOpenTodoText = Callable[[Optional[dict[str, Any]]], Optional[str]]
89
+
90
+
91
+ @dataclass(frozen=True)
92
+ class _TodoGroupLanes:
93
+ open_items: list[dict[str, Any]]
94
+ terminal_items: list[dict[str, Any]]
95
+ deferred_items: list[dict[str, Any]]
96
+ done_items: list[dict[str, Any]]
97
+ projected_open_items: list[dict[str, Any]]
98
+ projected_deferred_items: list[dict[str, Any]]
99
+ budgeted_items: list[dict[str, Any]]
100
+ claimed_open_items: list[dict[str, Any]]
101
+ unclaimed_open_items: list[dict[str, Any]]
102
+ executable_items: list[dict[str, Any]]
103
+ blocker_items: list[dict[str, Any]]
104
+ resume_blocked_items: list[dict[str, Any]]
105
+ monitor_items: list[dict[str, Any]]
106
+ monitor_due_items: list[dict[str, Any]]
107
+ monitor_schedule_gap_items: list[dict[str, Any]]
108
+ claimed_advancement_items: list[dict[str, Any]]
109
+ claimed_monitor_items: list[dict[str, Any]]
110
+ active_next_action_items: list[dict[str, Any]]
111
+ active_next_action_executable_items: list[dict[str, Any]]
112
+ GITHUB_PULL_URL_PATTERN = re.compile(
113
+ r"https://github\.com/(?P<repo>[^/]+/[^/]+)/pull/(?P<number>[0-9]+)(?:\b|/|#|\?)",
114
+ re.IGNORECASE,
115
+ )
116
+ PR_REF_PATTERN = re.compile(
117
+ r"(?:(?P<repo>[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+)#|#|pr[-_\s]*)"
118
+ r"(?P<number>[0-9]+)",
119
+ re.IGNORECASE,
120
+ )
121
+ PR_MERGED_EVENT_KINDS = {
122
+ "pr_merge",
123
+ "pr_merged",
124
+ "pull_request_merge",
125
+ "pull_request_merged",
126
+ }
127
+ TASK_ORCHESTRATION_CANDIDATE_FIELDS = (
128
+ "todo_id",
129
+ "status",
130
+ "done",
131
+ "task_class",
132
+ "action_kind",
133
+ "task_domain",
134
+ "task_repository",
135
+ "required_write_scopes",
136
+ "required_capabilities",
137
+ "claimed_by",
138
+ "excluded_agents",
139
+ "resume_when",
140
+ "resume_ready",
141
+ "continuation_policy",
142
+ "target_key",
143
+ "title",
144
+ "text",
145
+ )
146
+ TASK_ORCHESTRATION_USER_BLOCKER_FIELDS = (
147
+ "todo_id",
148
+ "status",
149
+ "done",
150
+ "task_class",
151
+ "unblocks_todo_id",
152
+ )
153
+
154
+
155
+ def normalize_todo_text(text: str, *, limit: int = 500) -> str:
156
+ compact = " ".join(str(text or "").strip().split())
157
+ if len(compact) <= limit:
158
+ return compact
159
+ return compact[: limit - 1].rstrip() + "…"
160
+
161
+
162
+ def todo_item_status(item: dict[str, Any]) -> str:
163
+ """Return one Todo's explicit status with marker compatibility."""
164
+
165
+ status = normalize_todo_status(item.get("status"))
166
+ if status:
167
+ return status
168
+ return TODO_STATUS_DONE if item.get("done") else TODO_STATUS_OPEN
169
+
170
+
171
+ def todo_archive_state(item: dict[str, Any]) -> str:
172
+ value = str(item.get("archive_state") or TODO_ARCHIVE_STATE_ACTIVE).strip()
173
+ return value or TODO_ARCHIVE_STATE_ACTIVE
174
+
175
+
176
+ def active_state_todo_attention_item(
177
+ goal: dict[str, Any],
178
+ fields: dict[str, Any],
179
+ current_run: dict[str, Any] | None,
180
+ *,
181
+ public_safe_compact_text: PublicSafeText,
182
+ first_open_todo_text: FirstOpenTodoText,
183
+ todo_summary_open_count: TodoOpenCount,
184
+ goal_lifecycle_fields: GoalLifecycleFields,
185
+ attention_item: AttentionItemBuilder,
186
+ ) -> dict[str, Any] | None:
187
+ """Surface active-state todos even when the latest run classification is passive."""
188
+
189
+ user_todos = fields.get("user_todos") if isinstance(fields.get("user_todos"), dict) else None
190
+ agent_todos = fields.get("agent_todos") if isinstance(fields.get("agent_todos"), dict) else None
191
+ active_next_action = public_safe_compact_text(
192
+ fields.get("active_state_next_action"),
193
+ limit=320,
194
+ )
195
+ user_gate_items = open_user_gate_todo_items(user_todos)
196
+ user_gate_action = public_safe_compact_text(
197
+ user_gate_items[0].get("text") if user_gate_items else None,
198
+ limit=320,
199
+ )
200
+ user_action = public_safe_compact_text(first_open_todo_text(user_todos), limit=320)
201
+ agent_action = public_safe_compact_text(first_open_todo_text(agent_todos), limit=320)
202
+ agent_has_open = bool(agent_action or todo_summary_open_count(agent_todos) > 0)
203
+ lifecycle_fields = goal_lifecycle_fields(goal, current_run)
204
+ goal_id = str(goal.get("id") or "unknown-goal")
205
+
206
+ if user_gate_action or user_gate_items:
207
+ return attention_item(
208
+ goal_id=goal_id,
209
+ status="active_state_user_gate",
210
+ waiting_on="controller",
211
+ severity="action",
212
+ recommended_action=(
213
+ user_gate_action
214
+ or active_next_action
215
+ or "resolve the open user_gate todo from the active goal state"
216
+ ),
217
+ source="active_state",
218
+ **lifecycle_fields,
219
+ )
220
+
221
+ if user_action or todo_summary_open_count(user_todos) > 0:
222
+ user_items = [
223
+ item
224
+ for item in (user_todos.get("first_open_items") if user_todos else []) or []
225
+ if isinstance(item, dict) and item.get("done") is not True
226
+ ]
227
+ explicit_user_actions_only = bool(user_items) and all(
228
+ str(item.get("task_class") or "").strip()
229
+ and projection_todo_item_task_class(item) == TODO_TASK_CLASS_USER_ACTION
230
+ for item in user_items
231
+ )
232
+ if not explicit_user_actions_only:
233
+ return attention_item(
234
+ goal_id=goal_id,
235
+ status="active_state_user_todo",
236
+ waiting_on="controller",
237
+ severity="action",
238
+ recommended_action=(
239
+ user_action
240
+ or active_next_action
241
+ or "resolve the open user todo from the active goal state"
242
+ ),
243
+ source="active_state",
244
+ **lifecycle_fields,
245
+ )
246
+
247
+ if agent_has_open:
248
+ return attention_item(
249
+ goal_id=goal_id,
250
+ status="active_state_agent_todo",
251
+ waiting_on="codex",
252
+ severity="action",
253
+ recommended_action=(
254
+ agent_action
255
+ or active_next_action
256
+ or "run the open agent todo from the active goal state"
257
+ ),
258
+ source="active_state",
259
+ **lifecycle_fields,
260
+ )
261
+
262
+ projection_gap = fields.get("state_projection_gap")
263
+ if isinstance(projection_gap, dict):
264
+ return attention_item(
265
+ goal_id=goal_id,
266
+ status="state_projection_gap",
267
+ waiting_on="codex",
268
+ severity="action",
269
+ recommended_action=str(
270
+ projection_gap.get("recommended_action")
271
+ or "expand the active-state Next Action into parseable todos"
272
+ ),
273
+ source="active_state",
274
+ **lifecycle_fields,
275
+ )
276
+
277
+ return None
278
+
279
+
280
+ def sync_connected_attention_action_from_todos(
281
+ item: dict[str, Any],
282
+ *,
283
+ first_open_todo_text: FirstOpenTodoText,
284
+ ) -> None:
285
+ if item.get("status") != "connected_without_run":
286
+ return
287
+ agent_lane_action = (
288
+ item.get("agent_lane_next_action")
289
+ if isinstance(item.get("agent_lane_next_action"), dict)
290
+ else None
291
+ )
292
+ if agent_lane_action is None and isinstance(item.get("project_asset"), dict):
293
+ project_asset = item["project_asset"]
294
+ agent_lane_action = (
295
+ project_asset.get("agent_lane_next_action")
296
+ if isinstance(project_asset.get("agent_lane_next_action"), dict)
297
+ else None
298
+ )
299
+ agent_action = normalize_todo_text(agent_lane_action.get("text")) if agent_lane_action else None
300
+ if not agent_action:
301
+ agent_action = first_open_todo_text(
302
+ item.get("agent_todos") if isinstance(item.get("agent_todos"), dict) else None
303
+ )
304
+ if not agent_action:
305
+ return
306
+ item["recommended_action"] = agent_action
307
+ project_asset = item.get("project_asset")
308
+ if isinstance(project_asset, dict):
309
+ project_asset["next_action"] = agent_action
310
+
311
+
312
+ def todo_priority_parts(text: str) -> tuple[str | None, str]:
313
+ return projection_todo_priority_parts(text)
314
+
315
+
316
+ def structured_todo_item(
317
+ item: dict[str, Any],
318
+ *,
319
+ role: str | None,
320
+ source_section: str | None,
321
+ archive_state: str = "active",
322
+ ) -> dict[str, Any]:
323
+ text = normalize_todo_text(str(item.get("text") or ""))
324
+ priority, title = todo_priority_parts(text)
325
+ index = item.get("index")
326
+ explicit_status = normalize_todo_status(item.get("status"))
327
+ status = explicit_status or ("done" if item.get("done") else "open")
328
+ done = todo_done_for_status(status) if explicit_status else bool(item.get("done"))
329
+ todo_id = item.get("todo_id") or build_todo_id(
330
+ role=role,
331
+ source_section=source_section,
332
+ index=index,
333
+ text=text,
334
+ )
335
+ normalized = project_completion_validation_authority(item)
336
+ normalized.update(
337
+ {
338
+ "schema_version": TODO_ITEM_SCHEMA_VERSION,
339
+ "todo_id": todo_id,
340
+ "role": role,
341
+ "status": status,
342
+ "done": done,
343
+ "archive_state": archive_state,
344
+ "source_section": source_section,
345
+ "text": text,
346
+ "task_class": normalize_todo_task_class(
347
+ item.get("task_class"),
348
+ text=text,
349
+ action_kind=item.get("action_kind"),
350
+ ),
351
+ }
352
+ )
353
+ action_kind = normalize_todo_action_kind(item.get("action_kind"))
354
+ if action_kind:
355
+ normalized["action_kind"] = action_kind
356
+ task_domain = normalize_todo_task_domain(item.get("task_domain"))
357
+ if task_domain:
358
+ normalized["task_domain"] = task_domain
359
+ capability_binding_ref = normalize_todo_capability_binding_ref(
360
+ item.get("capability_binding_ref")
361
+ )
362
+ if capability_binding_ref:
363
+ normalized["capability_binding_ref"] = capability_binding_ref
364
+ task_repository = normalize_todo_task_repository(item.get("task_repository"))
365
+ if task_repository:
366
+ normalized["task_repository"] = task_repository
367
+ continuation_policy = normalize_todo_continuation_policy(
368
+ item.get("continuation_policy")
369
+ )
370
+ if continuation_policy:
371
+ normalized["continuation_policy"] = continuation_policy
372
+ removed_continuation_policy = normalize_removed_todo_continuation_policy(
373
+ item.get("removed_continuation_policy")
374
+ )
375
+ if removed_continuation_policy:
376
+ normalized["removed_continuation_policy"] = removed_continuation_policy
377
+ required_write_scopes = normalize_required_write_scopes(item.get("required_write_scopes"))
378
+ if required_write_scopes:
379
+ normalized["required_write_scopes"] = required_write_scopes
380
+ required_capabilities = normalize_required_capabilities(item.get("required_capabilities"))
381
+ if required_capabilities:
382
+ normalized["required_capabilities"] = required_capabilities
383
+ target_capabilities = normalize_target_capabilities(item.get("target_capabilities"))
384
+ if target_capabilities:
385
+ normalized["target_capabilities"] = target_capabilities
386
+ explore_result_node_refs = normalize_explore_result_node_refs(
387
+ item.get("explore_result_node_refs")
388
+ )
389
+ if explore_result_node_refs:
390
+ normalized["explore_result_node_refs"] = explore_result_node_refs
391
+ decision_scope = normalize_todo_decision_scope(item.get("decision_scope"))
392
+ if decision_scope:
393
+ normalized["decision_scope"] = decision_scope
394
+ required_decision_scopes = normalize_todo_required_decision_scopes(
395
+ item.get("required_decision_scopes")
396
+ )
397
+ if required_decision_scopes:
398
+ normalized["required_decision_scopes"] = required_decision_scopes
399
+ decision_outcome = normalize_todo_decision_outcome(item.get("decision_outcome"))
400
+ if decision_outcome:
401
+ normalized["decision_outcome"] = decision_outcome
402
+ decision_scope_outcomes = normalize_todo_decision_scope_outcomes(
403
+ item.get("decision_scope_outcomes")
404
+ )
405
+ if decision_scope_outcomes:
406
+ normalized["decision_scope_outcomes"] = decision_scope_outcomes
407
+ claimed_by = normalize_todo_claimed_by(item.get("claimed_by"))
408
+ if claimed_by:
409
+ normalized["claimed_by"] = claimed_by
410
+ bound_agent = normalize_todo_bound_agent(item.get("bound_agent"))
411
+ if bound_agent:
412
+ normalized["bound_agent"] = bound_agent
413
+ goal_bound = normalize_todo_goal_bound(item.get("goal_bound"))
414
+ if goal_bound is not None:
415
+ normalized["goal_bound"] = goal_bound
416
+ blocks_agent = normalize_todo_blocks_agent(item.get("blocks_agent"))
417
+ if blocks_agent:
418
+ normalized["blocks_agent"] = blocks_agent
419
+ excluded_agents = normalize_todo_excluded_agents(item.get("excluded_agents"))
420
+ if excluded_agents:
421
+ normalized["excluded_agents"] = excluded_agents
422
+ global_gate = normalize_todo_global_gate(item.get("global_gate"))
423
+ if global_gate is not None:
424
+ normalized["global_gate"] = global_gate
425
+ unblocks_todo_id = normalize_todo_id(item.get("unblocks_todo_id"))
426
+ if unblocks_todo_id:
427
+ normalized["unblocks_todo_id"] = unblocks_todo_id
428
+ resume_when = normalize_todo_resume_when(item.get("resume_when"))
429
+ if resume_when:
430
+ normalized["resume_when"] = resume_when
431
+ no_followup = normalize_todo_no_followup(item.get("no_followup"))
432
+ if no_followup is not None:
433
+ normalized["no_followup"] = no_followup
434
+ if priority:
435
+ normalized["priority"] = priority
436
+ normalized["title"] = normalize_todo_text(title)
437
+ return normalized
438
+
439
+
440
+ def compact_todo_item(item: dict[str, Any]) -> dict[str, Any]:
441
+ compact: dict[str, Any] = {
442
+ "index": item.get("index"),
443
+ "done": bool(item.get("done")),
444
+ "text": item.get("text"),
445
+ }
446
+ for key in (
447
+ "schema_version",
448
+ "todo_id",
449
+ "role",
450
+ "status",
451
+ "priority",
452
+ "title",
453
+ "archive_state",
454
+ "source_section",
455
+ "task_class",
456
+ "action_kind",
457
+ "task_domain",
458
+ "capability_binding_ref",
459
+ "task_repository",
460
+ "continuation_policy",
461
+ "removed_continuation_policy",
462
+ "required_write_scopes",
463
+ "required_capabilities",
464
+ "target_capabilities",
465
+ "explore_result_node_refs",
466
+ "decision_scope",
467
+ "required_decision_scopes",
468
+ "decision_outcome",
469
+ "decision_scope_outcomes",
470
+ "claimed_by",
471
+ "created_by",
472
+ "last_actor_agent_id",
473
+ "bound_agent",
474
+ "goal_bound",
475
+ "blocks_agent",
476
+ "excluded_agents",
477
+ "global_gate",
478
+ "unblocks_todo_id",
479
+ "resume_when",
480
+ "resume_condition",
481
+ "resume_ready",
482
+ "no_followup",
483
+ "successor_todo_ids",
484
+ "replan_obligation_id",
485
+ "target_key",
486
+ "cadence",
487
+ "next_due_at",
488
+ "expires_at",
489
+ "watch_only",
490
+ "last_checked_at",
491
+ "result_hash",
492
+ "consecutive_no_change",
493
+ "material_change",
494
+ "max_no_change_before_replan",
495
+ "note",
496
+ "evidence",
497
+ "reason",
498
+ "completed_at",
499
+ "updated_at",
500
+ "superseded_by",
501
+ "completion_validation_required",
502
+ ):
503
+ if item.get(key) is not None:
504
+ compact[key] = item.get(key)
505
+ attach_todo_handoff_note(compact)
506
+ return compact
507
+
508
+
509
+ def _task_orchestration_authority(
510
+ lanes: _TodoGroupLanes,
511
+ *,
512
+ role: str | None,
513
+ ) -> dict[str, Any]:
514
+ candidate_items = (
515
+ [
516
+ {
517
+ key: compact[key]
518
+ for key in TASK_ORCHESTRATION_CANDIDATE_FIELDS
519
+ if key in compact
520
+ }
521
+ for item in lanes.projected_open_items
522
+ if todo_item_task_class(item) == TODO_TASK_CLASS_ADVANCEMENT
523
+ for compact in [compact_todo_item(item)]
524
+ ]
525
+ if role == "agent"
526
+ else []
527
+ )
528
+ user_blocker_items = (
529
+ [
530
+ {
531
+ key: compact[key]
532
+ for key in TASK_ORCHESTRATION_USER_BLOCKER_FIELDS
533
+ if key in compact
534
+ }
535
+ for item in lanes.projected_open_items
536
+ if normalize_todo_id(item.get("unblocks_todo_id"))
537
+ for compact in [compact_todo_item(item)]
538
+ ]
539
+ if role == "user"
540
+ else []
541
+ )
542
+ return {
543
+ "schema_version": TASK_ORCHESTRATION_AUTHORITY_SCHEMA_VERSION,
544
+ "role": role,
545
+ "candidate_items": candidate_items,
546
+ "user_blocker_items": user_blocker_items,
547
+ }
548
+
549
+
550
+ def compact_active_next_action_todo_item(item: dict[str, Any]) -> dict[str, Any]:
551
+ compact = compact_todo_item(item)
552
+ for key in (
553
+ "note",
554
+ "evidence",
555
+ "reason",
556
+ "completed_at",
557
+ "updated_at",
558
+ "superseded_by",
559
+ ):
560
+ compact.pop(key, None)
561
+ return compact
562
+
563
+
564
+ def todo_item_task_class(item: dict[str, Any]) -> str:
565
+ return projection_todo_item_task_class(item, task_text_keys=("text",))
566
+
567
+
568
+ def todo_item_is_actionable_open(item: dict[str, Any]) -> bool:
569
+ return projection_todo_item_is_actionable_open(item)
570
+
571
+
572
+ def todo_item_next_due_at(item: dict[str, Any]):
573
+ return projection_todo_item_next_due_at(item)
574
+
575
+
576
+ def todo_item_expires_at(item: dict[str, Any]):
577
+ return projection_todo_item_expires_at(item)
578
+
579
+
580
+ def todo_item_is_due_monitor(item: dict[str, Any], *, now=None) -> bool:
581
+ return projection_todo_item_is_due_monitor(item, now=now, task_text_keys=("text",))
582
+
583
+
584
+ def todo_item_missing_monitor_schedule(item: dict[str, Any], *, now=None) -> bool:
585
+ return projection_todo_item_missing_monitor_schedule(item, now=now, task_text_keys=("text",))
586
+
587
+
588
+ def todo_priority_rank(priority: Any) -> int:
589
+ return projection_todo_priority_rank(priority)
590
+
591
+
592
+ def todo_projection_sort_key(item: dict[str, Any]) -> tuple[int, int]:
593
+ return projection_todo_projection_sort_key(item, text_mode="prefix")
594
+
595
+
596
+ def claimed_visibility_items(items: list[dict[str, Any]], *, limit: int) -> list[dict[str, Any]]:
597
+ return projection_todo_claimed_visibility_items(items, limit=limit)
598
+
599
+
600
+ def todo_item_is_deferred(item: dict[str, Any]) -> bool:
601
+ return projection_todo_item_is_deferred(item)
602
+
603
+
604
+ def open_todo_items(
605
+ todos: dict[str, Any] | None,
606
+ *,
607
+ limit: int = MAX_PROJECT_ASSET_TODO_ITEMS,
608
+ text_limit: int = 220,
609
+ source_keys: tuple[str, ...] = ("first_open_items", "items"),
610
+ ) -> list[dict[str, Any]]:
611
+ if not isinstance(todos, dict):
612
+ return []
613
+ result: list[dict[str, Any]] = []
614
+ seen: set[tuple[Any, str]] = set()
615
+ for source_key in source_keys:
616
+ source_items = todos.get(source_key)
617
+ if not isinstance(source_items, list):
618
+ continue
619
+ for item in source_items:
620
+ if not isinstance(item, dict) or item.get("done"):
621
+ continue
622
+ text = normalize_todo_text(str(item.get("text") or ""), limit=text_limit)
623
+ if not text:
624
+ continue
625
+ key = (item.get("index"), text)
626
+ if key in seen:
627
+ continue
628
+ seen.add(key)
629
+ compact = compact_todo_item(item)
630
+ compact["done"] = False
631
+ compact["text"] = text
632
+ result.append(compact)
633
+ if len(result) >= limit:
634
+ return sorted(result, key=todo_projection_sort_key)
635
+ return sorted(result, key=todo_projection_sort_key)
636
+
637
+
638
+ def todo_lane_items(
639
+ todos: dict[str, Any] | None,
640
+ lane: str,
641
+ *,
642
+ limit: int = MAX_STATUS_TODOS_PER_ROLE,
643
+ text_limit: int = 220,
644
+ ) -> list[dict[str, Any]]:
645
+ return open_todo_items(
646
+ todos,
647
+ limit=limit,
648
+ text_limit=text_limit,
649
+ source_keys=(lane,),
650
+ )
651
+
652
+
653
+ def first_open_todo_text(
654
+ todos: dict[str, Any] | None,
655
+ *,
656
+ item_limit: int = 220,
657
+ ) -> str | None:
658
+ items = open_todo_items(todos, limit=1, text_limit=item_limit)
659
+ if not items:
660
+ return None
661
+ return str(items[0].get("text") or "") or None
662
+
663
+
664
+ def first_open_todo_item(
665
+ todos: dict[str, Any] | None,
666
+ *,
667
+ item_limit: int = MAX_PROJECT_ASSET_TODO_ITEMS,
668
+ text_limit: int = 220,
669
+ ) -> dict[str, Any] | None:
670
+ for todo in open_todo_items(todos, limit=item_limit, text_limit=text_limit):
671
+ if not isinstance(todo, dict) or todo.get("done"):
672
+ continue
673
+ return todo
674
+ return None
675
+
676
+
677
+ def project_asset_todo_summary(
678
+ todos: dict[str, Any] | None,
679
+ *,
680
+ role: str | None = None,
681
+ item_limit: int = MAX_PROJECT_ASSET_TODO_ITEMS,
682
+ deferred_item_limit: int = MAX_DEFERRED_TODO_VISIBILITY_ITEMS,
683
+ advancement_task_class: str = TODO_TASK_CLASS_ADVANCEMENT,
684
+ ) -> dict[str, Any] | None:
685
+ return build_project_asset_todo_summary(
686
+ todos,
687
+ role=role,
688
+ item_limit=item_limit,
689
+ deferred_item_limit=deferred_item_limit,
690
+ advancement_task_class=advancement_task_class,
691
+ open_todo_items=lambda value, **kwargs: open_todo_items(
692
+ value,
693
+ limit=kwargs.get("limit", item_limit),
694
+ text_limit=kwargs.get("text_limit", 220),
695
+ source_keys=kwargs.get("source_keys", ("first_open_items", "items")),
696
+ ),
697
+ compact_todo_item=compact_todo_item,
698
+ todo_lane_items=lambda value, lane, **kwargs: todo_lane_items(
699
+ value,
700
+ lane,
701
+ limit=kwargs.get("limit", MAX_STATUS_TODOS_PER_ROLE),
702
+ text_limit=kwargs.get("text_limit", 220),
703
+ ),
704
+ todo_item_is_actionable_open=todo_item_is_actionable_open,
705
+ todo_item_task_class=todo_item_task_class,
706
+ )
707
+
708
+
709
+ def dependency_blocker_summary(
710
+ items: list[dict[str, Any]],
711
+ *,
712
+ current_goal_id: str,
713
+ limit: int = MAX_DEPENDENCY_BLOCKERS,
714
+ ) -> dict[str, Any] | None:
715
+ blockers: list[dict[str, Any]] = []
716
+ for item in items:
717
+ if not isinstance(item, dict):
718
+ continue
719
+ goal_id = str(item.get("goal_id") or "")
720
+ if not goal_id or goal_id == current_goal_id:
721
+ continue
722
+ user_todos = item.get("user_todos") if isinstance(item.get("user_todos"), dict) else {}
723
+ for todo in user_todos.get("items") or []:
724
+ if not isinstance(todo, dict) or todo.get("done"):
725
+ continue
726
+ text = normalize_todo_text(str(todo.get("text") or ""), limit=220)
727
+ if not text:
728
+ continue
729
+ blockers.append(
730
+ {
731
+ "goal_id": goal_id,
732
+ "status": item.get("status"),
733
+ "waiting_on": item.get("waiting_on"),
734
+ "severity": item.get("severity"),
735
+ "index": todo.get("index"),
736
+ "text": text,
737
+ "source": "user_todos",
738
+ }
739
+ )
740
+ if not blockers:
741
+ return None
742
+ return {
743
+ "source": "attention_queue.user_todos",
744
+ "open_count": len(blockers),
745
+ "items": blockers[:limit],
746
+ }
747
+
748
+
749
+ def attach_dependency_blockers(
750
+ items: list[dict[str, Any]],
751
+ *,
752
+ limit: int = MAX_DEPENDENCY_BLOCKERS,
753
+ ) -> None:
754
+ for item in items:
755
+ if not isinstance(item, dict):
756
+ continue
757
+ goal_id = str(item.get("goal_id") or "")
758
+ if not goal_id:
759
+ continue
760
+ blockers = dependency_blocker_summary(items, current_goal_id=goal_id, limit=limit)
761
+ if blockers:
762
+ item["dependency_blockers"] = blockers
763
+
764
+
765
+ def normalized_pr_ref_parts(value: Any) -> dict[str, Any] | None:
766
+ candidate = str(value or "").strip().lower()
767
+ if not candidate:
768
+ return None
769
+ pull_url_match = GITHUB_PULL_URL_PATTERN.match(candidate)
770
+ if pull_url_match:
771
+ return {
772
+ "repo": pull_url_match.group("repo"),
773
+ "number": int(pull_url_match.group("number")),
774
+ "normalized": f"{pull_url_match.group('repo')}#{pull_url_match.group('number')}",
775
+ }
776
+ match = PR_REF_PATTERN.match(candidate)
777
+ if not match:
778
+ return None
779
+ repo = match.group("repo")
780
+ number = int(match.group("number"))
781
+ normalized = f"{repo}#{number}" if repo else f"#{number}"
782
+ return {"repo": repo, "number": number, "normalized": normalized}
783
+
784
+
785
+ def rollout_event_pr_refs(event: dict[str, Any]) -> list[dict[str, Any]]:
786
+ refs: list[dict[str, Any]] = []
787
+ code_refs = event.get("code_refs") if isinstance(event.get("code_refs"), dict) else {}
788
+ for value in (code_refs.get("pr_ref"), event.get("pr_ref")):
789
+ parsed = normalized_pr_ref_parts(value)
790
+ if parsed:
791
+ refs.append(parsed)
792
+ source_refs = event.get("source_refs")
793
+ if isinstance(source_refs, list):
794
+ for source_ref in source_refs:
795
+ if not isinstance(source_ref, dict):
796
+ continue
797
+ if str(source_ref.get("kind") or "").strip().lower() not in {"pull_request", "pr"}:
798
+ continue
799
+ parsed = normalized_pr_ref_parts(source_ref.get("ref"))
800
+ if parsed:
801
+ refs.append(parsed)
802
+ unique: list[dict[str, Any]] = []
803
+ seen: set[tuple[str | None, int]] = set()
804
+ for ref in refs:
805
+ key = (ref.get("repo"), int(ref.get("number") or 0))
806
+ if key in seen:
807
+ continue
808
+ seen.add(key)
809
+ unique.append(ref)
810
+ return unique
811
+
812
+
813
+ def _github_pr_repo_from_task_repository(value: Any) -> str | None:
814
+ repository = normalize_todo_task_repository(value)
815
+ prefix = "git:github.com/"
816
+ if not repository or not repository.casefold().startswith(prefix):
817
+ return None
818
+ path = repository[len(prefix) :].strip("/")
819
+ if len(path.split("/")) != 2:
820
+ return None
821
+ return path.casefold()
822
+
823
+
824
+ def pr_merged_condition(
825
+ target: str,
826
+ rollout_events: list[dict[str, Any]],
827
+ *,
828
+ task_repository: Any = None,
829
+ ) -> dict[str, Any]:
830
+ condition: dict[str, Any] = {
831
+ "pr_number": None,
832
+ "pr_repo": None,
833
+ "source": "rollout_event_log",
834
+ }
835
+ target_ref = normalized_pr_ref_parts(target)
836
+ if not target_ref:
837
+ condition["invalid_target"] = True
838
+ return condition
839
+ condition["pr_number"] = target_ref["number"]
840
+ target_repo = target_ref.get("repo")
841
+ if target_repo:
842
+ condition["pr_repo"] = target_repo
843
+ condition["repository_binding_source"] = "qualified_resume_when"
844
+ else:
845
+ target_repo = _github_pr_repo_from_task_repository(task_repository)
846
+ if target_repo:
847
+ condition["pr_repo"] = target_repo
848
+ condition["repository_binding_source"] = "task_repository"
849
+ else:
850
+ candidate_pr_refs = sorted(
851
+ {
852
+ str(event_ref.get("normalized") or "")
853
+ for event in rollout_events
854
+ if isinstance(event, dict)
855
+ and str(event.get("event_kind") or "").strip().lower()
856
+ in PR_MERGED_EVENT_KINDS
857
+ for event_ref in rollout_event_pr_refs(event)
858
+ if int(event_ref.get("number") or 0) == int(target_ref["number"])
859
+ and event_ref.get("repo")
860
+ }
861
+ )
862
+ condition.update(
863
+ {
864
+ "repository_binding_state": "ambiguous",
865
+ "repository_binding_reason": (
866
+ "task_repository_missing"
867
+ if not normalize_todo_task_repository(task_repository)
868
+ else "task_repository_not_github"
869
+ ),
870
+ "candidate_pr_refs": candidate_pr_refs[:8],
871
+ }
872
+ )
873
+ return condition
874
+ for event in rollout_events:
875
+ if not isinstance(event, dict):
876
+ continue
877
+ if str(event.get("event_kind") or "").strip().lower() not in PR_MERGED_EVENT_KINDS:
878
+ continue
879
+ for event_ref in rollout_event_pr_refs(event):
880
+ if int(event_ref.get("number") or 0) != int(target_ref["number"]):
881
+ continue
882
+ if event_ref.get("repo") != target_repo:
883
+ continue
884
+ condition.update(
885
+ {
886
+ "satisfied": True,
887
+ "matched_event_id": event.get("event_id"),
888
+ "matched_event_kind": event.get("event_kind"),
889
+ "matched_pr_ref": event_ref.get("normalized"),
890
+ "matched_event_at": event.get("recorded_at"),
891
+ }
892
+ )
893
+ return condition
894
+ return condition
895
+
896
+
897
+ def apply_resume_conditions(
898
+ items: list[dict[str, Any]],
899
+ *,
900
+ resume_source_items: list[dict[str, Any]] | None = None,
901
+ rollout_events: list[dict[str, Any]] | None = None,
902
+ ) -> None:
903
+ by_id: dict[str, dict[str, Any]] = {}
904
+ for source_item in [*(resume_source_items or []), *items]:
905
+ todo_id = normalize_todo_id(source_item.get("todo_id"))
906
+ if todo_id:
907
+ by_id[todo_id] = source_item
908
+ for item in items:
909
+ resume_when = normalize_todo_resume_when(item.get("resume_when"))
910
+ if not resume_when:
911
+ continue
912
+ condition: dict[str, Any] = {
913
+ "schema_version": "todo_resume_condition_v0",
914
+ "resume_when": resume_when,
915
+ "satisfied": False,
916
+ }
917
+ kind, separator, target = resume_when.partition(":")
918
+ condition["kind"] = kind
919
+ if separator:
920
+ condition["target"] = target
921
+ if kind == TODO_RESUME_KIND_TODO_DONE and target:
922
+ target_item = by_id.get(target)
923
+ condition["target_todo_id"] = target
924
+ condition["target_status"] = (
925
+ normalize_todo_status(target_item.get("status"))
926
+ if isinstance(target_item, dict)
927
+ else None
928
+ )
929
+ if isinstance(target_item, dict):
930
+ condition["target_archive_state"] = target_item.get("archive_state")
931
+ condition["target_source_section"] = target_item.get("source_section")
932
+ condition["target_task_class"] = todo_item_task_class(target_item)
933
+ target_claimed_by = normalize_todo_claimed_by(target_item.get("claimed_by"))
934
+ if target_claimed_by:
935
+ condition["target_claimed_by"] = target_claimed_by
936
+ condition["satisfied"] = condition["target_status"] == "done"
937
+ elif kind == TODO_RESUME_KIND_PR_MERGED and target:
938
+ condition.update(
939
+ pr_merged_condition(
940
+ target,
941
+ rollout_events or [],
942
+ task_repository=item.get("task_repository"),
943
+ )
944
+ )
945
+ elif kind == TODO_RESUME_KIND_CAPACITY_AVAILABLE and target:
946
+ condition.update(
947
+ {
948
+ "provider": "runtime_available_capabilities",
949
+ "provider_required": True,
950
+ "capability": target,
951
+ }
952
+ )
953
+ else:
954
+ condition["unsupported"] = True
955
+ item["resume_condition"] = condition
956
+ item["resume_ready"] = bool(condition.get("satisfied"))
957
+
958
+
959
+ def active_next_action_todo_ids(value: Any) -> set[str]:
960
+ todo_ids: set[str] = set()
961
+ for match in re.findall(r"\btodo_[A-Za-z0-9_-]+\b", str(value or "")):
962
+ todo_id = normalize_todo_id(match)
963
+ if todo_id:
964
+ todo_ids.add(todo_id)
965
+ return todo_ids
966
+
967
+
968
+ def _normalized_todo_id_list(value: Any) -> list[str]:
969
+ return normalize_todo_id_list(value)
970
+
971
+
972
+ def todo_successor_todo_ids(
973
+ item: dict[str, Any],
974
+ *,
975
+ items: list[dict[str, Any]],
976
+ ) -> list[str]:
977
+ successor_ids = _normalized_todo_id_list(item.get("successor_todo_ids"))
978
+ superseded_by = normalize_todo_id(item.get("superseded_by"))
979
+ if superseded_by and superseded_by not in successor_ids:
980
+ successor_ids.append(superseded_by)
981
+
982
+ source_todo_id = normalize_todo_id(item.get("todo_id"))
983
+ if not source_todo_id:
984
+ return successor_ids
985
+
986
+ for candidate in items:
987
+ if not isinstance(candidate, dict):
988
+ continue
989
+ candidate_id = normalize_todo_id(candidate.get("todo_id"))
990
+ if not candidate_id or candidate_id == source_todo_id:
991
+ continue
992
+ if todo_item_task_class(candidate) != TODO_TASK_CLASS_ADVANCEMENT:
993
+ continue
994
+ resume_when = normalize_todo_resume_when(candidate.get("resume_when")) or ""
995
+ resume_kind, separator, resume_target = resume_when.partition(":")
996
+ candidate_unblocks = normalize_todo_id(candidate.get("unblocks_todo_id"))
997
+ if candidate_unblocks != source_todo_id and not (
998
+ separator
999
+ and resume_kind == TODO_RESUME_KIND_TODO_DONE
1000
+ and normalize_todo_id(resume_target) == source_todo_id
1001
+ ):
1002
+ continue
1003
+ if candidate_id not in successor_ids:
1004
+ successor_ids.append(candidate_id)
1005
+ return successor_ids
1006
+
1007
+
1008
+ def todo_item_is_succession_tracked_completion(item: dict[str, Any]) -> bool:
1009
+ if todo_archive_state(item) != TODO_ARCHIVE_STATE_ACTIVE:
1010
+ return False
1011
+ if not item.get("done"):
1012
+ return False
1013
+ if todo_item_is_deferred(item):
1014
+ return False
1015
+ if todo_item_task_class(item) != TODO_TASK_CLASS_ADVANCEMENT:
1016
+ return False
1017
+ return any(
1018
+ item.get(key) is not None
1019
+ for key in (
1020
+ "action_kind",
1021
+ "task_repository",
1022
+ "continuation_policy",
1023
+ "claimed_by",
1024
+ "completed_at",
1025
+ "updated_at",
1026
+ "required_write_scopes",
1027
+ "required_capabilities",
1028
+ "target_capabilities",
1029
+ "explore_result_node_refs",
1030
+ "decision_scope",
1031
+ "required_decision_scopes",
1032
+ "unblocks_todo_id",
1033
+ "resume_when",
1034
+ "blocks_agent",
1035
+ "excluded_agents",
1036
+ "global_gate",
1037
+ )
1038
+ )
1039
+
1040
+
1041
+ def _completed_succession_sort_key(item: dict[str, Any]) -> tuple[str, int]:
1042
+ try:
1043
+ index = int(item.get("index"))
1044
+ except (TypeError, ValueError):
1045
+ index = 0
1046
+ timestamp = str(item.get("updated_at") or item.get("completed_at") or "")
1047
+ return (timestamp, index)
1048
+
1049
+
1050
+ def completed_without_successor_items(
1051
+ done_items: list[dict[str, Any]],
1052
+ *,
1053
+ all_items: list[dict[str, Any]],
1054
+ ) -> list[dict[str, Any]]:
1055
+ gap_items: list[dict[str, Any]] = []
1056
+ for item in done_items:
1057
+ if not todo_item_is_succession_tracked_completion(item):
1058
+ continue
1059
+ if normalize_todo_no_followup(item.get("no_followup")) is True:
1060
+ continue
1061
+ if todo_successor_todo_ids(item, items=all_items):
1062
+ continue
1063
+ compact = compact_todo_item(item)
1064
+ for key in ("note", "evidence", "reason"):
1065
+ compact.pop(key, None)
1066
+ compact["succession_tracked"] = True
1067
+ compact["recommended_action"] = (
1068
+ "record no_followup=true or add/link a successor todo"
1069
+ )
1070
+ gap_items.append(compact)
1071
+ return sorted(gap_items, key=_completed_succession_sort_key, reverse=True)
1072
+
1073
+
1074
+ def _structured_todo_group_items(
1075
+ items: list[dict[str, Any]],
1076
+ *,
1077
+ source_section: str | None,
1078
+ role: str | None,
1079
+ ) -> list[dict[str, Any]]:
1080
+ return [
1081
+ structured_todo_item(
1082
+ item,
1083
+ role=role,
1084
+ source_section=source_section,
1085
+ archive_state=todo_archive_state(item),
1086
+ )
1087
+ if isinstance(item, dict)
1088
+ else item
1089
+ for item in items
1090
+ ]
1091
+
1092
+
1093
+ def _structured_resume_source_items(
1094
+ items: list[dict[str, Any]] | None,
1095
+ *,
1096
+ source_section: str | None,
1097
+ ) -> list[dict[str, Any]]:
1098
+ return [
1099
+ structured_todo_item(
1100
+ item,
1101
+ role=item.get("role") if isinstance(item.get("role"), str) else None,
1102
+ source_section=(
1103
+ item.get("source_section")
1104
+ if isinstance(item.get("source_section"), str)
1105
+ else source_section
1106
+ ),
1107
+ archive_state=(
1108
+ str(item.get("archive_state"))
1109
+ if item.get("archive_state") is not None
1110
+ else TODO_ARCHIVE_STATE_ACTIVE
1111
+ ),
1112
+ )
1113
+ for item in (items or [])
1114
+ if isinstance(item, dict)
1115
+ ]
1116
+
1117
+
1118
+ def _todo_group_lanes(
1119
+ items: list[dict[str, Any]],
1120
+ *,
1121
+ preferred_todo_ids: set[str] | None,
1122
+ ) -> _TodoGroupLanes:
1123
+ open_items = [item for item in items if not item.get("done")]
1124
+ terminal_items = [item for item in items if item.get("done")]
1125
+ deferred_items = [item for item in terminal_items if todo_item_is_deferred(item)]
1126
+ done_items = [item for item in terminal_items if not todo_item_is_deferred(item)]
1127
+ projected_open_items = sorted(open_items, key=todo_projection_sort_key)
1128
+ projected_deferred_items = sorted(deferred_items, key=todo_projection_sort_key)
1129
+ budgeted_items = [
1130
+ *projected_open_items,
1131
+ *projected_deferred_items,
1132
+ *done_items,
1133
+ ]
1134
+ claimed_open_items = [item for item in projected_open_items if item.get("claimed_by")]
1135
+ unclaimed_open_items = [item for item in projected_open_items if not item.get("claimed_by")]
1136
+ executable_items = [
1137
+ item
1138
+ for item in projected_open_items
1139
+ if todo_item_is_actionable_open(item)
1140
+ if todo_item_task_class(item) == TODO_TASK_CLASS_ADVANCEMENT
1141
+ ]
1142
+ blocker_items = [
1143
+ item
1144
+ for item in projected_open_items
1145
+ if normalize_todo_status(item.get("status")) == "blocked"
1146
+ if todo_item_task_class(item) == TODO_TASK_CLASS_BLOCKER
1147
+ ]
1148
+ resume_blocked_items = [
1149
+ item
1150
+ for item in projected_open_items
1151
+ if normalize_todo_resume_when(item.get("resume_when"))
1152
+ if item.get("resume_ready") is False
1153
+ ]
1154
+ monitor_items = [
1155
+ item
1156
+ for item in projected_open_items
1157
+ if todo_item_is_actionable_open(item)
1158
+ if todo_item_task_class(item) == TODO_TASK_CLASS_MONITOR
1159
+ ]
1160
+ monitor_due_items = [item for item in monitor_items if todo_item_is_due_monitor(item)]
1161
+ monitor_schedule_gap_items = [
1162
+ item
1163
+ for item in monitor_items
1164
+ if todo_item_missing_monitor_schedule(item)
1165
+ ]
1166
+ claimed_advancement_items = [
1167
+ item
1168
+ for item in claimed_open_items
1169
+ if todo_item_is_actionable_open(item)
1170
+ if todo_item_task_class(item) == TODO_TASK_CLASS_ADVANCEMENT
1171
+ ]
1172
+ claimed_monitor_items = [
1173
+ item
1174
+ for item in claimed_open_items
1175
+ if todo_item_is_actionable_open(item)
1176
+ if todo_item_task_class(item) == TODO_TASK_CLASS_MONITOR
1177
+ ]
1178
+ preferred_ids = {
1179
+ todo_id
1180
+ for todo_id in (preferred_todo_ids or set())
1181
+ if normalize_todo_id(todo_id)
1182
+ }
1183
+ active_next_action_items = [
1184
+ item
1185
+ for item in projected_open_items
1186
+ if normalize_todo_id(item.get("todo_id")) in preferred_ids
1187
+ ]
1188
+ active_next_action_executable_items = [
1189
+ item
1190
+ for item in executable_items
1191
+ if normalize_todo_id(item.get("todo_id")) in preferred_ids
1192
+ ]
1193
+ return _TodoGroupLanes(
1194
+ open_items=open_items,
1195
+ terminal_items=terminal_items,
1196
+ deferred_items=deferred_items,
1197
+ done_items=done_items,
1198
+ projected_open_items=projected_open_items,
1199
+ projected_deferred_items=projected_deferred_items,
1200
+ budgeted_items=budgeted_items,
1201
+ claimed_open_items=claimed_open_items,
1202
+ unclaimed_open_items=unclaimed_open_items,
1203
+ executable_items=executable_items,
1204
+ blocker_items=blocker_items,
1205
+ resume_blocked_items=resume_blocked_items,
1206
+ monitor_items=monitor_items,
1207
+ monitor_due_items=monitor_due_items,
1208
+ monitor_schedule_gap_items=monitor_schedule_gap_items,
1209
+ claimed_advancement_items=claimed_advancement_items,
1210
+ claimed_monitor_items=claimed_monitor_items,
1211
+ active_next_action_items=active_next_action_items,
1212
+ active_next_action_executable_items=active_next_action_executable_items,
1213
+ )
1214
+
1215
+
1216
+ def compact_todo_group(
1217
+ items: list[dict[str, Any]],
1218
+ *,
1219
+ source_section: str | None,
1220
+ role: str | None = None,
1221
+ include_empty_source: bool = False,
1222
+ preferred_todo_ids: set[str] | None = None,
1223
+ resume_source_items: list[dict[str, Any]] | None = None,
1224
+ rollout_events: list[dict[str, Any]] | None = None,
1225
+ item_limit: int | None = MAX_STATUS_TODOS_PER_ROLE,
1226
+ include_task_orchestration_authority: bool = False,
1227
+ ) -> dict[str, Any] | None:
1228
+ if not items and not include_empty_source:
1229
+ return None
1230
+ items = _structured_todo_group_items(
1231
+ items,
1232
+ source_section=source_section,
1233
+ role=role,
1234
+ )
1235
+ apply_resume_conditions(
1236
+ items,
1237
+ resume_source_items=_structured_resume_source_items(
1238
+ resume_source_items,
1239
+ source_section=source_section,
1240
+ ),
1241
+ rollout_events=rollout_events,
1242
+ )
1243
+ lanes = _todo_group_lanes(items, preferred_todo_ids=preferred_todo_ids)
1244
+ source_valid = role in {"user", "agent"} and bool(str(source_section or "").strip())
1245
+ no_followup_items = [
1246
+ item
1247
+ for item in items
1248
+ if todo_done_for_status(item.get("status"))
1249
+ and normalize_todo_no_followup(item.get("no_followup")) is True
1250
+ ]
1251
+ successor_gap_items = completed_without_successor_items(
1252
+ lanes.done_items,
1253
+ all_items=items,
1254
+ )
1255
+ recent_completed_advancement_items = [
1256
+ compact_todo_item(item)
1257
+ for item in sorted(
1258
+ (
1259
+ item
1260
+ for item in lanes.done_items
1261
+ if todo_item_task_class(item) == TODO_TASK_CLASS_ADVANCEMENT
1262
+ and str(item.get("completed_at") or "").strip()
1263
+ ),
1264
+ key=_completed_succession_sort_key,
1265
+ reverse=True,
1266
+ )[:MAX_RECENT_COMPLETED_ADVANCEMENT_ITEMS]
1267
+ ]
1268
+ for item in recent_completed_advancement_items:
1269
+ for key in ("note", "evidence", "reason"):
1270
+ item.pop(key, None)
1271
+ handoff_gates = build_todo_handoff_gate_states(items)
1272
+ route_replan_required = any(
1273
+ item.get("route_continuation_replan_required") is True
1274
+ for item in [*items, *handoff_gates]
1275
+ )
1276
+ watch_only_monitor_items = [
1277
+ item
1278
+ for item in lanes.monitor_items
1279
+ if projection_todo_item_is_watch_only_monitor(item)
1280
+ ]
1281
+ watch_only_ids = {
1282
+ normalize_todo_id(item.get("todo_id"))
1283
+ for item in watch_only_monitor_items
1284
+ }
1285
+ watch_only_monitor_due_items = [
1286
+ item
1287
+ for item in lanes.monitor_due_items
1288
+ if normalize_todo_id(item.get("todo_id")) in watch_only_ids
1289
+ ]
1290
+ convergent_open_items = [
1291
+ item
1292
+ for item in lanes.open_items
1293
+ if normalize_todo_id(item.get("todo_id")) not in watch_only_ids
1294
+ ]
1295
+ summary = {
1296
+ "schema_version": "todo_summary_v0",
1297
+ "source_section": source_section,
1298
+ "total_count": len(items),
1299
+ "open_count": len(lanes.open_items),
1300
+ "done_count": len(lanes.terminal_items),
1301
+ "deferred_count": len(lanes.deferred_items),
1302
+ "first_open_items": [
1303
+ compact_todo_item(item) for item in lanes.projected_open_items[:3]
1304
+ ],
1305
+ "first_executable_items": [
1306
+ compact_todo_item(item) for item in lanes.executable_items[:3]
1307
+ ],
1308
+ "monitor_open_items": [
1309
+ compact_todo_item(item) for item in lanes.monitor_items
1310
+ ],
1311
+ "monitor_due_count": len(lanes.monitor_due_items),
1312
+ "monitor_due_items": [
1313
+ compact_todo_item(item)
1314
+ for item in lanes.monitor_due_items[:MAX_MONITOR_DUE_ITEMS]
1315
+ ],
1316
+ "monitor_schedule_gap_count": len(lanes.monitor_schedule_gap_items),
1317
+ "monitor_schedule_gap_items": [
1318
+ compact_todo_item(item)
1319
+ for item in lanes.monitor_schedule_gap_items[:MAX_MONITOR_DUE_ITEMS]
1320
+ ],
1321
+ "unclaimed_priority_open_items": [
1322
+ compact_todo_item(item)
1323
+ for item in lanes.unclaimed_open_items[:MAX_PROJECT_ASSET_TODO_BACKLOG_ITEMS]
1324
+ ],
1325
+ "claimed_open_items": [
1326
+ compact_todo_item(item)
1327
+ for item in claimed_visibility_items(
1328
+ lanes.claimed_open_items,
1329
+ limit=MAX_TODO_VISIBILITY_LANE_ITEMS,
1330
+ )
1331
+ ],
1332
+ "claimed_advancement_open_items": [
1333
+ compact_todo_item(item)
1334
+ for item in claimed_visibility_items(
1335
+ lanes.claimed_advancement_items,
1336
+ limit=MAX_TODO_VISIBILITY_LANE_ITEMS,
1337
+ )
1338
+ ],
1339
+ "claimed_monitor_open_items": [
1340
+ compact_todo_item(item)
1341
+ for item in claimed_visibility_items(
1342
+ lanes.claimed_monitor_items,
1343
+ limit=MAX_TODO_VISIBILITY_LANE_ITEMS,
1344
+ )
1345
+ ],
1346
+ "backlog_items": [
1347
+ compact_todo_item(item)
1348
+ for item in lanes.projected_open_items[:MAX_PROJECT_ASSET_TODO_BACKLOG_ITEMS]
1349
+ ],
1350
+ "executable_backlog_items": [
1351
+ compact_todo_item(item)
1352
+ for item in lanes.executable_items[:MAX_PROJECT_ASSET_TODO_BACKLOG_ITEMS]
1353
+ ],
1354
+ "deferred_items": [
1355
+ compact_todo_item(item)
1356
+ for item in lanes.projected_deferred_items[:MAX_DEFERRED_TODO_VISIBILITY_ITEMS]
1357
+ ],
1358
+ "deferred_resume_candidates": [
1359
+ compact_todo_item(item)
1360
+ for item in lanes.projected_deferred_items
1361
+ if item.get("resume_ready") is True
1362
+ ][:MAX_DEFERRED_TODO_VISIBILITY_ITEMS],
1363
+ "items": lanes.budgeted_items if item_limit is None else lanes.budgeted_items[:item_limit],
1364
+ }
1365
+ if watch_only_monitor_items:
1366
+ summary["watch_only_monitor_count"] = len(watch_only_monitor_items)
1367
+ summary["watch_only_monitor_due_count"] = len(watch_only_monitor_due_items)
1368
+ summary["convergence_open_count"] = len(convergent_open_items)
1369
+ if recent_completed_advancement_items:
1370
+ summary["recent_completed_advancement_items"] = recent_completed_advancement_items
1371
+ if include_task_orchestration_authority:
1372
+ summary["task_orchestration_authority"] = _task_orchestration_authority(
1373
+ lanes,
1374
+ role=role,
1375
+ )
1376
+ if lanes.blocker_items:
1377
+ summary["blocker_open_count"] = len(lanes.blocker_items)
1378
+ summary["blocker_items"] = [
1379
+ compact_todo_item(item) for item in lanes.blocker_items
1380
+ ]
1381
+ if not convergent_open_items and not lanes.deferred_items:
1382
+ summary["source_proof"] = {
1383
+ "schema_version": TODO_SOURCE_PROOF_SCHEMA_VERSION,
1384
+ "role": role,
1385
+ "item_count": len(items),
1386
+ "derived": source_valid,
1387
+ }
1388
+ if (
1389
+ source_valid
1390
+ and len(lanes.done_items) + len(watch_only_monitor_items) == len(items)
1391
+ and not convergent_open_items
1392
+ and not successor_gap_items
1393
+ and not route_replan_required
1394
+ ):
1395
+ summary["terminal_closure_proof"] = {
1396
+ "schema_version": TODO_TERMINAL_CLOSURE_PROOF_SCHEMA_VERSION,
1397
+ "role": role,
1398
+ "source_section": source_section,
1399
+ "item_count": len(items),
1400
+ "all_todos_done": not watch_only_monitor_items,
1401
+ "monitor_open_count": len(watch_only_monitor_items),
1402
+ "successor_gap_count": 0,
1403
+ "route_replan_count": 0,
1404
+ "no_followup_count": len(no_followup_items),
1405
+ "derived": True,
1406
+ }
1407
+ if watch_only_monitor_items:
1408
+ summary["terminal_closure_proof"].update(
1409
+ {
1410
+ "all_convergent_todos_done": True,
1411
+ "watch_only_monitor_count": len(watch_only_monitor_items),
1412
+ }
1413
+ )
1414
+ if no_followup_items:
1415
+ summary["closure_intent"] = {
1416
+ "schema_version": TODO_CLOSURE_INTENT_SCHEMA_VERSION,
1417
+ "kind": "no_followup",
1418
+ "derived": True,
1419
+ "count": len(no_followup_items),
1420
+ }
1421
+ if lanes.resume_blocked_items:
1422
+ summary["resume_blocked_count"] = len(lanes.resume_blocked_items)
1423
+ summary["resume_blocked_items"] = [
1424
+ compact_todo_item(item)
1425
+ for item in lanes.resume_blocked_items[:MAX_DEFERRED_TODO_VISIBILITY_ITEMS]
1426
+ ]
1427
+ if handoff_gates:
1428
+ summary["handoff_gates"] = handoff_gates
1429
+ if successor_gap_items:
1430
+ compact_gap_items = successor_gap_items[:MAX_COMPLETED_SUCCESSION_WARNING_ITEMS]
1431
+ summary["completed_without_successor_count"] = len(successor_gap_items)
1432
+ summary["completed_without_successor_items"] = compact_gap_items
1433
+ summary["todo_succession_warning"] = {
1434
+ "schema_version": TODO_SUCCESSION_WARNING_SCHEMA_VERSION,
1435
+ "reason_code": "completed_advancement_without_successor",
1436
+ "count": len(successor_gap_items),
1437
+ "items": compact_gap_items,
1438
+ "recommended_action": (
1439
+ "record no_followup=true on completed tracked work, "
1440
+ "or add/link a successor todo before closing the slice"
1441
+ ),
1442
+ }
1443
+ if lanes.active_next_action_items:
1444
+ summary["active_next_action_items"] = [
1445
+ compact_active_next_action_todo_item(item)
1446
+ for item in lanes.active_next_action_items
1447
+ ]
1448
+ if lanes.active_next_action_executable_items:
1449
+ summary["active_next_action_executable_items"] = [
1450
+ compact_active_next_action_todo_item(item)
1451
+ for item in lanes.active_next_action_executable_items
1452
+ ]
1453
+ if lanes.claimed_open_items:
1454
+ summary["claimed_open_count"] = len(lanes.claimed_open_items)
1455
+ summary["unclaimed_open_count"] = len(lanes.open_items) - len(lanes.claimed_open_items)
1456
+ summary["claimed_advancement_open_count"] = len(lanes.claimed_advancement_items)
1457
+ summary["claimed_monitor_open_count"] = len(lanes.claimed_monitor_items)
1458
+ return summary