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.
- phase_loop_runtime/__init__.py +20 -0
- phase_loop_runtime/_contract_docs/phase-loop/extraction-readiness.md +90 -0
- phase_loop_runtime/_contract_docs/phase-loop/granular-execution-policy.md +137 -0
- phase_loop_runtime/_contract_docs/phase-loop/harness-capability-matrix.md +510 -0
- phase_loop_runtime/_contract_docs/phase-loop/harness-skill-matrix.md +125 -0
- phase_loop_runtime/_contract_docs/phase-loop/harness-substrate-manifest.md +232 -0
- phase_loop_runtime/_contract_docs/phase-loop/pi-loop-control.md +247 -0
- phase_loop_runtime/_contract_docs/phase-loop/protocol.md +2759 -0
- phase_loop_runtime/_contract_docs/phase-loop/runtime-boundary.md +289 -0
- phase_loop_runtime/_contract_docs/phase-loop/spec-discovery-roots.md +47 -0
- phase_loop_runtime/_contract_docs/phase-loop/substrate-soak-governed-pipeline.md +42 -0
- phase_loop_runtime/_contract_docs/phase-loop/substrate-soak-portal-projection.md +34 -0
- phase_loop_runtime/_contract_docs/phase-loop/substrate-soak-regenesis.md +29 -0
- phase_loop_runtime/_contract_docs/phase-loop/substrate-soak-report.md +32 -0
- phase_loop_runtime/_contract_docs/runtime/verification-evidence-contract.md +52 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-mixed-signals/commands.txt +2 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-mixed-signals/manifest.json +16 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-mixed-signals/mixed.json +5 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-provider-timeout/manifest.json +16 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-provider-timeout/timeout-note.txt +2 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-redacted-summary/manifest.json +16 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-redacted-summary/redacted-summary.json +6 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-small-sample/manifest.json +16 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-small-sample/sample.json +7 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-threshold-edge/edge.txt +2 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-threshold-edge/manifest.json +16 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-duplicate-png/manifest.json +16 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-duplicate-png/shot-a.txt +1 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-duplicate-png/shot-b.txt +1 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-duplicate-png/shot-c.txt +1 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-missing-references/claimed-results.json +5 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-missing-references/manifest.json +16 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-subtle-low-variance/manifest.json +16 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-subtle-low-variance/metrics.json +9 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-templated-prose/findings.md +10 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-templated-prose/manifest.json +16 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-uniform-scores/manifest.json +16 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-uniform-scores/scores.json +4 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-uniform-scores/summary.txt +1 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-adoption-bundle/bundle-check.json +8 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-adoption-bundle/manifest.json +16 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-adoption-bundle/operator-note.txt +2 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-handoff-ledger/events-sample.jsonl +3 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-handoff-ledger/manifest.json +16 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-handoff-ledger/receipt.txt +1 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-runner-diff/evidence.json +13 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-runner-diff/manifest.json +16 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-runner-diff/summary.txt +2 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-test-matrix/manifest.json +16 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-test-matrix/matrix.json +9 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-test-matrix/notes.md +4 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-ui-screenshot-standin/console-summary.txt +2 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-ui-screenshot-standin/manifest.json +16 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-ui-screenshot-standin/screenshot-standin.ppm +5 -0
- phase_loop_runtime/adoption_bundle.py +273 -0
- phase_loop_runtime/advisor_board/CONTRACTS.md +257 -0
- phase_loop_runtime/advisor_board/__init__.py +288 -0
- phase_loop_runtime/advisor_board/backing.py +137 -0
- phase_loop_runtime/advisor_board/backing_omnigent.py +498 -0
- phase_loop_runtime/advisor_board/config.py +225 -0
- phase_loop_runtime/advisor_board/events.py +115 -0
- phase_loop_runtime/advisor_board/fixtures/advisor-boards.example.toml +59 -0
- phase_loop_runtime/advisor_board/fixtures.py +91 -0
- phase_loop_runtime/advisor_board/harness_mapping.py +125 -0
- phase_loop_runtime/advisor_board/matrix.py +146 -0
- phase_loop_runtime/advisor_board/observability.py +481 -0
- phase_loop_runtime/advisor_board/presets.py +188 -0
- phase_loop_runtime/advisor_board/registries.py +309 -0
- phase_loop_runtime/advisor_board/resolver.py +253 -0
- phase_loop_runtime/advisor_board/schema.py +269 -0
- phase_loop_runtime/advisor_board/standin.py +166 -0
- phase_loop_runtime/advisor_board/validation.py +159 -0
- phase_loop_runtime/agent_runtime_provider.py +676 -0
- phase_loop_runtime/baml_modular.py +656 -0
- phase_loop_runtime/baml_src/dotfiles_adoption_manifest.baml +37 -0
- phase_loop_runtime/baml_src/dotfiles_c4_document.baml +14 -0
- phase_loop_runtime/baml_src/dotfiles_plan_manifest.baml +50 -0
- phase_loop_runtime/baml_src/dotfiles_runtime_projection.baml +16 -0
- phase_loop_runtime/baml_src/dotfiles_task_catalog.baml +20 -0
- phase_loop_runtime/baml_src/emit_phase_closeout.baml +86 -0
- phase_loop_runtime/baml_src/evaluate_suspected_fake_evidence.baml +36 -0
- phase_loop_runtime/baml_src/verification_evidence.baml +40 -0
- phase_loop_runtime/broker.py +181 -0
- phase_loop_runtime/build_bundle.py +294 -0
- phase_loop_runtime/capability_registry.py +835 -0
- phase_loop_runtime/classifier.py +87 -0
- phase_loop_runtime/claude_agent_view.py +625 -0
- phase_loop_runtime/claude_channel_sidecar.py +1086 -0
- phase_loop_runtime/cli.py +2599 -0
- phase_loop_runtime/closeout.py +865 -0
- phase_loop_runtime/closeout_classifier.py +120 -0
- phase_loop_runtime/closeout_evidence_audit.py +112 -0
- phase_loop_runtime/closeout_validation.py +144 -0
- phase_loop_runtime/closeout_validators.py +203 -0
- phase_loop_runtime/consiliency_gates.py +374 -0
- phase_loop_runtime/consiliency_ingest.py +399 -0
- phase_loop_runtime/consiliency_layout.py +176 -0
- phase_loop_runtime/consiliency_scaffold.py +305 -0
- phase_loop_runtime/cross_repo_channel.py +331 -0
- phase_loop_runtime/discovery.py +2368 -0
- phase_loop_runtime/dispatch_lock.py +167 -0
- phase_loop_runtime/doc_delta_validator.py +98 -0
- phase_loop_runtime/docs_audit.py +285 -0
- phase_loop_runtime/docs_freshness.py +468 -0
- phase_loop_runtime/docs_surfaces.py +150 -0
- phase_loop_runtime/dotfiles_profile_plugin.py +96 -0
- phase_loop_runtime/events.py +154 -0
- phase_loop_runtime/events_migration.py +96 -0
- phase_loop_runtime/evidence_audit.py +1082 -0
- phase_loop_runtime/evidence_audit_config.py +172 -0
- phase_loop_runtime/fleet_map.py +426 -0
- phase_loop_runtime/fleet_metrics.py +298 -0
- phase_loop_runtime/fleet_metrics_export.py +196 -0
- phase_loop_runtime/git_discipline.py +479 -0
- phase_loop_runtime/git_ops.py +122 -0
- phase_loop_runtime/git_topology.py +252 -0
- phase_loop_runtime/governed_bundle.py +137 -0
- phase_loop_runtime/governed_premerge.py +219 -0
- phase_loop_runtime/governed_review.py +264 -0
- phase_loop_runtime/handoff.py +932 -0
- phase_loop_runtime/handoff_path.py +14 -0
- phase_loop_runtime/injection.py +740 -0
- phase_loop_runtime/install_status.py +88 -0
- phase_loop_runtime/lane_scheduler.py +317 -0
- phase_loop_runtime/launcher.py +2760 -0
- phase_loop_runtime/lease_store.py +553 -0
- phase_loop_runtime/maintenance.py +401 -0
- phase_loop_runtime/migrate_handoffs.py +201 -0
- phase_loop_runtime/models.py +2144 -0
- phase_loop_runtime/observability.py +1290 -0
- phase_loop_runtime/panel_invoker.py +1638 -0
- phase_loop_runtime/phase_loop_drift_audit.py +388 -0
- phase_loop_runtime/phase_worktree_executor.py +433 -0
- phase_loop_runtime/pipeline_adapter/__init__.py +12 -0
- phase_loop_runtime/pipeline_adapter/branch_ops.py +274 -0
- phase_loop_runtime/pipeline_adapter/flag.py +55 -0
- phase_loop_runtime/pipeline_adapter/markers.py +13 -0
- phase_loop_runtime/pipeline_adapter/merge_policy.py +72 -0
- phase_loop_runtime/pipeline_adapter/ratification.py +100 -0
- phase_loop_runtime/pipeline_adapter/sibling_matcher.py +123 -0
- phase_loop_runtime/plan_ir.py +523 -0
- phase_loop_runtime/plan_manifest.py +415 -0
- phase_loop_runtime/planner_validation.py +176 -0
- phase_loop_runtime/profiles.py +462 -0
- phase_loop_runtime/prompts.py +365 -0
- phase_loop_runtime/provenance.py +165 -0
- phase_loop_runtime/publishing.py +339 -0
- phase_loop_runtime/py.typed +1 -0
- phase_loop_runtime/reconcile.py +1550 -0
- phase_loop_runtime/redaction.py +184 -0
- phase_loop_runtime/reflection_sync.py +293 -0
- phase_loop_runtime/release_guard.py +186 -0
- phase_loop_runtime/render.py +432 -0
- phase_loop_runtime/repo_validation.py +427 -0
- phase_loop_runtime/review_summary.py +150 -0
- phase_loop_runtime/roadmap_lint.py +425 -0
- phase_loop_runtime/route_log.py +59 -0
- phase_loop_runtime/route_policy/__init__.py +11 -0
- phase_loop_runtime/route_policy/fixtures/route_selection.golden.json +263 -0
- phase_loop_runtime/runner.py +8400 -0
- phase_loop_runtime/runtime_paths.py +225 -0
- phase_loop_runtime/runtime_projection.py +113 -0
- phase_loop_runtime/runtime_resources.py +36 -0
- phase_loop_runtime/schema_export.py +292 -0
- phase_loop_runtime/schemas/phase_loop_closeout.schema.json +366 -0
- phase_loop_runtime/skill_install.py +176 -0
- phase_loop_runtime/skill_inventory.py +662 -0
- phase_loop_runtime/skill_paths.py +42 -0
- phase_loop_runtime/skill_sources_plugin.py +37 -0
- phase_loop_runtime/skills_bundle/claude-advisor-board/README.md +1 -0
- phase_loop_runtime/skills_bundle/claude-advisor-board/SKILL.md +38 -0
- phase_loop_runtime/skills_bundle/claude-advisor-panel/README.md +1 -0
- phase_loop_runtime/skills_bundle/claude-advisor-panel/SKILL.md +38 -0
- phase_loop_runtime/skills_bundle/claude-execute-detailed/README.md +1 -0
- phase_loop_runtime/skills_bundle/claude-execute-detailed/SKILL.md +152 -0
- phase_loop_runtime/skills_bundle/claude-execute-phase/README.md +1 -0
- phase_loop_runtime/skills_bundle/claude-execute-phase/SKILL.md +745 -0
- phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/allocate_worktree_name.sh +33 -0
- phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/audit_lane_file_touches.py +197 -0
- phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/cleanup_lane_worktrees.sh +103 -0
- phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/parent_tree_leakage_check.sh +125 -0
- phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/post_merge_import_smoke.sh +62 -0
- phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/pre_merge_destructiveness_check.sh +127 -0
- phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/resolve_branch_from_sha.sh +51 -0
- phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/state.py +274 -0
- phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/sweep_stale_worktrees.sh +62 -0
- phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/team_teardown.sh +32 -0
- phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/validate_plan_doc.py +642 -0
- phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/verify_harness.sh +96 -0
- phase_loop_runtime/skills_bundle/claude-phase-loop/README.md +1 -0
- phase_loop_runtime/skills_bundle/claude-phase-loop/SKILL.md +115 -0
- phase_loop_runtime/skills_bundle/claude-phase-roadmap-builder/README.md +1 -0
- phase_loop_runtime/skills_bundle/claude-phase-roadmap-builder/SKILL.md +439 -0
- phase_loop_runtime/skills_bundle/claude-phase-roadmap-builder/assets/review_prompt.md +19 -0
- phase_loop_runtime/skills_bundle/claude-phase-roadmap-builder/references/parallelization-heuristics.md +111 -0
- phase_loop_runtime/skills_bundle/claude-phase-roadmap-builder/references/roadmap-template.md +193 -0
- phase_loop_runtime/skills_bundle/claude-phase-roadmap-builder/scripts/validate_roadmap.py +32 -0
- phase_loop_runtime/skills_bundle/claude-plan-detailed/README.md +1 -0
- phase_loop_runtime/skills_bundle/claude-plan-detailed/SKILL.md +334 -0
- phase_loop_runtime/skills_bundle/claude-plan-detailed/assets/review_prompt.md +25 -0
- phase_loop_runtime/skills_bundle/claude-plan-phase/README.md +1 -0
- phase_loop_runtime/skills_bundle/claude-plan-phase/SKILL.md +776 -0
- phase_loop_runtime/skills_bundle/claude-plan-phase/assets/review_prompt.md +17 -0
- phase_loop_runtime/skills_bundle/claude-plan-phase/scripts/validate_plan_doc.py +1092 -0
- phase_loop_runtime/skills_bundle/claude-run-train/README.md +1 -0
- phase_loop_runtime/skills_bundle/claude-run-train/SKILL.md +75 -0
- phase_loop_runtime/skills_bundle/claude-skill-editor/README.md +1 -0
- phase_loop_runtime/skills_bundle/claude-skill-editor/SKILL.md +243 -0
- phase_loop_runtime/skills_bundle/claude-skill-editor/assets/editor_prompt.md +72 -0
- phase_loop_runtime/skills_bundle/claude-skill-improvement-planner/README.md +1 -0
- phase_loop_runtime/skills_bundle/claude-skill-improvement-planner/SKILL.md +249 -0
- phase_loop_runtime/skills_bundle/claude-skill-improvement-planner/assets/aggregator_prompt.md +79 -0
- phase_loop_runtime/skills_bundle/claude-task-contextualizer/README.md +1 -0
- phase_loop_runtime/skills_bundle/claude-task-contextualizer/SKILL.md +74 -0
- phase_loop_runtime/skills_bundle/claude-task-contextualizer/references/task-templates.md +103 -0
- phase_loop_runtime/skills_bundle/codex-advisor-board/README.md +1 -0
- phase_loop_runtime/skills_bundle/codex-advisor-board/SKILL.md +38 -0
- phase_loop_runtime/skills_bundle/codex-advisor-panel/README.md +1 -0
- phase_loop_runtime/skills_bundle/codex-advisor-panel/SKILL.md +38 -0
- phase_loop_runtime/skills_bundle/codex-execute-detailed/README.md +1 -0
- phase_loop_runtime/skills_bundle/codex-execute-detailed/SKILL.md +154 -0
- phase_loop_runtime/skills_bundle/codex-execute-phase/README.md +1 -0
- phase_loop_runtime/skills_bundle/codex-execute-phase/SKILL.md +231 -0
- phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/allocate_worktree_name.sh +33 -0
- phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/audit_lane_file_touches.py +197 -0
- phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/cleanup_lane_worktrees.sh +103 -0
- phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/parent_tree_leakage_check.sh +125 -0
- phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/post_merge_import_smoke.sh +62 -0
- phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/pre_merge_destructiveness_check.sh +127 -0
- phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/resolve_branch_from_sha.sh +51 -0
- phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/state.py +274 -0
- phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/sweep_stale_worktrees.sh +62 -0
- phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/team_teardown.sh +32 -0
- phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/validate_plan_doc.py +642 -0
- phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/verify_harness.sh +96 -0
- phase_loop_runtime/skills_bundle/codex-phase-loop/README.md +1 -0
- phase_loop_runtime/skills_bundle/codex-phase-loop/SKILL.md +248 -0
- phase_loop_runtime/skills_bundle/codex-phase-roadmap-builder/README.md +1 -0
- phase_loop_runtime/skills_bundle/codex-phase-roadmap-builder/SKILL.md +150 -0
- phase_loop_runtime/skills_bundle/codex-phase-roadmap-builder/assets/review_prompt.md +19 -0
- phase_loop_runtime/skills_bundle/codex-phase-roadmap-builder/references/parallelization-heuristics.md +111 -0
- phase_loop_runtime/skills_bundle/codex-phase-roadmap-builder/references/roadmap-template.md +193 -0
- phase_loop_runtime/skills_bundle/codex-phase-roadmap-builder/scripts/validate_roadmap.py +32 -0
- phase_loop_runtime/skills_bundle/codex-plan-detailed/README.md +1 -0
- phase_loop_runtime/skills_bundle/codex-plan-detailed/SKILL.md +119 -0
- phase_loop_runtime/skills_bundle/codex-plan-detailed/assets/review_prompt.md +25 -0
- phase_loop_runtime/skills_bundle/codex-plan-phase/README.md +1 -0
- phase_loop_runtime/skills_bundle/codex-plan-phase/SKILL.md +307 -0
- phase_loop_runtime/skills_bundle/codex-plan-phase/assets/review_prompt.md +17 -0
- phase_loop_runtime/skills_bundle/codex-plan-phase/scripts/validate_plan_doc.py +1092 -0
- phase_loop_runtime/skills_bundle/codex-run-train/README.md +1 -0
- phase_loop_runtime/skills_bundle/codex-run-train/SKILL.md +75 -0
- phase_loop_runtime/skills_bundle/codex-skill-editor/README.md +1 -0
- phase_loop_runtime/skills_bundle/codex-skill-editor/SKILL.md +94 -0
- phase_loop_runtime/skills_bundle/codex-skill-editor/assets/editor_prompt.md +72 -0
- phase_loop_runtime/skills_bundle/codex-skill-improvement-planner/README.md +1 -0
- phase_loop_runtime/skills_bundle/codex-skill-improvement-planner/SKILL.md +114 -0
- phase_loop_runtime/skills_bundle/codex-skill-improvement-planner/assets/aggregator_prompt.md +79 -0
- phase_loop_runtime/skills_bundle/codex-task-contextualizer/README.md +1 -0
- phase_loop_runtime/skills_bundle/codex-task-contextualizer/SKILL.md +102 -0
- phase_loop_runtime/skills_bundle/codex-task-contextualizer/references/task-templates.md +103 -0
- phase_loop_runtime/skills_bundle/gemini-advisor-board/README.md +1 -0
- phase_loop_runtime/skills_bundle/gemini-advisor-board/SKILL.md +38 -0
- phase_loop_runtime/skills_bundle/gemini-advisor-panel/README.md +1 -0
- phase_loop_runtime/skills_bundle/gemini-advisor-panel/SKILL.md +38 -0
- phase_loop_runtime/skills_bundle/gemini-execute-detailed/README.md +1 -0
- phase_loop_runtime/skills_bundle/gemini-execute-detailed/SKILL.md +144 -0
- phase_loop_runtime/skills_bundle/gemini-execute-phase/README.md +1 -0
- phase_loop_runtime/skills_bundle/gemini-execute-phase/SKILL.md +198 -0
- phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/allocate_worktree_name.sh +33 -0
- phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/audit_lane_file_touches.py +197 -0
- phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/cleanup_lane_worktrees.sh +103 -0
- phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/parent_tree_leakage_check.sh +125 -0
- phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/post_merge_import_smoke.sh +62 -0
- phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/pre_merge_destructiveness_check.sh +127 -0
- phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/resolve_branch_from_sha.sh +51 -0
- phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/state.py +274 -0
- phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/sweep_stale_worktrees.sh +62 -0
- phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/team_teardown.sh +32 -0
- phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/validate_plan_doc.py +642 -0
- phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/verify_harness.sh +96 -0
- phase_loop_runtime/skills_bundle/gemini-phase-loop/README.md +1 -0
- phase_loop_runtime/skills_bundle/gemini-phase-loop/SKILL.md +111 -0
- phase_loop_runtime/skills_bundle/gemini-phase-roadmap-builder/README.md +1 -0
- phase_loop_runtime/skills_bundle/gemini-phase-roadmap-builder/SKILL.md +147 -0
- phase_loop_runtime/skills_bundle/gemini-phase-roadmap-builder/assets/review_prompt.md +19 -0
- phase_loop_runtime/skills_bundle/gemini-phase-roadmap-builder/references/parallelization-heuristics.md +111 -0
- phase_loop_runtime/skills_bundle/gemini-phase-roadmap-builder/references/roadmap-template.md +193 -0
- phase_loop_runtime/skills_bundle/gemini-phase-roadmap-builder/scripts/validate_roadmap.py +32 -0
- phase_loop_runtime/skills_bundle/gemini-plan-detailed/README.md +1 -0
- phase_loop_runtime/skills_bundle/gemini-plan-detailed/SKILL.md +119 -0
- phase_loop_runtime/skills_bundle/gemini-plan-detailed/assets/review_prompt.md +25 -0
- phase_loop_runtime/skills_bundle/gemini-plan-phase/README.md +1 -0
- phase_loop_runtime/skills_bundle/gemini-plan-phase/SKILL.md +217 -0
- phase_loop_runtime/skills_bundle/gemini-plan-phase/assets/review_prompt.md +17 -0
- phase_loop_runtime/skills_bundle/gemini-plan-phase/scripts/validate_plan_doc.py +1092 -0
- phase_loop_runtime/skills_bundle/gemini-run-train/README.md +1 -0
- phase_loop_runtime/skills_bundle/gemini-run-train/SKILL.md +75 -0
- phase_loop_runtime/skills_bundle/gemini-skill-editor/README.md +1 -0
- phase_loop_runtime/skills_bundle/gemini-skill-editor/SKILL.md +87 -0
- phase_loop_runtime/skills_bundle/gemini-skill-editor/assets/editor_prompt.md +72 -0
- phase_loop_runtime/skills_bundle/gemini-skill-improvement-planner/README.md +1 -0
- phase_loop_runtime/skills_bundle/gemini-skill-improvement-planner/SKILL.md +109 -0
- phase_loop_runtime/skills_bundle/gemini-skill-improvement-planner/assets/aggregator_prompt.md +79 -0
- phase_loop_runtime/skills_bundle/gemini-task-contextualizer/README.md +1 -0
- phase_loop_runtime/skills_bundle/gemini-task-contextualizer/SKILL.md +98 -0
- phase_loop_runtime/skills_bundle/gemini-task-contextualizer/references/task-templates.md +103 -0
- phase_loop_runtime/skills_bundle/opencode-advisor-board/README.md +1 -0
- phase_loop_runtime/skills_bundle/opencode-advisor-board/SKILL.md +38 -0
- phase_loop_runtime/skills_bundle/opencode-advisor-panel/README.md +1 -0
- phase_loop_runtime/skills_bundle/opencode-advisor-panel/SKILL.md +38 -0
- phase_loop_runtime/skills_bundle/opencode-execute-detailed/README.md +1 -0
- phase_loop_runtime/skills_bundle/opencode-execute-detailed/SKILL.md +144 -0
- phase_loop_runtime/skills_bundle/opencode-execute-phase/README.md +1 -0
- phase_loop_runtime/skills_bundle/opencode-execute-phase/SKILL.md +196 -0
- phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/allocate_worktree_name.sh +33 -0
- phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/audit_lane_file_touches.py +197 -0
- phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/cleanup_lane_worktrees.sh +103 -0
- phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/parent_tree_leakage_check.sh +125 -0
- phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/post_merge_import_smoke.sh +62 -0
- phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/pre_merge_destructiveness_check.sh +127 -0
- phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/resolve_branch_from_sha.sh +51 -0
- phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/state.py +274 -0
- phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/sweep_stale_worktrees.sh +62 -0
- phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/team_teardown.sh +32 -0
- phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/validate_plan_doc.py +642 -0
- phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/verify_harness.sh +96 -0
- phase_loop_runtime/skills_bundle/opencode-phase-loop/README.md +1 -0
- phase_loop_runtime/skills_bundle/opencode-phase-loop/SKILL.md +113 -0
- phase_loop_runtime/skills_bundle/opencode-phase-roadmap-builder/README.md +1 -0
- phase_loop_runtime/skills_bundle/opencode-phase-roadmap-builder/SKILL.md +147 -0
- phase_loop_runtime/skills_bundle/opencode-phase-roadmap-builder/assets/review_prompt.md +19 -0
- phase_loop_runtime/skills_bundle/opencode-phase-roadmap-builder/references/parallelization-heuristics.md +111 -0
- phase_loop_runtime/skills_bundle/opencode-phase-roadmap-builder/references/roadmap-template.md +193 -0
- phase_loop_runtime/skills_bundle/opencode-phase-roadmap-builder/scripts/validate_roadmap.py +32 -0
- phase_loop_runtime/skills_bundle/opencode-plan-detailed/README.md +1 -0
- phase_loop_runtime/skills_bundle/opencode-plan-detailed/SKILL.md +119 -0
- phase_loop_runtime/skills_bundle/opencode-plan-detailed/assets/review_prompt.md +25 -0
- phase_loop_runtime/skills_bundle/opencode-plan-phase/README.md +1 -0
- phase_loop_runtime/skills_bundle/opencode-plan-phase/SKILL.md +211 -0
- phase_loop_runtime/skills_bundle/opencode-plan-phase/assets/review_prompt.md +17 -0
- phase_loop_runtime/skills_bundle/opencode-plan-phase/scripts/validate_plan_doc.py +1092 -0
- phase_loop_runtime/skills_bundle/opencode-run-train/README.md +1 -0
- phase_loop_runtime/skills_bundle/opencode-run-train/SKILL.md +75 -0
- phase_loop_runtime/skills_bundle/opencode-skill-editor/README.md +1 -0
- phase_loop_runtime/skills_bundle/opencode-skill-editor/SKILL.md +87 -0
- phase_loop_runtime/skills_bundle/opencode-skill-editor/assets/editor_prompt.md +72 -0
- phase_loop_runtime/skills_bundle/opencode-skill-improvement-planner/README.md +1 -0
- phase_loop_runtime/skills_bundle/opencode-skill-improvement-planner/SKILL.md +109 -0
- phase_loop_runtime/skills_bundle/opencode-skill-improvement-planner/assets/aggregator_prompt.md +79 -0
- phase_loop_runtime/skills_bundle/opencode-task-contextualizer/README.md +1 -0
- phase_loop_runtime/skills_bundle/opencode-task-contextualizer/SKILL.md +98 -0
- phase_loop_runtime/skills_bundle/opencode-task-contextualizer/references/task-templates.md +103 -0
- phase_loop_runtime/state.py +156 -0
- phase_loop_runtime/state_degradation.py +136 -0
- phase_loop_runtime/state_ops.py +676 -0
- phase_loop_runtime/train_ledger.py +215 -0
- phase_loop_runtime/train_roadmap.py +339 -0
- phase_loop_runtime/train_runner.py +1197 -0
- phase_loop_runtime/verification_evidence.py +650 -0
- phase_loop_runtime/verification_evidence_validator.py +75 -0
- phase_loop_runtime/visual_evidence_validator.py +36 -0
- phase_loop_runtime/worker_pool.py +123 -0
- phase_loop_runtime/worktree_index.py +270 -0
- phase_loop_runtime-0.2.0.data/data/share/phase-loop-runtime/protocol/protocol.md +2475 -0
- phase_loop_runtime-0.2.0.dist-info/METADATA +106 -0
- phase_loop_runtime-0.2.0.dist-info/RECORD +370 -0
- phase_loop_runtime-0.2.0.dist-info/WHEEL +5 -0
- phase_loop_runtime-0.2.0.dist-info/entry_points.txt +9 -0
- 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
|