phase-loop-runtime 0.2.0__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 (370) hide show
  1. phase_loop_runtime/__init__.py +20 -0
  2. phase_loop_runtime/_contract_docs/phase-loop/extraction-readiness.md +90 -0
  3. phase_loop_runtime/_contract_docs/phase-loop/granular-execution-policy.md +137 -0
  4. phase_loop_runtime/_contract_docs/phase-loop/harness-capability-matrix.md +510 -0
  5. phase_loop_runtime/_contract_docs/phase-loop/harness-skill-matrix.md +125 -0
  6. phase_loop_runtime/_contract_docs/phase-loop/harness-substrate-manifest.md +232 -0
  7. phase_loop_runtime/_contract_docs/phase-loop/pi-loop-control.md +247 -0
  8. phase_loop_runtime/_contract_docs/phase-loop/protocol.md +2759 -0
  9. phase_loop_runtime/_contract_docs/phase-loop/runtime-boundary.md +289 -0
  10. phase_loop_runtime/_contract_docs/phase-loop/spec-discovery-roots.md +47 -0
  11. phase_loop_runtime/_contract_docs/phase-loop/substrate-soak-governed-pipeline.md +42 -0
  12. phase_loop_runtime/_contract_docs/phase-loop/substrate-soak-portal-projection.md +34 -0
  13. phase_loop_runtime/_contract_docs/phase-loop/substrate-soak-regenesis.md +29 -0
  14. phase_loop_runtime/_contract_docs/phase-loop/substrate-soak-report.md +32 -0
  15. phase_loop_runtime/_contract_docs/runtime/verification-evidence-contract.md +52 -0
  16. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-mixed-signals/commands.txt +2 -0
  17. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-mixed-signals/manifest.json +16 -0
  18. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-mixed-signals/mixed.json +5 -0
  19. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-provider-timeout/manifest.json +16 -0
  20. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-provider-timeout/timeout-note.txt +2 -0
  21. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-redacted-summary/manifest.json +16 -0
  22. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-redacted-summary/redacted-summary.json +6 -0
  23. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-small-sample/manifest.json +16 -0
  24. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-small-sample/sample.json +7 -0
  25. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-threshold-edge/edge.txt +2 -0
  26. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-threshold-edge/manifest.json +16 -0
  27. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-duplicate-png/manifest.json +16 -0
  28. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-duplicate-png/shot-a.txt +1 -0
  29. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-duplicate-png/shot-b.txt +1 -0
  30. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-duplicate-png/shot-c.txt +1 -0
  31. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-missing-references/claimed-results.json +5 -0
  32. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-missing-references/manifest.json +16 -0
  33. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-subtle-low-variance/manifest.json +16 -0
  34. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-subtle-low-variance/metrics.json +9 -0
  35. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-templated-prose/findings.md +10 -0
  36. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-templated-prose/manifest.json +16 -0
  37. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-uniform-scores/manifest.json +16 -0
  38. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-uniform-scores/scores.json +4 -0
  39. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-uniform-scores/summary.txt +1 -0
  40. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-adoption-bundle/bundle-check.json +8 -0
  41. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-adoption-bundle/manifest.json +16 -0
  42. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-adoption-bundle/operator-note.txt +2 -0
  43. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-handoff-ledger/events-sample.jsonl +3 -0
  44. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-handoff-ledger/manifest.json +16 -0
  45. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-handoff-ledger/receipt.txt +1 -0
  46. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-runner-diff/evidence.json +13 -0
  47. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-runner-diff/manifest.json +16 -0
  48. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-runner-diff/summary.txt +2 -0
  49. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-test-matrix/manifest.json +16 -0
  50. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-test-matrix/matrix.json +9 -0
  51. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-test-matrix/notes.md +4 -0
  52. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-ui-screenshot-standin/console-summary.txt +2 -0
  53. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-ui-screenshot-standin/manifest.json +16 -0
  54. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-ui-screenshot-standin/screenshot-standin.ppm +5 -0
  55. phase_loop_runtime/adoption_bundle.py +273 -0
  56. phase_loop_runtime/advisor_board/CONTRACTS.md +257 -0
  57. phase_loop_runtime/advisor_board/__init__.py +288 -0
  58. phase_loop_runtime/advisor_board/backing.py +137 -0
  59. phase_loop_runtime/advisor_board/backing_omnigent.py +498 -0
  60. phase_loop_runtime/advisor_board/config.py +225 -0
  61. phase_loop_runtime/advisor_board/events.py +115 -0
  62. phase_loop_runtime/advisor_board/fixtures/advisor-boards.example.toml +59 -0
  63. phase_loop_runtime/advisor_board/fixtures.py +91 -0
  64. phase_loop_runtime/advisor_board/harness_mapping.py +125 -0
  65. phase_loop_runtime/advisor_board/matrix.py +146 -0
  66. phase_loop_runtime/advisor_board/observability.py +481 -0
  67. phase_loop_runtime/advisor_board/presets.py +188 -0
  68. phase_loop_runtime/advisor_board/registries.py +309 -0
  69. phase_loop_runtime/advisor_board/resolver.py +253 -0
  70. phase_loop_runtime/advisor_board/schema.py +269 -0
  71. phase_loop_runtime/advisor_board/standin.py +166 -0
  72. phase_loop_runtime/advisor_board/validation.py +159 -0
  73. phase_loop_runtime/agent_runtime_provider.py +676 -0
  74. phase_loop_runtime/baml_modular.py +656 -0
  75. phase_loop_runtime/baml_src/dotfiles_adoption_manifest.baml +37 -0
  76. phase_loop_runtime/baml_src/dotfiles_c4_document.baml +14 -0
  77. phase_loop_runtime/baml_src/dotfiles_plan_manifest.baml +50 -0
  78. phase_loop_runtime/baml_src/dotfiles_runtime_projection.baml +16 -0
  79. phase_loop_runtime/baml_src/dotfiles_task_catalog.baml +20 -0
  80. phase_loop_runtime/baml_src/emit_phase_closeout.baml +86 -0
  81. phase_loop_runtime/baml_src/evaluate_suspected_fake_evidence.baml +36 -0
  82. phase_loop_runtime/baml_src/verification_evidence.baml +40 -0
  83. phase_loop_runtime/broker.py +181 -0
  84. phase_loop_runtime/build_bundle.py +294 -0
  85. phase_loop_runtime/capability_registry.py +835 -0
  86. phase_loop_runtime/classifier.py +87 -0
  87. phase_loop_runtime/claude_agent_view.py +625 -0
  88. phase_loop_runtime/claude_channel_sidecar.py +1086 -0
  89. phase_loop_runtime/cli.py +2599 -0
  90. phase_loop_runtime/closeout.py +865 -0
  91. phase_loop_runtime/closeout_classifier.py +120 -0
  92. phase_loop_runtime/closeout_evidence_audit.py +112 -0
  93. phase_loop_runtime/closeout_validation.py +144 -0
  94. phase_loop_runtime/closeout_validators.py +203 -0
  95. phase_loop_runtime/consiliency_gates.py +374 -0
  96. phase_loop_runtime/consiliency_ingest.py +399 -0
  97. phase_loop_runtime/consiliency_layout.py +176 -0
  98. phase_loop_runtime/consiliency_scaffold.py +305 -0
  99. phase_loop_runtime/cross_repo_channel.py +331 -0
  100. phase_loop_runtime/discovery.py +2368 -0
  101. phase_loop_runtime/dispatch_lock.py +167 -0
  102. phase_loop_runtime/doc_delta_validator.py +98 -0
  103. phase_loop_runtime/docs_audit.py +285 -0
  104. phase_loop_runtime/docs_freshness.py +468 -0
  105. phase_loop_runtime/docs_surfaces.py +150 -0
  106. phase_loop_runtime/dotfiles_profile_plugin.py +96 -0
  107. phase_loop_runtime/events.py +154 -0
  108. phase_loop_runtime/events_migration.py +96 -0
  109. phase_loop_runtime/evidence_audit.py +1082 -0
  110. phase_loop_runtime/evidence_audit_config.py +172 -0
  111. phase_loop_runtime/fleet_map.py +426 -0
  112. phase_loop_runtime/fleet_metrics.py +298 -0
  113. phase_loop_runtime/fleet_metrics_export.py +196 -0
  114. phase_loop_runtime/git_discipline.py +479 -0
  115. phase_loop_runtime/git_ops.py +122 -0
  116. phase_loop_runtime/git_topology.py +252 -0
  117. phase_loop_runtime/governed_bundle.py +137 -0
  118. phase_loop_runtime/governed_premerge.py +219 -0
  119. phase_loop_runtime/governed_review.py +264 -0
  120. phase_loop_runtime/handoff.py +932 -0
  121. phase_loop_runtime/handoff_path.py +14 -0
  122. phase_loop_runtime/injection.py +740 -0
  123. phase_loop_runtime/install_status.py +88 -0
  124. phase_loop_runtime/lane_scheduler.py +317 -0
  125. phase_loop_runtime/launcher.py +2760 -0
  126. phase_loop_runtime/lease_store.py +553 -0
  127. phase_loop_runtime/maintenance.py +401 -0
  128. phase_loop_runtime/migrate_handoffs.py +201 -0
  129. phase_loop_runtime/models.py +2144 -0
  130. phase_loop_runtime/observability.py +1290 -0
  131. phase_loop_runtime/panel_invoker.py +1638 -0
  132. phase_loop_runtime/phase_loop_drift_audit.py +388 -0
  133. phase_loop_runtime/phase_worktree_executor.py +433 -0
  134. phase_loop_runtime/pipeline_adapter/__init__.py +12 -0
  135. phase_loop_runtime/pipeline_adapter/branch_ops.py +274 -0
  136. phase_loop_runtime/pipeline_adapter/flag.py +55 -0
  137. phase_loop_runtime/pipeline_adapter/markers.py +13 -0
  138. phase_loop_runtime/pipeline_adapter/merge_policy.py +72 -0
  139. phase_loop_runtime/pipeline_adapter/ratification.py +100 -0
  140. phase_loop_runtime/pipeline_adapter/sibling_matcher.py +123 -0
  141. phase_loop_runtime/plan_ir.py +523 -0
  142. phase_loop_runtime/plan_manifest.py +415 -0
  143. phase_loop_runtime/planner_validation.py +176 -0
  144. phase_loop_runtime/profiles.py +462 -0
  145. phase_loop_runtime/prompts.py +365 -0
  146. phase_loop_runtime/provenance.py +165 -0
  147. phase_loop_runtime/publishing.py +339 -0
  148. phase_loop_runtime/py.typed +1 -0
  149. phase_loop_runtime/reconcile.py +1550 -0
  150. phase_loop_runtime/redaction.py +184 -0
  151. phase_loop_runtime/reflection_sync.py +293 -0
  152. phase_loop_runtime/release_guard.py +186 -0
  153. phase_loop_runtime/render.py +432 -0
  154. phase_loop_runtime/repo_validation.py +427 -0
  155. phase_loop_runtime/review_summary.py +150 -0
  156. phase_loop_runtime/roadmap_lint.py +425 -0
  157. phase_loop_runtime/route_log.py +59 -0
  158. phase_loop_runtime/route_policy/__init__.py +11 -0
  159. phase_loop_runtime/route_policy/fixtures/route_selection.golden.json +263 -0
  160. phase_loop_runtime/runner.py +8400 -0
  161. phase_loop_runtime/runtime_paths.py +225 -0
  162. phase_loop_runtime/runtime_projection.py +113 -0
  163. phase_loop_runtime/runtime_resources.py +36 -0
  164. phase_loop_runtime/schema_export.py +292 -0
  165. phase_loop_runtime/schemas/phase_loop_closeout.schema.json +366 -0
  166. phase_loop_runtime/skill_install.py +176 -0
  167. phase_loop_runtime/skill_inventory.py +662 -0
  168. phase_loop_runtime/skill_paths.py +42 -0
  169. phase_loop_runtime/skill_sources_plugin.py +37 -0
  170. phase_loop_runtime/skills_bundle/claude-advisor-board/README.md +1 -0
  171. phase_loop_runtime/skills_bundle/claude-advisor-board/SKILL.md +38 -0
  172. phase_loop_runtime/skills_bundle/claude-advisor-panel/README.md +1 -0
  173. phase_loop_runtime/skills_bundle/claude-advisor-panel/SKILL.md +38 -0
  174. phase_loop_runtime/skills_bundle/claude-execute-detailed/README.md +1 -0
  175. phase_loop_runtime/skills_bundle/claude-execute-detailed/SKILL.md +152 -0
  176. phase_loop_runtime/skills_bundle/claude-execute-phase/README.md +1 -0
  177. phase_loop_runtime/skills_bundle/claude-execute-phase/SKILL.md +745 -0
  178. phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/allocate_worktree_name.sh +33 -0
  179. phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/audit_lane_file_touches.py +197 -0
  180. phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/cleanup_lane_worktrees.sh +103 -0
  181. phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/parent_tree_leakage_check.sh +125 -0
  182. phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/post_merge_import_smoke.sh +62 -0
  183. phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/pre_merge_destructiveness_check.sh +127 -0
  184. phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/resolve_branch_from_sha.sh +51 -0
  185. phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/state.py +274 -0
  186. phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/sweep_stale_worktrees.sh +62 -0
  187. phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/team_teardown.sh +32 -0
  188. phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/validate_plan_doc.py +642 -0
  189. phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/verify_harness.sh +96 -0
  190. phase_loop_runtime/skills_bundle/claude-phase-loop/README.md +1 -0
  191. phase_loop_runtime/skills_bundle/claude-phase-loop/SKILL.md +115 -0
  192. phase_loop_runtime/skills_bundle/claude-phase-roadmap-builder/README.md +1 -0
  193. phase_loop_runtime/skills_bundle/claude-phase-roadmap-builder/SKILL.md +439 -0
  194. phase_loop_runtime/skills_bundle/claude-phase-roadmap-builder/assets/review_prompt.md +19 -0
  195. phase_loop_runtime/skills_bundle/claude-phase-roadmap-builder/references/parallelization-heuristics.md +111 -0
  196. phase_loop_runtime/skills_bundle/claude-phase-roadmap-builder/references/roadmap-template.md +193 -0
  197. phase_loop_runtime/skills_bundle/claude-phase-roadmap-builder/scripts/validate_roadmap.py +32 -0
  198. phase_loop_runtime/skills_bundle/claude-plan-detailed/README.md +1 -0
  199. phase_loop_runtime/skills_bundle/claude-plan-detailed/SKILL.md +334 -0
  200. phase_loop_runtime/skills_bundle/claude-plan-detailed/assets/review_prompt.md +25 -0
  201. phase_loop_runtime/skills_bundle/claude-plan-phase/README.md +1 -0
  202. phase_loop_runtime/skills_bundle/claude-plan-phase/SKILL.md +776 -0
  203. phase_loop_runtime/skills_bundle/claude-plan-phase/assets/review_prompt.md +17 -0
  204. phase_loop_runtime/skills_bundle/claude-plan-phase/scripts/validate_plan_doc.py +1092 -0
  205. phase_loop_runtime/skills_bundle/claude-run-train/README.md +1 -0
  206. phase_loop_runtime/skills_bundle/claude-run-train/SKILL.md +75 -0
  207. phase_loop_runtime/skills_bundle/claude-skill-editor/README.md +1 -0
  208. phase_loop_runtime/skills_bundle/claude-skill-editor/SKILL.md +243 -0
  209. phase_loop_runtime/skills_bundle/claude-skill-editor/assets/editor_prompt.md +72 -0
  210. phase_loop_runtime/skills_bundle/claude-skill-improvement-planner/README.md +1 -0
  211. phase_loop_runtime/skills_bundle/claude-skill-improvement-planner/SKILL.md +249 -0
  212. phase_loop_runtime/skills_bundle/claude-skill-improvement-planner/assets/aggregator_prompt.md +79 -0
  213. phase_loop_runtime/skills_bundle/claude-task-contextualizer/README.md +1 -0
  214. phase_loop_runtime/skills_bundle/claude-task-contextualizer/SKILL.md +74 -0
  215. phase_loop_runtime/skills_bundle/claude-task-contextualizer/references/task-templates.md +103 -0
  216. phase_loop_runtime/skills_bundle/codex-advisor-board/README.md +1 -0
  217. phase_loop_runtime/skills_bundle/codex-advisor-board/SKILL.md +38 -0
  218. phase_loop_runtime/skills_bundle/codex-advisor-panel/README.md +1 -0
  219. phase_loop_runtime/skills_bundle/codex-advisor-panel/SKILL.md +38 -0
  220. phase_loop_runtime/skills_bundle/codex-execute-detailed/README.md +1 -0
  221. phase_loop_runtime/skills_bundle/codex-execute-detailed/SKILL.md +154 -0
  222. phase_loop_runtime/skills_bundle/codex-execute-phase/README.md +1 -0
  223. phase_loop_runtime/skills_bundle/codex-execute-phase/SKILL.md +231 -0
  224. phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/allocate_worktree_name.sh +33 -0
  225. phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/audit_lane_file_touches.py +197 -0
  226. phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/cleanup_lane_worktrees.sh +103 -0
  227. phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/parent_tree_leakage_check.sh +125 -0
  228. phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/post_merge_import_smoke.sh +62 -0
  229. phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/pre_merge_destructiveness_check.sh +127 -0
  230. phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/resolve_branch_from_sha.sh +51 -0
  231. phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/state.py +274 -0
  232. phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/sweep_stale_worktrees.sh +62 -0
  233. phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/team_teardown.sh +32 -0
  234. phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/validate_plan_doc.py +642 -0
  235. phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/verify_harness.sh +96 -0
  236. phase_loop_runtime/skills_bundle/codex-phase-loop/README.md +1 -0
  237. phase_loop_runtime/skills_bundle/codex-phase-loop/SKILL.md +248 -0
  238. phase_loop_runtime/skills_bundle/codex-phase-roadmap-builder/README.md +1 -0
  239. phase_loop_runtime/skills_bundle/codex-phase-roadmap-builder/SKILL.md +150 -0
  240. phase_loop_runtime/skills_bundle/codex-phase-roadmap-builder/assets/review_prompt.md +19 -0
  241. phase_loop_runtime/skills_bundle/codex-phase-roadmap-builder/references/parallelization-heuristics.md +111 -0
  242. phase_loop_runtime/skills_bundle/codex-phase-roadmap-builder/references/roadmap-template.md +193 -0
  243. phase_loop_runtime/skills_bundle/codex-phase-roadmap-builder/scripts/validate_roadmap.py +32 -0
  244. phase_loop_runtime/skills_bundle/codex-plan-detailed/README.md +1 -0
  245. phase_loop_runtime/skills_bundle/codex-plan-detailed/SKILL.md +119 -0
  246. phase_loop_runtime/skills_bundle/codex-plan-detailed/assets/review_prompt.md +25 -0
  247. phase_loop_runtime/skills_bundle/codex-plan-phase/README.md +1 -0
  248. phase_loop_runtime/skills_bundle/codex-plan-phase/SKILL.md +307 -0
  249. phase_loop_runtime/skills_bundle/codex-plan-phase/assets/review_prompt.md +17 -0
  250. phase_loop_runtime/skills_bundle/codex-plan-phase/scripts/validate_plan_doc.py +1092 -0
  251. phase_loop_runtime/skills_bundle/codex-run-train/README.md +1 -0
  252. phase_loop_runtime/skills_bundle/codex-run-train/SKILL.md +75 -0
  253. phase_loop_runtime/skills_bundle/codex-skill-editor/README.md +1 -0
  254. phase_loop_runtime/skills_bundle/codex-skill-editor/SKILL.md +94 -0
  255. phase_loop_runtime/skills_bundle/codex-skill-editor/assets/editor_prompt.md +72 -0
  256. phase_loop_runtime/skills_bundle/codex-skill-improvement-planner/README.md +1 -0
  257. phase_loop_runtime/skills_bundle/codex-skill-improvement-planner/SKILL.md +114 -0
  258. phase_loop_runtime/skills_bundle/codex-skill-improvement-planner/assets/aggregator_prompt.md +79 -0
  259. phase_loop_runtime/skills_bundle/codex-task-contextualizer/README.md +1 -0
  260. phase_loop_runtime/skills_bundle/codex-task-contextualizer/SKILL.md +102 -0
  261. phase_loop_runtime/skills_bundle/codex-task-contextualizer/references/task-templates.md +103 -0
  262. phase_loop_runtime/skills_bundle/gemini-advisor-board/README.md +1 -0
  263. phase_loop_runtime/skills_bundle/gemini-advisor-board/SKILL.md +38 -0
  264. phase_loop_runtime/skills_bundle/gemini-advisor-panel/README.md +1 -0
  265. phase_loop_runtime/skills_bundle/gemini-advisor-panel/SKILL.md +38 -0
  266. phase_loop_runtime/skills_bundle/gemini-execute-detailed/README.md +1 -0
  267. phase_loop_runtime/skills_bundle/gemini-execute-detailed/SKILL.md +144 -0
  268. phase_loop_runtime/skills_bundle/gemini-execute-phase/README.md +1 -0
  269. phase_loop_runtime/skills_bundle/gemini-execute-phase/SKILL.md +198 -0
  270. phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/allocate_worktree_name.sh +33 -0
  271. phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/audit_lane_file_touches.py +197 -0
  272. phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/cleanup_lane_worktrees.sh +103 -0
  273. phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/parent_tree_leakage_check.sh +125 -0
  274. phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/post_merge_import_smoke.sh +62 -0
  275. phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/pre_merge_destructiveness_check.sh +127 -0
  276. phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/resolve_branch_from_sha.sh +51 -0
  277. phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/state.py +274 -0
  278. phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/sweep_stale_worktrees.sh +62 -0
  279. phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/team_teardown.sh +32 -0
  280. phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/validate_plan_doc.py +642 -0
  281. phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/verify_harness.sh +96 -0
  282. phase_loop_runtime/skills_bundle/gemini-phase-loop/README.md +1 -0
  283. phase_loop_runtime/skills_bundle/gemini-phase-loop/SKILL.md +111 -0
  284. phase_loop_runtime/skills_bundle/gemini-phase-roadmap-builder/README.md +1 -0
  285. phase_loop_runtime/skills_bundle/gemini-phase-roadmap-builder/SKILL.md +147 -0
  286. phase_loop_runtime/skills_bundle/gemini-phase-roadmap-builder/assets/review_prompt.md +19 -0
  287. phase_loop_runtime/skills_bundle/gemini-phase-roadmap-builder/references/parallelization-heuristics.md +111 -0
  288. phase_loop_runtime/skills_bundle/gemini-phase-roadmap-builder/references/roadmap-template.md +193 -0
  289. phase_loop_runtime/skills_bundle/gemini-phase-roadmap-builder/scripts/validate_roadmap.py +32 -0
  290. phase_loop_runtime/skills_bundle/gemini-plan-detailed/README.md +1 -0
  291. phase_loop_runtime/skills_bundle/gemini-plan-detailed/SKILL.md +119 -0
  292. phase_loop_runtime/skills_bundle/gemini-plan-detailed/assets/review_prompt.md +25 -0
  293. phase_loop_runtime/skills_bundle/gemini-plan-phase/README.md +1 -0
  294. phase_loop_runtime/skills_bundle/gemini-plan-phase/SKILL.md +217 -0
  295. phase_loop_runtime/skills_bundle/gemini-plan-phase/assets/review_prompt.md +17 -0
  296. phase_loop_runtime/skills_bundle/gemini-plan-phase/scripts/validate_plan_doc.py +1092 -0
  297. phase_loop_runtime/skills_bundle/gemini-run-train/README.md +1 -0
  298. phase_loop_runtime/skills_bundle/gemini-run-train/SKILL.md +75 -0
  299. phase_loop_runtime/skills_bundle/gemini-skill-editor/README.md +1 -0
  300. phase_loop_runtime/skills_bundle/gemini-skill-editor/SKILL.md +87 -0
  301. phase_loop_runtime/skills_bundle/gemini-skill-editor/assets/editor_prompt.md +72 -0
  302. phase_loop_runtime/skills_bundle/gemini-skill-improvement-planner/README.md +1 -0
  303. phase_loop_runtime/skills_bundle/gemini-skill-improvement-planner/SKILL.md +109 -0
  304. phase_loop_runtime/skills_bundle/gemini-skill-improvement-planner/assets/aggregator_prompt.md +79 -0
  305. phase_loop_runtime/skills_bundle/gemini-task-contextualizer/README.md +1 -0
  306. phase_loop_runtime/skills_bundle/gemini-task-contextualizer/SKILL.md +98 -0
  307. phase_loop_runtime/skills_bundle/gemini-task-contextualizer/references/task-templates.md +103 -0
  308. phase_loop_runtime/skills_bundle/opencode-advisor-board/README.md +1 -0
  309. phase_loop_runtime/skills_bundle/opencode-advisor-board/SKILL.md +38 -0
  310. phase_loop_runtime/skills_bundle/opencode-advisor-panel/README.md +1 -0
  311. phase_loop_runtime/skills_bundle/opencode-advisor-panel/SKILL.md +38 -0
  312. phase_loop_runtime/skills_bundle/opencode-execute-detailed/README.md +1 -0
  313. phase_loop_runtime/skills_bundle/opencode-execute-detailed/SKILL.md +144 -0
  314. phase_loop_runtime/skills_bundle/opencode-execute-phase/README.md +1 -0
  315. phase_loop_runtime/skills_bundle/opencode-execute-phase/SKILL.md +196 -0
  316. phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/allocate_worktree_name.sh +33 -0
  317. phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/audit_lane_file_touches.py +197 -0
  318. phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/cleanup_lane_worktrees.sh +103 -0
  319. phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/parent_tree_leakage_check.sh +125 -0
  320. phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/post_merge_import_smoke.sh +62 -0
  321. phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/pre_merge_destructiveness_check.sh +127 -0
  322. phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/resolve_branch_from_sha.sh +51 -0
  323. phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/state.py +274 -0
  324. phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/sweep_stale_worktrees.sh +62 -0
  325. phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/team_teardown.sh +32 -0
  326. phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/validate_plan_doc.py +642 -0
  327. phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/verify_harness.sh +96 -0
  328. phase_loop_runtime/skills_bundle/opencode-phase-loop/README.md +1 -0
  329. phase_loop_runtime/skills_bundle/opencode-phase-loop/SKILL.md +113 -0
  330. phase_loop_runtime/skills_bundle/opencode-phase-roadmap-builder/README.md +1 -0
  331. phase_loop_runtime/skills_bundle/opencode-phase-roadmap-builder/SKILL.md +147 -0
  332. phase_loop_runtime/skills_bundle/opencode-phase-roadmap-builder/assets/review_prompt.md +19 -0
  333. phase_loop_runtime/skills_bundle/opencode-phase-roadmap-builder/references/parallelization-heuristics.md +111 -0
  334. phase_loop_runtime/skills_bundle/opencode-phase-roadmap-builder/references/roadmap-template.md +193 -0
  335. phase_loop_runtime/skills_bundle/opencode-phase-roadmap-builder/scripts/validate_roadmap.py +32 -0
  336. phase_loop_runtime/skills_bundle/opencode-plan-detailed/README.md +1 -0
  337. phase_loop_runtime/skills_bundle/opencode-plan-detailed/SKILL.md +119 -0
  338. phase_loop_runtime/skills_bundle/opencode-plan-detailed/assets/review_prompt.md +25 -0
  339. phase_loop_runtime/skills_bundle/opencode-plan-phase/README.md +1 -0
  340. phase_loop_runtime/skills_bundle/opencode-plan-phase/SKILL.md +211 -0
  341. phase_loop_runtime/skills_bundle/opencode-plan-phase/assets/review_prompt.md +17 -0
  342. phase_loop_runtime/skills_bundle/opencode-plan-phase/scripts/validate_plan_doc.py +1092 -0
  343. phase_loop_runtime/skills_bundle/opencode-run-train/README.md +1 -0
  344. phase_loop_runtime/skills_bundle/opencode-run-train/SKILL.md +75 -0
  345. phase_loop_runtime/skills_bundle/opencode-skill-editor/README.md +1 -0
  346. phase_loop_runtime/skills_bundle/opencode-skill-editor/SKILL.md +87 -0
  347. phase_loop_runtime/skills_bundle/opencode-skill-editor/assets/editor_prompt.md +72 -0
  348. phase_loop_runtime/skills_bundle/opencode-skill-improvement-planner/README.md +1 -0
  349. phase_loop_runtime/skills_bundle/opencode-skill-improvement-planner/SKILL.md +109 -0
  350. phase_loop_runtime/skills_bundle/opencode-skill-improvement-planner/assets/aggregator_prompt.md +79 -0
  351. phase_loop_runtime/skills_bundle/opencode-task-contextualizer/README.md +1 -0
  352. phase_loop_runtime/skills_bundle/opencode-task-contextualizer/SKILL.md +98 -0
  353. phase_loop_runtime/skills_bundle/opencode-task-contextualizer/references/task-templates.md +103 -0
  354. phase_loop_runtime/state.py +156 -0
  355. phase_loop_runtime/state_degradation.py +136 -0
  356. phase_loop_runtime/state_ops.py +676 -0
  357. phase_loop_runtime/train_ledger.py +215 -0
  358. phase_loop_runtime/train_roadmap.py +339 -0
  359. phase_loop_runtime/train_runner.py +1197 -0
  360. phase_loop_runtime/verification_evidence.py +650 -0
  361. phase_loop_runtime/verification_evidence_validator.py +75 -0
  362. phase_loop_runtime/visual_evidence_validator.py +36 -0
  363. phase_loop_runtime/worker_pool.py +123 -0
  364. phase_loop_runtime/worktree_index.py +270 -0
  365. phase_loop_runtime-0.2.0.data/data/share/phase-loop-runtime/protocol/protocol.md +2475 -0
  366. phase_loop_runtime-0.2.0.dist-info/METADATA +106 -0
  367. phase_loop_runtime-0.2.0.dist-info/RECORD +370 -0
  368. phase_loop_runtime-0.2.0.dist-info/WHEEL +5 -0
  369. phase_loop_runtime-0.2.0.dist-info/entry_points.txt +9 -0
  370. phase_loop_runtime-0.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,676 @@
