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,1476 @@
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+ import re
5
+ from dataclasses import dataclass
6
+ from enum import Enum
7
+ from typing import Any, Callable
8
+ from urllib.parse import quote, unquote
9
+
10
+ from ...repository_identity import normalize_repository_identity
11
+
12
+
13
+ TODO_TASK_PATTERN = re.compile(r"^\s*[-*]\s+\[([ xX-])\]\s+(.+?)\s*$")
14
+ TODO_METADATA_PATTERN = re.compile(r"^\s*<!--\s*loopx:(?:todo\s+)?(?P<body>.*?)\s*-->\s*$")
15
+ TODO_METADATA_TOKEN_PATTERN = re.compile(
16
+ r"(?<!\S)(?P<key>[a-z_][a-z0-9_]*)=(?P<value>[^\s<>]+)"
17
+ )
18
+ TODO_ACTION_KIND_PATTERN = re.compile(r"^[a-z][a-z0-9_-]{0,63}$")
19
+ TODO_TASK_DOMAIN_PATTERN = re.compile(r"^[a-z][a-z0-9_.-]{0,63}$")
20
+ TODO_ID_PATTERN = re.compile(r"^todo_[a-z0-9_-]{3,64}$")
21
+ TODO_AGENT_CLAIM_PATTERN = re.compile(r"^[a-z][a-z0-9_.:@-]{0,79}$")
22
+ TODO_CAPABILITY_PATTERN = re.compile(r"^[a-z][a-z0-9_:-]{0,63}$")
23
+ TODO_CAPABILITY_BINDING_REF_PATTERN = re.compile(
24
+ r"^[a-z][a-z0-9_.-]{0,31}:[a-z][a-z0-9_.:-]{2,95}$"
25
+ )
26
+ TODO_REPLAN_OBLIGATION_ID_PATTERN = re.compile(r"^replan-[a-f0-9]{16}$")
27
+ TODO_EXPLORE_RESULT_NODE_REF_PATTERN = re.compile(r"^[A-Za-z][A-Za-z0-9_.:-]{0,95}$")
28
+ TODO_EXPLORE_RESULT_NODE_REF_LIMIT = 8
29
+ TODO_DECISION_SCOPE_KEY_PATTERN = re.compile(r"^(?:\*|[a-z0-9][a-z0-9_.:@*/-]{0,95})$")
30
+ TODO_RESUME_KIND_TODO_DONE = "todo_done"
31
+ TODO_RESUME_KIND_PR_MERGED = "pr_merged"
32
+ TODO_RESUME_KIND_CAPACITY_AVAILABLE = "capacity_available"
33
+ TODO_RESUME_KIND_VALUES = {
34
+ TODO_RESUME_KIND_TODO_DONE,
35
+ TODO_RESUME_KIND_PR_MERGED,
36
+ TODO_RESUME_KIND_CAPACITY_AVAILABLE,
37
+ }
38
+ TODO_RESUME_WHEN_PATTERN = re.compile(r"^[a-z][a-z0-9_-]{0,31}(?::[a-z0-9_.:@-]{1,96})?$")
39
+ TODO_RESUME_PR_MERGED_PATTERN = re.compile(
40
+ r"^pr_merged:(?:(?:[a-z0-9_.-]{1,80})/(?:[a-z0-9_.-]{1,100}))?#[1-9][0-9]{0,8}$"
41
+ )
42
+ TODO_WRITE_SCOPE_MAX_CHARS = 160
43
+ TODO_MONITOR_METADATA_FIELDS = (
44
+ "target_key",
45
+ "cadence",
46
+ "next_due_at",
47
+ "expires_at",
48
+ "last_checked_at",
49
+ "result_hash",
50
+ "consecutive_no_change",
51
+ "material_change",
52
+ "max_no_change_before_replan",
53
+ "watch_only",
54
+ )
55
+
56
+
57
+ def normalize_todo_watch_only(value: Any) -> bool | None:
58
+ if value is True or str(value or "").strip().lower() == "true":
59
+ return True
60
+ if value is False or str(value or "").strip().lower() == "false":
61
+ return False
62
+ return None
63
+
64
+ TODO_TASK_CLASS_ADVANCEMENT = "advancement_task"
65
+ TODO_TASK_CLASS_MONITOR = "continuous_monitor"
66
+ TODO_TASK_CLASS_USER_GATE = "user_gate"
67
+ TODO_TASK_CLASS_USER_ACTION = "user_action"
68
+ TODO_TASK_CLASS_BLOCKER = "blocker"
69
+ TODO_TASK_CLASS_VALUES = {
70
+ TODO_TASK_CLASS_ADVANCEMENT,
71
+ TODO_TASK_CLASS_MONITOR,
72
+ TODO_TASK_CLASS_USER_GATE,
73
+ TODO_TASK_CLASS_USER_ACTION,
74
+ TODO_TASK_CLASS_BLOCKER,
75
+ }
76
+
77
+
78
+ def resolve_next_user_task_class(
79
+ next_user_todo: str | None,
80
+ next_user_task_class: str | None,
81
+ ) -> str | None:
82
+ if not next_user_todo:
83
+ if next_user_task_class:
84
+ raise ValueError("--next-user-task-class requires --next-user-todo")
85
+ return None
86
+ if not next_user_task_class:
87
+ raise ValueError(
88
+ "--next-user-todo requires explicit --next-user-task-class "
89
+ "user_action|user_gate"
90
+ )
91
+ if next_user_task_class not in {
92
+ TODO_TASK_CLASS_USER_GATE,
93
+ TODO_TASK_CLASS_USER_ACTION,
94
+ }:
95
+ raise ValueError(
96
+ "next_user_task_class must be one of: user_action, user_gate"
97
+ )
98
+ return next_user_task_class
99
+
100
+
101
+ TODO_DECISION_SCOPE_SCHEMA_VERSION = "decision_scope_v0"
102
+ TODO_DECISION_SCOPE_OUTCOME_SCHEMA_VERSION = "todo_decision_scope_outcome_v0"
103
+ TODO_DECISION_OUTCOME_VALUES = {"approve", "reject", "cancel"}
104
+ TODO_DECISION_SCOPE_KIND_VALUES = {
105
+ "private_read",
106
+ "write_scope",
107
+ "resource",
108
+ "production",
109
+ "public_claim",
110
+ "direction",
111
+ "other",
112
+ }
113
+ TODO_DECISION_SCOPE_GRANULARITY_VALUES = {
114
+ "action",
115
+ "lane",
116
+ "goal",
117
+ "project",
118
+ "global",
119
+ }
120
+
121
+ TODO_STATUS_OPEN = "open"
122
+ TODO_STATUS_DONE = "done"
123
+ TODO_STATUS_BLOCKED = "blocked"
124
+ TODO_STATUS_DEFERRED = "deferred"
125
+ TODO_STATUS_VALUES = {
126
+ TODO_STATUS_OPEN,
127
+ TODO_STATUS_DONE,
128
+ TODO_STATUS_BLOCKED,
129
+ TODO_STATUS_DEFERRED,
130
+ }
131
+ TODO_TERMINAL_STATUS_VALUES = {TODO_STATUS_DONE, TODO_STATUS_DEFERRED}
132
+ TODO_LEGACY_TERMINAL_STATUS_VALUES = {"completed", "closed", "archived"}
133
+
134
+ TODO_ACTION_KIND_ADVANCEMENT_VALUES = {
135
+ "advance",
136
+ "analyze",
137
+ "benchmark_run",
138
+ "codex_run",
139
+ "compact_blocker_writeback",
140
+ "compare",
141
+ "execute",
142
+ "fix",
143
+ "implement",
144
+ "rebuild",
145
+ "rebuild_score",
146
+ "repair",
147
+ "run",
148
+ "run_eval",
149
+ "test",
150
+ "validate",
151
+ "writeback",
152
+ }
153
+ TODO_ACTION_KIND_MONITOR_VALUES = {
154
+ "external_evidence",
155
+ "monitor",
156
+ "observe",
157
+ "poll",
158
+ "watch",
159
+ }
160
+
161
+
162
+ class TodoContinuationPolicy(str, Enum):
163
+ INDEPENDENT_HANDOFF = "independent_handoff"
164
+ SAME_AGENT_NON_DELIVERY = "same_agent_non_delivery"
165
+
166
+
167
+ TODO_CONTINUATION_POLICY_VALUES = {
168
+ policy.value for policy in TodoContinuationPolicy
169
+ }
170
+ TODO_REMOVED_REVIEW_CONTINUATION_POLICY_VALUES = {
171
+ "primary_review",
172
+ "review_handoff",
173
+ }
174
+
175
+ TODO_HARD_MONITOR_PATTERNS = (
176
+ re.compile(r"(?i)\bdo not\b.*\b(?:launch|run|execute|start)\b.*\buntil\b"),
177
+ re.compile(r"(?i)\b(?:only|just)\b.*\b(?:after|when|once)\b.*\b(?:owner|user|credential|approval|prerequisite|evidence)\b"),
178
+ re.compile(r"(?i)\b(?:credential|gcp|gcs|gcp_project|gcp_sa_key|gs://)\b.*\b(?:missing|required|provide|proof|prerequisite|gate|gated)\b"),
179
+ re.compile(r"(?i)\b(?:readiness|proof)\b.*\bbefore any formal\b.*\brun\b"),
180
+ re.compile(r"(?i)\bremaining formal\b.*\bpath\b"),
181
+ re.compile(r"(?i)\b(?:route|input)\b.*\babsent\b"),
182
+ re.compile(r"(?i)\b0\b.*\b(?:candidate|candidates)\b"),
183
+ )
184
+
185
+ TODO_ADVANCEMENT_OVERRIDE_PATTERNS = (
186
+ re.compile(
187
+ r"(?i)(?:^|[::]\s*)(?:implement|add|make|fix|build|wire|define|compare|run|"
188
+ r"execute|test|validate|rebuild|repair|archive|publish|merge|write|attribute|"
189
+ r"collect|aggregate|generate|produce|materialize|rerun|repeat|eval|evaluate|score)\b"
190
+ ),
191
+ re.compile(
192
+ r"(?i)\b(?:implementation slice|validation-backed patch|smoke fixture|"
193
+ r"regression suite|readiness scan|source preflight|setup-readiness scan)\b"
194
+ ),
195
+ )
196
+ TODO_BLOCKED_MONITOR_PATTERNS = (
197
+ *TODO_HARD_MONITOR_PATTERNS,
198
+ re.compile(r"(?i)\b(?:blocked|gated|waiting)\b.*\b(?:owner|user|credential|substrate|proof|prerequisite|evidence)\b"),
199
+ )
200
+ TODO_MONITOR_PATTERNS = (
201
+ re.compile(r"(?i)\bdependency monitor\b"),
202
+ re.compile(r"(?i)\bobservation lane\b"),
203
+ re.compile(r"(?i)(?:^|[::]\s*)observe\b"),
204
+ re.compile(r"(?i)(?:^|[::]\s*)poll\b"),
205
+ re.compile(r"(?i)(?:^|[::]\s*)watch\b"),
206
+ re.compile(r"(?i)\bmonitor-only\b"),
207
+ *TODO_BLOCKED_MONITOR_PATTERNS,
208
+ )
209
+ TODO_ADVANCEMENT_PATTERNS = (
210
+ *TODO_ADVANCEMENT_OVERRIDE_PATTERNS,
211
+ re.compile(r"(?i)\b(?:task|validation hypothesis|validation step|bounded step|learning run)\b"),
212
+ )
213
+
214
+ NEXT_ACTION_HARD_MONITOR_PATTERNS = (
215
+ re.compile(r"(?i)\bdo not\b.*\b(?:launch|run|execute|start)\b.*\buntil\b"),
216
+ re.compile(r"(?i)\b(?:waiting|blocked|gated)\b.*\b(?:owner|user|credential|approval|prerequisite|evidence)\b"),
217
+ )
218
+ NEXT_ACTION_ADVANCEMENT_HINT_PATTERNS = (
219
+ re.compile(r"(?i)\bplanning/self[- ]?repair\b"),
220
+ re.compile(r"(?i)\bplanning[- ]?self[- ]?repair\b"),
221
+ re.compile(r"(?i)\bself[- ]?repair capability\b"),
222
+ re.compile(r"(?i)\badvance(?:ment)?[- ]class\b"),
223
+ re.compile(r"(?i)\badvance primary backlog\b"),
224
+ re.compile(r"(?i)\bnext eligible advancement turn\b"),
225
+ re.compile(r"(?i)\bpackage\b.*\b(?:adapter|contract|artifact)\b"),
226
+ re.compile(r"(?i)\bselect\b.*\b(?:task|validation hypothesis|validation step)\b"),
227
+ re.compile(r"(?i)\b(?:local-material-ready|material-ready)\b.*\b(?:task|run|validation)\b"),
228
+ re.compile(r"(?i)\b(?:run|test)\b.*\bvalidation hypothesis\b"),
229
+ re.compile(
230
+ r"(?i)\b(?:collect|aggregate|generate|produce|materialize)\b.*\b(?:then|and)\b.*\b"
231
+ r"(?:run|rerun|repeat|rebuild|score|scorer|gate|eval|evaluate|validate)\b"
232
+ ),
233
+ re.compile(
234
+ r"(?i)(?:^|[.;::]\s*)(?:run|execute|test|validate|rebuild|compare|implement|fix|"
235
+ r"write|package|collect|aggregate|generate|produce|materialize|rerun|repeat|"
236
+ r"eval|evaluate|score)\b"
237
+ ),
238
+ )
239
+
240
+
241
+ def compact_todo_text(value: Any) -> str:
242
+ return " ".join(str(value or "").strip().split())
243
+
244
+
245
+ def normalize_todo_action_kind(value: Any) -> str | None:
246
+ candidate = str(value or "").strip().lower()
247
+ if not candidate:
248
+ return None
249
+ if TODO_ACTION_KIND_PATTERN.match(candidate):
250
+ return candidate
251
+ return None
252
+
253
+
254
+ def normalize_todo_task_domain(value: Any) -> str | None:
255
+ candidate = str(value or "").strip().lower()
256
+ if not candidate:
257
+ return None
258
+ if TODO_TASK_DOMAIN_PATTERN.fullmatch(candidate):
259
+ return candidate
260
+ return None
261
+
262
+
263
+ def normalize_todo_replan_obligation_id(value: Any) -> str | None:
264
+ candidate = str(value or "").strip()
265
+ if not candidate:
266
+ return None
267
+ if TODO_REPLAN_OBLIGATION_ID_PATTERN.fullmatch(candidate):
268
+ return candidate
269
+ return None
270
+
271
+
272
+ def normalize_todo_capability_binding_ref(value: Any) -> str | None:
273
+ candidate = str(value or "").strip().lower()
274
+ if not candidate:
275
+ return None
276
+ if TODO_CAPABILITY_BINDING_REF_PATTERN.fullmatch(candidate):
277
+ return candidate
278
+ return None
279
+
280
+
281
+ def normalize_todo_task_repository(value: Any) -> str | None:
282
+ candidate = str(value or "").strip()
283
+ if not candidate:
284
+ return None
285
+ try:
286
+ return normalize_repository_identity(candidate)
287
+ except ValueError:
288
+ return None
289
+
290
+
291
+ def normalize_todo_continuation_policy(value: Any) -> str | None:
292
+ candidate = str(value or "").strip().lower()
293
+ if candidate in TODO_CONTINUATION_POLICY_VALUES:
294
+ return candidate
295
+ return None
296
+
297
+
298
+ def normalize_removed_todo_continuation_policy(value: Any) -> str | None:
299
+ candidate = str(value or "").strip().lower()
300
+ if candidate in TODO_REMOVED_REVIEW_CONTINUATION_POLICY_VALUES:
301
+ return candidate
302
+ return None
303
+
304
+
305
+ def resolve_todo_continuation_policy(
306
+ value: Any,
307
+ *,
308
+ action_kind: Any = None,
309
+ ) -> TodoContinuationPolicy:
310
+ del action_kind
311
+ explicit = normalize_todo_continuation_policy(value)
312
+ if explicit:
313
+ return TodoContinuationPolicy(explicit)
314
+ return TodoContinuationPolicy.INDEPENDENT_HANDOFF
315
+
316
+
317
+ def normalize_todo_claimed_by(value: Any) -> str | None:
318
+ candidate = compact_todo_text(value).lower().replace(" ", "-")
319
+ if candidate and TODO_AGENT_CLAIM_PATTERN.match(candidate):
320
+ return candidate
321
+ return None
322
+
323
+
324
+ def normalize_todo_blocks_agent(value: Any) -> str | None:
325
+ return normalize_todo_claimed_by(value)
326
+
327
+
328
+ def normalize_todo_bound_agent(value: Any) -> str | None:
329
+ return normalize_todo_claimed_by(value)
330
+
331
+
332
+ def normalize_todo_goal_bound(value: Any) -> bool | None:
333
+ return normalize_todo_global_gate(value)
334
+
335
+
336
+ def normalize_todo_excluded_agents(value: Any) -> list[str]:
337
+ if value is None:
338
+ return []
339
+ if isinstance(value, str):
340
+ candidates = value.split(",")
341
+ elif isinstance(value, (list, tuple, set)):
342
+ candidates = list(value)
343
+ else:
344
+ candidates = [value]
345
+ return sorted(
346
+ {
347
+ agent_id
348
+ for candidate in candidates
349
+ for agent_id in [normalize_todo_claimed_by(candidate)]
350
+ if agent_id
351
+ }
352
+ )
353
+
354
+
355
+ def require_todo_excluded_agents(
356
+ value: Any,
357
+ *,
358
+ field: str = "excluded_agents",
359
+ ) -> list[str]:
360
+ if value is None:
361
+ return []
362
+ if isinstance(value, str):
363
+ candidates = value.split(",")
364
+ elif isinstance(value, (list, tuple, set)):
365
+ candidates = list(value)
366
+ else:
367
+ candidates = [value]
368
+ normalized: set[str] = set()
369
+ for candidate in candidates:
370
+ agent_id = normalize_todo_claimed_by(candidate)
371
+ if not agent_id:
372
+ raise ValueError(
373
+ f"{field} must contain public-safe agent tokens such as codex-side-bypass"
374
+ )
375
+ normalized.add(agent_id)
376
+ return sorted(normalized)
377
+
378
+
379
+ def normalize_todo_resume_when(value: Any) -> str | None:
380
+ candidate = compact_todo_text(value).lower()
381
+ if candidate and TODO_RESUME_PR_MERGED_PATTERN.match(candidate):
382
+ return candidate
383
+ if candidate and TODO_RESUME_WHEN_PATTERN.match(candidate):
384
+ return candidate
385
+ return None
386
+
387
+
388
+ def normalize_supported_todo_resume_when(value: Any) -> str | None:
389
+ """Normalize resume conditions that LoopX can safely evaluate."""
390
+ candidate = normalize_todo_resume_when(value)
391
+ if not candidate:
392
+ return None
393
+ kind, separator, target = candidate.partition(":")
394
+ if kind == TODO_RESUME_KIND_TODO_DONE:
395
+ return candidate if separator and normalize_todo_id(target) else None
396
+ if kind == TODO_RESUME_KIND_PR_MERGED:
397
+ return candidate if TODO_RESUME_PR_MERGED_PATTERN.match(candidate) else None
398
+ if kind == TODO_RESUME_KIND_CAPACITY_AVAILABLE:
399
+ return candidate if separator and TODO_CAPABILITY_PATTERN.match(target) else None
400
+ return None
401
+
402
+
403
+ def require_supported_todo_resume_when(value: Any) -> str | None:
404
+ if value is None or not str(value).strip():
405
+ return None
406
+ normalized = normalize_supported_todo_resume_when(value)
407
+ if normalized:
408
+ return normalized
409
+ raise ValueError(
410
+ "resume_when must use a supported condition: todo_done:<todo_id>, "
411
+ "pr_merged:[owner/repo]#<number>, or capacity_available:<capability>"
412
+ )
413
+
414
+
415
+ def normalize_todo_no_followup(value: Any) -> bool | None:
416
+ if isinstance(value, bool):
417
+ return value
418
+ candidate = compact_todo_text(value).lower()
419
+ if candidate in {"1", "true", "yes", "y", "no_followup", "no-followup"}:
420
+ return True
421
+ if candidate in {"0", "false", "no", "n"}:
422
+ return False
423
+ return None
424
+
425
+
426
+ def normalize_todo_global_gate(value: Any) -> bool | None:
427
+ if isinstance(value, bool):
428
+ return value
429
+ candidate = compact_todo_text(value).lower()
430
+ if candidate in {"1", "true", "yes", "y", "global_gate", "global-gate"}:
431
+ return True
432
+ if candidate in {"0", "false", "no", "n"}:
433
+ return False
434
+ return None
435
+
436
+
437
+ def normalize_todo_id(value: Any) -> str | None:
438
+ candidate = str(value or "").strip().lower()
439
+ if candidate and TODO_ID_PATTERN.match(candidate):
440
+ return candidate
441
+ return None
442
+
443
+
444
+ def normalize_todo_id_list(value: Any) -> list[str]:
445
+ if value is None:
446
+ return []
447
+ raw_values = value if isinstance(value, (list, tuple, set)) else [value]
448
+ todo_ids: list[str] = []
449
+ for raw_value in raw_values:
450
+ for match in re.findall(r"\btodo_[A-Za-z0-9_-]+\b", str(raw_value or "")):
451
+ todo_id = normalize_todo_id(match)
452
+ if todo_id and todo_id not in todo_ids:
453
+ todo_ids.append(todo_id)
454
+ todo_id = normalize_todo_id(raw_value)
455
+ if todo_id and todo_id not in todo_ids:
456
+ todo_ids.append(todo_id)
457
+ return todo_ids
458
+
459
+
460
+ def merge_todo_id_lists(*values: Any) -> list[str]:
461
+ todo_ids: list[str] = []
462
+ for value in values:
463
+ for todo_id in normalize_todo_id_list(value):
464
+ if todo_id not in todo_ids:
465
+ todo_ids.append(todo_id)
466
+ return todo_ids
467
+
468
+
469
+ def normalize_required_write_scopes(value: Any) -> list[str]:
470
+ if value is None:
471
+ return []
472
+ if isinstance(value, (list, tuple, set)):
473
+ raw_values = [str(item or "") for item in value]
474
+ else:
475
+ raw_values = re.split(r"[,;|]", str(value or ""))
476
+ scopes: list[str] = []
477
+ seen: set[str] = set()
478
+ for raw in raw_values:
479
+ scope = compact_todo_text(raw)
480
+ if not scope:
481
+ continue
482
+ if len(scope) > TODO_WRITE_SCOPE_MAX_CHARS:
483
+ continue
484
+ if scope.startswith(("/", "~")) or ".." in scope.split("/"):
485
+ continue
486
+ if re.search(r"[\s<>]", scope):
487
+ continue
488
+ if scope in seen:
489
+ continue
490
+ seen.add(scope)
491
+ scopes.append(scope)
492
+ return scopes
493
+
494
+
495
+ def normalize_required_capabilities(value: Any) -> list[str]:
496
+ if value is None:
497
+ return []
498
+ if isinstance(value, (list, tuple, set)):
499
+ raw_values = [str(item or "") for item in value]
500
+ else:
501
+ raw_values = re.split(r"[,;|]", str(value or ""))
502
+ capabilities: list[str] = []
503
+ seen: set[str] = set()
504
+ for raw in raw_values:
505
+ capability = compact_todo_text(raw).lower().replace("-", "_").replace(" ", "_")
506
+ if not capability:
507
+ continue
508
+ if not TODO_CAPABILITY_PATTERN.match(capability):
509
+ continue
510
+ if capability in seen:
511
+ continue
512
+ seen.add(capability)
513
+ capabilities.append(capability)
514
+ return capabilities
515
+
516
+
517
+ def normalize_target_capabilities(value: Any) -> list[str]:
518
+ return normalize_required_capabilities(value)
519
+
520
+
521
+ def normalize_explore_result_node_refs(value: Any) -> list[str]:
522
+ """Normalize explicit public-safe Explore node ids attached to a todo."""
523
+
524
+ if value is None:
525
+ return []
526
+ if isinstance(value, (list, tuple, set)):
527
+ raw_values = [str(item or "") for item in value]
528
+ else:
529
+ raw_values = re.split(r"[,;|]", str(value or ""))
530
+ refs: list[str] = []
531
+ for raw in raw_values:
532
+ ref = compact_todo_text(raw)
533
+ if not ref or not TODO_EXPLORE_RESULT_NODE_REF_PATTERN.match(ref):
534
+ continue
535
+ if ref not in refs:
536
+ refs.append(ref)
537
+ if len(refs) >= TODO_EXPLORE_RESULT_NODE_REF_LIMIT:
538
+ break
539
+ return refs
540
+
541
+
542
+ def replan_successor_semantic_binding(
543
+ *,
544
+ action_kind: Any,
545
+ target_key: Any,
546
+ explore_result_node_refs: Any,
547
+ ) -> dict[str, Any] | None:
548
+ """Return the typed executable identity of a replan successor.
549
+
550
+ An obligation id establishes causality, but it does not turn a planning
551
+ instruction into executable work. A successor also needs an explicit
552
+ action and at least one stable target identity.
553
+ """
554
+
555
+ safe_action_kind = normalize_todo_action_kind(action_kind)
556
+ safe_target_key = compact_todo_text(target_key) or None
557
+ safe_explore_refs = normalize_explore_result_node_refs(
558
+ explore_result_node_refs
559
+ )
560
+ if not safe_action_kind or not (safe_target_key or safe_explore_refs):
561
+ return None
562
+ binding: dict[str, Any] = {"action_kind": safe_action_kind}
563
+ if safe_target_key:
564
+ binding["target_key"] = safe_target_key
565
+ if safe_explore_refs:
566
+ binding["explore_result_node_refs"] = safe_explore_refs
567
+ return binding
568
+
569
+
570
+ def normalize_todo_decision_scope(value: Any) -> dict[str, str] | None:
571
+ """Normalize the compact decision-scope metadata token.
572
+
573
+ Markdown todo metadata stores the v0 shape as
574
+ ``kind:granularity:scope_key`` so authors do not need to embed JSON in
575
+ active-state comments. Status and quota expose the normalized object.
576
+ """
577
+
578
+ raw_kind: Any = None
579
+ raw_granularity: Any = None
580
+ raw_scope_key: Any = None
581
+ raw_decision_id: Any = None
582
+ if isinstance(value, dict):
583
+ raw_kind = value.get("kind")
584
+ raw_granularity = value.get("granularity")
585
+ raw_scope_key = value.get("scope_key")
586
+ raw_decision_id = value.get("decision_id")
587
+ else:
588
+ candidate = compact_todo_text(value).lower()
589
+ parts = candidate.split(":", 2)
590
+ if len(parts) != 3:
591
+ return None
592
+ raw_kind, raw_granularity, raw_scope_key = parts
593
+
594
+ kind = compact_todo_text(raw_kind).lower()
595
+ granularity = compact_todo_text(raw_granularity).lower()
596
+ scope_key = compact_todo_text(raw_scope_key).lower()
597
+ if kind not in TODO_DECISION_SCOPE_KIND_VALUES:
598
+ return None
599
+ if granularity not in TODO_DECISION_SCOPE_GRANULARITY_VALUES:
600
+ return None
601
+ if not TODO_DECISION_SCOPE_KEY_PATTERN.match(scope_key):
602
+ return None
603
+
604
+ payload: dict[str, str] = {
605
+ "schema_version": TODO_DECISION_SCOPE_SCHEMA_VERSION,
606
+ "kind": kind,
607
+ "granularity": granularity,
608
+ "scope_key": scope_key,
609
+ }
610
+ decision_id = normalize_todo_id(raw_decision_id)
611
+ if decision_id:
612
+ payload["decision_id"] = decision_id
613
+ return payload
614
+
615
+
616
+ def normalize_todo_decision_outcome(value: Any) -> str | None:
617
+ candidate = compact_todo_text(value).lower()
618
+ return candidate if candidate in TODO_DECISION_OUTCOME_VALUES else None
619
+
620
+
621
+ def require_todo_decision_outcome(value: Any) -> str:
622
+ outcome = normalize_todo_decision_outcome(value)
623
+ if not outcome:
624
+ raise ValueError(
625
+ "decision_outcome must be one of: "
626
+ + ", ".join(sorted(TODO_DECISION_OUTCOME_VALUES))
627
+ )
628
+ return outcome
629
+
630
+
631
+ def require_todo_decision_scope(value: Any) -> dict[str, str]:
632
+ scope = normalize_todo_decision_scope(value)
633
+ if not scope:
634
+ raise ValueError(
635
+ "decision_scope must use kind:granularity:scope_key, such as "
636
+ "private_read:project:authority"
637
+ )
638
+ return scope
639
+
640
+
641
+ def _normalize_todo_required_decision_scopes(
642
+ value: Any,
643
+ *,
644
+ strict: bool,
645
+ ) -> list[dict[str, str]]:
646
+ if value is None:
647
+ return []
648
+ if isinstance(value, (list, tuple, set)):
649
+ raw_values = list(value)
650
+ else:
651
+ raw_values = re.split(r"[,;|]", str(value or ""))
652
+ scopes: list[dict[str, str]] = []
653
+ seen: set[tuple[str, str, str]] = set()
654
+ for raw in raw_values:
655
+ scope = normalize_todo_decision_scope(raw)
656
+ if not scope:
657
+ if strict:
658
+ raise ValueError(
659
+ "required_decision_scopes must contain "
660
+ "kind:granularity:scope_key tokens"
661
+ )
662
+ continue
663
+ identity = (scope["kind"], scope["granularity"], scope["scope_key"])
664
+ if identity in seen:
665
+ continue
666
+ seen.add(identity)
667
+ scopes.append(scope)
668
+ return scopes
669
+
670
+
671
+ def normalize_todo_required_decision_scopes(value: Any) -> list[dict[str, str]]:
672
+ return _normalize_todo_required_decision_scopes(value, strict=False)
673
+
674
+
675
+ def require_todo_required_decision_scopes(value: Any) -> list[dict[str, str]]:
676
+ return _normalize_todo_required_decision_scopes(value, strict=True)
677
+
678
+
679
+ def decision_scope_metadata_value(value: Any) -> str | None:
680
+ if value is None:
681
+ return None
682
+ scope = require_todo_decision_scope(value)
683
+ return f"{scope['kind']}:{scope['granularity']}:{scope['scope_key']}"
684
+
685
+
686
+ def required_decision_scopes_metadata_value(value: Any) -> str | None:
687
+ if value is None:
688
+ return None
689
+ scopes = require_todo_required_decision_scopes(value)
690
+ if not scopes:
691
+ return None
692
+ return ",".join(
693
+ f"{scope['kind']}:{scope['granularity']}:{scope['scope_key']}"
694
+ for scope in scopes
695
+ )
696
+
697
+
698
+ def normalize_todo_decision_scope_outcomes(value: Any) -> list[dict[str, Any]]:
699
+ if value is None:
700
+ return []
701
+ raw_values = list(value) if isinstance(value, (list, tuple, set)) else [value]
702
+ if isinstance(value, str):
703
+ raw_values = [item for item in value.split(",") if item]
704
+ outcomes: list[dict[str, Any]] = []
705
+ positions: dict[tuple[str, str, str], int] = {}
706
+ for raw in raw_values:
707
+ raw_outcome: Any = None
708
+ raw_scope: Any = None
709
+ raw_source_todo_id: Any = None
710
+ if isinstance(raw, dict):
711
+ raw_outcome = raw.get("outcome")
712
+ raw_scope = raw.get("decision_scope") or raw.get("scope")
713
+ raw_source_todo_id = raw.get("source_todo_id")
714
+ else:
715
+ parts = str(raw or "").split("|", 2)
716
+ if len(parts) == 3:
717
+ raw_outcome, raw_scope, raw_source_todo_id = parts
718
+ outcome = normalize_todo_decision_outcome(raw_outcome)
719
+ scope = normalize_todo_decision_scope(raw_scope)
720
+ source_todo_id = normalize_todo_id(raw_source_todo_id)
721
+ if not outcome or not scope or not source_todo_id:
722
+ continue
723
+ payload = {
724
+ "schema_version": TODO_DECISION_SCOPE_OUTCOME_SCHEMA_VERSION,
725
+ "outcome": outcome,
726
+ "decision_scope": scope,
727
+ "source_todo_id": source_todo_id,
728
+ }
729
+ identity = (scope["kind"], scope["granularity"], scope["scope_key"])
730
+ if identity in positions:
731
+ outcomes[positions[identity]] = payload
732
+ else:
733
+ positions[identity] = len(outcomes)
734
+ outcomes.append(payload)
735
+ return outcomes
736
+
737
+
738
+ def decision_scope_outcomes_metadata_value(value: Any) -> str | None:
739
+ outcomes = normalize_todo_decision_scope_outcomes(value)
740
+ if not outcomes:
741
+ return None
742
+ return ",".join(
743
+ "|".join(
744
+ (
745
+ item["outcome"],
746
+ ":".join(
747
+ (
748
+ item["decision_scope"]["kind"],
749
+ item["decision_scope"]["granularity"],
750
+ item["decision_scope"]["scope_key"],
751
+ )
752
+ ),
753
+ item["source_todo_id"],
754
+ )
755
+ )
756
+ for item in outcomes
757
+ )
758
+
759
+
760
+ def build_todo_id(
761
+ *,
762
+ role: Any,
763
+ source_section: Any,
764
+ index: Any,
765
+ text: Any,
766
+ ) -> str:
767
+ identity = "|".join(str(part or "") for part in (role, source_section, index, compact_todo_text(text)))
768
+ return f"todo_{hashlib.sha1(identity.encode('utf-8')).hexdigest()[:12]}"
769
+
770
+
771
+ def normalize_explicit_todo_task_class(value: Any) -> str | None:
772
+ candidate = str(value or "").strip().lower()
773
+ if candidate in TODO_TASK_CLASS_VALUES:
774
+ return candidate
775
+ return None
776
+
777
+
778
+ def normalize_todo_status(value: Any) -> str | None:
779
+ candidate = str(value or "").strip().lower()
780
+ if candidate in TODO_STATUS_VALUES:
781
+ return candidate
782
+ return None
783
+
784
+
785
+ def todo_done_for_status(status: Any) -> bool:
786
+ return normalize_todo_status(status) in TODO_TERMINAL_STATUS_VALUES
787
+
788
+
789
+ def todo_terminal_for_status(status: Any) -> bool:
790
+ candidate = str(status or "").strip().lower()
791
+ return (
792
+ normalize_todo_status(candidate) in TODO_TERMINAL_STATUS_VALUES
793
+ or candidate in TODO_LEGACY_TERMINAL_STATUS_VALUES
794
+ )
795
+
796
+
797
+ def todo_status_from_marker(marker: Any) -> str:
798
+ candidate = str(marker or "").strip().lower()
799
+ if candidate == "x":
800
+ return TODO_STATUS_DONE
801
+ if candidate == "-":
802
+ return TODO_STATUS_DEFERRED
803
+ return TODO_STATUS_OPEN
804
+
805
+
806
+ def todo_marker_for_status(status: Any) -> str:
807
+ normalized = normalize_todo_status(status) or TODO_STATUS_OPEN
808
+ if normalized == TODO_STATUS_DEFERRED:
809
+ return "-"
810
+ if normalized == TODO_STATUS_DONE:
811
+ return "x"
812
+ return " "
813
+
814
+
815
+ def encode_metadata_value(value: Any) -> str:
816
+ compact = compact_todo_text(value)
817
+ return quote(compact, safe="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_.:-")
818
+
819
+
820
+ def decode_metadata_value(value: Any) -> str:
821
+ return compact_todo_text(unquote(str(value or "")))
822
+
823
+
824
+ def parse_todo_metadata_tokens(line: str) -> list[tuple[str, str]] | None:
825
+ match = TODO_METADATA_PATTERN.match(line)
826
+ if not match:
827
+ return None
828
+ return [
829
+ (
830
+ token.group("key"),
831
+ decode_metadata_value(token.group("value")),
832
+ )
833
+ for token in TODO_METADATA_TOKEN_PATTERN.finditer(match.group("body"))
834
+ ]
835
+
836
+
837
+ MetadataValueNormalizer = Callable[[Any], Any]
838
+ MetadataValueEncoder = Callable[[Any], str | None]
839
+ MetadataInvalidPredicate = Callable[[Any], bool]
840
+
841
+
842
+ def _truthy_value(value: Any) -> Any:
843
+ return value if value else None
844
+
845
+
846
+ def _nonempty_metadata_text(value: Any) -> str | None:
847
+ text = compact_todo_text(value)
848
+ return text or None
849
+
850
+
851
+ def _optional_output_bool(value: Any) -> bool | None:
852
+ return None if value is None else bool(value)
853
+
854
+
855
+ def _metadata_bool_text(value: Any) -> str:
856
+ return "true" if value else "false"
857
+
858
+
859
+ def _metadata_csv(value: Any) -> str:
860
+ return ",".join(str(item) for item in value)
861
+
862
+
863
+ def _normalize_optional_decision_scope_for_write(value: Any) -> dict[str, str] | None:
864
+ return None if value is None else require_todo_decision_scope(value)
865
+
866
+
867
+ def _normalize_optional_required_decision_scopes_for_write(
868
+ value: Any,
869
+ ) -> list[dict[str, str]]:
870
+ return [] if value is None else require_todo_required_decision_scopes(value)
871
+
872
+
873
+ @dataclass(frozen=True)
874
+ class _TodoMetadataField:
875
+ key: str
876
+ read_normalizer: MetadataValueNormalizer
877
+ write_normalizer: MetadataValueNormalizer | None = None
878
+ encoder: MetadataValueEncoder = str
879
+ invalid_when: MetadataInvalidPredicate = bool
880
+ invalid_message: str | None = None
881
+ read_fallback_key: str | None = None
882
+ read_fallback_normalizer: MetadataValueNormalizer | None = None
883
+ block_field: bool = True
884
+
885
+ def normalize_for_write(self, value: Any) -> Any:
886
+ normalizer = self.write_normalizer or self.read_normalizer
887
+ normalized = normalizer(value)
888
+ if (
889
+ self.invalid_message
890
+ and self.invalid_when(value)
891
+ and not _metadata_value_is_present(normalized)
892
+ ):
893
+ raise ValueError(self.invalid_message)
894
+ return normalized
895
+
896
+
897
+ def _metadata_value_is_present(value: Any) -> bool:
898
+ return value is not None and value != "" and value != []
899
+
900
+
901
+ _TODO_METADATA_FIELD_SCHEMA = (
902
+ _TodoMetadataField(
903
+ "todo_id",
904
+ normalize_todo_id,
905
+ invalid_message=(
906
+ "todo_id must use the public token shape "
907
+ "todo_<letters-digits-underscore-hyphen>"
908
+ ),
909
+ ),
910
+ _TodoMetadataField(
911
+ "status",
912
+ normalize_todo_status,
913
+ invalid_message=(
914
+ "todo status must be one of: " + ", ".join(sorted(TODO_STATUS_VALUES))
915
+ ),
916
+ ),
917
+ _TodoMetadataField(
918
+ "task_class",
919
+ normalize_explicit_todo_task_class,
920
+ invalid_message=(
921
+ "todo task_class must be one of: "
922
+ + ", ".join(sorted(TODO_TASK_CLASS_VALUES))
923
+ ),
924
+ ),
925
+ _TodoMetadataField(
926
+ "action_kind",
927
+ normalize_todo_action_kind,
928
+ invalid_message=(
929
+ "todo action_kind must be a public-safe token: lowercase letters, "
930
+ "digits, '_' or '-'"
931
+ ),
932
+ ),
933
+ _TodoMetadataField(
934
+ "task_domain",
935
+ normalize_todo_task_domain,
936
+ invalid_message=(
937
+ "todo task_domain must be a public-safe token: lowercase letters, "
938
+ "digits, '_', '-' or '.'"
939
+ ),
940
+ ),
941
+ _TodoMetadataField(
942
+ "capability_binding_ref",
943
+ normalize_todo_capability_binding_ref,
944
+ invalid_message=(
945
+ "capability_binding_ref must be a public-safe namespaced token"
946
+ ),
947
+ ),
948
+ _TodoMetadataField(
949
+ "task_repository",
950
+ normalize_todo_task_repository,
951
+ invalid_message=(
952
+ "todo task_repository must be a credential-free Git remote or canonical "
953
+ "git:<host>/<path> identity"
954
+ ),
955
+ ),
956
+ _TodoMetadataField(
957
+ "continuation_policy",
958
+ normalize_todo_continuation_policy,
959
+ invalid_message=(
960
+ "todo continuation_policy must be one of: "
961
+ + ", ".join(sorted(TODO_CONTINUATION_POLICY_VALUES))
962
+ ),
963
+ read_fallback_key="removed_continuation_policy",
964
+ read_fallback_normalizer=normalize_removed_todo_continuation_policy,
965
+ ),
966
+ _TodoMetadataField(
967
+ "removed_continuation_policy",
968
+ normalize_removed_todo_continuation_policy,
969
+ invalid_message=(
970
+ "removed_continuation_policy must be one of: "
971
+ + ", ".join(sorted(TODO_REMOVED_REVIEW_CONTINUATION_POLICY_VALUES))
972
+ ),
973
+ block_field=False,
974
+ ),
975
+ _TodoMetadataField(
976
+ "required_write_scopes",
977
+ normalize_required_write_scopes,
978
+ encoder=_metadata_csv,
979
+ invalid_message=(
980
+ "required_write_scopes must contain public-safe relative scope tokens"
981
+ ),
982
+ ),
983
+ _TodoMetadataField(
984
+ "required_capabilities",
985
+ normalize_required_capabilities,
986
+ encoder=_metadata_csv,
987
+ invalid_message=(
988
+ "required_capabilities must contain public-safe capability tokens"
989
+ ),
990
+ ),
991
+ _TodoMetadataField(
992
+ "target_capabilities",
993
+ normalize_target_capabilities,
994
+ encoder=_metadata_csv,
995
+ invalid_message=(
996
+ "target_capabilities must contain public-safe capability tokens"
997
+ ),
998
+ ),
999
+ _TodoMetadataField(
1000
+ "explore_result_node_refs",
1001
+ normalize_explore_result_node_refs,
1002
+ encoder=_metadata_csv,
1003
+ invalid_message=(
1004
+ "explore_result_node_refs must contain public-safe Explore node ids"
1005
+ ),
1006
+ ),
1007
+ _TodoMetadataField(
1008
+ "decision_scope",
1009
+ normalize_todo_decision_scope,
1010
+ write_normalizer=_normalize_optional_decision_scope_for_write,
1011
+ encoder=decision_scope_metadata_value,
1012
+ invalid_when=lambda value: value is not None,
1013
+ ),
1014
+ _TodoMetadataField(
1015
+ "required_decision_scopes",
1016
+ normalize_todo_required_decision_scopes,
1017
+ write_normalizer=_normalize_optional_required_decision_scopes_for_write,
1018
+ encoder=required_decision_scopes_metadata_value,
1019
+ ),
1020
+ _TodoMetadataField(
1021
+ "decision_outcome",
1022
+ normalize_todo_decision_outcome,
1023
+ invalid_message=(
1024
+ "decision_outcome must be one of: "
1025
+ + ", ".join(sorted(TODO_DECISION_OUTCOME_VALUES))
1026
+ ),
1027
+ ),
1028
+ _TodoMetadataField(
1029
+ "decision_scope_outcomes",
1030
+ normalize_todo_decision_scope_outcomes,
1031
+ encoder=decision_scope_outcomes_metadata_value,
1032
+ invalid_message=(
1033
+ "decision_scope_outcomes must contain outcome, decision_scope, and "
1034
+ "source_todo_id"
1035
+ ),
1036
+ ),
1037
+ _TodoMetadataField(
1038
+ "claimed_by",
1039
+ normalize_todo_claimed_by,
1040
+ invalid_message=(
1041
+ "claimed_by must be a public-safe agent token such as codex-main-control"
1042
+ ),
1043
+ ),
1044
+ _TodoMetadataField(
1045
+ "bound_agent",
1046
+ normalize_todo_bound_agent,
1047
+ invalid_message=(
1048
+ "bound_agent must be a public-safe agent token such as codex-main-control"
1049
+ ),
1050
+ ),
1051
+ _TodoMetadataField(
1052
+ "goal_bound",
1053
+ normalize_todo_goal_bound,
1054
+ write_normalizer=_optional_output_bool,
1055
+ encoder=_metadata_bool_text,
1056
+ invalid_when=lambda value: value is not None,
1057
+ ),
1058
+ _TodoMetadataField(
1059
+ "blocks_agent",
1060
+ normalize_todo_blocks_agent,
1061
+ invalid_message=(
1062
+ "blocks_agent must be a public-safe agent token such as codex-side-bypass"
1063
+ ),
1064
+ ),
1065
+ _TodoMetadataField(
1066
+ "excluded_agents",
1067
+ normalize_todo_excluded_agents,
1068
+ write_normalizer=require_todo_excluded_agents,
1069
+ encoder=_metadata_csv,
1070
+ ),
1071
+ _TodoMetadataField(
1072
+ "global_gate",
1073
+ normalize_todo_global_gate,
1074
+ write_normalizer=_optional_output_bool,
1075
+ encoder=_metadata_bool_text,
1076
+ invalid_when=lambda value: value is not None,
1077
+ ),
1078
+ _TodoMetadataField(
1079
+ "unblocks_todo_id",
1080
+ normalize_todo_id,
1081
+ invalid_message=(
1082
+ "unblocks_todo_id must use the public token shape "
1083
+ "todo_<letters-digits-underscore-hyphen>"
1084
+ ),
1085
+ ),
1086
+ _TodoMetadataField(
1087
+ "successor_todo_ids",
1088
+ normalize_todo_id_list,
1089
+ encoder=_metadata_csv,
1090
+ invalid_message=(
1091
+ "successor_todo_ids must contain public "
1092
+ "todo_<letters-digits-underscore-hyphen> tokens"
1093
+ ),
1094
+ ),
1095
+ _TodoMetadataField(
1096
+ "replan_obligation_id",
1097
+ normalize_todo_replan_obligation_id,
1098
+ invalid_message=(
1099
+ "replan_obligation_id must use the current replan-<16 lowercase hex> "
1100
+ "identity"
1101
+ ),
1102
+ ),
1103
+ _TodoMetadataField(
1104
+ "resume_when",
1105
+ normalize_todo_resume_when,
1106
+ invalid_message=(
1107
+ "resume_when must be public-safe, e.g. "
1108
+ "todo_done:todo_ab12cd34ef56 or pr_merged:#532"
1109
+ ),
1110
+ ),
1111
+ _TodoMetadataField(
1112
+ "no_followup",
1113
+ normalize_todo_no_followup,
1114
+ write_normalizer=_optional_output_bool,
1115
+ encoder=_metadata_bool_text,
1116
+ invalid_when=lambda value: value is not None,
1117
+ ),
1118
+ *(
1119
+ _TodoMetadataField(
1120
+ key,
1121
+ _nonempty_metadata_text,
1122
+ write_normalizer=_truthy_value,
1123
+ )
1124
+ for key in TODO_MONITOR_METADATA_FIELDS
1125
+ ),
1126
+ *(
1127
+ _TodoMetadataField(
1128
+ key,
1129
+ _nonempty_metadata_text,
1130
+ write_normalizer=_truthy_value,
1131
+ )
1132
+ for key in (
1133
+ "note",
1134
+ "evidence",
1135
+ "reason",
1136
+ "completed_at",
1137
+ "updated_at",
1138
+ "completion_turn_key",
1139
+ "validation_command",
1140
+ "validation_command_argv",
1141
+ "validation_label",
1142
+ "validation_timeout_seconds",
1143
+ )
1144
+ ),
1145
+ _TodoMetadataField(
1146
+ "superseded_by",
1147
+ normalize_todo_id,
1148
+ invalid_message=(
1149
+ "superseded_by must use the public token shape "
1150
+ "todo_<letters-digits-underscore-hyphen>"
1151
+ ),
1152
+ ),
1153
+ )
1154
+
1155
+ _TODO_METADATA_FIELD_BY_TOKEN = {
1156
+ field.key: field for field in _TODO_METADATA_FIELD_SCHEMA
1157
+ }
1158
+
1159
+
1160
+ def _todo_metadata_block_fields() -> tuple[str, ...]:
1161
+ fields = [
1162
+ field.key for field in _TODO_METADATA_FIELD_SCHEMA if field.block_field
1163
+ ]
1164
+ repository_index = fields.index("task_repository")
1165
+ continuation_index = fields.index("continuation_policy")
1166
+ fields[repository_index], fields[continuation_index] = (
1167
+ fields[continuation_index],
1168
+ fields[repository_index],
1169
+ )
1170
+ return tuple(fields)
1171
+
1172
+
1173
+ TODO_METADATA_FIELDS = _todo_metadata_block_fields()
1174
+
1175
+
1176
+ def parse_todo_metadata_line(line: str) -> dict[str, Any] | None:
1177
+ tokens = parse_todo_metadata_tokens(line)
1178
+ if tokens is None:
1179
+ return None
1180
+ metadata: dict[str, Any] = {}
1181
+ for key, value in tokens:
1182
+ field = _TODO_METADATA_FIELD_BY_TOKEN.get(key)
1183
+ if field is None:
1184
+ continue
1185
+ normalized = field.read_normalizer(value)
1186
+ output_key = field.key
1187
+ if (
1188
+ not _metadata_value_is_present(normalized)
1189
+ and field.read_fallback_key
1190
+ and field.read_fallback_normalizer
1191
+ ):
1192
+ normalized = field.read_fallback_normalizer(value)
1193
+ output_key = field.read_fallback_key
1194
+ if _metadata_value_is_present(normalized):
1195
+ metadata[output_key] = normalized
1196
+ return metadata or None
1197
+
1198
+
1199
+ def _normalize_todo_metadata_for_write(values: dict[str, Any]) -> dict[str, Any]:
1200
+ normalized: dict[str, Any] = {}
1201
+ for field in _TODO_METADATA_FIELD_SCHEMA:
1202
+ value = field.normalize_for_write(values.get(field.key))
1203
+ if _metadata_value_is_present(value):
1204
+ normalized[field.key] = value
1205
+ if field.key == "removed_continuation_policy" and {
1206
+ "continuation_policy",
1207
+ "removed_continuation_policy",
1208
+ }.issubset(normalized):
1209
+ raise ValueError(
1210
+ "continuation_policy and removed_continuation_policy are mutually exclusive"
1211
+ )
1212
+ if (
1213
+ field.key == "excluded_agents"
1214
+ and normalized.get("claimed_by")
1215
+ and normalized["claimed_by"] in normalized.get("excluded_agents", [])
1216
+ ):
1217
+ raise ValueError(
1218
+ f"claimed_by={normalized['claimed_by']!r} cannot also appear in "
1219
+ "excluded_agents"
1220
+ )
1221
+ return normalized
1222
+
1223
+
1224
+ def _format_todo_metadata_values(values: dict[str, Any]) -> str | None:
1225
+ normalized = _normalize_todo_metadata_for_write(values)
1226
+ fields = [
1227
+ f"{field.key}={encode_metadata_value(field.encoder(normalized[field.key]))}"
1228
+ for field in _TODO_METADATA_FIELD_SCHEMA
1229
+ if field.key in normalized
1230
+ ]
1231
+ if not fields:
1232
+ return None
1233
+ return f" <!-- loopx:todo {' '.join(fields)} -->"
1234
+
1235
+
1236
+ def format_todo_metadata_line(
1237
+ *,
1238
+ todo_id: str | None = None,
1239
+ status: str | None = None,
1240
+ task_class: str | None = None,
1241
+ action_kind: str | None = None,
1242
+ task_domain: str | None = None,
1243
+ capability_binding_ref: str | None = None,
1244
+ task_repository: str | None = None,
1245
+ continuation_policy: str | None = None,
1246
+ removed_continuation_policy: str | None = None,
1247
+ required_write_scopes: Any = None,
1248
+ required_capabilities: Any = None,
1249
+ target_capabilities: Any = None,
1250
+ explore_result_node_refs: Any = None,
1251
+ decision_scope: Any = None,
1252
+ required_decision_scopes: Any = None,
1253
+ decision_outcome: str | None = None,
1254
+ decision_scope_outcomes: Any = None,
1255
+ claimed_by: str | None = None,
1256
+ bound_agent: str | None = None,
1257
+ goal_bound: bool | None = None,
1258
+ blocks_agent: str | None = None,
1259
+ excluded_agents: Any = None,
1260
+ global_gate: bool | None = None,
1261
+ unblocks_todo_id: str | None = None,
1262
+ successor_todo_ids: Any = None,
1263
+ replan_obligation_id: str | None = None,
1264
+ resume_when: str | None = None,
1265
+ no_followup: bool | None = None,
1266
+ target_key: str | None = None,
1267
+ cadence: str | None = None,
1268
+ next_due_at: str | None = None,
1269
+ expires_at: str | None = None,
1270
+ last_checked_at: str | None = None,
1271
+ result_hash: str | None = None,
1272
+ consecutive_no_change: str | None = None,
1273
+ material_change: str | None = None,
1274
+ max_no_change_before_replan: str | None = None,
1275
+ watch_only: str | None = None,
1276
+ note: str | None = None,
1277
+ evidence: str | None = None,
1278
+ completion_turn_key: str | None = None,
1279
+ validation_command: str | None = None,
1280
+ validation_command_argv: str | None = None,
1281
+ validation_label: str | None = None,
1282
+ validation_timeout_seconds: str | None = None,
1283
+ reason: str | None = None,
1284
+ completed_at: str | None = None,
1285
+ updated_at: str | None = None,
1286
+ superseded_by: str | None = None,
1287
+ ) -> str | None:
1288
+ values = locals()
1289
+ return _format_todo_metadata_values(values)
1290
+
1291
+
1292
+ def todo_block_metadata(block: dict[str, Any]) -> dict[str, Any]:
1293
+ metadata: dict[str, Any] = {}
1294
+ for key in TODO_METADATA_FIELDS:
1295
+ value = block.get(key)
1296
+ if value is None:
1297
+ continue
1298
+ normalized: Any
1299
+ if key == "required_write_scopes":
1300
+ normalized = normalize_required_write_scopes(value)
1301
+ elif key == "task_domain":
1302
+ normalized = normalize_todo_task_domain(value)
1303
+ elif key == "task_repository":
1304
+ normalized = normalize_todo_task_repository(value)
1305
+ elif key == "required_capabilities":
1306
+ normalized = normalize_required_capabilities(value)
1307
+ elif key == "target_capabilities":
1308
+ normalized = normalize_target_capabilities(value)
1309
+ elif key == "excluded_agents":
1310
+ normalized = normalize_todo_excluded_agents(value)
1311
+ elif key == "explore_result_node_refs":
1312
+ normalized = normalize_explore_result_node_refs(value)
1313
+ elif key == "continuation_policy":
1314
+ normalized = normalize_todo_continuation_policy(value)
1315
+ elif key == "decision_scope":
1316
+ normalized = normalize_todo_decision_scope(value)
1317
+ elif key == "required_decision_scopes":
1318
+ normalized = normalize_todo_required_decision_scopes(value)
1319
+ elif key == "decision_outcome":
1320
+ normalized = normalize_todo_decision_outcome(value)
1321
+ elif key == "decision_scope_outcomes":
1322
+ normalized = normalize_todo_decision_scope_outcomes(value)
1323
+ elif key == "no_followup":
1324
+ normalized = normalize_todo_no_followup(value)
1325
+ elif key in {"global_gate", "goal_bound"}:
1326
+ normalized = normalize_todo_global_gate(value)
1327
+ else:
1328
+ normalized = str(value).strip()
1329
+ if normalized is not None and normalized != [] and normalized != "":
1330
+ metadata[key] = normalized
1331
+ return metadata
1332
+
1333
+
1334
+ def metadata_line_for_todo_block(
1335
+ block: dict[str, Any],
1336
+ updates: dict[str, Any],
1337
+ ) -> str | None:
1338
+ metadata = todo_block_metadata(block)
1339
+ for key, value in updates.items():
1340
+ if key not in TODO_METADATA_FIELDS:
1341
+ continue
1342
+ if value is None:
1343
+ metadata.pop(key, None)
1344
+ elif key == "required_write_scopes":
1345
+ normalized = normalize_required_write_scopes(value)
1346
+ if normalized:
1347
+ metadata[key] = normalized
1348
+ else:
1349
+ metadata.pop(key, None)
1350
+ elif key == "task_domain":
1351
+ metadata[key] = _TODO_METADATA_FIELD_BY_TOKEN[
1352
+ key
1353
+ ].normalize_for_write(value)
1354
+ elif key == "task_repository":
1355
+ normalized = normalize_todo_task_repository(value)
1356
+ if normalized:
1357
+ metadata[key] = normalized
1358
+ else:
1359
+ metadata.pop(key, None)
1360
+ elif key == "required_capabilities":
1361
+ normalized = normalize_required_capabilities(value)
1362
+ if normalized:
1363
+ metadata[key] = normalized
1364
+ else:
1365
+ metadata.pop(key, None)
1366
+ elif key == "target_capabilities":
1367
+ normalized = normalize_target_capabilities(value)
1368
+ if normalized:
1369
+ metadata[key] = normalized
1370
+ else:
1371
+ metadata.pop(key, None)
1372
+ elif key == "excluded_agents":
1373
+ normalized = normalize_todo_excluded_agents(value)
1374
+ if normalized:
1375
+ metadata[key] = normalized
1376
+ else:
1377
+ metadata.pop(key, None)
1378
+ elif key == "explore_result_node_refs":
1379
+ normalized = normalize_explore_result_node_refs(value)
1380
+ if normalized:
1381
+ metadata[key] = normalized
1382
+ else:
1383
+ metadata.pop(key, None)
1384
+ elif key == "continuation_policy":
1385
+ normalized = normalize_todo_continuation_policy(value)
1386
+ if normalized:
1387
+ metadata[key] = normalized
1388
+ elif value:
1389
+ raise ValueError(
1390
+ "todo continuation_policy must be one of: "
1391
+ + ", ".join(sorted(TODO_CONTINUATION_POLICY_VALUES))
1392
+ )
1393
+ else:
1394
+ metadata.pop(key, None)
1395
+ elif key == "decision_scope":
1396
+ metadata[key] = require_todo_decision_scope(value)
1397
+ elif key == "required_decision_scopes":
1398
+ normalized = require_todo_required_decision_scopes(value)
1399
+ if normalized:
1400
+ metadata[key] = normalized
1401
+ else:
1402
+ metadata.pop(key, None)
1403
+ elif key == "decision_outcome":
1404
+ metadata[key] = require_todo_decision_outcome(value)
1405
+ elif key == "decision_scope_outcomes":
1406
+ normalized_scope_outcomes = normalize_todo_decision_scope_outcomes(value)
1407
+ if normalized_scope_outcomes:
1408
+ metadata[key] = normalized_scope_outcomes
1409
+ else:
1410
+ metadata.pop(key, None)
1411
+ elif key == "no_followup":
1412
+ normalized = normalize_todo_no_followup(value)
1413
+ if normalized is not None:
1414
+ metadata[key] = normalized
1415
+ else:
1416
+ metadata.pop(key, None)
1417
+ elif key == "successor_todo_ids":
1418
+ normalized = normalize_todo_id_list(value)
1419
+ if normalized:
1420
+ metadata[key] = normalized
1421
+ else:
1422
+ metadata.pop(key, None)
1423
+ elif key in {"global_gate", "goal_bound"}:
1424
+ normalized = normalize_todo_global_gate(value)
1425
+ if normalized is not None:
1426
+ metadata[key] = normalized
1427
+ else:
1428
+ metadata.pop(key, None)
1429
+ elif str(value).strip():
1430
+ metadata[key] = str(value).strip()
1431
+ if "todo_id" not in metadata and block.get("todo_id"):
1432
+ metadata["todo_id"] = str(block["todo_id"])
1433
+ if "status" not in metadata:
1434
+ metadata["status"] = TODO_STATUS_DONE if block.get("done") else TODO_STATUS_OPEN
1435
+ return format_todo_metadata_line(**metadata)
1436
+
1437
+
1438
+ def todo_task_class_for_text(text: str) -> str:
1439
+ compact = compact_todo_text(text)
1440
+ for pattern in TODO_HARD_MONITOR_PATTERNS:
1441
+ if pattern.search(compact):
1442
+ return TODO_TASK_CLASS_MONITOR
1443
+ for pattern in TODO_ADVANCEMENT_OVERRIDE_PATTERNS:
1444
+ if pattern.search(compact):
1445
+ return TODO_TASK_CLASS_ADVANCEMENT
1446
+ for pattern in TODO_BLOCKED_MONITOR_PATTERNS:
1447
+ if pattern.search(compact):
1448
+ return TODO_TASK_CLASS_MONITOR
1449
+ for pattern in TODO_MONITOR_PATTERNS:
1450
+ if pattern.search(compact):
1451
+ return TODO_TASK_CLASS_MONITOR
1452
+ for pattern in TODO_ADVANCEMENT_PATTERNS:
1453
+ if pattern.search(compact):
1454
+ return TODO_TASK_CLASS_ADVANCEMENT
1455
+ return TODO_TASK_CLASS_ADVANCEMENT
1456
+
1457
+
1458
+ def normalize_todo_task_class(value: Any, *, text: str, action_kind: Any = None) -> str:
1459
+ candidate = normalize_explicit_todo_task_class(value)
1460
+ if candidate:
1461
+ return candidate
1462
+ normalized_action_kind = normalize_todo_action_kind(action_kind)
1463
+ if normalized_action_kind in TODO_ACTION_KIND_ADVANCEMENT_VALUES:
1464
+ return TODO_TASK_CLASS_ADVANCEMENT
1465
+ if normalized_action_kind in TODO_ACTION_KIND_MONITOR_VALUES:
1466
+ return TODO_TASK_CLASS_MONITOR
1467
+ return todo_task_class_for_text(text)
1468
+
1469
+
1470
+ def next_action_requires_advancement_text(text: str) -> bool:
1471
+ compact = compact_todo_text(text)
1472
+ if not compact:
1473
+ return False
1474
+ if any(pattern.search(compact) for pattern in NEXT_ACTION_HARD_MONITOR_PATTERNS):
1475
+ return False
1476
+ return any(pattern.search(compact) for pattern in NEXT_ACTION_ADVANCEMENT_HINT_PATTERNS)