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,1123 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from collections.abc import Callable
5
+ from copy import deepcopy
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+ from ..agents.workspace_guard import (
10
+ build_delivery_workspace_guard,
11
+ delivery_workspace_repository,
12
+ )
13
+ from ..runtime.run_artifacts import (
14
+ next_run_artifact_paths,
15
+ reserve_run_artifact_paths,
16
+ run_file_stem,
17
+ )
18
+ from ..runtime.time import now_local_iso
19
+ from ..todos.contract import normalize_todo_claimed_by, normalize_todo_id
20
+ from ..work_items.delivery_outcome import (
21
+ ACCOUNTABLE_DELIVERY_OUTCOMES,
22
+ normalize_delivery_outcome,
23
+ )
24
+ from .decision_summary import compact_quota_decision, quota_decision_agent_id
25
+ from .monitor_poll import QUOTA_MONITOR_POLL_CLASSIFICATION
26
+ from .scheduler_ack import QUOTA_SCHEDULER_ACK_CLASSIFICATION
27
+ from .settlement import (
28
+ SettlementIdentity,
29
+ infer_persisted_heartbeat_settlement_identity,
30
+ require_settlement_writeback,
31
+ resolve_heartbeat_settlement_identity,
32
+ resolve_settlement_delivery_workspace_causality,
33
+ settlement_result_payload,
34
+ )
35
+ from .settlement_workspace_causality import completed_todo_workspace_causality
36
+ from .settlement_validation import completion_validation_spend_error
37
+ from .spend_sources import DEFAULT_SLOT_SPEND_SOURCE, VALID_SLOT_SPEND_SOURCES
38
+
39
+ QUOTA_SLOT_SPENT_CLASSIFICATION = "quota_slot_spent"
40
+ QUOTA_SLOT_VOIDED_CLASSIFICATION = "quota_slot_voided"
41
+
42
+ QuotaDecisionBuilder = Callable[[dict[str, Any]], dict[str, Any]]
43
+ QuotaStatusBuilder = Callable[..., dict[str, Any]]
44
+
45
+
46
+ def _todo_binding_error(
47
+ *,
48
+ source: str,
49
+ status_payload: dict[str, Any],
50
+ goal_id: str,
51
+ before: dict[str, Any],
52
+ requested_todo_id: str | None,
53
+ agent_id: str | None,
54
+ settlement_identity: SettlementIdentity | None = None,
55
+ ) -> str | None:
56
+ selected = (
57
+ before.get("selected_todo")
58
+ if isinstance(before.get("selected_todo"), dict)
59
+ else {}
60
+ )
61
+ if settlement_identity is not None:
62
+ if requested_todo_id != settlement_identity.todo_id:
63
+ return (
64
+ "quota spend Todo does not match the original settlement identity: "
65
+ f"expected {settlement_identity.todo_id} but received "
66
+ f"{requested_todo_id or 'missing'}"
67
+ )
68
+ return completion_validation_spend_error(
69
+ status_payload,
70
+ goal_id=goal_id,
71
+ todo_id=requested_todo_id,
72
+ agent_id=agent_id,
73
+ selected_todo=selected,
74
+ )
75
+ selected_todo_id = normalize_todo_id(selected.get("todo_id"))
76
+ if requested_todo_id and selected_todo_id and requested_todo_id != selected_todo_id:
77
+ return (
78
+ f"quota spend todo binding mismatch: selected todo is "
79
+ f"{selected_todo_id} but --todo-id is {requested_todo_id}; "
80
+ "complete or update the selected todo first, or record a replan"
81
+ )
82
+ if (
83
+ source == DEFAULT_SLOT_SPEND_SOURCE
84
+ and before.get("normal_delivery_allowed") is True
85
+ and selected_todo_id
86
+ and not requested_todo_id
87
+ ):
88
+ return (
89
+ f"quota spend requires --todo-id {selected_todo_id} for heartbeat "
90
+ "delivery so the accounted turn is bound to the selected todo"
91
+ )
92
+ return completion_validation_spend_error(
93
+ status_payload,
94
+ goal_id=goal_id,
95
+ todo_id=requested_todo_id,
96
+ agent_id=agent_id,
97
+ selected_todo=selected,
98
+ )
99
+
100
+
101
+ def _resolve_preview_settlement(
102
+ *,
103
+ raw_runtime_root: Any,
104
+ source: str,
105
+ goal_id: str,
106
+ agent_id: str | None,
107
+ todo_id: str | None,
108
+ turn_instance_id: str | None,
109
+ ) -> dict[str, Any]:
110
+ if turn_instance_id and source != DEFAULT_SLOT_SPEND_SOURCE:
111
+ return {"reason": "turn-scoped settlement is valid only for heartbeat spend"}
112
+ if turn_instance_id and not raw_runtime_root:
113
+ return {"reason": "status payload does not include runtime_root"}
114
+ if source != DEFAULT_SLOT_SPEND_SOURCE or not raw_runtime_root:
115
+ return {}
116
+
117
+ runtime_root = Path(str(raw_runtime_root)).expanduser()
118
+ result = (
119
+ resolve_heartbeat_settlement_identity(
120
+ runtime_root,
121
+ goal_id=goal_id,
122
+ agent_id=agent_id,
123
+ todo_id=todo_id,
124
+ turn_instance_id=turn_instance_id,
125
+ )
126
+ if turn_instance_id
127
+ else infer_persisted_heartbeat_settlement_identity(
128
+ runtime_root,
129
+ goal_id=goal_id,
130
+ agent_id=agent_id,
131
+ todo_id=todo_id,
132
+ )
133
+ )
134
+ if result is None:
135
+ return {}
136
+ identity = result.value if result.failure is None else None
137
+ delivery_workspace_causality = (
138
+ resolve_settlement_delivery_workspace_causality(runtime_root, identity)
139
+ if identity is not None
140
+ else None
141
+ )
142
+ if identity is not None:
143
+ result = result.bind(
144
+ lambda resolved: require_settlement_writeback(
145
+ runtime_root,
146
+ resolved,
147
+ )
148
+ )
149
+ return {
150
+ "identity": identity,
151
+ "result": result,
152
+ "delivery_run": result.value if result.failure is None else None,
153
+ "delivery_workspace_causality": delivery_workspace_causality,
154
+ "reason": result.failure.reason if result.failure is not None else None,
155
+ }
156
+
157
+
158
+ def _now_local() -> str:
159
+ return now_local_iso()
160
+
161
+
162
+ def _validate_goal_id_path_segment(goal_id: str) -> str:
163
+ value = goal_id.strip()
164
+ if not value:
165
+ raise ValueError("goal id is required")
166
+ if value in {".", ".."} or "/" in value or "\\" in value:
167
+ raise ValueError("goal id must be a single path segment")
168
+ if Path(value).name != value:
169
+ raise ValueError("goal id must not include path traversal")
170
+ return value
171
+
172
+
173
+ def _int_number(value: Any, *, default: int) -> int:
174
+ if isinstance(value, bool):
175
+ return default
176
+ if isinstance(value, int):
177
+ return value
178
+ if isinstance(value, float):
179
+ return int(value)
180
+ if isinstance(value, str):
181
+ try:
182
+ return int(float(value.strip()))
183
+ except ValueError:
184
+ return default
185
+ return default
186
+
187
+
188
+ def _queue_item_for_goal(status_payload: dict[str, Any], *, goal_id: str) -> dict[str, Any]:
189
+ queue = status_payload.get("attention_queue") if isinstance(status_payload.get("attention_queue"), dict) else {}
190
+ queue_items = queue.get("items") if isinstance(queue.get("items"), list) else []
191
+ return next(
192
+ (
193
+ item
194
+ for item in queue_items
195
+ if isinstance(item, dict) and str(item.get("goal_id") or "") == goal_id
196
+ ),
197
+ {},
198
+ )
199
+
200
+
201
+ def _set_quota_for_goal(status_payload: dict[str, Any], *, goal_id: str, quota: dict[str, Any]) -> None:
202
+ run_history = status_payload.get("run_history") if isinstance(status_payload.get("run_history"), dict) else {}
203
+ run_goals = run_history.get("goals") if isinstance(run_history.get("goals"), list) else []
204
+ for goal in run_goals:
205
+ if isinstance(goal, dict) and str(goal.get("id") or "") == goal_id:
206
+ goal["quota"] = dict(quota)
207
+
208
+ queue = status_payload.get("attention_queue") if isinstance(status_payload.get("attention_queue"), dict) else {}
209
+ queue_items = queue.get("items") if isinstance(queue.get("items"), list) else []
210
+ for item in queue_items:
211
+ if isinstance(item, dict) and str(item.get("goal_id") or "") == goal_id:
212
+ item["quota"] = dict(quota)
213
+ project_asset = item.get("project_asset") if isinstance(item.get("project_asset"), dict) else {}
214
+ if project_asset:
215
+ project_asset["quota"] = dict(quota)
216
+
217
+
218
+ def _load_goal_run_index_records(runtime_root: Path, goal_id: str) -> list[dict[str, Any]]:
219
+ index_path = runtime_root / "goals" / goal_id / "runs" / "index.jsonl"
220
+ if not index_path.exists():
221
+ return []
222
+ records: list[dict[str, Any]] = []
223
+ try:
224
+ lines = index_path.read_text(encoding="utf-8").splitlines()
225
+ except OSError:
226
+ return []
227
+ for line in lines:
228
+ if not line.strip():
229
+ continue
230
+ try:
231
+ record = json.loads(line)
232
+ except json.JSONDecodeError:
233
+ continue
234
+ if isinstance(record, dict):
235
+ records.append(record)
236
+ return records
237
+
238
+
239
+ def _is_quota_neutral_state_refresh(run: dict[str, Any]) -> bool:
240
+ """Recognize refresh-state provenance without relying on its classification."""
241
+
242
+ if str(run.get("delivery_outcome") or "").strip():
243
+ return False
244
+ if str(run.get("classification") or "").strip() == "state_refreshed":
245
+ return True
246
+ return (
247
+ isinstance(run.get("state"), dict)
248
+ and isinstance(run.get("runtime_projection_route"), dict)
249
+ and bool(str(run.get("recommended_action_source") or "").strip())
250
+ )
251
+
252
+
253
+ def _latest_unspent_accountable_delivery_run(
254
+ runtime_root: Path,
255
+ goal_id: str,
256
+ *,
257
+ agent_id: str | None = None,
258
+ ) -> dict[str, Any] | None:
259
+ """Return the latest same-agent delivery that still needs accounting.
260
+
261
+ Unchanged monitor polls, scheduler acknowledgements, and plain state
262
+ refreshes are quota-neutral. They may occur after validation and before
263
+ accounting, so they must not hide the accountable run. A state refresh
264
+ with an explicit non-accountable delivery outcome and other non-delivery
265
+ events remain fail closed.
266
+ """
267
+
268
+ safe_agent_id = normalize_todo_claimed_by(agent_id)
269
+ for run in reversed(_load_goal_run_index_records(runtime_root, goal_id)):
270
+ run_agent_id = normalize_todo_claimed_by(run.get("agent_id"))
271
+ if safe_agent_id and run_agent_id and safe_agent_id != run_agent_id:
272
+ continue
273
+ classification = str(run.get("classification") or "").strip()
274
+ if classification == QUOTA_SLOT_VOIDED_CLASSIFICATION:
275
+ continue
276
+ if classification == QUOTA_SLOT_SPENT_CLASSIFICATION:
277
+ return None
278
+ if (
279
+ classification == QUOTA_MONITOR_POLL_CLASSIFICATION
280
+ and run.get("material_change") is not True
281
+ ):
282
+ continue
283
+ if classification == QUOTA_SCHEDULER_ACK_CLASSIFICATION:
284
+ continue
285
+ if _is_quota_neutral_state_refresh(run):
286
+ continue
287
+ delivery_outcome = normalize_delivery_outcome(run.get("delivery_outcome"))
288
+ if delivery_outcome in ACCOUNTABLE_DELIVERY_OUTCOMES:
289
+ return run
290
+ return None
291
+ return None
292
+
293
+
294
+ def _missing_delivery_workspace_preview(
295
+ *,
296
+ delivery_workspace_causality: dict[str, Any] | None,
297
+ delivery_workspace: dict[str, Any] | None,
298
+ goal_id: str,
299
+ slots: int,
300
+ agent_id: str | None,
301
+ before: dict[str, Any],
302
+ ) -> dict[str, Any] | None:
303
+ requirement = str(
304
+ (delivery_workspace_causality or {}).get("requirement") or ""
305
+ )
306
+ if (
307
+ not delivery_workspace_causality
308
+ or requirement == "not_required"
309
+ or delivery_workspace_repository(delivery_workspace)
310
+ ):
311
+ return None
312
+ return {
313
+ "ok": False,
314
+ "mode": "spend-slot",
315
+ "dry_run": True,
316
+ "goal_id": goal_id,
317
+ "slots": slots,
318
+ "agent_id": agent_id,
319
+ "appended": False,
320
+ "registry_mutated": False,
321
+ "reason": (
322
+ "quota spend requires a valid delivery workspace snapshot for "
323
+ f"settlement causality requirement {requirement}"
324
+ ),
325
+ "delivery_workspace": delivery_workspace,
326
+ "delivery_workspace_causality": delivery_workspace_causality,
327
+ "delivery_workspace_validated": False,
328
+ "before": before,
329
+ "after": None,
330
+ }
331
+
332
+
333
+ def build_quota_slot_preview_for_decision(
334
+ status_payload: dict[str, Any],
335
+ *,
336
+ goal_id: str,
337
+ before: dict[str, Any],
338
+ after_decision: QuotaDecisionBuilder,
339
+ quota_status_builder: QuotaStatusBuilder,
340
+ self_repair_spend_actions: set[str] | frozenset[str],
341
+ slots: int = 1,
342
+ agent_id: str | None = None,
343
+ workspace_path: Path | None = None,
344
+ todo_id: str | None = None,
345
+ turn_instance_id: str | None = None,
346
+ source: str = DEFAULT_SLOT_SPEND_SOURCE,
347
+ ) -> dict[str, Any]:
348
+ safe_goal_id = _validate_goal_id_path_segment(str(goal_id or ""))
349
+ safe_slots = max(1, _int_number(slots, default=1))
350
+ safe_requested_agent_id = normalize_todo_claimed_by(agent_id)
351
+ normalized_todo_id = normalize_todo_id(todo_id) if todo_id else None
352
+ raw_runtime_root = status_payload.get("runtime_root")
353
+ settlement_identity = None
354
+ settlement_result = None
355
+ delivery_completion_run = None
356
+ delivery_workspace_causality = None
357
+ settlement = _resolve_preview_settlement(
358
+ raw_runtime_root=raw_runtime_root,
359
+ source=source,
360
+ goal_id=safe_goal_id,
361
+ agent_id=safe_requested_agent_id,
362
+ todo_id=normalized_todo_id,
363
+ turn_instance_id=turn_instance_id,
364
+ )
365
+ settlement_identity = settlement.get("identity")
366
+ settlement_result = settlement.get("result")
367
+ delivery_completion_run = settlement.get("delivery_run")
368
+ delivery_workspace_causality = settlement.get(
369
+ "delivery_workspace_causality"
370
+ )
371
+ if settlement.get("reason"):
372
+ return {
373
+ "ok": False,
374
+ "mode": "spend-slot",
375
+ "dry_run": True,
376
+ "goal_id": safe_goal_id,
377
+ "slots": safe_slots,
378
+ "agent_id": safe_requested_agent_id,
379
+ "appended": False,
380
+ "registry_mutated": False,
381
+ "reason": settlement["reason"],
382
+ "settlement_result": (
383
+ settlement_result_payload(settlement_result)
384
+ if settlement_result is not None
385
+ else None
386
+ ),
387
+ "before": before,
388
+ "after": None,
389
+ }
390
+ binding_error = _todo_binding_error(
391
+ source=source,
392
+ status_payload=status_payload,
393
+ goal_id=safe_goal_id,
394
+ before=before,
395
+ requested_todo_id=normalized_todo_id,
396
+ agent_id=safe_requested_agent_id,
397
+ settlement_identity=settlement_identity,
398
+ )
399
+ if binding_error:
400
+ return {
401
+ "ok": False,
402
+ "mode": "spend-slot",
403
+ "dry_run": True,
404
+ "goal_id": safe_goal_id,
405
+ "slots": safe_slots,
406
+ "agent_id": safe_requested_agent_id,
407
+ "appended": False,
408
+ "registry_mutated": False,
409
+ "reason": binding_error,
410
+ "before": before,
411
+ "after": None,
412
+ }
413
+ safe_bypass_requested = (
414
+ (
415
+ before.get("state") == "operator_gate"
416
+ or before.get("recovery_delivery_allowed") is True
417
+ or before.get("effective_action") == "outcome_floor_recovery"
418
+ )
419
+ and before.get("safe_bypass_allowed") is True
420
+ )
421
+ self_repair_spend = before.get("effective_action") in self_repair_spend_actions
422
+ capability_repair_spend = (
423
+ before.get("effective_action") == "capability_bridge_repair"
424
+ and before.get("capability_repair_allowed") is True
425
+ )
426
+ delivery_completion_run = delivery_completion_run or (
427
+ _latest_unspent_accountable_delivery_run(
428
+ Path(str(raw_runtime_root)).expanduser(),
429
+ safe_goal_id,
430
+ agent_id=safe_requested_agent_id,
431
+ )
432
+ if raw_runtime_root
433
+ else None
434
+ )
435
+ if settlement_identity is not None and not delivery_workspace_causality:
436
+ delivery_workspace_causality = completed_todo_workspace_causality(
437
+ status_payload,
438
+ goal_id=safe_goal_id,
439
+ todo_id=settlement_identity.todo_id,
440
+ )
441
+ safe_bypass_without_delivery = (
442
+ safe_bypass_requested and delivery_completion_run is None
443
+ )
444
+ if safe_bypass_without_delivery:
445
+ return {
446
+ "ok": False,
447
+ "mode": "spend-slot",
448
+ "dry_run": True,
449
+ "goal_id": safe_goal_id,
450
+ "slots": safe_slots,
451
+ "agent_id": safe_requested_agent_id,
452
+ "appended": False,
453
+ "registry_mutated": False,
454
+ "reason": (
455
+ "safe-bypass quota spend requires a latest "
456
+ "unspent accountable delivery writeback"
457
+ ),
458
+ "before": before,
459
+ "after": None,
460
+ }
461
+ safe_bypass_spend = safe_bypass_requested
462
+ raw_delivery_workspace = (
463
+ delivery_completion_run.get("delivery_workspace")
464
+ if isinstance(delivery_completion_run, dict)
465
+ and isinstance(delivery_completion_run.get("delivery_workspace"), dict)
466
+ else None
467
+ )
468
+ missing_delivery_workspace_preview = _missing_delivery_workspace_preview(
469
+ delivery_workspace_causality=delivery_workspace_causality,
470
+ delivery_workspace=raw_delivery_workspace,
471
+ goal_id=safe_goal_id,
472
+ slots=safe_slots,
473
+ agent_id=safe_requested_agent_id,
474
+ before=before,
475
+ )
476
+ if missing_delivery_workspace_preview:
477
+ return missing_delivery_workspace_preview
478
+ workspace_requirement = str(
479
+ (delivery_workspace_causality or {}).get("requirement") or ""
480
+ )
481
+ raw_delivery_workspace_repository = delivery_workspace_repository(
482
+ raw_delivery_workspace
483
+ )
484
+ delivery_workspace = (
485
+ raw_delivery_workspace
486
+ if raw_delivery_workspace_repository
487
+ and workspace_requirement != "not_required"
488
+ else None
489
+ )
490
+ delivery_workspace_guard = (
491
+ build_delivery_workspace_guard(
492
+ delivery_completion_run,
493
+ agent_id=safe_requested_agent_id,
494
+ current_path=workspace_path,
495
+ )
496
+ if delivery_completion_run and delivery_workspace
497
+ else None
498
+ )
499
+ if delivery_workspace_guard:
500
+ return {
501
+ "ok": False,
502
+ "mode": "spend-slot",
503
+ "dry_run": True,
504
+ "goal_id": safe_goal_id,
505
+ "slots": safe_slots,
506
+ "agent_id": safe_requested_agent_id,
507
+ "appended": False,
508
+ "registry_mutated": False,
509
+ "reason": delivery_workspace_guard["reason"],
510
+ "workspace_guard": delivery_workspace_guard,
511
+ "delivery_workspace": delivery_workspace,
512
+ "delivery_workspace_causality": delivery_workspace_causality,
513
+ "delivery_workspace_validated": False,
514
+ "before": before,
515
+ "after": None,
516
+ }
517
+ delivery_workspace_validated = bool(delivery_workspace)
518
+ workspace_repair_no_spend = (
519
+ before.get("effective_action") == "agent_workspace_repair"
520
+ and before.get("workspace_repair_allowed") is True
521
+ and not delivery_workspace_validated
522
+ )
523
+ if workspace_repair_no_spend:
524
+ return {
525
+ "ok": False,
526
+ "mode": "spend-slot",
527
+ "dry_run": True,
528
+ "goal_id": safe_goal_id,
529
+ "slots": safe_slots,
530
+ "agent_id": safe_requested_agent_id,
531
+ "appended": False,
532
+ "registry_mutated": False,
533
+ "delivery_workspace": delivery_workspace,
534
+ "delivery_workspace_causality": delivery_workspace_causality,
535
+ "delivery_workspace_validated": False,
536
+ "reason": (
537
+ "agent workspace guard requires moving to an independent "
538
+ "worktree and rerunning quota should-run before quota spend"
539
+ ),
540
+ "before": before,
541
+ "after": None,
542
+ }
543
+ delivery_completion_spend = (
544
+ delivery_completion_run is not None
545
+ and before.get("ok")
546
+ and (
547
+ settlement_identity is not None
548
+ or not before.get("should_run")
549
+ or before.get("effective_action") == "external_evidence_observe"
550
+ or (
551
+ before.get("effective_action") == "agent_workspace_repair"
552
+ and delivery_workspace_validated
553
+ )
554
+ )
555
+ and before.get("effective_action") != "automation_prompt_upgrade_required"
556
+ and not safe_bypass_spend
557
+ and str(before.get("state") or "") in {"waiting", "focus_wait", "operator_gate", "eligible"}
558
+ )
559
+ if not before.get("ok") or (
560
+ not before.get("should_run")
561
+ and not safe_bypass_spend
562
+ and not capability_repair_spend
563
+ and not delivery_completion_spend
564
+ ):
565
+ return {
566
+ "ok": False,
567
+ "mode": "spend-slot",
568
+ "dry_run": True,
569
+ "goal_id": safe_goal_id,
570
+ "slots": safe_slots,
571
+ "agent_id": safe_requested_agent_id,
572
+ "appended": False,
573
+ "registry_mutated": False,
574
+ "reason": before.get("reason") or "goal is not eligible for quota accounting preview",
575
+ "before": before,
576
+ "after": None,
577
+ }
578
+ before_quota = before.get("quota") if isinstance(before.get("quota"), dict) else {}
579
+ if not before_quota:
580
+ return {
581
+ "ok": False,
582
+ "mode": "spend-slot",
583
+ "dry_run": True,
584
+ "goal_id": safe_goal_id,
585
+ "slots": safe_slots,
586
+ "agent_id": safe_requested_agent_id,
587
+ "appended": False,
588
+ "registry_mutated": False,
589
+ "reason": "goal has no quota payload to preview",
590
+ "before": before,
591
+ "after": None,
592
+ }
593
+
594
+ queue_item = _queue_item_for_goal(status_payload, goal_id=safe_goal_id)
595
+ after_status = deepcopy(status_payload)
596
+ after_goal = {
597
+ "quota": {
598
+ **before_quota,
599
+ "spent_slots": _int_number(before_quota.get("spent_slots"), default=0) + safe_slots,
600
+ }
601
+ }
602
+ after_quota = quota_status_builder(
603
+ after_goal,
604
+ waiting_on=str(before.get("waiting_on") or ""),
605
+ severity=str(queue_item.get("severity") or ""),
606
+ lifecycle_phase=before.get("lifecycle_phase") or queue_item.get("lifecycle_phase"),
607
+ lifecycle_flags=before.get("lifecycle_flags") or queue_item.get("lifecycle_flags"),
608
+ status=before.get("status") or queue_item.get("status"),
609
+ )
610
+ _set_quota_for_goal(after_status, goal_id=safe_goal_id, quota=after_quota)
611
+ after = after_decision(after_status)
612
+
613
+ return {
614
+ "ok": True,
615
+ "mode": "spend-slot",
616
+ "dry_run": True,
617
+ "goal_id": safe_goal_id,
618
+ "slots": safe_slots,
619
+ "agent_id": safe_requested_agent_id,
620
+ "appended": False,
621
+ "registry_mutated": False,
622
+ "before": before,
623
+ "after": after,
624
+ "would_throttle": after.get("state") == "throttled",
625
+ "reason": (
626
+ f"dry-run preview: spending {safe_slots} slot(s) accounts for latest "
627
+ f"validated delivery {delivery_completion_run.get('classification')} "
628
+ f"after current {before.get('state')} guard"
629
+ if delivery_completion_spend and delivery_completion_run
630
+ else (
631
+ f"dry-run preview: spending {safe_slots} slot(s) would move "
632
+ f"{safe_goal_id} from {before.get('state')} to {after.get('state')}"
633
+ )
634
+ ),
635
+ "rolling_window_note": (
636
+ "before -> after is a same-status-payload projection. Later quota status "
637
+ "recomputes spent_slots from quota_slot_spent events still inside window_hours, "
638
+ "so the visible total can stay flat if an older spend expires."
639
+ ),
640
+ "todo_id": normalized_todo_id,
641
+ "turn_instance_id": (
642
+ settlement_identity.turn_instance_id
643
+ if settlement_identity is not None
644
+ else None
645
+ ),
646
+ "settlement_identity": (
647
+ settlement_identity.as_dict()
648
+ if settlement_identity is not None
649
+ else None
650
+ ),
651
+ "settlement_result": (
652
+ settlement_result_payload(settlement_result)
653
+ if settlement_result is not None
654
+ else None
655
+ ),
656
+ "safe_bypass_spend": safe_bypass_spend,
657
+ "self_repair_spend": self_repair_spend,
658
+ "capability_repair_spend": capability_repair_spend,
659
+ "delivery_completion_spend": delivery_completion_spend,
660
+ "delivery_run_generated_at": delivery_completion_run.get("generated_at")
661
+ if delivery_completion_run
662
+ else None,
663
+ "delivery_run_classification": delivery_completion_run.get("classification")
664
+ if delivery_completion_run
665
+ else None,
666
+ "delivery_run_agent_id": normalize_todo_claimed_by(delivery_completion_run.get("agent_id"))
667
+ if delivery_completion_run
668
+ else None,
669
+ "delivery_run_recommended_action": delivery_completion_run.get("recommended_action")
670
+ if delivery_completion_run
671
+ else None,
672
+ "delivery_workspace": delivery_workspace,
673
+ "delivery_workspace_causality": delivery_workspace_causality,
674
+ "delivery_workspace_validated": delivery_workspace_validated,
675
+ }
676
+
677
+
678
+ def build_quota_slot_spend_event(
679
+ preview: dict[str, Any],
680
+ *,
681
+ self_repair_spend_actions: set[str] | frozenset[str],
682
+ source: str = DEFAULT_SLOT_SPEND_SOURCE,
683
+ generated_at: str | None = None,
684
+ ) -> dict[str, Any]:
685
+ if not preview.get("ok"):
686
+ raise ValueError(preview.get("reason") or "quota slot spend requires an eligible preview")
687
+ safe_source = str(source or DEFAULT_SLOT_SPEND_SOURCE).strip()
688
+ if safe_source not in VALID_SLOT_SPEND_SOURCES:
689
+ raise ValueError(f"quota slot spend source must be one of: {', '.join(sorted(VALID_SLOT_SPEND_SOURCES))}")
690
+ before = preview.get("before") if isinstance(preview.get("before"), dict) else {}
691
+ after = preview.get("after") if isinstance(preview.get("after"), dict) else {}
692
+ safe_agent_id = normalize_todo_claimed_by(preview.get("agent_id")) or quota_decision_agent_id(before)
693
+ slots = max(1, _int_number(preview.get("slots"), default=1))
694
+ before_compact = compact_quota_decision(before)
695
+ after_compact = compact_quota_decision(after)
696
+ if _int_number(after_compact.get("spent_slots"), default=0) != _int_number(
697
+ before_compact.get("spent_slots"), default=0
698
+ ) + slots:
699
+ raise ValueError("after.spent_slots must equal before.spent_slots + slots")
700
+ self_repair_spend = (
701
+ before_compact["should_run"] is True
702
+ and before_compact["effective_action"] in self_repair_spend_actions
703
+ and before_compact["self_repair_allowed"] is True
704
+ )
705
+ capability_repair_spend = (
706
+ before_compact["should_run"] is True
707
+ and before_compact["effective_action"] == "capability_bridge_repair"
708
+ and before_compact["capability_repair_allowed"] is True
709
+ )
710
+ delivery_completion_spend = bool(preview.get("delivery_completion_spend"))
711
+ eligible_spend = (
712
+ before_compact["should_run"] is True
713
+ and before_compact["state"] == "eligible"
714
+ and before_compact["effective_action"] != "external_evidence_observe"
715
+ and not self_repair_spend
716
+ and not capability_repair_spend
717
+ and before_compact["workspace_repair_allowed"] is not True
718
+ and not delivery_completion_spend
719
+ )
720
+ safe_bypass_spend = bool(preview.get("safe_bypass_spend")) and (
721
+ (
722
+ before_compact["state"] == "operator_gate"
723
+ or before_compact["recovery_delivery_allowed"] is True
724
+ or before_compact["effective_action"] == "outcome_floor_recovery"
725
+ )
726
+ and before_compact["safe_bypass_allowed"] is True
727
+ )
728
+ if (
729
+ not eligible_spend
730
+ and not safe_bypass_spend
731
+ and not self_repair_spend
732
+ and not capability_repair_spend
733
+ and not delivery_completion_spend
734
+ ):
735
+ raise ValueError(
736
+ "quota slot spend requires an eligible, safe-bypass, control-plane self-repair, "
737
+ "capability bridge repair, or latest validated delivery-completion quota should-run decision"
738
+ )
739
+
740
+ delivery_run_action = str(preview.get("delivery_run_recommended_action") or "").strip()
741
+ record = {
742
+ "generated_at": generated_at or _now_local(),
743
+ "goal_id": preview.get("goal_id"),
744
+ "classification": QUOTA_SLOT_SPENT_CLASSIFICATION,
745
+ "recommended_action": (
746
+ delivery_run_action
747
+ if delivery_completion_spend and delivery_run_action
748
+ else after.get("recommended_action") or "inspect next quota should-run decision"
749
+ ),
750
+ "health_check": (
751
+ "quota should-run eligible; quota slot spend event public-safe"
752
+ if eligible_spend
753
+ else (
754
+ "quota outcome-floor recovery safe-bypass; quota slot spend event public-safe"
755
+ if before_compact.get("effective_action") == "outcome_floor_recovery"
756
+ else (
757
+ "quota control-plane self-repair; quota slot spend event public-safe"
758
+ if self_repair_spend
759
+ else (
760
+ "quota capability bridge repair; quota slot spend event public-safe"
761
+ if capability_repair_spend
762
+ else (
763
+ "quota validated delivery completion; quota slot spend event public-safe"
764
+ if delivery_completion_spend
765
+ else "quota safe-bypass delivery; quota slot spend event public-safe"
766
+ )
767
+ )
768
+ )
769
+ )
770
+ ),
771
+ "quota_event": {
772
+ "event_type": QUOTA_SLOT_SPENT_CLASSIFICATION,
773
+ "source": safe_source,
774
+ "todo_id": preview.get("todo_id"),
775
+ "turn_instance_id": preview.get("turn_instance_id"),
776
+ "settlement_identity": preview.get("settlement_identity"),
777
+ "slots": slots,
778
+ "reason_summary": (
779
+ f"{slots} automatic agent slot(s) completed under an eligible quota guard"
780
+ if eligible_spend
781
+ else (
782
+ f"{slots} automatic agent slot(s) completed as outcome-floor recovery safe-bypass work"
783
+ if before_compact.get("effective_action") == "outcome_floor_recovery"
784
+ else (
785
+ f"{slots} automatic agent slot(s) completed as control-plane self-repair work"
786
+ if self_repair_spend
787
+ else (
788
+ f"{slots} automatic agent slot(s) completed as capability bridge repair work"
789
+ if capability_repair_spend
790
+ else (
791
+ f"{slots} automatic agent slot(s) accounted after validated delivery "
792
+ f"{preview.get('delivery_run_classification')}"
793
+ if delivery_completion_spend
794
+ else f"{slots} automatic agent slot(s) completed as safe-bypass work"
795
+ )
796
+ )
797
+ )
798
+ )
799
+ ),
800
+ "delivery_run_generated_at": preview.get("delivery_run_generated_at"),
801
+ "delivery_run_classification": preview.get("delivery_run_classification"),
802
+ "delivery_run_agent_id": preview.get("delivery_run_agent_id"),
803
+ "delivery_run_recommended_action": delivery_run_action or None,
804
+ "delivery_workspace": preview.get("delivery_workspace"),
805
+ "delivery_workspace_causality": preview.get(
806
+ "delivery_workspace_causality"
807
+ ),
808
+ "delivery_workspace_validated": bool(
809
+ preview.get("delivery_workspace_validated")
810
+ ),
811
+ "before": before_compact,
812
+ "after": after_compact,
813
+ },
814
+ }
815
+ if safe_agent_id:
816
+ record["agent_id"] = safe_agent_id
817
+ record["quota_event"]["agent_id"] = safe_agent_id
818
+ if preview.get("turn_instance_id"):
819
+ record["turn_instance_id"] = preview["turn_instance_id"]
820
+ record["todo_id"] = preview.get("todo_id")
821
+ record["settlement_identity"] = preview.get("settlement_identity")
822
+ return record
823
+
824
+
825
+ def _find_quota_spend_run(
826
+ runtime_root: Path,
827
+ *,
828
+ goal_id: str,
829
+ generated_at: str,
830
+ ) -> tuple[dict[str, Any], dict[str, Any]] | None:
831
+ for run in reversed(_load_goal_run_index_records(runtime_root, goal_id)):
832
+ if str(run.get("goal_id") or goal_id) != goal_id:
833
+ continue
834
+ if str(run.get("generated_at") or "") != generated_at:
835
+ continue
836
+ if str(run.get("classification") or "") != QUOTA_SLOT_SPENT_CLASSIFICATION:
837
+ continue
838
+ event = load_quota_event_from_run(run)
839
+ if not event or str(event.get("event_type") or "") != QUOTA_SLOT_SPENT_CLASSIFICATION:
840
+ continue
841
+ return run, event
842
+ return None
843
+
844
+
845
+ def load_quota_event_from_run(run: dict[str, Any]) -> dict[str, Any] | None:
846
+ if str(run.get("classification") or "") not in {
847
+ QUOTA_SLOT_SPENT_CLASSIFICATION,
848
+ QUOTA_SLOT_VOIDED_CLASSIFICATION,
849
+ }:
850
+ return None
851
+ event = run.get("quota_event") if isinstance(run.get("quota_event"), dict) else None
852
+ if event:
853
+ return event
854
+
855
+ raw_json_path = str(run.get("json_path") or "")
856
+ if not raw_json_path:
857
+ return None
858
+ json_path = Path(raw_json_path).expanduser()
859
+ if not json_path.exists():
860
+ return None
861
+ try:
862
+ record = json.loads(json_path.read_text(encoding="utf-8"))
863
+ except (OSError, json.JSONDecodeError):
864
+ return None
865
+ if not isinstance(record, dict):
866
+ return None
867
+ event = record.get("quota_event") if isinstance(record.get("quota_event"), dict) else None
868
+ return event
869
+
870
+
871
+ def build_quota_slot_void_preview_for_decision(
872
+ status_payload: dict[str, Any],
873
+ *,
874
+ goal_id: str,
875
+ voided_run_generated_at: str,
876
+ before: dict[str, Any],
877
+ ) -> dict[str, Any]:
878
+ safe_goal_id = _validate_goal_id_path_segment(str(goal_id or ""))
879
+ safe_voided_at = str(voided_run_generated_at or "").strip()
880
+ if not safe_voided_at:
881
+ return {
882
+ "ok": False,
883
+ "mode": "void-slot",
884
+ "dry_run": True,
885
+ "goal_id": safe_goal_id,
886
+ "appended": False,
887
+ "registry_mutated": False,
888
+ "reason": "`quota void-slot` requires --void-generated-at",
889
+ }
890
+
891
+ raw_runtime_root = status_payload.get("runtime_root")
892
+ if not raw_runtime_root:
893
+ raise ValueError("status payload does not include runtime_root")
894
+ runtime_root = Path(str(raw_runtime_root)).expanduser()
895
+ target = _find_quota_spend_run(runtime_root, goal_id=safe_goal_id, generated_at=safe_voided_at)
896
+ if target is None:
897
+ return {
898
+ "ok": False,
899
+ "mode": "void-slot",
900
+ "dry_run": True,
901
+ "goal_id": safe_goal_id,
902
+ "voided_run_generated_at": safe_voided_at,
903
+ "appended": False,
904
+ "registry_mutated": False,
905
+ "reason": "target quota_slot_spent run was not found in the goal runtime index",
906
+ }
907
+ target_run, target_event = target
908
+ slots = max(1, _int_number(target_event.get("slots"), default=1))
909
+ before_quota = before.get("quota") if isinstance(before.get("quota"), dict) else {}
910
+ after = deepcopy(before)
911
+ after_quota = deepcopy(before_quota)
912
+ after_quota["spent_slots"] = max(0, _int_number(before_quota.get("spent_slots"), default=0) - slots)
913
+ after["quota"] = after_quota
914
+ return {
915
+ "ok": True,
916
+ "mode": "void-slot",
917
+ "dry_run": True,
918
+ "goal_id": safe_goal_id,
919
+ "slots": slots,
920
+ "voided_run_generated_at": safe_voided_at,
921
+ "voided_run_classification": target_run.get("classification"),
922
+ "voided_run_json_path": target_run.get("json_path"),
923
+ "appended": False,
924
+ "registry_mutated": False,
925
+ "before": before,
926
+ "after": after,
927
+ "would_throttle": False,
928
+ "reason": (
929
+ f"dry-run preview: voiding {slots} slot(s) from {safe_goal_id} "
930
+ f"quota spend run {safe_voided_at}"
931
+ ),
932
+ "rolling_window_note": (
933
+ "quota void-slot appends a quota_slot_voided accounting event. It does not delete the "
934
+ "original spend event; rolling-window ledgers subtract the void only when the target "
935
+ "spend event is inside the same accounting window."
936
+ ),
937
+ "classification": QUOTA_SLOT_VOIDED_CLASSIFICATION,
938
+ }
939
+
940
+
941
+ def build_quota_slot_void_event(
942
+ preview: dict[str, Any],
943
+ *,
944
+ source: str = DEFAULT_SLOT_SPEND_SOURCE,
945
+ reason_summary: str | None = None,
946
+ generated_at: str | None = None,
947
+ ) -> dict[str, Any]:
948
+ if not preview.get("ok"):
949
+ raise ValueError(preview.get("reason") or "quota slot void requires a valid preview")
950
+ safe_source = str(source or DEFAULT_SLOT_SPEND_SOURCE).strip()
951
+ if safe_source not in VALID_SLOT_SPEND_SOURCES:
952
+ raise ValueError(f"quota slot void source must be one of: {', '.join(sorted(VALID_SLOT_SPEND_SOURCES))}")
953
+ safe_reason = str(reason_summary or "").strip() or "void duplicate or invalid quota slot spend event"
954
+ before = preview.get("before") if isinstance(preview.get("before"), dict) else {}
955
+ after = preview.get("after") if isinstance(preview.get("after"), dict) else {}
956
+ safe_agent_id = quota_decision_agent_id(before)
957
+ record = {
958
+ "generated_at": generated_at or _now_local(),
959
+ "goal_id": preview.get("goal_id"),
960
+ "classification": QUOTA_SLOT_VOIDED_CLASSIFICATION,
961
+ "recommended_action": safe_reason,
962
+ "health_check": "quota slot void event public-safe; original spend preserved for audit",
963
+ "quota_event": {
964
+ "event_type": QUOTA_SLOT_VOIDED_CLASSIFICATION,
965
+ "source": safe_source,
966
+ "slots": max(1, _int_number(preview.get("slots"), default=1)),
967
+ "reason_summary": safe_reason,
968
+ "voided_run_generated_at": preview.get("voided_run_generated_at"),
969
+ "voided_run_classification": preview.get("voided_run_classification"),
970
+ "before": compact_quota_decision(before) if before else {},
971
+ "after": compact_quota_decision(after) if after else {},
972
+ },
973
+ }
974
+ if safe_agent_id:
975
+ record["agent_id"] = safe_agent_id
976
+ record["quota_event"]["agent_id"] = safe_agent_id
977
+ return record
978
+
979
+
980
+ def record_quota_slot_void_from_preview(
981
+ preview: dict[str, Any],
982
+ status_payload: dict[str, Any],
983
+ *,
984
+ goal_id: str,
985
+ render_markdown: Callable[[dict[str, Any]], str],
986
+ execute: bool = False,
987
+ source: str = DEFAULT_SLOT_SPEND_SOURCE,
988
+ reason_summary: str | None = None,
989
+ ) -> dict[str, Any]:
990
+ safe_goal_id = _validate_goal_id_path_segment(str(goal_id or ""))
991
+ if not preview.get("ok"):
992
+ return preview
993
+
994
+ generated_at = _now_local()
995
+ record = build_quota_slot_void_event(
996
+ preview,
997
+ source=source,
998
+ reason_summary=reason_summary,
999
+ generated_at=generated_at,
1000
+ )
1001
+ raw_runtime_root = status_payload.get("runtime_root")
1002
+ if not raw_runtime_root:
1003
+ raise ValueError("status payload does not include runtime_root")
1004
+ runtime_root = Path(str(raw_runtime_root)).expanduser()
1005
+ runs_dir = runtime_root / "goals" / safe_goal_id / "runs"
1006
+ stem = run_file_stem(generated_at)
1007
+ path_allocator = reserve_run_artifact_paths if execute else next_run_artifact_paths
1008
+ json_path, markdown_path = path_allocator(runs_dir, stem, "quota-slot-voided")
1009
+ index_path = runs_dir / "index.jsonl"
1010
+ index_record = {
1011
+ "generated_at": generated_at,
1012
+ "goal_id": safe_goal_id,
1013
+ "classification": QUOTA_SLOT_VOIDED_CLASSIFICATION,
1014
+ "recommended_action": record["recommended_action"],
1015
+ "health_check": record["health_check"],
1016
+ "json_path": str(json_path),
1017
+ "markdown_path": str(markdown_path),
1018
+ }
1019
+ if record.get("agent_id"):
1020
+ index_record["agent_id"] = record["agent_id"]
1021
+ payload = {
1022
+ **preview,
1023
+ "dry_run": not execute,
1024
+ "appended": execute,
1025
+ "registry_mutated": False,
1026
+ "source": record["quota_event"]["source"],
1027
+ "classification": QUOTA_SLOT_VOIDED_CLASSIFICATION,
1028
+ "generated_at": generated_at,
1029
+ "agent_id": record.get("agent_id"),
1030
+ "quota_event": record["quota_event"],
1031
+ "json_path": str(json_path),
1032
+ "markdown_path": str(markdown_path),
1033
+ "index_path": str(index_path),
1034
+ "reason": (
1035
+ f"{'appended' if execute else 'dry-run preview'} quota slot void event: "
1036
+ f"{safe_goal_id} voided {record['quota_event']['slots']} slot(s) from "
1037
+ f"{record['quota_event']['voided_run_generated_at']}"
1038
+ ),
1039
+ }
1040
+ if execute:
1041
+ payload["before"] = record["quota_event"]["before"]
1042
+ payload["after"] = record["quota_event"]["after"]
1043
+ if execute:
1044
+ json_path.write_text(json.dumps(record, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
1045
+ markdown_path.write_text(render_markdown(payload) + "\n", encoding="utf-8")
1046
+ with index_path.open("a", encoding="utf-8") as f:
1047
+ f.write(json.dumps(index_record, ensure_ascii=False) + "\n")
1048
+ return payload
1049
+
1050
+
1051
+ def record_quota_slot_spend_from_preview(
1052
+ preview: dict[str, Any],
1053
+ status_payload: dict[str, Any],
1054
+ *,
1055
+ goal_id: str,
1056
+ self_repair_spend_actions: set[str] | frozenset[str],
1057
+ render_markdown: Callable[[dict[str, Any]], str],
1058
+ execute: bool = False,
1059
+ source: str = DEFAULT_SLOT_SPEND_SOURCE,
1060
+ ) -> dict[str, Any]:
1061
+ safe_goal_id = _validate_goal_id_path_segment(str(goal_id or ""))
1062
+ if not preview.get("ok"):
1063
+ return preview
1064
+
1065
+ generated_at = _now_local()
1066
+ record = build_quota_slot_spend_event(
1067
+ preview,
1068
+ self_repair_spend_actions=self_repair_spend_actions,
1069
+ source=source,
1070
+ generated_at=generated_at,
1071
+ )
1072
+ raw_runtime_root = status_payload.get("runtime_root")
1073
+ if not raw_runtime_root:
1074
+ raise ValueError("status payload does not include runtime_root")
1075
+ runtime_root = Path(str(raw_runtime_root)).expanduser()
1076
+ runs_dir = runtime_root / "goals" / safe_goal_id / "runs"
1077
+ stem = run_file_stem(generated_at)
1078
+ path_allocator = reserve_run_artifact_paths if execute else next_run_artifact_paths
1079
+ json_path, markdown_path = path_allocator(runs_dir, stem, "quota-slot-spent")
1080
+ index_path = runs_dir / "index.jsonl"
1081
+ index_record = {
1082
+ "generated_at": generated_at,
1083
+ "goal_id": safe_goal_id,
1084
+ "classification": QUOTA_SLOT_SPENT_CLASSIFICATION,
1085
+ "recommended_action": record["recommended_action"],
1086
+ "health_check": record["health_check"],
1087
+ "json_path": str(json_path),
1088
+ "markdown_path": str(markdown_path),
1089
+ }
1090
+ if record.get("agent_id"):
1091
+ index_record["agent_id"] = record["agent_id"]
1092
+ for field in ("turn_instance_id", "todo_id", "settlement_identity"):
1093
+ if record.get(field):
1094
+ index_record[field] = record[field]
1095
+
1096
+ payload = {
1097
+ **preview,
1098
+ "dry_run": not execute,
1099
+ "appended": execute,
1100
+ "registry_mutated": False,
1101
+ "source": record["quota_event"]["source"],
1102
+ "classification": QUOTA_SLOT_SPENT_CLASSIFICATION,
1103
+ "generated_at": generated_at,
1104
+ "agent_id": record.get("agent_id"),
1105
+ "quota_event": record["quota_event"],
1106
+ "json_path": str(json_path),
1107
+ "markdown_path": str(markdown_path),
1108
+ "index_path": str(index_path),
1109
+ "reason": (
1110
+ f"{'appended' if execute else 'dry-run preview'} quota slot spend event: "
1111
+ f"{safe_goal_id} {record['quota_event']['before']['spent_slots']}->"
1112
+ f"{record['quota_event']['after']['spent_slots']} slots"
1113
+ ),
1114
+ }
1115
+ if execute:
1116
+ payload["before"] = record["quota_event"]["before"]
1117
+ payload["after"] = record["quota_event"]["after"]
1118
+ if execute:
1119
+ json_path.write_text(json.dumps(record, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
1120
+ markdown_path.write_text(render_markdown(payload) + "\n", encoding="utf-8")
1121
+ with index_path.open("a", encoding="utf-8") as f:
1122
+ f.write(json.dumps(index_record, ensure_ascii=False) + "\n")
1123
+ return payload