1
+ """Agent-runtime provider seam (CS-0.8).
2
+
3
+ The deterministic Python runner spawns subscription CLIs (advisor-panel legs
4
+ in :mod:`panel_invoker`, harness phase executors elsewhere) as one-shot child
5
+ processes. That is a *pluggable backend* wearing a hard-coded shape: callers
6
+ reach straight for ``subprocess``/``spawn`` instead of a stable interface, so
7
+ swapping in a session-oriented runtime later means touching every call site.
8
+
9
+ This module names that seam. ``AgentRuntimeProvider`` mirrors the method set
10
+ of omniagent-plus's ``core-contracts`` TypeScript interface
11
+ (``packages/core-contracts/src/provider.ts``): ``create_session`` /
12
+ ``send_turn`` / ``read_history`` / ``stream_events`` / ``cancel_turn`` /
13
+ ``close_session`` / ``get_session_info`` / ``health``. It is deliberately a
14
+ *method-set* port, not a schema-for-schema port — there is no zod-equivalent
15
+ validation layer here; Python callers get dataclasses and a ``Protocol``.
16
+
17
+ ``HomebrewAgentRuntimeProvider`` is the concrete "degraded profile"
18
+ implementation (template: omniagent-plus's ``fake-provider.ts``): it wraps a
19
+ single one-shot CLI spawn as a SINGLE-TURN session with BUFFERED event replay.
20
+ There is no live event stream (the wrapped CLI call is synchronous and
21
+ already complete by the time ``send_turn`` returns), so ``stream_events``
22
+ replays the same buffer ``read_history`` would return. ``cancel_turn`` kills a
23
+ process handle *if the spawn function registered one*; a spawn that runs to
24
+ completion synchronously (the common case today — see
25
+ ``panel_invoker._default_spawn``) has nothing live to kill, so cancelling an
26
+ already-terminal turn is a no-op success, mirroring fake-provider's cancelled
27
+ -state short-circuit. These gaps versus a live/streaming/omnigent-backed
28
+ runtime are declared, not hidden: ``health()`` reports them in
29
+ ``unsupported_capabilities``.
30
+
31
+ Session/turn bookkeeping here is in-memory and per-process only. The
32
+ loop/train ledger (:mod:`train_ledger`) stays the enforcement layer's
33
+ responsibility — it may reference a provider session by ``session_id``, but
34
+ this module never reads or writes ledger state.
35
+ """
36
+ from __future__ import annotations
37
+
38
+ import os
39
+ import signal
40
+ import subprocess
41
+ import time
42
+ import uuid
43
+ from dataclasses import dataclass, field
44
+ from datetime import datetime, timezone
45
+ from typing import TYPE_CHECKING, Any, Callable, Iterator, Mapping, Protocol, runtime_checkable
46
+
47
+ if TYPE_CHECKING: # avoid a runtime import cycle (backing_omnigent imports THIS module)
48
+ from .advisor_board.backing_omnigent import OmnigentHttpClient
49
+
50
+ # ---------------------------------------------------------------------------
51
+ # Identifiers (open strings, not closed enums: `runtime.ts` reserves the closed
52
+ # `["omnigent"]` set for the eventual Omnigent-backed provider; a Python-only
53
+ # closed set here would break the moment that provider drops in).
54
+
55
+ RUNTIME_HOMEBREW = "homebrew"
56
+ RUNTIME_OMNIGENT = "omnigent"
57
+
58
+ # Reserved ``CreateSessionRequest.metadata`` key carrying the per-seat vendor-key
59
+ # HTTP headers (the never-silent-key auth material) through to the omnigent create
60
+ # call — metadata is the seam's documented pass-through for transport-specific
61
+ # fields, so no new request field is needed.
62
+ OMNIGENT_VENDOR_KEY_HEADERS_META = "_omnigent_vendor_key_headers"
63
+
64
+ AGENT_SESSION_STATES: tuple[str, ...] = (
65
+ "created", "starting", "idle", "turn_active",
66
+ "blocked_on_approval", "cancelling", "closed", "failed",
67
+ )
68
+ TURN_STATES: tuple[str, ...] = (
69
+ "accepted", "queued", "running", "blocked_on_tool_approval",
70
+ "cancelling", "cancelled", "timed_out", "completed", "failed",
71
+ )
72
+ CANCELLATION_REASONS: tuple[str, ...] = (
73
+ "user_request", "approval_denied", "timeout", "provider_interrupt", "session_close",
74
+ )
75
+
76
+
77
+ def _now() -> str:
78
+ return datetime.now(timezone.utc).isoformat()
79
+
80
+
81
+ def _new_id(prefix: str) -> str:
82
+ return f"{prefix}-{uuid.uuid4().hex[:12]}"
83
+
84
+
85
+ # ---------------------------------------------------------------------------
86
+ # Request/response shapes (mirrors provider.ts's types.ts, simplified: no
87
+ # worktree-lease / handoff-packet / retry-policy sub-schemas — callers that
88
+ # need those pass them through ``metadata``).
89
+
90
+
91
+ @dataclass(frozen=True)
92
+ class CreateSessionRequest:
93
+ target_harness: str
94
+ idempotency_key: str
95
+ title: str
96
+ runtime: str = RUNTIME_HOMEBREW
97
+ correlation_id: str | None = None
98
+ repo_root: str | None = None
99
+ metadata: Mapping[str, Any] = field(default_factory=dict)
100
+
101
+
102
+ @dataclass(frozen=True)
103
+ class SendTurnRequest:
104
+ session_id: str
105
+ idempotency_key: str
106
+ message: str
107
+ turn_id: str | None = None
108
+ timeout_seconds: float | None = None
109
+ metadata: Mapping[str, Any] = field(default_factory=dict)
110
+
111
+
112
+ @dataclass(frozen=True)
113
+ class RuntimeEvent:
114
+ """Single buffered runtime event. ``type`` mirrors the ``runtime.*`` event
115
+ names in events.ts (e.g. ``runtime.session.created``, ``runtime.turn.started``,
116
+ ``runtime.text.delta``, ``runtime.turn.completed``, ``runtime.turn.failed``,
117
+ ``runtime.turn.cancelled``); ``payload`` carries that event's fields."""
118
+
119
+ sequence: int
120
+ session_id: str
121
+ type: str
122
+ occurred_at: str
123
+ payload: Mapping[str, Any] = field(default_factory=dict)
124
+ turn_id: str | None = None
125
+ terminal: bool = False
126
+
127
+
128
+ @dataclass
129
+ class AgentSessionInfo:
130
+ id: str
131
+ runtime: str
132
+ target_harness: str
133
+ title: str
134
+ state: str
135
+ created_at: str
136
+ updated_at: str
137
+ active_turn_id: str | None = None
138
+ event_cursor: int = 0
139
+ correlation_id: str | None = None
140
+ repo_root: str | None = None
141
+ metadata: Mapping[str, Any] = field(default_factory=dict)
142
+
143
+
144
+ @dataclass
145
+ class TurnHandle:
146
+ session_id: str
147
+ turn_id: str
148
+ idempotency_key: str
149
+ state: str
150
+ created_at: str
151
+ updated_at: str
152
+ event_cursor: int = 0
153
+
154
+
155
+ @dataclass(frozen=True)
156
+ class SessionHistory:
157
+ session_id: str
158
+ events: tuple[RuntimeEvent, ...]
159
+ next_cursor: int | None = None
160
+
161
+
162
+ @dataclass(frozen=True)
163
+ class ProviderHealth:
164
+ runtime: str
165
+ backend: str
166
+ available: bool
167
+ active_sessions: int
168
+ unsupported_capabilities: tuple[str, ...] = ()
169
+ notes: tuple[str, ...] = ()
170
+
171
+
172
+ class RuntimeProviderError(RuntimeError):
173
+ """Raised for caller-facing provider failures (unknown session/turn, etc.).
174
+
175
+ Mirrors errors.ts's ``RuntimeFailure`` shape loosely via ``category`` /
176
+ ``scope`` attributes rather than a parallel zod-validated schema.
177
+ """
178
+
179
+ def __init__(self, message: str, *, category: str, scope: str) -> None:
180
+ super().__init__(message)
181
+ self.category = category
182
+ self.scope = scope
183
+
184
+
185
+ # ---------------------------------------------------------------------------
186
+ # The interface. `Protocol` (structural) rather than `abc.ABC`: callers that
187
+ # already have a spawn-shaped object should not be forced into inheritance to
188
+ # satisfy this seam.
189
+
190
+
191
+ @runtime_checkable
192
+ class AgentRuntimeProvider(Protocol):
193
+ def create_session(self, request: CreateSessionRequest) -> AgentSessionInfo: ...
194
+
195
+ def send_turn(self, request: SendTurnRequest) -> TurnHandle: ...
196
+
197
+ def read_history(
198
+ self, session_id: str, *, after_sequence: int = 0, limit: int | None = None
199
+ ) -> SessionHistory: ...
200
+
201
+ def stream_events(
202
+ self, session_id: str, *, after_sequence: int = 0
203
+ ) -> Iterator[RuntimeEvent]: ...
204
+
205
+ def cancel_turn(self, handle: TurnHandle, reason: str = "user_request") -> TurnHandle: ...
206
+
207
+ def close_session(self, session_id: str) -> None: ...
208
+
209
+ def get_session_info(self, session_id: str) -> AgentSessionInfo: ...
210
+
211
+ def health(self) -> ProviderHealth: ...
212
+
213
+
214
+ # ---------------------------------------------------------------------------
215
+ # Homebrew degraded-profile implementation.
216
+
217
+ # A spawn function performs the actual one-shot CLI call. It receives the
218
+ # `SendTurnRequest` and an optional `register_process` callback; if the spawn
219
+ # launches a real child process it MAY call `register_process(pid)` so
220
+ # `cancel_turn` has something to kill while the turn is still running. Most
221
+ # existing spawns (panel_invoker._default_spawn and friends) run a blocking
222
+ # `subprocess.run`/PTY session to completion and return before `send_turn`
223
+ # gets a chance to hand back a handle to cancel against — that is the
224
+ # documented degraded capability, not a bug in this seam.
225
+ SpawnResult = tuple[str, str] # (status, text) — matches panel_invoker.SpawnFn
226
+ SpawnFn = Callable[..., SpawnResult]
227
+
228
+
229
+ @dataclass
230
+ class _SessionRecord:
231
+ info: AgentSessionInfo
232
+ events: list[RuntimeEvent] = field(default_factory=list)
233
+ turns: dict[str, TurnHandle] = field(default_factory=dict)
234
+ turns_by_key: dict[str, TurnHandle] = field(default_factory=dict)
235
+ live_pids: dict[str, int] = field(default_factory=dict)
236
+
237
+
238
+ class HomebrewAgentRuntimeProvider:
239
+ """Presents a one-shot CLI spawn as a single-turn, buffered-replay session.
240
+
241
+ ``spawn(leg_or_target, artifact, **kwargs) -> (status, text)`` is the same
242
+ shape as :data:`panel_invoker.SpawnFn` — this class is the adaptation
243
+ layer, not a rewrite of the CLI legs themselves. Each session supports
244
+ exactly one turn (the "single-turn session" the CS-0.8 spec calls for);
245
+ a second ``send_turn`` on the same session is rejected the same way
246
+ fake-provider.ts rejects a concurrent turn.
247
+ """
248
+
249
+ def __init__(self, spawn: SpawnFn, *, backend: str = "cli-spawn") -> None:
250
+ self._spawn = spawn
251
+ self._backend = backend
252
+ self._sessions: dict[str, _SessionRecord] = {}
253
+
254
+ # -- session lifecycle ------------------------------------------------
255
+
256
+ def create_session(self, request: CreateSessionRequest) -> AgentSessionInfo:
257
+ session_id = _new_id("session")
258
+ now = _now()
259
+ info = AgentSessionInfo(
260
+ id=session_id,
261
+ runtime=request.runtime,
262
+ target_harness=request.target_harness,
263
+ title=request.title,
264
+ state="idle",
265
+ created_at=now,
266
+ updated_at=now,
267
+ correlation_id=request.correlation_id,
268
+ repo_root=request.repo_root,
269
+ metadata=dict(request.metadata),
270
+ )
271
+ record = _SessionRecord(info=info)
272
+ record.events.append(
273
+ RuntimeEvent(
274
+ sequence=1,
275
+ session_id=session_id,
276
+ type="runtime.session.created",
277
+ occurred_at=now,
278
+ payload={"state": "idle", "title": request.title},
279
+ )
280
+ )
281
+ info.event_cursor = 1
282
+ self._sessions[session_id] = record
283
+ return info
284
+
285
+ def _record(self, session_id: str) -> _SessionRecord:
286
+ record = self._sessions.get(session_id)
287
+ if record is None:
288
+ raise RuntimeProviderError(
289
+ f"unknown session {session_id}", category="validation", scope="session"
290
+ )
291
+ return record
292
+
293
+ def _append(self, record: _SessionRecord, *, type: str, payload: Mapping[str, Any],
294
+ turn_id: str | None = None, terminal: bool = False) -> RuntimeEvent:
295
+ event = RuntimeEvent(
296
+ sequence=len(record.events) + 1,
297
+ session_id=record.info.id,
298
+ type=type,
299
+ occurred_at=_now(),
300
+ payload=dict(payload),
301
+ turn_id=turn_id,
302
+ terminal=terminal,
303
+ )
304
+ record.events.append(event)
305
+ record.info.event_cursor = event.sequence
306
+ return event
307
+
308
+ # -- single turn --------------------------------------------------------
309
+
310
+ def send_turn(self, request: SendTurnRequest) -> TurnHandle:
311
+ record = self._record(request.session_id)
312
+ existing = record.turns_by_key.get(request.idempotency_key)
313
+ if existing is not None:
314
+ return existing
315
+ if record.info.active_turn_id is not None:
316
+ raise RuntimeProviderError(
317
+ "only one turn is allowed per homebrew (single-turn) session",
318
+ category="concurrency_limit",
319
+ scope="session",
320
+ )
321
+
322
+ turn_id = request.turn_id or _new_id("turn")
323
+ now = _now()
324
+ handle = TurnHandle(
325
+ session_id=request.session_id,
326
+ turn_id=turn_id,
327
+ idempotency_key=request.idempotency_key,
328
+ state="running",
329
+ created_at=now,
330
+ updated_at=now,
331
+ )
332
+ record.turns[turn_id] = handle
333
+ record.turns_by_key[request.idempotency_key] = handle
334
+ record.info.active_turn_id = turn_id
335
+ record.info.state = "turn_active"
336
+ record.info.updated_at = now
337
+ self._append(
338
+ record, type="runtime.turn.started",
339
+ payload={"message": request.message, "state": "running"}, turn_id=turn_id,
340
+ )
341
+
342
+ def register_process(pid: int) -> None:
343
+ record.live_pids[turn_id] = pid
344
+
345
+ try:
346
+ status, text = self._spawn(request, register_process=register_process)
347
+ except Exception as exc: # fail-closed: a broken spawn fails the turn, never raises
348
+ return self._finish_turn(record, handle, ok=False, text=str(exc), status="ERROR")
349
+ finally:
350
+ record.live_pids.pop(turn_id, None)
351
+
352
+ return self._finish_turn(record, handle, ok=(status == "OK"), text=text, status=status)
353
+
354
+ def _finish_turn(
355
+ self, record: _SessionRecord, handle: TurnHandle, *, ok: bool, text: str, status: str
356
+ ) -> TurnHandle:
357
+ if handle.state == "cancelled":
358
+ return handle
359
+ now = _now()
360
+ self._append(
361
+ record, type="runtime.text.delta", payload={"delta": text}, turn_id=handle.turn_id,
362
+ )
363
+ if ok:
364
+ self._append(
365
+ record, type="runtime.turn.completed",
366
+ payload={"outcome": "completed", "status": status}, turn_id=handle.turn_id,
367
+ terminal=True,
368
+ )
369
+ handle.state = "completed"
370
+ else:
371
+ self._append(
372
+ record, type="runtime.turn.failed",
373
+ payload={"outcome": "failed", "status": status, "message": text},
374
+ turn_id=handle.turn_id, terminal=True,
375
+ )
376
+ handle.state = "failed"
377
+ handle.updated_at = now
378
+ handle.event_cursor = record.info.event_cursor
379
+ record.info.active_turn_id = None
380
+ record.info.state = "idle"
381
+ record.info.updated_at = now
382
+ return handle
383
+
384
+ # -- history / replay ----------------------------------------------------
385
+
386
+ def read_history(
387
+ self, session_id: str, *, after_sequence: int = 0, limit: int | None = None
388
+ ) -> SessionHistory:
389
+ record = self._record(session_id)
390
+ events = [e for e in record.events if e.sequence > after_sequence]
391
+ if limit is not None:
392
+ events = events[:limit]
393
+ next_cursor = events[-1].sequence if events else (after_sequence or None)
394
+ return SessionHistory(session_id=session_id, events=tuple(events), next_cursor=next_cursor)
395
+
396
+ def stream_events(self, session_id: str, *, after_sequence: int = 0) -> Iterator[RuntimeEvent]:
397
+ # No live stream: the wrapped spawn is already synchronous/complete by
398
+ # the time any turn exists, so "streaming" is a replay of the buffer.
399
+ record = self._record(session_id)
400
+ for event in record.events:
401
+ if event.sequence > after_sequence:
402
+ yield event
403
+
404
+ # -- cancellation ---------------------------------------------------------
405
+
406
+ def cancel_turn(self, handle: TurnHandle, reason: str = "user_request") -> TurnHandle:
407
+ record = self._record(handle.session_id)
408
+ current = record.turns.get(handle.turn_id)
409
+ if current is None:
410
+ raise RuntimeProviderError(
411
+ f"unknown turn {handle.turn_id}", category="validation", scope="turn",
412
+ )
413
+ if current.state in {"cancelled", "completed", "failed", "timed_out"}:
414
+ return current # idempotent: nothing live to cancel
415
+
416
+ pid = record.live_pids.pop(current.turn_id, None)
417
+ if pid is not None:
418
+ _terminate_pid(pid)
419
+
420
+ now = _now()
421
+ self._append(
422
+ record, type="runtime.turn.cancelled",
423
+ payload={"outcome": "cancelled", "reason": reason}, turn_id=current.turn_id,
424
+ terminal=True,
425
+ )
426
+ current.state = "cancelled"
427
+ current.updated_at = now
428
+ current.event_cursor = record.info.event_cursor
429
+ record.info.active_turn_id = None
430
+ record.info.state = "idle"
431
+ record.info.updated_at = now
432
+ return current
433
+
434
+ # -- session close / introspection -----------------------------------------
435
+
436
+ def close_session(self, session_id: str) -> None:
437
+ record = self._record(session_id)
438
+ if record.info.active_turn_id is not None:
439
+ active = record.turns[record.info.active_turn_id]
440
+ self.cancel_turn(active, reason="session_close")
441
+ now = _now()
442
+ self._append(record, type="runtime.session.closed", payload={"reason": "logical_close"}, terminal=True)
443
+ record.info.state = "closed"
444
+ record.info.updated_at = now
445
+
446
+ def get_session_info(self, session_id: str) -> AgentSessionInfo:
447
+ return self._record(session_id).info
448
+
449
+ def health(self) -> ProviderHealth:
450
+ return ProviderHealth(
451
+ runtime=RUNTIME_HOMEBREW,
452
+ backend=self._backend,
453
+ available=True,
454
+ active_sessions=sum(1 for r in self._sessions.values() if r.info.state != "closed"),
455
+ unsupported_capabilities=(
456
+ "live_event_streaming",
457
+ "mid_turn_cancellation_of_synchronous_spawns",
458
+ "multi_turn_sessions",
459
+ ),
460
+ notes=("homebrew degraded profile: one-shot CLI spawn presented as a single-turn, buffered-replay session",),
461
+ )
462
+
463
+
464
+ # ---------------------------------------------------------------------------
465
+ # Omnigent-backed implementation (ABDOMNI). The `["omnigent"]` runtime this
466
+ # module's header reserves, now filled: the sibling of HomebrewAgentRuntimeProvider
467
+ # on the SAME AgentRuntimeProvider seam. Template: omniagent-plus's
468
+ # `omnigent-transport/http-provider.ts` (`OmnigentHttpProvider implements
469
+ # AgentRuntimeProvider`), ported method-for-method over the frozen v0.4.0 HTTP
470
+ # surface. The transport itself lives in `advisor_board.backing_omnigent`
471
+ # (`OmnigentHttpClient`); this class is the Protocol adaptation, exactly as
472
+ # HomebrewAgentRuntimeProvider adapts a one-shot CLI spawn.
473
+
474
+
475
+ def _map_omnigent_state(status: Any) -> str:
476
+ return {
477
+ "launching": "starting",
478
+ "running": "turn_active",
479
+ "waiting": "turn_active",
480
+ "failed": "failed",
481
+ "idle": "idle",
482
+ }.get(str(status), "idle")
483
+
484
+
485
+ class OmnigentAgentRuntimeProvider:
486
+ """Presents Omnigent v0.4.0 (over ``OmnigentHttpClient``) as an
487
+ ``AgentRuntimeProvider``. Single-turn/buffered-replay degraded profile, like
488
+ the homebrew provider: the public transport surface is driven per-turn and the
489
+ SSE stream is replayed from history rather than held open, and child-session /
490
+ public harness-override capabilities are declared unsupported in ``health()``
491
+ (mirroring http-provider.ts's notes)."""
492
+
493
+ def __init__(self, client: "OmnigentHttpClient", *, backend: str = "omnigent-http") -> None:
494
+ self._client = client
495
+ self._backend = backend
496
+ self._sessions: dict[str, AgentSessionInfo] = {}
497
+ self._turns: dict[str, TurnHandle] = {}
498
+
499
+ # -- session lifecycle -----------------------------------------------------
500
+
501
+ def create_session(self, request: CreateSessionRequest) -> AgentSessionInfo:
502
+ headers = dict(request.metadata).get(OMNIGENT_VENDOR_KEY_HEADERS_META) or None
503
+ snapshot = self._client.create_session(
504
+ target_harness=request.target_harness,
505
+ idempotency_key=request.idempotency_key,
506
+ title=request.title,
507
+ vendor_key_headers=headers,
508
+ )
509
+ info = self._to_info(request, snapshot)
510
+ self._sessions[info.id] = info
511
+ return info
512
+
513
+ def _to_info(self, request: CreateSessionRequest, snapshot: Mapping[str, Any]) -> AgentSessionInfo:
514
+ now = _now()
515
+ return AgentSessionInfo(
516
+ id=str(snapshot.get("id")),
517
+ runtime=RUNTIME_OMNIGENT,
518
+ target_harness=request.target_harness,
519
+ title=str(snapshot.get("title", request.title)),
520
+ state=_map_omnigent_state(snapshot.get("status")),
521
+ created_at=str(snapshot.get("createdAt", now)),
522
+ updated_at=str(snapshot.get("updatedAt", now)),
523
+ correlation_id=request.correlation_id,
524
+ repo_root=request.repo_root,
525
+ metadata=dict(snapshot.get("metadata") or {}),
526
+ )
527
+
528
+ def send_turn(self, request: SendTurnRequest) -> TurnHandle:
529
+ key = f"{request.session_id}:{request.idempotency_key}"
530
+ ack = self._client.send_turn(request.session_id, request.message)
531
+ now = _now()
532
+ handle = TurnHandle(
533
+ session_id=request.session_id,
534
+ turn_id=str(ack.get("turnId")),
535
+ idempotency_key=request.idempotency_key,
536
+ state="queued" if ack.get("queued") else "running",
537
+ created_at=now,
538
+ updated_at=now,
539
+ )
540
+ self._turns[key] = handle
541
+ info = self._sessions.get(request.session_id)
542
+ if info is not None:
543
+ info.active_turn_id = handle.turn_id
544
+ info.state = "turn_active"
545
+ info.updated_at = now
546
+ return handle
547
+
548
+ # -- history / replay ------------------------------------------------------
549
+
550
+ def _map_history(self, session_id: str, items: list[Any]) -> list[RuntimeEvent]:
551
+ events: list[RuntimeEvent] = []
552
+ for index, item in enumerate(items):
553
+ raw = item.get("event", item) if isinstance(item, Mapping) else {}
554
+ events.append(
555
+ RuntimeEvent(
556
+ sequence=index + 1,
557
+ session_id=session_id,
558
+ type=str(raw.get("type", "")),
559
+ occurred_at=str(raw.get("occurredAt", _now())),
560
+ payload={
561
+ k: raw.get(k)
562
+ for k in ("delta", "outputText", "reason", "status", "message")
563
+ if raw.get(k) is not None
564
+ },
565
+ turn_id=raw.get("turnId"),
566
+ terminal=bool(raw.get("terminal")),
567
+ )
568
+ )
569
+ return events
570
+
571
+ def read_history(
572
+ self, session_id: str, *, after_sequence: int = 0, limit: int | None = None
573
+ ) -> SessionHistory:
574
+ events = [
575
+ e for e in self._map_history(session_id, self._client.get_history(session_id))
576
+ if e.sequence > after_sequence
577
+ ]
578
+ if limit is not None:
579
+ events = events[:limit]
580
+ next_cursor = events[-1].sequence if events else (after_sequence or None)
581
+ return SessionHistory(session_id=session_id, events=tuple(events), next_cursor=next_cursor)
582
+
583
+ def stream_events(self, session_id: str, *, after_sequence: int = 0) -> Iterator[RuntimeEvent]:
584
+ # No held-open stream in the degraded profile — replay the buffer (same as
585
+ # the homebrew provider; the per-turn call is already complete).
586
+ for event in self.read_history(session_id).events:
587
+ if event.sequence > after_sequence:
588
+ yield event
589
+
590
+ # -- cancellation / close --------------------------------------------------
591
+
592
+ def cancel_turn(self, handle: TurnHandle, reason: str = "user_request") -> TurnHandle:
593
+ self._client.interrupt(handle.session_id, reason)
594
+ handle.state = "cancelled"
595
+ handle.updated_at = _now()
596
+ info = self._sessions.get(handle.session_id)
597
+ if info is not None:
598
+ info.active_turn_id = None
599
+ info.state = "idle"
600
+ info.updated_at = handle.updated_at
601
+ return handle
602
+
603
+ def close_session(self, session_id: str) -> None:
604
+ self._client.delete_session(session_id)
605
+ info = self._sessions.get(session_id)
606
+ if info is not None:
607
+ info.state = "closed"
608
+ info.active_turn_id = None
609
+ info.updated_at = _now()
610
+
611
+ def get_session_info(self, session_id: str) -> AgentSessionInfo:
612
+ snapshot = self._client.get_session(session_id)
613
+ existing = self._sessions.get(session_id)
614
+ info = AgentSessionInfo(
615
+ id=str(snapshot.get("id", session_id)),
616
+ runtime=RUNTIME_OMNIGENT,
617
+ target_harness=existing.target_harness if existing else "",
618
+ title=str(snapshot.get("title", existing.title if existing else "")),
619
+ state="closed" if (existing and existing.state == "closed") else _map_omnigent_state(snapshot.get("status")),
620
+ created_at=str(snapshot.get("createdAt", existing.created_at if existing else _now())),
621
+ updated_at=str(snapshot.get("updatedAt", _now())),
622
+ correlation_id=existing.correlation_id if existing else None,
623
+ repo_root=existing.repo_root if existing else None,
624
+ metadata=dict(snapshot.get("metadata") or {}),
625
+ )
626
+ self._sessions[session_id] = info
627
+ return info
628
+
629
+ def health(self) -> ProviderHealth:
630
+ try:
631
+ self._client.list_harnesses()
632
+ available = True
633
+ except Exception: # any transport failure → not available
634
+ available = False
635
+ return ProviderHealth(
636
+ runtime=RUNTIME_OMNIGENT,
637
+ backend=self._backend,
638
+ available=available,
639
+ active_sessions=sum(1 for i in self._sessions.values() if i.state != "closed"),
640
+ unsupported_capabilities=(
641
+ "child_session_creation",
642
+ "public_harness_override",
643
+ "held_open_event_streaming",
644
+ ),
645
+ notes=(
646
+ "omnigent v0.4.0 public transport: single-turn board leg presented as a "
647
+ "buffered-replay session; logical close via DELETE",
648
+ ),
649
+ )
650
+
651
+
652
+ def _terminate_pid(pid: int) -> None:
653
+ """Best-effort kill of a registered live process group (mirrors panel_invoker's
654
+ `_terminate_process_group`, generalized to a bare pid since spawn functions
655
+ register only a pid, not a `subprocess.Popen`)."""
656
+ try:
657
+ os.killpg(pid, signal.SIGTERM)
658
+ except (ProcessLookupError, PermissionError, OSError):
659
+ try:
660
+ os.kill(pid, signal.SIGTERM)
661
+ except (ProcessLookupError, PermissionError, OSError):
662
+ return
663
+ deadline = time.monotonic() + 5
664
+ while time.monotonic() < deadline:
665
+ try:
666
+ os.kill(pid, 0)
667
+ except (ProcessLookupError, PermissionError, OSError):
668
+ return
669
+ time.sleep(0.05)
670
+ try:
671
+ os.killpg(pid, signal.SIGKILL)
672
+ except (ProcessLookupError, PermissionError, OSError):
673
+ try:
674
+ os.kill(pid, signal.SIGKILL)
675
+ except (ProcessLookupError, PermissionError, OSError):
676
+ pass