loopx 0.4.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- loopx/__init__.py +5 -0
- loopx/agent_onboarding.py +654 -0
- loopx/agent_registry.py +112 -0
- loopx/ark_managed_agent_host.py +59 -0
- loopx/authority.py +805 -0
- loopx/benchmark.py +2875 -0
- loopx/benchmark_adapters/__init__.py +1 -0
- loopx/benchmark_adapters/agentissue.py +2644 -0
- loopx/benchmark_adapters/agents_last_exam.py +3998 -0
- loopx/benchmark_adapters/edgebench.py +322 -0
- loopx/benchmark_adapters/skillsbench.py +5978 -0
- loopx/benchmark_adapters/skillsbench_acp_failure_policy.py +143 -0
- loopx/benchmark_adapters/skillsbench_acp_process.py +31 -0
- loopx/benchmark_adapters/skillsbench_acp_relay.py +4832 -0
- loopx/benchmark_adapters/skillsbench_batch.py +124 -0
- loopx/benchmark_adapters/skillsbench_bridge_guard.py +209 -0
- loopx/benchmark_adapters/skillsbench_bridge_summary.py +203 -0
- loopx/benchmark_adapters/skillsbench_codex_goal_recovery.py +271 -0
- loopx/benchmark_adapters/skillsbench_codex_goal_trace.py +81 -0
- loopx/benchmark_adapters/skillsbench_codex_runtime.py +339 -0
- loopx/benchmark_adapters/skillsbench_dockerfile_runtime.py +467 -0
- loopx/benchmark_adapters/skillsbench_failure_signals.py +652 -0
- loopx/benchmark_adapters/skillsbench_proxy_runtime.py +327 -0
- loopx/benchmark_adapters/skillsbench_remote_bridge.py +402 -0
- loopx/benchmark_adapters/skillsbench_result_discovery.py +143 -0
- loopx/benchmark_adapters/skillsbench_runner_profile.py +436 -0
- loopx/benchmark_adapters/skillsbench_runner_source.py +99 -0
- loopx/benchmark_adapters/skillsbench_setup_preflight.py +771 -0
- loopx/benchmark_adapters/skillsbench_signals.py +15 -0
- loopx/benchmark_adapters/skillsbench_task_source.py +141 -0
- loopx/benchmark_adapters/skillsbench_turn_route.py +723 -0
- loopx/benchmark_adapters/skillsbench_turn_runtime.py +1069 -0
- loopx/benchmark_adapters/skillsbench_typed_repair.py +689 -0
- loopx/benchmark_adapters/skillsbench_uv_cache.py +111 -0
- loopx/benchmark_adapters/skillsbench_verifier_bootstrap.py +227 -0
- loopx/benchmark_adapters/skillsbench_verifier_cache.py +138 -0
- loopx/benchmark_adapters/terminal_bench.py +10078 -0
- loopx/benchmark_case_analysis.py +1276 -0
- loopx/benchmark_case_state.py +1079 -0
- loopx/benchmark_core/__init__.py +239 -0
- loopx/benchmark_core/adapter.py +84 -0
- loopx/benchmark_core/artifacts.py +517 -0
- loopx/benchmark_core/attempts.py +199 -0
- loopx/benchmark_core/container_exec.py +216 -0
- loopx/benchmark_core/io.py +68 -0
- loopx/benchmark_core/lifecycle.py +211 -0
- loopx/benchmark_core/loop_protocol.py +689 -0
- loopx/benchmark_core/observable_handles.py +348 -0
- loopx/benchmark_core/parity.py +256 -0
- loopx/benchmark_core/remote_closeout.py +482 -0
- loopx/benchmark_core/rounds.py +215 -0
- loopx/benchmark_core/route_profile.py +509 -0
- loopx/benchmark_core/run_permissions.py +206 -0
- loopx/benchmark_core/split_control.py +925 -0
- loopx/benchmark_core/turn_fidelity.py +326 -0
- loopx/benchmark_ledger.py +3793 -0
- loopx/benchmark_ledger_countability.py +372 -0
- loopx/benchmark_ledger_current.py +724 -0
- loopx/benchmark_trajectory.py +405 -0
- loopx/benchmarks/__init__.py +1 -0
- loopx/benchmarks/qualification/__init__.py +1 -0
- loopx/benchmarks/qualification/release_outcome_baseline.py +360 -0
- loopx/benchmarks/read_models/__init__.py +1 -0
- loopx/benchmarks/read_models/benchmark_attempt_accounting.py +53 -0
- loopx/benchmarks/read_models/benchmark_comparison.py +414 -0
- loopx/benchmarks/read_models/benchmark_event_timeline.py +113 -0
- loopx/benchmarks/read_models/benchmark_experiment_report.py +475 -0
- loopx/benchmarks/read_models/benchmark_learning_ledger.py +137 -0
- loopx/benchmarks/read_models/benchmark_lifecycle_contracts.py +228 -0
- loopx/benchmarks/read_models/benchmark_projection.py +723 -0
- loopx/benchmarks/read_models/benchmark_result.py +146 -0
- loopx/benchmarks/read_models/benchmark_run_execution_contract.py +116 -0
- loopx/benchmarks/read_models/benchmark_run_failure.py +157 -0
- loopx/benchmarks/read_models/benchmark_run_metrics.py +213 -0
- loopx/benchmarks/read_models/benchmark_run_post_execution.py +635 -0
- loopx/benchmarks/read_models/benchmark_run_pre_execution.py +541 -0
- loopx/benchmarks/read_models/benchmark_status_compaction.py +1255 -0
- loopx/benchmarks/read_models/benchmark_status_runner.py +780 -0
- loopx/benchmarks/read_models/goal_start_control_score.py +857 -0
- loopx/benchmarks/read_models/skillsbench_post_run_debug.py +746 -0
- loopx/benchmarks/read_models/skillsbench_verifier_attribution.py +269 -0
- loopx/bootstrap.py +1116 -0
- loopx/bootstrap_command_pack.py +2167 -0
- loopx/boundary_authority.py +199 -0
- loopx/canary/__init__.py +1 -0
- loopx/canary/maintainability_ratchet.py +800 -0
- loopx/canary/planner.py +1984 -0
- loopx/canary/premerge.py +1130 -0
- loopx/canary/qualification_profiles.py +309 -0
- loopx/canary/quality_surface_catalog.py +838 -0
- loopx/canary/release_profiles.py +51 -0
- loopx/canary/runner.py +1107 -0
- loopx/canary/smoke_health.py +581 -0
- loopx/canary/smoke_profiles.py +212 -0
- loopx/capabilities/__init__.py +0 -0
- loopx/capabilities/agent_turn_recall/__init__.py +17 -0
- loopx/capabilities/agent_turn_recall/cli.py +369 -0
- loopx/capabilities/agent_turn_recall/core.py +296 -0
- loopx/capabilities/auto_research/__init__.py +16 -0
- loopx/capabilities/auto_research/bootstrap_contract.py +157 -0
- loopx/capabilities/auto_research/cli.py +1468 -0
- loopx/capabilities/auto_research/core.py +11 -0
- loopx/capabilities/auto_research/defaults.py +79 -0
- loopx/capabilities/auto_research/demo_e2e.py +1848 -0
- loopx/capabilities/auto_research/demo_supervisor.py +186 -0
- loopx/capabilities/auto_research/evidence_packet.py +767 -0
- loopx/capabilities/auto_research/human_view.py +794 -0
- loopx/capabilities/auto_research/kernel.py +191 -0
- loopx/capabilities/auto_research/knn_demo_workspace.py +322 -0
- loopx/capabilities/auto_research/live_evidence.py +248 -0
- loopx/capabilities/auto_research/preset.py +176 -0
- loopx/capabilities/auto_research/research_state.py +1085 -0
- loopx/capabilities/auto_research/role_profiles.py +394 -0
- loopx/capabilities/auto_research/rollout_append.py +97 -0
- loopx/capabilities/auto_research/terminal_result_contract.py +422 -0
- loopx/capabilities/auto_research/terminal_result_projection.py +171 -0
- loopx/capabilities/auto_research/terminal_result_query.py +233 -0
- loopx/capabilities/auto_research/terminal_results.py +349 -0
- loopx/capabilities/auto_research/user_contract.py +190 -0
- loopx/capabilities/auto_research/worker_loop.py +163 -0
- loopx/capabilities/auto_research/worker_runtime.py +777 -0
- loopx/capabilities/auto_research/worker_skill/SKILL.md +343 -0
- loopx/capabilities/benchmark_toolkit/__init__.py +19 -0
- loopx/capabilities/benchmark_toolkit/integrity.py +387 -0
- loopx/capabilities/catalog.py +1875 -0
- loopx/capabilities/change_quality/__init__.py +19 -0
- loopx/capabilities/change_quality/cli.py +171 -0
- loopx/capabilities/change_quality/context.py +156 -0
- loopx/capabilities/change_quality/oracles.py +269 -0
- loopx/capabilities/change_quality/policy.py +34 -0
- loopx/capabilities/change_quality/receipt.py +482 -0
- loopx/capabilities/change_quality/result.py +493 -0
- loopx/capabilities/change_quality/scope.py +171 -0
- loopx/capabilities/change_quality/shadow.py +680 -0
- loopx/capabilities/content_ops/__init__.py +0 -0
- loopx/capabilities/content_ops/cli.py +649 -0
- loopx/capabilities/content_ops/connector_packets.py +164 -0
- loopx/capabilities/content_ops/item_lifecycle.py +1000 -0
- loopx/capabilities/content_ops/layout.py +451 -0
- loopx/capabilities/content_ops/markdown.py +456 -0
- loopx/capabilities/content_ops/schemas.py +51 -0
- loopx/capabilities/content_ops/social_browser_x.py +107 -0
- loopx/capabilities/content_ops/surface.py +1956 -0
- loopx/capabilities/content_ops/templates/layout-catalog-v0.json +72 -0
- loopx/capabilities/context_providers/__init__.py +36 -0
- loopx/capabilities/context_providers/base.py +189 -0
- loopx/capabilities/context_providers/factory.py +32 -0
- loopx/capabilities/context_providers/openviking.py +702 -0
- loopx/capabilities/context_providers/service_ownership.py +185 -0
- loopx/capabilities/decision_context/__init__.py +129 -0
- loopx/capabilities/decision_context/architecture.py +83 -0
- loopx/capabilities/decision_context/assembler.py +849 -0
- loopx/capabilities/decision_context/catalog_entry.py +195 -0
- loopx/capabilities/decision_context/cli.py +310 -0
- loopx/capabilities/decision_context/cursor_commit.py +535 -0
- loopx/capabilities/decision_context/outcome_feedback.py +352 -0
- loopx/capabilities/decision_context/packets.py +654 -0
- loopx/capabilities/decision_context/private_state.py +189 -0
- loopx/capabilities/decision_context/profile.py +453 -0
- loopx/capabilities/decision_context/providers.py +228 -0
- loopx/capabilities/decision_context/review_settlement.py +136 -0
- loopx/capabilities/decision_context/runtime.py +273 -0
- loopx/capabilities/decision_context/sources.py +415 -0
- loopx/capabilities/explore/__init__.py +1 -0
- loopx/capabilities/explore/activation.py +198 -0
- loopx/capabilities/explore/adaptive_replay_planner.py +221 -0
- loopx/capabilities/explore/child_replay_runtime.py +463 -0
- loopx/capabilities/explore/composition_frontier.py +291 -0
- loopx/capabilities/explore/counterfactual_runtime.py +578 -0
- loopx/capabilities/explore/episode_runtime.py +647 -0
- loopx/capabilities/explore/harness_checkpoint.py +171 -0
- loopx/capabilities/explore/harness_gate.py +115 -0
- loopx/capabilities/explore/harness_runtime.py +1124 -0
- loopx/capabilities/explore/replay_metrics.py +206 -0
- loopx/capabilities/explore/replay_runtime.py +1271 -0
- loopx/capabilities/explore/resource_portfolio.py +173 -0
- loopx/capabilities/explore/result_log.py +974 -0
- loopx/capabilities/explore/router_state.py +432 -0
- loopx/capabilities/explore/source_history_reconcile.py +255 -0
- loopx/capabilities/explore/speculative_scheduler.py +498 -0
- loopx/capabilities/explore/todo_branch_plan.py +650 -0
- loopx/capabilities/explore/todo_evidence.py +141 -0
- loopx/capabilities/explore/trace_runtime.py +284 -0
- loopx/capabilities/explore/worker_branch_plan.py +1257 -0
- loopx/capabilities/integration_branch/__init__.py +13 -0
- loopx/capabilities/integration_branch/cli.py +148 -0
- loopx/capabilities/integration_branch/core.py +916 -0
- loopx/capabilities/issue_fix/__init__.py +19 -0
- loopx/capabilities/issue_fix/acceptance_loop.py +1050 -0
- loopx/capabilities/issue_fix/candidate_evidence.py +503 -0
- loopx/capabilities/issue_fix/candidate_preflight.py +676 -0
- loopx/capabilities/issue_fix/cli.py +1822 -0
- loopx/capabilities/issue_fix/cli_input.py +87 -0
- loopx/capabilities/issue_fix/content_ops_cli.py +148 -0
- loopx/capabilities/issue_fix/discovered_issue_promotion.py +947 -0
- loopx/capabilities/issue_fix/explore_projection.py +710 -0
- loopx/capabilities/issue_fix/feasibility.py +542 -0
- loopx/capabilities/issue_fix/github_public.py +661 -0
- loopx/capabilities/issue_fix/intake_surface.py +832 -0
- loopx/capabilities/issue_fix/metadata_preview.py +218 -0
- loopx/capabilities/issue_fix/metrics_projection.py +1340 -0
- loopx/capabilities/issue_fix/metrics_supplement.py +634 -0
- loopx/capabilities/issue_fix/metrics_supplement_cli.py +127 -0
- loopx/capabilities/issue_fix/outcome_projection.py +1235 -0
- loopx/capabilities/issue_fix/periodic_report.py +189 -0
- loopx/capabilities/issue_fix/pr_description.py +418 -0
- loopx/capabilities/issue_fix/pr_gate_reconcile.py +496 -0
- loopx/capabilities/issue_fix/pr_gate_reconcile_cli.py +464 -0
- loopx/capabilities/issue_fix/pr_lifecycle.py +1327 -0
- loopx/capabilities/issue_fix/pr_lifecycle_rollout.py +85 -0
- loopx/capabilities/issue_fix/pr_monitor_materialization.py +257 -0
- loopx/capabilities/issue_fix/pr_review_ack.py +439 -0
- loopx/capabilities/issue_fix/provider_hooks.py +24 -0
- loopx/capabilities/issue_fix/repository_commit_evidence.py +186 -0
- loopx/capabilities/issue_fix/repository_context.py +457 -0
- loopx/capabilities/issue_fix/repository_memory.py +459 -0
- loopx/capabilities/issue_fix/repository_memory_provider.py +1454 -0
- loopx/capabilities/issue_fix/repository_snapshot.py +454 -0
- loopx/capabilities/issue_fix/reviewer_cli.py +917 -0
- loopx/capabilities/issue_fix/reviewer_notification.py +882 -0
- loopx/capabilities/issue_fix/reviewer_notification_drain.py +942 -0
- loopx/capabilities/issue_fix/reviewer_recommendation.py +1057 -0
- loopx/capabilities/issue_fix/reviewer_request.py +1282 -0
- loopx/capabilities/issue_fix/reward_memory.py +879 -0
- loopx/capabilities/issue_fix/workflow_plan.py +1286 -0
- loopx/capabilities/material_lifecycle/__init__.py +161 -0
- loopx/capabilities/material_lifecycle/_validation.py +183 -0
- loopx/capabilities/material_lifecycle/apply.py +672 -0
- loopx/capabilities/material_lifecycle/architecture.py +122 -0
- loopx/capabilities/material_lifecycle/cli.py +161 -0
- loopx/capabilities/material_lifecycle/decision_planning.py +470 -0
- loopx/capabilities/material_lifecycle/explore_execution.py +306 -0
- loopx/capabilities/material_lifecycle/intake.py +869 -0
- loopx/capabilities/material_lifecycle/inventory.py +147 -0
- loopx/capabilities/material_lifecycle/lifecycle.py +98 -0
- loopx/capabilities/material_lifecycle/preparation.py +147 -0
- loopx/capabilities/material_lifecycle/project_skill.py +83 -0
- loopx/capabilities/material_lifecycle/ranking.py +267 -0
- loopx/capabilities/material_lifecycle/readable_projection.py +500 -0
- loopx/capabilities/material_lifecycle/rebuild.py +480 -0
- loopx/capabilities/material_lifecycle/settlement.py +238 -0
- loopx/capabilities/periodic_report/__init__.py +71 -0
- loopx/capabilities/periodic_report/adapters.py +939 -0
- loopx/capabilities/periodic_report/archive.py +422 -0
- loopx/capabilities/periodic_report/bindings.py +705 -0
- loopx/capabilities/periodic_report/cli.py +277 -0
- loopx/capabilities/periodic_report/core.py +691 -0
- loopx/capabilities/periodic_report/extension_envelope.py +66 -0
- loopx/capabilities/periodic_report/presets.py +103 -0
- loopx/capabilities/periodic_report/profile.py +235 -0
- loopx/capabilities/periodic_report/project_progress.py +179 -0
- loopx/capabilities/periodic_report/triggers.py +452 -0
- loopx/capabilities/pr_review_queue/__init__.py +17 -0
- loopx/capabilities/pr_review_queue/core.py +506 -0
- loopx/capabilities/pr_review_queue/review_contract.py +506 -0
- loopx/capabilities/registry.py +192 -0
- loopx/capabilities/reward_memory/__init__.py +75 -0
- loopx/capabilities/reward_memory/application.py +819 -0
- loopx/capabilities/reward_memory/architecture.py +572 -0
- loopx/capabilities/reward_memory/candidate_review.py +511 -0
- loopx/capabilities/reward_memory/cli.py +469 -0
- loopx/capabilities/reward_memory/dogfood.py +574 -0
- loopx/capabilities/reward_memory/evaluation.py +296 -0
- loopx/capabilities/reward_memory/evaluation_fixtures.py +362 -0
- loopx/capabilities/reward_memory/experiment.py +567 -0
- loopx/capabilities/reward_memory/health.py +222 -0
- loopx/capabilities/reward_memory/ingestion.py +519 -0
- loopx/capabilities/reward_memory/registry.py +600 -0
- loopx/capabilities/reward_memory/runtime_hooks.py +312 -0
- loopx/capabilities/reward_memory/scoped_feedback.py +173 -0
- loopx/capabilities/semantic_preference/__init__.py +12 -0
- loopx/capabilities/semantic_preference/cli.py +189 -0
- loopx/capabilities/semantic_preference/contract.py +592 -0
- loopx/capabilities/semantic_preference/reward_memory.py +62 -0
- loopx/capabilities/value_connectors/__init__.py +1 -0
- loopx/capabilities/value_connectors/cli.py +401 -0
- loopx/capabilities/value_connectors/finance_extension_migration.py +108 -0
- loopx/capabilities/value_connectors/install_check.py +147 -0
- loopx/capabilities/value_connectors/planner.py +733 -0
- loopx/capabilities/value_connectors/source_map.py +446 -0
- loopx/claude_goal_baseline.py +138 -0
- loopx/claude_goal_mode/__init__.py +23 -0
- loopx/claude_goal_mode/hooks/goal_policy.py +212 -0
- loopx/claude_goal_mode/hooks/goal_state.py +139 -0
- loopx/claude_goal_mode/mcp/loopx_mcp.py +167 -0
- loopx/claude_goal_mode/scripts/connect.py +103 -0
- loopx/claude_goal_mode/scripts/goalmode_cmd.py +241 -0
- loopx/claude_goal_mode/scripts/install.py +328 -0
- loopx/claude_goal_mode/statusline/goal_status.py +97 -0
- loopx/cli.py +836 -0
- loopx/cli_commands/__init__.py +334 -0
- loopx/cli_commands/_host_thread.py +13 -0
- loopx/cli_commands/agentissue_runner_flow.py +447 -0
- loopx/cli_commands/agents_last_exam.py +160 -0
- loopx/cli_commands/agents_last_exam_baked_input.py +302 -0
- loopx/cli_commands/agents_last_exam_host_codex.py +374 -0
- loopx/cli_commands/agents_last_exam_launch_dry_run.py +372 -0
- loopx/cli_commands/agents_last_exam_local_plan.py +322 -0
- loopx/cli_commands/agents_last_exam_runner_source.py +352 -0
- loopx/cli_commands/agents_last_exam_task_material.py +335 -0
- loopx/cli_commands/agents_last_exam_validation_gate.py +236 -0
- loopx/cli_commands/benchmark_boundary.py +499 -0
- loopx/cli_commands/benchmark_dispatch.py +161 -0
- loopx/cli_commands/benchmark_release_outcome.py +123 -0
- loopx/cli_commands/benchmark_review_lifecycle.py +1275 -0
- loopx/cli_commands/benchmark_run_ledger.py +763 -0
- loopx/cli_commands/benchmark_run_ledger_case_analysis.py +249 -0
- loopx/cli_commands/benchmark_run_ledger_classification.py +45 -0
- loopx/cli_commands/benchmark_run_ledger_maintenance.py +486 -0
- loopx/cli_commands/benchmark_run_ledger_maintenance_registration.py +342 -0
- loopx/cli_commands/benchmark_run_ledger_maintenance_rendering.py +233 -0
- loopx/cli_commands/benchmark_run_ledger_parity.py +92 -0
- loopx/cli_commands/bootstrap_connect.py +238 -0
- loopx/cli_commands/canary.py +707 -0
- loopx/cli_commands/canary_release_qualification.py +79 -0
- loopx/cli_commands/capability.py +96 -0
- loopx/cli_commands/doctor.py +43 -0
- loopx/cli_commands/dreaming.py +143 -0
- loopx/cli_commands/edgebench.py +205 -0
- loopx/cli_commands/evidence_log.py +275 -0
- loopx/cli_commands/explore.py +989 -0
- loopx/cli_commands/explore_planning_commands.py +157 -0
- loopx/cli_commands/extension.py +271 -0
- loopx/cli_commands/first_run_report.py +73 -0
- loopx/cli_commands/goal_channel.py +656 -0
- loopx/cli_commands/handoff_mode.py +158 -0
- loopx/cli_commands/history.py +622 -0
- loopx/cli_commands/host_mode_plan.py +113 -0
- loopx/cli_commands/lark_inbox.py +431 -0
- loopx/cli_commands/lark_kanban.py +629 -0
- loopx/cli_commands/ml_experiment.py +321 -0
- loopx/cli_commands/multi_agent.py +211 -0
- loopx/cli_commands/opencode2_goal_worker.py +217 -0
- loopx/cli_commands/pr_review.py +167 -0
- loopx/cli_commands/presentation.py +218 -0
- loopx/cli_commands/preset.py +96 -0
- loopx/cli_commands/project.py +150 -0
- loopx/cli_commands/project_lifecycle.py +915 -0
- loopx/cli_commands/quota.py +859 -0
- loopx/cli_commands/quota_registration.py +241 -0
- loopx/cli_commands/quota_request.py +113 -0
- loopx/cli_commands/ready_score.py +110 -0
- loopx/cli_commands/registry_admin.py +975 -0
- loopx/cli_commands/registry_admin_configure.py +344 -0
- loopx/cli_commands/registry_admin_peer.py +84 -0
- loopx/cli_commands/registry_authority.py +218 -0
- loopx/cli_commands/review_batch.py +146 -0
- loopx/cli_commands/slash_commands.py +145 -0
- loopx/cli_commands/start_goal.py +251 -0
- loopx/cli_commands/starter.py +175 -0
- loopx/cli_commands/starter_bootstrap.py +179 -0
- loopx/cli_commands/starter_bootstrap_registration.py +198 -0
- loopx/cli_commands/starter_runtime_idle.py +107 -0
- loopx/cli_commands/starter_scheduler.py +207 -0
- loopx/cli_commands/starter_session_runtime.py +152 -0
- loopx/cli_commands/starter_visible_common.py +54 -0
- loopx/cli_commands/starter_visible_driver.py +161 -0
- loopx/cli_commands/starter_visible_pilot.py +278 -0
- loopx/cli_commands/status.py +867 -0
- loopx/cli_commands/status_registration.py +239 -0
- loopx/cli_commands/summary_all.py +222 -0
- loopx/cli_commands/support_control.py +809 -0
- loopx/cli_commands/support_control_registry.py +68 -0
- loopx/cli_commands/support_control_supervisor.py +289 -0
- loopx/cli_commands/task_lease.py +306 -0
- loopx/cli_commands/terminal_bench_adapter.py +717 -0
- loopx/cli_commands/terminal_bench_environment_result.py +1246 -0
- loopx/cli_commands/todo.py +940 -0
- loopx/cli_commands/todo_argument_validation.py +572 -0
- loopx/cli_commands/todo_event.py +114 -0
- loopx/cli_commands/turn.py +804 -0
- loopx/cli_commands/version.py +46 -0
- loopx/cli_commands/worker_bridge.py +659 -0
- loopx/cli_rollout.py +314 -0
- loopx/codex_cli_goal_tui.py +672 -0
- loopx/codex_cli_probe.py +1530 -0
- loopx/codex_cli_probe_markdown.py +935 -0
- loopx/codex_cli_runtime_probe.py +733 -0
- loopx/codex_cli_scheduler.py +564 -0
- loopx/codex_goal_baseline.py +620 -0
- loopx/configuration_catalog.py +617 -0
- loopx/configure_goal.py +1375 -0
- loopx/contract.py +996 -0
- loopx/control_plane/__init__.py +71 -0
- loopx/control_plane/agents/__init__.py +1 -0
- loopx/control_plane/agents/agent_lane_recommendation.py +516 -0
- loopx/control_plane/agents/agent_scope.py +1578 -0
- loopx/control_plane/agents/agent_scope_frontier.py +60 -0
- loopx/control_plane/agents/capability_gate.py +531 -0
- loopx/control_plane/agents/identity.py +140 -0
- loopx/control_plane/agents/legacy_migration.py +169 -0
- loopx/control_plane/agents/management_projection.py +658 -0
- loopx/control_plane/agents/material_frontier.py +608 -0
- loopx/control_plane/agents/material_handoff.py +156 -0
- loopx/control_plane/agents/multi_agent/__init__.py +1 -0
- loopx/control_plane/agents/multi_agent/codex_executable.py +207 -0
- loopx/control_plane/agents/multi_agent/collective_round_ledger.py +387 -0
- loopx/control_plane/agents/multi_agent/contract.py +474 -0
- loopx/control_plane/agents/multi_agent/recipe.py +110 -0
- loopx/control_plane/agents/multi_agent/role_successor.py +297 -0
- loopx/control_plane/agents/multi_agent/runtime_scripts.py +426 -0
- loopx/control_plane/agents/multi_agent/visible_launch_policy.py +149 -0
- loopx/control_plane/agents/multi_agent/visible_wake_scheduler.py +392 -0
- loopx/control_plane/agents/profile.py +216 -0
- loopx/control_plane/agents/runtime_model.py +73 -0
- loopx/control_plane/agents/subagent_activity.py +164 -0
- loopx/control_plane/agents/supervisor.py +544 -0
- loopx/control_plane/agents/supervisor_events.py +462 -0
- loopx/control_plane/agents/supervisor_inject.py +204 -0
- loopx/control_plane/agents/work_mode.py +56 -0
- loopx/control_plane/agents/workspace_guard.py +364 -0
- loopx/control_plane/effect_program.py +644 -0
- loopx/control_plane/goals/__init__.py +1 -0
- loopx/control_plane/goals/active_state_event_projection.py +103 -0
- loopx/control_plane/goals/active_state_metadata.py +47 -0
- loopx/control_plane/goals/active_state_sections.py +58 -0
- loopx/control_plane/goals/configure_goal_service.py +354 -0
- loopx/control_plane/goals/contract_health.py +132 -0
- loopx/control_plane/goals/dreaming.py +152 -0
- loopx/control_plane/goals/global_registry_health.py +199 -0
- loopx/control_plane/goals/global_registry_shadow.py +33 -0
- loopx/control_plane/goals/goal_channel.py +34 -0
- loopx/control_plane/goals/goal_channel_projection.py +560 -0
- loopx/control_plane/goals/goal_frontier/__init__.py +1917 -0
- loopx/control_plane/goals/goal_frontier/ack_policy.py +149 -0
- loopx/control_plane/goals/goal_frontier/outcome_continuity.py +437 -0
- loopx/control_plane/goals/goal_frontier/replan_rules.py +210 -0
- loopx/control_plane/goals/goal_frontier/semantic_history.py +314 -0
- loopx/control_plane/goals/goal_frontier/terminal.py +180 -0
- loopx/control_plane/goals/goal_vision.py +443 -0
- loopx/control_plane/goals/goal_vision_policy.py +36 -0
- loopx/control_plane/goals/goal_vision_state.py +62 -0
- loopx/control_plane/goals/goal_vision_wait.py +290 -0
- loopx/control_plane/goals/path_resolution.py +20 -0
- loopx/control_plane/goals/start_contract.py +206 -0
- loopx/control_plane/goals/vision_checkpoint.py +92 -0
- loopx/control_plane/handoff/__init__.py +1 -0
- loopx/control_plane/handoff/cross_runtime_impl_review.py +311 -0
- loopx/control_plane/handoff/delivery_contract.py +161 -0
- loopx/control_plane/handoff/handoff_runs.py +71 -0
- loopx/control_plane/handoff/project_handoff.py +155 -0
- loopx/control_plane/handoff/review_batch.py +463 -0
- loopx/control_plane/handoff/review_packet_context.py +216 -0
- loopx/control_plane/heartbeat/agent.py +173 -0
- loopx/control_plane/heartbeat/budget.py +66 -0
- loopx/control_plane/heartbeat/builder.py +501 -0
- loopx/control_plane/heartbeat/host.py +64 -0
- loopx/control_plane/heartbeat/rules.py +68 -0
- loopx/control_plane/heartbeat/task_body.py +759 -0
- loopx/control_plane/heartbeat/visible_goal.py +86 -0
- loopx/control_plane/projects/__init__.py +1 -0
- loopx/control_plane/projects/contract.py +25 -0
- loopx/control_plane/projects/registry.py +663 -0
- loopx/control_plane/quota/__init__.py +1 -0
- loopx/control_plane/quota/cli_projection.py +704 -0
- loopx/control_plane/quota/decision_summary.py +431 -0
- loopx/control_plane/quota/effect_program.py +152 -0
- loopx/control_plane/quota/error_codes.py +19 -0
- loopx/control_plane/quota/goal_boundary.py +464 -0
- loopx/control_plane/quota/heartbeat_receipt.py +277 -0
- loopx/control_plane/quota/heartbeat_recommendation.py +718 -0
- loopx/control_plane/quota/host_poll_receipts.py +162 -0
- loopx/control_plane/quota/live_decision.py +142 -0
- loopx/control_plane/quota/monitor_poll.py +786 -0
- loopx/control_plane/quota/policy_constants.py +40 -0
- loopx/control_plane/quota/projection_repair.py +262 -0
- loopx/control_plane/quota/recent_runs.py +210 -0
- loopx/control_plane/quota/scheduler_ack.py +490 -0
- loopx/control_plane/quota/selected_todo_projection.py +139 -0
- loopx/control_plane/quota/settlement.py +437 -0
- loopx/control_plane/quota/settlement_cli.py +246 -0
- loopx/control_plane/quota/settlement_validation.py +64 -0
- loopx/control_plane/quota/settlement_workspace_causality.py +180 -0
- loopx/control_plane/quota/should_run.py +249 -0
- loopx/control_plane/quota/should_run_packet.py +1165 -0
- loopx/control_plane/quota/should_run_prepare.py +675 -0
- loopx/control_plane/quota/slot_accounting.py +1123 -0
- loopx/control_plane/quota/spend_sources.py +11 -0
- loopx/control_plane/quota/stall_repair.py +397 -0
- loopx/control_plane/quota/states.py +29 -0
- loopx/control_plane/quota/task_orchestration.py +448 -0
- loopx/control_plane/quota/task_orchestration_admission.py +497 -0
- loopx/control_plane/quota/turn_envelope.py +889 -0
- loopx/control_plane/quota/usage_summary.py +140 -0
- loopx/control_plane/reward_memory.py +43 -0
- loopx/control_plane/runtime/__init__.py +2 -0
- loopx/control_plane/runtime/active_user_assisted_pilot.py +275 -0
- loopx/control_plane/runtime/agent_scoped_evidence_log.py +435 -0
- loopx/control_plane/runtime/decision_freshness.py +203 -0
- loopx/control_plane/runtime/event_ledger.py +197 -0
- loopx/control_plane/runtime/event_store_migration_bridge.py +196 -0
- loopx/control_plane/runtime/goal_project_route.py +70 -0
- loopx/control_plane/runtime/local_state_write_correctness.py +242 -0
- loopx/control_plane/runtime/promotion_readiness.py +152 -0
- loopx/control_plane/runtime/public_safety.py +120 -0
- loopx/control_plane/runtime/run_artifacts.py +78 -0
- loopx/control_plane/runtime/run_compaction.py +397 -0
- loopx/control_plane/runtime/run_context_retention.py +241 -0
- loopx/control_plane/runtime/run_history.py +132 -0
- loopx/control_plane/runtime/run_index_duplicates.py +205 -0
- loopx/control_plane/runtime/run_index_rebuild.py +263 -0
- loopx/control_plane/runtime/run_ingest_health.py +336 -0
- loopx/control_plane/runtime/runtime_projection_route.py +624 -0
- loopx/control_plane/runtime/runtime_projection_writer.py +98 -0
- loopx/control_plane/runtime/session_runtime.py +339 -0
- loopx/control_plane/runtime/shared_runtime_material_projection.py +332 -0
- loopx/control_plane/runtime/shared_runtime_refresh_projection.py +183 -0
- loopx/control_plane/runtime/stale_latest_run.py +90 -0
- loopx/control_plane/runtime/status_classifications.py +49 -0
- loopx/control_plane/runtime/status_projection_cache.py +235 -0
- loopx/control_plane/runtime/stride_observation.py +144 -0
- loopx/control_plane/runtime/time.py +39 -0
- loopx/control_plane/runtime/trajectory_hygiene.py +149 -0
- loopx/control_plane/runtime/validation_command.py +69 -0
- loopx/control_plane/scheduler/__init__.py +1 -0
- loopx/control_plane/scheduler/ack.py +329 -0
- loopx/control_plane/scheduler/arbitration.py +188 -0
- loopx/control_plane/scheduler/automation_liveness.py +183 -0
- loopx/control_plane/scheduler/execution_context.py +555 -0
- loopx/control_plane/scheduler/external_evidence_observation.py +428 -0
- loopx/control_plane/scheduler/monitor_display.py +143 -0
- loopx/control_plane/scheduler/monitor_poll_policy.py +161 -0
- loopx/control_plane/scheduler/monitor_poll_writeback.py +351 -0
- loopx/control_plane/scheduler/monitor_target.py +64 -0
- loopx/control_plane/scheduler/monitor_todo.py +146 -0
- loopx/control_plane/scheduler/monitor_wait.py +237 -0
- loopx/control_plane/scheduler/scheduler_hint.py +1284 -0
- loopx/control_plane/scheduler/state.py +354 -0
- loopx/control_plane/scheduler/state_transition_rules.py +179 -0
- loopx/control_plane/scheduler/time.py +10 -0
- loopx/control_plane/settlement_driver.py +293 -0
- loopx/control_plane/status/__init__.py +6 -0
- loopx/control_plane/status/active_state_projection.py +105 -0
- loopx/control_plane/status/agent_lane_projection.py +375 -0
- loopx/control_plane/status/attention_projection.py +74 -0
- loopx/control_plane/status/autonomous_replan_projection.py +103 -0
- loopx/control_plane/status/collection.py +140 -0
- loopx/control_plane/status/contract_projection.py +31 -0
- loopx/control_plane/status/dreaming_projection.py +52 -0
- loopx/control_plane/status/goal_attention_projection.py +157 -0
- loopx/control_plane/status/lifecycle_projection.py +110 -0
- loopx/control_plane/status/monitor_display_projection.py +69 -0
- loopx/control_plane/status/registry_health_projection.py +75 -0
- loopx/control_plane/status/run_projection.py +70 -0
- loopx/control_plane/status/runtime_summaries.py +161 -0
- loopx/control_plane/testing/__init__.py +1 -0
- loopx/control_plane/testing/actual_default_model_behavior_portfolio.py +1371 -0
- loopx/control_plane/testing/canary_harness.py +182 -0
- loopx/control_plane/testing/capability_monitor_repair_tool_behavior.py +674 -0
- loopx/control_plane/testing/cli_output_budget.py +807 -0
- loopx/control_plane/testing/cli_output_differential.py +250 -0
- loopx/control_plane/testing/cli_output_semantics.py +87 -0
- loopx/control_plane/testing/control_plane_composition_scenarios.py +225 -0
- loopx/control_plane/testing/decision_replay.py +268 -0
- loopx/control_plane/testing/doubao_model_behavior_actor.py +559 -0
- loopx/control_plane/testing/model_behavior_corpus.py +344 -0
- loopx/control_plane/testing/model_behavior_qualification.py +769 -0
- loopx/control_plane/testing/model_behavior_retained_cases.py +235 -0
- loopx/control_plane/testing/model_tool_behavior.py +536 -0
- loopx/control_plane/testing/onboarding_model_behavior_qualification.py +642 -0
- loopx/control_plane/testing/quota_fixtures.py +208 -0
- loopx/control_plane/testing/quota_should_run_parity.py +57 -0
- loopx/control_plane/testing/release_commit_qualification.py +671 -0
- loopx/control_plane/testing/replan_semantic_action_behavior.py +1302 -0
- loopx/control_plane/testing/scoped_gate_successor_tool_behavior.py +527 -0
- loopx/control_plane/testing/selected_todo_tool_behavior.py +1002 -0
- loopx/control_plane/testing/terminal_settlement_tool_behavior.py +656 -0
- loopx/control_plane/todos/__init__.py +1 -0
- loopx/control_plane/todos/active_state_editing.py +296 -0
- loopx/control_plane/todos/active_state_todo_parser.py +138 -0
- loopx/control_plane/todos/active_state_todos.py +175 -0
- loopx/control_plane/todos/addition.py +103 -0
- loopx/control_plane/todos/claim_visibility.py +253 -0
- loopx/control_plane/todos/completed_archive.py +139 -0
- loopx/control_plane/todos/completion_fence.py +49 -0
- loopx/control_plane/todos/completion_policy.py +153 -0
- loopx/control_plane/todos/completion_validation.py +248 -0
- loopx/control_plane/todos/completion_validation_accountability.py +27 -0
- loopx/control_plane/todos/completion_validation_projection.py +57 -0
- loopx/control_plane/todos/contract.py +1476 -0
- loopx/control_plane/todos/decision_scope.py +554 -0
- loopx/control_plane/todos/deferred_resume.py +546 -0
- loopx/control_plane/todos/durable_completion.py +201 -0
- loopx/control_plane/todos/event_writeback.py +484 -0
- loopx/control_plane/todos/frontier_deadline.py +132 -0
- loopx/control_plane/todos/handoff_gate.py +283 -0
- loopx/control_plane/todos/handoff_mode.py +444 -0
- loopx/control_plane/todos/handoff_note.py +202 -0
- loopx/control_plane/todos/line_update.py +361 -0
- loopx/control_plane/todos/list_projection.py +205 -0
- loopx/control_plane/todos/markdown.py +199 -0
- loopx/control_plane/todos/monitor_metadata.py +88 -0
- loopx/control_plane/todos/mutation_authority.py +299 -0
- loopx/control_plane/todos/projection.py +655 -0
- loopx/control_plane/todos/quota_summary.py +1138 -0
- loopx/control_plane/todos/route_continuation.py +267 -0
- loopx/control_plane/todos/succession_warning.py +174 -0
- loopx/control_plane/todos/summary_item.py +223 -0
- loopx/control_plane/todos/text.py +30 -0
- loopx/control_plane/todos/todo_index.py +226 -0
- loopx/control_plane/todos/todo_summary.py +1458 -0
- loopx/control_plane/todos/unblock_resume.py +326 -0
- loopx/control_plane/todos/user_gate.py +263 -0
- loopx/control_plane/todos/write_hint.py +63 -0
- loopx/control_plane/todos/write_policy.py +135 -0
- loopx/control_plane/turn_driver/__init__.py +85 -0
- loopx/control_plane/turn_driver/codex_cli.py +502 -0
- loopx/control_plane/turn_driver/driver.py +355 -0
- loopx/control_plane/turn_driver/executor.py +1468 -0
- loopx/control_plane/turn_driver/loop_controller.py +669 -0
- loopx/control_plane/turn_driver/settlement.py +318 -0
- loopx/control_plane/turn_driver/transaction.py +375 -0
- loopx/control_plane/work_items/__init__.py +1 -0
- loopx/control_plane/work_items/attention_fields.py +56 -0
- loopx/control_plane/work_items/attention_item.py +77 -0
- loopx/control_plane/work_items/attention_queue.py +322 -0
- loopx/control_plane/work_items/attention_routing.py +213 -0
- loopx/control_plane/work_items/autonomous_candidates.py +135 -0
- loopx/control_plane/work_items/autonomous_replan_ack.py +276 -0
- loopx/control_plane/work_items/autonomous_replan_obligation.py +786 -0
- loopx/control_plane/work_items/backlog_hygiene.py +59 -0
- loopx/control_plane/work_items/capability_monitor_fallback.py +221 -0
- loopx/control_plane/work_items/delivery_batch_scale.py +66 -0
- loopx/control_plane/work_items/delivery_outcome.py +152 -0
- loopx/control_plane/work_items/delivery_signals.py +113 -0
- loopx/control_plane/work_items/execution_obligation.py +235 -0
- loopx/control_plane/work_items/goal_route_hint.py +320 -0
- loopx/control_plane/work_items/interaction_contract.py +1540 -0
- loopx/control_plane/work_items/issue_meta_surface.py +159 -0
- loopx/control_plane/work_items/lifecycle.py +139 -0
- loopx/control_plane/work_items/operator_inbox.py +266 -0
- loopx/control_plane/work_items/outcome_followthrough.py +69 -0
- loopx/control_plane/work_items/primary_action.py +326 -0
- loopx/control_plane/work_items/progress_observation.py +630 -0
- loopx/control_plane/work_items/project_asset.py +675 -0
- loopx/control_plane/work_items/repair_delta.py +693 -0
- loopx/control_plane/work_items/runtime_capability_reentry.py +168 -0
- loopx/control_plane/work_items/semantic_replan_writeback.py +177 -0
- loopx/control_plane/work_items/status_contract.py +49 -0
- loopx/control_plane/work_items/task_graph.py +1046 -0
- loopx/control_plane/work_items/task_lease.py +1254 -0
- loopx/control_plane/work_items/task_lease_settlement.py +422 -0
- loopx/control_plane/work_items/work_lane.py +510 -0
- loopx/control_plane/work_items/work_lane_context.py +161 -0
- loopx/demo.py +247 -0
- loopx/diagnose.py +633 -0
- loopx/doctor.py +1251 -0
- loopx/domain_packs/__init__.py +1 -0
- loopx/domain_packs/issue_fix.py +571 -0
- loopx/domain_packs/ml_experiment.py +854 -0
- loopx/domain_state.py +137 -0
- loopx/dreaming.py +706 -0
- loopx/entrypoint.py +16 -0
- loopx/event_sourced_state.py +981 -0
- loopx/execution_profile.py +286 -0
- loopx/experiments/__init__.py +1 -0
- loopx/experiments/planner_worker/__init__.py +1 -0
- loopx/experiments/planner_worker/contract.py +523 -0
- loopx/experiments/planner_worker/runtime.py +391 -0
- loopx/experiments/planner_worker/traex.py +461 -0
- loopx/explore_graph.py +11 -0
- loopx/extensions/__init__.py +1 -0
- loopx/extensions/bundled.py +28 -0
- loopx/extensions/execution_envelope.py +126 -0
- loopx/extensions/lark/__init__.py +11 -0
- loopx/extensions/lark/event_collector.py +478 -0
- loopx/extensions/lark/event_collector_runtime.py +506 -0
- loopx/extensions/lark/event_inbox.py +454 -0
- loopx/extensions/lark/extension.toml +88 -0
- loopx/extensions/lark/goal_channel.py +44 -0
- loopx/extensions/lark/goal_channel_contracts.py +388 -0
- loopx/extensions/lark/goal_channel_lifecycle.py +218 -0
- loopx/extensions/lark/goal_channel_runtime.py +792 -0
- loopx/extensions/lark/goal_channel_setup.py +805 -0
- loopx/extensions/lark/goal_channel_targets.py +215 -0
- loopx/extensions/lark/goal_channel_transport.py +281 -0
- loopx/extensions/lark/inbox_reactions.py +650 -0
- loopx/extensions/lark/inbox_reply.py +430 -0
- loopx/extensions/lark/presentation/__init__.py +11 -0
- loopx/extensions/lark/presentation/explore_results.py +2276 -0
- loopx/extensions/lark/presentation/explore_singleflight.py +127 -0
- loopx/extensions/lark/presentation/explore_source_guard.py +121 -0
- loopx/extensions/lark/presentation/explore_stage_document.py +703 -0
- loopx/extensions/lark/presentation/explore_visual_integrity.py +122 -0
- loopx/extensions/lark/presentation/explore_visual_readback.py +452 -0
- loopx/extensions/lark/presentation/explore_visual_styles.py +156 -0
- loopx/extensions/lark/presentation/issue_fix_surface.py +612 -0
- loopx/extensions/lark/presentation/kanban.py +2791 -0
- loopx/extensions/lark/presentation/message_card.py +112 -0
- loopx/extensions/lark/presentation/periodic_report.py +261 -0
- loopx/extensions/lark/presentation/projection_rows.py +600 -0
- loopx/extensions/lark/presentation/record_io.py +95 -0
- loopx/extensions/lark/presentation/sync_receipt.py +145 -0
- loopx/extensions/lark/private_json.py +40 -0
- loopx/extensions/lark/provider.py +86 -0
- loopx/extensions/lark/reviewer_notification.py +604 -0
- loopx/extensions/manifest.py +385 -0
- loopx/extensions/openviking_periodic_report/__init__.py +17 -0
- loopx/extensions/openviking_periodic_report/activation.py +173 -0
- loopx/extensions/openviking_periodic_report/extension.toml +17 -0
- loopx/extensions/openviking_periodic_report/provider.py +355 -0
- loopx/extensions/openviking_periodic_report/sink.py +117 -0
- loopx/extensions/openviking_semantic_preference/__init__.py +5 -0
- loopx/extensions/openviking_semantic_preference/extension.toml +16 -0
- loopx/extensions/openviking_semantic_preference/history_export.py +484 -0
- loopx/extensions/openviking_semantic_preference/project_peer.py +68 -0
- loopx/extensions/openviking_semantic_preference/provider.py +312 -0
- loopx/extensions/presentation.py +979 -0
- loopx/extensions/process_runtime.py +204 -0
- loopx/extensions/readiness.py +168 -0
- loopx/extensions/runtime.py +931 -0
- loopx/extensions/scaffold.py +335 -0
- loopx/feedback.py +581 -0
- loopx/file_lock.py +382 -0
- loopx/global_registry.py +842 -0
- loopx/global_risks.py +970 -0
- loopx/global_todos.py +568 -0
- loopx/handoff_budget.py +28 -0
- loopx/heartbeat_prequota.py +80 -0
- loopx/heartbeat_prompt.py +159 -0
- loopx/help_surface.py +516 -0
- loopx/history.py +1507 -0
- loopx/host_loop_activation.py +1311 -0
- loopx/host_mode_planner.py +991 -0
- loopx/install_contract.py +1 -0
- loopx/interface_budget.py +196 -0
- loopx/long_task_cadence.py +208 -0
- loopx/materials.py +185 -0
- loopx/ml_experiment.py +3 -0
- loopx/onboarding.py +214 -0
- loopx/opencode2_goal_mode/README.md +81 -0
- loopx/opencode2_goal_mode/__init__.py +9 -0
- loopx/opencode2_goal_mode/opencode2-goal-worker.mjs +1018 -0
- loopx/opencode_goal_mode/README.md +99 -0
- loopx/opencode_goal_mode/__init__.py +13 -0
- loopx/opencode_goal_mode/goal-bridge-runtime.mjs +858 -0
- loopx/opencode_goal_mode/loopx-goal.js +8 -0
- loopx/operator_gate.py +420 -0
- loopx/orchestration.py +127 -0
- loopx/paths.py +59 -0
- loopx/pi_goal_mode/README.md +67 -0
- loopx/pi_goal_mode/__init__.py +13 -0
- loopx/pi_goal_mode/loopx-goal.ts +254 -0
- loopx/pi_goal_mode/pi-goal-loop-runtime.mjs +574 -0
- loopx/pr_review.py +1206 -0
- loopx/presentation/__init__.py +1 -0
- loopx/presentation/explore_views.py +1334 -0
- loopx/presentation/markdown.py +61 -0
- loopx/presentation/projection_source_reconcile.py +140 -0
- loopx/presentation/public_safety.py +42 -0
- loopx/presentation/renderers/__init__.py +17 -0
- loopx/presentation/renderers/goal_channel_html.py +269 -0
- loopx/presentation/renderers/periodic_report_html.py +786 -0
- loopx/presentation/renderers/periodic_report_markdown.py +184 -0
- loopx/presentation/renderers/quota_event_markdown.py +116 -0
- loopx/presentation/renderers/quota_markdown.py +1112 -0
- loopx/presentation/renderers/status_markdown.py +1570 -0
- loopx/presentation/renderers/trajectory_hygiene_markdown.py +39 -0
- loopx/presentation/renderers/turn_envelope_markdown.py +33 -0
- loopx/presentation/sinks/__init__.py +5 -0
- loopx/presentation/sinks/openviking_periodic_report.py +7 -0
- loopx/presentation/static_site.py +691 -0
- loopx/presets.py +369 -0
- loopx/project_alias.py +217 -0
- loopx/project_map.py +589 -0
- loopx/project_prompt.py +1153 -0
- loopx/project_skill_cli.py +125 -0
- loopx/project_skill_delivery.py +470 -0
- loopx/project_uninstall.py +462 -0
- loopx/promotion_gate.py +197 -0
- loopx/quota.py +1197 -0
- loopx/ready_score.py +413 -0
- loopx/registry.py +621 -0
- loopx/registry_writability.py +64 -0
- loopx/release_candidate.py +148 -0
- loopx/release_manifest.py +316 -0
- loopx/repository_identity.py +100 -0
- loopx/review_packet.py +1024 -0
- loopx/rollout_event_log.py +505 -0
- loopx/runtime.py +112 -0
- loopx/self_update.py +750 -0
- loopx/session_runtime.py +418 -0
- loopx/skill_install_readback.py +500 -0
- loopx/slash_command_install.py +1393 -0
- loopx/slash_commands.py +264 -0
- loopx/state_backup.py +573 -0
- loopx/state_migration.py +350 -0
- loopx/state_projection.py +809 -0
- loopx/state_refresh.py +1416 -0
- loopx/status.py +1383 -0
- loopx/status_server.py +935 -0
- loopx/summary_all.py +725 -0
- loopx/terminal_bench_agent.py +2056 -0
- loopx/thread_agent_binding.py +408 -0
- loopx/todo_followups.py +168 -0
- loopx/todo_suggestion_prompt.py +204 -0
- loopx/todos.py +2229 -0
- loopx/turn_identity.py +17 -0
- loopx/upgrade.py +1083 -0
- loopx/visible_governance.py +667 -0
- loopx/visible_multi_agent_launcher.py +1253 -0
- loopx/visible_multi_agent_tmux.py +429 -0
- loopx/worker_bridge.py +1574 -0
- loopx-0.4.8.dist-info/METADATA +708 -0
- loopx-0.4.8.dist-info/RECORD +811 -0
- loopx-0.4.8.dist-info/WHEEL +5 -0
- loopx-0.4.8.dist-info/entry_points.txt +5 -0
- loopx-0.4.8.dist-info/licenses/LICENSE +202 -0
- loopx-0.4.8.dist-info/licenses/LICENSE-MIT +21 -0
- loopx-0.4.8.dist-info/licenses/NOTICE +6 -0
- loopx-0.4.8.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1271 @@
|
|
|
1
|
+
"""Domain-neutral trace and replay-point contracts for Explore Episode V2.
|
|
2
|
+
|
|
3
|
+
The core owns addressable execution lineage and agent-history cursors. An
|
|
4
|
+
adapter owns every environment-specific state object behind an opaque runtime
|
|
5
|
+
binding. Public records deliberately exclude both agent-state keys and
|
|
6
|
+
adapter binding keys.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import threading
|
|
12
|
+
import time
|
|
13
|
+
import uuid
|
|
14
|
+
from dataclasses import dataclass, field, replace
|
|
15
|
+
from enum import Enum
|
|
16
|
+
from typing import Any, Callable, Mapping, Protocol, Sequence
|
|
17
|
+
|
|
18
|
+
from .replay_metrics import ReplayMetricLedger
|
|
19
|
+
from .trace_runtime import (
|
|
20
|
+
AgentCursor,
|
|
21
|
+
TraceCursor,
|
|
22
|
+
TraceEvent,
|
|
23
|
+
TraceEventKind,
|
|
24
|
+
TraceLog,
|
|
25
|
+
freeze_public_mapping,
|
|
26
|
+
require_nonempty_text as _required_text,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ReplayFidelity(str, Enum):
|
|
31
|
+
EXACT = "exact"
|
|
32
|
+
SEMANTIC_EQUIVALENT = "semantic_equivalent"
|
|
33
|
+
BEST_EFFORT = "best_effort"
|
|
34
|
+
NON_REPLAYABLE = "non_replayable"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class ReplayChildConcurrency(str, Enum):
|
|
38
|
+
"""Adapter-declared safety boundary for an isolated child lease."""
|
|
39
|
+
|
|
40
|
+
SERIAL_ONLY = "serial_only"
|
|
41
|
+
THREAD_SAFE = "thread_safe"
|
|
42
|
+
PROCESS_ISOLATED = "process_isolated"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class ReplayRiskDisposition(str, Enum):
|
|
46
|
+
ALLOW = "allow"
|
|
47
|
+
DENY = "deny"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class ReplayChildFailureStage(str, Enum):
|
|
51
|
+
INTENT = "intent"
|
|
52
|
+
SUFFIX = "suffix"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
_FIDELITY_RANK = {
|
|
56
|
+
ReplayFidelity.NON_REPLAYABLE: 0,
|
|
57
|
+
ReplayFidelity.BEST_EFFORT: 1,
|
|
58
|
+
ReplayFidelity.SEMANTIC_EQUIVALENT: 2,
|
|
59
|
+
ReplayFidelity.EXACT: 3,
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def fidelity_meets(
|
|
64
|
+
actual: ReplayFidelity,
|
|
65
|
+
required: ReplayFidelity,
|
|
66
|
+
) -> bool:
|
|
67
|
+
"""Return whether ``actual`` is at least as trustworthy as ``required``."""
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
_FIDELITY_RANK[ReplayFidelity(actual)]
|
|
71
|
+
>= _FIDELITY_RANK[ReplayFidelity(required)]
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class ReplayPointLifecycle(str, Enum):
|
|
76
|
+
READY = "ready"
|
|
77
|
+
RELEASING = "releasing"
|
|
78
|
+
RELEASED = "released"
|
|
79
|
+
FAILED = "failed"
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
REPLAYABLE_EPISODE_V2_MODE = "replayable-v2"
|
|
83
|
+
_V2_PROTOCOL_METHODS = (
|
|
84
|
+
"capture_replay_state",
|
|
85
|
+
"restore_replay_state",
|
|
86
|
+
"validate_replay_equivalence",
|
|
87
|
+
"release_replay_state",
|
|
88
|
+
)
|
|
89
|
+
_ISOLATED_CHILD_PROTOCOL_METHODS = (
|
|
90
|
+
"fork_replay_child",
|
|
91
|
+
"validate_replay_child_equivalence",
|
|
92
|
+
"release_replay_child",
|
|
93
|
+
)
|
|
94
|
+
_CHILD_RISK_PROTOCOL_METHODS = (
|
|
95
|
+
"assess_replay_child_intent",
|
|
96
|
+
"assess_replay_child_outcome",
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def replayable_episode_v2_mode(adapter: Any) -> str | None:
|
|
101
|
+
"""Detect the optional V2 protocol and reject partial implementations."""
|
|
102
|
+
|
|
103
|
+
declared = [hasattr(adapter, name) for name in _V2_PROTOCOL_METHODS]
|
|
104
|
+
implemented = [
|
|
105
|
+
callable(getattr(adapter, name, None)) for name in _V2_PROTOCOL_METHODS
|
|
106
|
+
]
|
|
107
|
+
if any(declared) and not all(implemented):
|
|
108
|
+
missing = [
|
|
109
|
+
name
|
|
110
|
+
for name, available in zip(_V2_PROTOCOL_METHODS, implemented)
|
|
111
|
+
if not available
|
|
112
|
+
]
|
|
113
|
+
raise ValueError(
|
|
114
|
+
"replayable Episode V2 adapters must implement all protocol methods; "
|
|
115
|
+
f"missing callable(s): {', '.join(missing)}"
|
|
116
|
+
)
|
|
117
|
+
return REPLAYABLE_EPISODE_V2_MODE if all(implemented) else None
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def isolated_replay_child_mode(
|
|
121
|
+
adapter: Any,
|
|
122
|
+
agent_state_store: Any,
|
|
123
|
+
) -> str | None:
|
|
124
|
+
"""Detect the optional two-sided child isolation contract."""
|
|
125
|
+
|
|
126
|
+
declared = [hasattr(adapter, name) for name in _ISOLATED_CHILD_PROTOCOL_METHODS]
|
|
127
|
+
implemented = [
|
|
128
|
+
callable(getattr(adapter, name, None))
|
|
129
|
+
for name in _ISOLATED_CHILD_PROTOCOL_METHODS
|
|
130
|
+
]
|
|
131
|
+
if any(declared) and not all(implemented):
|
|
132
|
+
missing = [
|
|
133
|
+
name
|
|
134
|
+
for name, available in zip(
|
|
135
|
+
_ISOLATED_CHILD_PROTOCOL_METHODS,
|
|
136
|
+
implemented,
|
|
137
|
+
)
|
|
138
|
+
if not available
|
|
139
|
+
]
|
|
140
|
+
raise ValueError(
|
|
141
|
+
"isolated replay child adapters must implement all protocol "
|
|
142
|
+
f"methods; missing callable(s): {', '.join(missing)}"
|
|
143
|
+
)
|
|
144
|
+
if not all(implemented):
|
|
145
|
+
return None
|
|
146
|
+
if not callable(getattr(agent_state_store, "fork_agent_state", None)):
|
|
147
|
+
return None
|
|
148
|
+
return "isolated-child-v0"
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def replay_child_risk_mode(adapter: Any) -> str | None:
|
|
152
|
+
"""Detect the optional two-stage child risk interception contract."""
|
|
153
|
+
|
|
154
|
+
declared = [hasattr(adapter, name) for name in _CHILD_RISK_PROTOCOL_METHODS]
|
|
155
|
+
implemented = [
|
|
156
|
+
callable(getattr(adapter, name, None)) for name in _CHILD_RISK_PROTOCOL_METHODS
|
|
157
|
+
]
|
|
158
|
+
if any(declared) and not all(implemented):
|
|
159
|
+
missing = [
|
|
160
|
+
name
|
|
161
|
+
for name, available in zip(
|
|
162
|
+
_CHILD_RISK_PROTOCOL_METHODS,
|
|
163
|
+
implemented,
|
|
164
|
+
)
|
|
165
|
+
if not available
|
|
166
|
+
]
|
|
167
|
+
raise ValueError(
|
|
168
|
+
"replay child risk adapters must implement both protocol "
|
|
169
|
+
f"methods; missing callable(s): {', '.join(missing)}"
|
|
170
|
+
)
|
|
171
|
+
return "child-risk-v0" if all(implemented) else None
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
@dataclass(frozen=True)
|
|
175
|
+
class CaptureReplayStateRequest:
|
|
176
|
+
replay_point_id: str
|
|
177
|
+
cursor: TraceCursor
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
@dataclass(frozen=True)
|
|
181
|
+
class RestoreReplayStateRequest:
|
|
182
|
+
replay_point_id: str
|
|
183
|
+
target_branch_id: str
|
|
184
|
+
minimum_fidelity: ReplayFidelity
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
@dataclass(frozen=True)
|
|
188
|
+
class ForkReplayChildRequest:
|
|
189
|
+
replay_point_id: str
|
|
190
|
+
child_id: str
|
|
191
|
+
target_branch_id: str
|
|
192
|
+
minimum_fidelity: ReplayFidelity
|
|
193
|
+
|
|
194
|
+
def __post_init__(self) -> None:
|
|
195
|
+
for field_name in (
|
|
196
|
+
"replay_point_id",
|
|
197
|
+
"child_id",
|
|
198
|
+
"target_branch_id",
|
|
199
|
+
):
|
|
200
|
+
object.__setattr__(
|
|
201
|
+
self,
|
|
202
|
+
field_name,
|
|
203
|
+
_required_text(getattr(self, field_name), field_name),
|
|
204
|
+
)
|
|
205
|
+
object.__setattr__(
|
|
206
|
+
self,
|
|
207
|
+
"minimum_fidelity",
|
|
208
|
+
ReplayFidelity(self.minimum_fidelity),
|
|
209
|
+
)
|
|
210
|
+
if self.minimum_fidelity == ReplayFidelity.NON_REPLAYABLE:
|
|
211
|
+
raise ValueError("isolated replay children require replayable fidelity")
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
@dataclass(frozen=True)
|
|
215
|
+
class AdapterStateLease:
|
|
216
|
+
"""Runtime-only Adapter binding; ``binding_key`` is never serialized."""
|
|
217
|
+
|
|
218
|
+
binding_key: object | None
|
|
219
|
+
fidelity: ReplayFidelity
|
|
220
|
+
projection_digest: str | None = None
|
|
221
|
+
|
|
222
|
+
def __post_init__(self) -> None:
|
|
223
|
+
object.__setattr__(self, "fidelity", ReplayFidelity(self.fidelity))
|
|
224
|
+
if self.fidelity == ReplayFidelity.NON_REPLAYABLE:
|
|
225
|
+
if self.binding_key is not None:
|
|
226
|
+
raise ValueError("non_replayable leases must not expose a binding_key")
|
|
227
|
+
elif self.binding_key is None:
|
|
228
|
+
raise ValueError("replayable leases require an opaque binding_key")
|
|
229
|
+
if self.projection_digest is not None:
|
|
230
|
+
object.__setattr__(
|
|
231
|
+
self,
|
|
232
|
+
"projection_digest",
|
|
233
|
+
_required_text(self.projection_digest, "projection_digest"),
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
@dataclass(frozen=True)
|
|
238
|
+
class RestoreReceipt:
|
|
239
|
+
achieved_fidelity: ReplayFidelity
|
|
240
|
+
restored_projection_digest: str | None = None
|
|
241
|
+
prefix_reused: bool | None = None
|
|
242
|
+
|
|
243
|
+
def __post_init__(self) -> None:
|
|
244
|
+
object.__setattr__(
|
|
245
|
+
self,
|
|
246
|
+
"achieved_fidelity",
|
|
247
|
+
ReplayFidelity(self.achieved_fidelity),
|
|
248
|
+
)
|
|
249
|
+
if self.restored_projection_digest is not None:
|
|
250
|
+
object.__setattr__(
|
|
251
|
+
self,
|
|
252
|
+
"restored_projection_digest",
|
|
253
|
+
_required_text(
|
|
254
|
+
self.restored_projection_digest,
|
|
255
|
+
"restored_projection_digest",
|
|
256
|
+
),
|
|
257
|
+
)
|
|
258
|
+
if self.prefix_reused is not None and not isinstance(
|
|
259
|
+
self.prefix_reused,
|
|
260
|
+
bool,
|
|
261
|
+
):
|
|
262
|
+
raise TypeError("prefix_reused must be bool or None")
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
@dataclass(frozen=True)
|
|
266
|
+
class ReplayChildLease:
|
|
267
|
+
"""Runtime-only isolated environment child and its restore receipt."""
|
|
268
|
+
|
|
269
|
+
child_key: object
|
|
270
|
+
receipt: RestoreReceipt
|
|
271
|
+
concurrency: ReplayChildConcurrency = ReplayChildConcurrency.SERIAL_ONLY
|
|
272
|
+
worker_identity: object | None = None
|
|
273
|
+
|
|
274
|
+
def __post_init__(self) -> None:
|
|
275
|
+
if self.child_key is None:
|
|
276
|
+
raise TypeError("ReplayChildLease requires an opaque child_key")
|
|
277
|
+
if not isinstance(self.receipt, RestoreReceipt):
|
|
278
|
+
raise TypeError("ReplayChildLease receipt must be a RestoreReceipt")
|
|
279
|
+
object.__setattr__(
|
|
280
|
+
self,
|
|
281
|
+
"concurrency",
|
|
282
|
+
ReplayChildConcurrency(self.concurrency),
|
|
283
|
+
)
|
|
284
|
+
if (
|
|
285
|
+
self.concurrency == ReplayChildConcurrency.PROCESS_ISOLATED
|
|
286
|
+
and self.worker_identity is None
|
|
287
|
+
):
|
|
288
|
+
raise TypeError(
|
|
289
|
+
"process-isolated ReplayChildLease requires an opaque worker_identity"
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
@dataclass(frozen=True)
|
|
294
|
+
class ReplayRiskAssessment:
|
|
295
|
+
disposition: ReplayRiskDisposition
|
|
296
|
+
policy_id: str
|
|
297
|
+
sanitized_reason: str | None = None
|
|
298
|
+
|
|
299
|
+
def __post_init__(self) -> None:
|
|
300
|
+
object.__setattr__(
|
|
301
|
+
self,
|
|
302
|
+
"disposition",
|
|
303
|
+
ReplayRiskDisposition(self.disposition),
|
|
304
|
+
)
|
|
305
|
+
object.__setattr__(
|
|
306
|
+
self,
|
|
307
|
+
"policy_id",
|
|
308
|
+
_required_text(self.policy_id, "policy_id"),
|
|
309
|
+
)
|
|
310
|
+
if self.sanitized_reason is not None:
|
|
311
|
+
frozen = freeze_public_mapping(
|
|
312
|
+
{"sanitized_reason": self.sanitized_reason},
|
|
313
|
+
field_path="replay_risk_assessment",
|
|
314
|
+
)
|
|
315
|
+
object.__setattr__(
|
|
316
|
+
self,
|
|
317
|
+
"sanitized_reason",
|
|
318
|
+
frozen["sanitized_reason"],
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
def to_record(self) -> dict[str, Any]:
|
|
322
|
+
return {
|
|
323
|
+
"disposition": self.disposition.value,
|
|
324
|
+
"policy_id": self.policy_id,
|
|
325
|
+
"sanitized_reason": self.sanitized_reason,
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
class ReplayRiskRejectedError(RuntimeError):
|
|
330
|
+
def __init__(self, stage: str, assessment: ReplayRiskAssessment) -> None:
|
|
331
|
+
self.stage = _required_text(stage, "stage")
|
|
332
|
+
self.assessment = assessment
|
|
333
|
+
super().__init__(f"replay child {self.stage} denied by {assessment.policy_id}")
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
@dataclass(frozen=True)
|
|
337
|
+
class EquivalenceCheck:
|
|
338
|
+
check_id: str
|
|
339
|
+
passed: bool
|
|
340
|
+
public_summary: str | None = None
|
|
341
|
+
|
|
342
|
+
def __post_init__(self) -> None:
|
|
343
|
+
object.__setattr__(self, "check_id", _required_text(self.check_id, "check_id"))
|
|
344
|
+
object.__setattr__(self, "passed", bool(self.passed))
|
|
345
|
+
if self.public_summary is not None:
|
|
346
|
+
frozen = freeze_public_mapping(
|
|
347
|
+
{"public_summary": self.public_summary},
|
|
348
|
+
field_path="equivalence_check",
|
|
349
|
+
)
|
|
350
|
+
object.__setattr__(
|
|
351
|
+
self,
|
|
352
|
+
"public_summary",
|
|
353
|
+
frozen["public_summary"],
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
@dataclass(frozen=True)
|
|
358
|
+
class EquivalenceReport:
|
|
359
|
+
achieved_fidelity: ReplayFidelity
|
|
360
|
+
equivalent: bool
|
|
361
|
+
expected_digest: str | None = None
|
|
362
|
+
observed_digest: str | None = None
|
|
363
|
+
checks: tuple[EquivalenceCheck, ...] = ()
|
|
364
|
+
sanitized_reason: str | None = None
|
|
365
|
+
|
|
366
|
+
def __post_init__(self) -> None:
|
|
367
|
+
object.__setattr__(
|
|
368
|
+
self,
|
|
369
|
+
"achieved_fidelity",
|
|
370
|
+
ReplayFidelity(self.achieved_fidelity),
|
|
371
|
+
)
|
|
372
|
+
object.__setattr__(self, "equivalent", bool(self.equivalent))
|
|
373
|
+
for field_name in ("expected_digest", "observed_digest", "sanitized_reason"):
|
|
374
|
+
value = getattr(self, field_name)
|
|
375
|
+
if value is not None:
|
|
376
|
+
if field_name == "sanitized_reason":
|
|
377
|
+
frozen = freeze_public_mapping(
|
|
378
|
+
{field_name: value},
|
|
379
|
+
field_path="equivalence_report",
|
|
380
|
+
)
|
|
381
|
+
object.__setattr__(self, field_name, frozen[field_name])
|
|
382
|
+
else:
|
|
383
|
+
object.__setattr__(
|
|
384
|
+
self,
|
|
385
|
+
field_name,
|
|
386
|
+
_required_text(value, field_name),
|
|
387
|
+
)
|
|
388
|
+
normalized_checks = tuple(self.checks)
|
|
389
|
+
if not all(isinstance(check, EquivalenceCheck) for check in normalized_checks):
|
|
390
|
+
raise TypeError("checks must contain EquivalenceCheck values")
|
|
391
|
+
object.__setattr__(self, "checks", normalized_checks)
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
@dataclass(frozen=True)
|
|
395
|
+
class ReplayRestoreResult:
|
|
396
|
+
replay_point_id: str
|
|
397
|
+
target_branch_id: str
|
|
398
|
+
achieved_fidelity: ReplayFidelity
|
|
399
|
+
equivalent: bool
|
|
400
|
+
promotion_eligible: bool
|
|
401
|
+
prefix_reused: bool | None
|
|
402
|
+
report: EquivalenceReport
|
|
403
|
+
restore_minutes: float
|
|
404
|
+
equivalence_minutes: float
|
|
405
|
+
suffix_minutes: float
|
|
406
|
+
replay_wall_minutes: float
|
|
407
|
+
|
|
408
|
+
def to_record(self) -> dict[str, Any]:
|
|
409
|
+
return {
|
|
410
|
+
"replay_point_id": self.replay_point_id,
|
|
411
|
+
"target_branch_id": self.target_branch_id,
|
|
412
|
+
"achieved_fidelity": self.achieved_fidelity.value,
|
|
413
|
+
"equivalent": self.equivalent,
|
|
414
|
+
"promotion_eligible": self.promotion_eligible,
|
|
415
|
+
"prefix_reused": self.prefix_reused,
|
|
416
|
+
"expected_digest": self.report.expected_digest,
|
|
417
|
+
"observed_digest": self.report.observed_digest,
|
|
418
|
+
"checks": [
|
|
419
|
+
{
|
|
420
|
+
"check_id": check.check_id,
|
|
421
|
+
"passed": check.passed,
|
|
422
|
+
"public_summary": check.public_summary,
|
|
423
|
+
}
|
|
424
|
+
for check in self.report.checks
|
|
425
|
+
],
|
|
426
|
+
"sanitized_reason": self.report.sanitized_reason,
|
|
427
|
+
"restore_minutes": self.restore_minutes,
|
|
428
|
+
"equivalence_minutes": self.equivalence_minutes,
|
|
429
|
+
"suffix_minutes": self.suffix_minutes,
|
|
430
|
+
"replay_wall_minutes": self.replay_wall_minutes,
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
@dataclass(frozen=True)
|
|
435
|
+
class ReplayChildExecutionContext:
|
|
436
|
+
child_id: str
|
|
437
|
+
restore: ReplayRestoreResult
|
|
438
|
+
isolated: bool
|
|
439
|
+
adapter_child_key: object | None = field(repr=False, compare=False)
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
@dataclass(frozen=True)
|
|
443
|
+
class ReplayChildTask:
|
|
444
|
+
child_id: str
|
|
445
|
+
target_branch_id: str
|
|
446
|
+
minimum_fidelity: ReplayFidelity
|
|
447
|
+
execute: Callable[[ReplayChildExecutionContext], Any] = field(
|
|
448
|
+
repr=False,
|
|
449
|
+
compare=False,
|
|
450
|
+
)
|
|
451
|
+
intent: Mapping[str, Any] = field(default_factory=dict)
|
|
452
|
+
|
|
453
|
+
def __post_init__(self) -> None:
|
|
454
|
+
object.__setattr__(
|
|
455
|
+
self,
|
|
456
|
+
"child_id",
|
|
457
|
+
_required_text(self.child_id, "child_id"),
|
|
458
|
+
)
|
|
459
|
+
object.__setattr__(
|
|
460
|
+
self,
|
|
461
|
+
"target_branch_id",
|
|
462
|
+
_required_text(self.target_branch_id, "target_branch_id"),
|
|
463
|
+
)
|
|
464
|
+
object.__setattr__(
|
|
465
|
+
self,
|
|
466
|
+
"minimum_fidelity",
|
|
467
|
+
ReplayFidelity(self.minimum_fidelity),
|
|
468
|
+
)
|
|
469
|
+
if self.minimum_fidelity == ReplayFidelity.NON_REPLAYABLE:
|
|
470
|
+
raise ValueError("ReplayChildTask requires replayable fidelity")
|
|
471
|
+
if not callable(self.execute):
|
|
472
|
+
raise TypeError("ReplayChildTask execute must be callable")
|
|
473
|
+
object.__setattr__(
|
|
474
|
+
self,
|
|
475
|
+
"intent",
|
|
476
|
+
freeze_public_mapping(self.intent, field_path="replay_child_intent"),
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
@dataclass(frozen=True)
|
|
481
|
+
class ReplayChildExecutionResult:
|
|
482
|
+
child_id: str
|
|
483
|
+
restore: ReplayRestoreResult
|
|
484
|
+
outcome: Any
|
|
485
|
+
isolated: bool
|
|
486
|
+
parallel: bool
|
|
487
|
+
intent_assessment: ReplayRiskAssessment | None = None
|
|
488
|
+
outcome_assessment: ReplayRiskAssessment | None = None
|
|
489
|
+
|
|
490
|
+
def to_record(self) -> dict[str, Any]:
|
|
491
|
+
return {
|
|
492
|
+
"child_id": self.child_id,
|
|
493
|
+
"restore": self.restore.to_record(),
|
|
494
|
+
"isolated": self.isolated,
|
|
495
|
+
"parallel": self.parallel,
|
|
496
|
+
"intent_assessment": (
|
|
497
|
+
self.intent_assessment.to_record()
|
|
498
|
+
if self.intent_assessment is not None
|
|
499
|
+
else None
|
|
500
|
+
),
|
|
501
|
+
"outcome_assessment": (
|
|
502
|
+
self.outcome_assessment.to_record()
|
|
503
|
+
if self.outcome_assessment is not None
|
|
504
|
+
else None
|
|
505
|
+
),
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
@dataclass(frozen=True)
|
|
510
|
+
class ReplayChildExecutionFailure:
|
|
511
|
+
child_id: str
|
|
512
|
+
restore: ReplayRestoreResult
|
|
513
|
+
stage: ReplayChildFailureStage
|
|
514
|
+
error_type: str
|
|
515
|
+
isolated: bool
|
|
516
|
+
parallel: bool
|
|
517
|
+
risk_assessment: ReplayRiskAssessment | None = None
|
|
518
|
+
|
|
519
|
+
def __post_init__(self) -> None:
|
|
520
|
+
object.__setattr__(self, "child_id", _required_text(self.child_id, "child_id"))
|
|
521
|
+
object.__setattr__(self, "stage", ReplayChildFailureStage(self.stage))
|
|
522
|
+
object.__setattr__(
|
|
523
|
+
self,
|
|
524
|
+
"error_type",
|
|
525
|
+
_required_text(self.error_type, "error_type"),
|
|
526
|
+
)
|
|
527
|
+
|
|
528
|
+
def to_record(self) -> dict[str, Any]:
|
|
529
|
+
return {
|
|
530
|
+
"child_id": self.child_id,
|
|
531
|
+
"restore": self.restore.to_record(),
|
|
532
|
+
"stage": self.stage.value,
|
|
533
|
+
"error_type": self.error_type,
|
|
534
|
+
"isolated": self.isolated,
|
|
535
|
+
"parallel": self.parallel,
|
|
536
|
+
"risk_assessment": (
|
|
537
|
+
self.risk_assessment.to_record()
|
|
538
|
+
if self.risk_assessment is not None
|
|
539
|
+
else None
|
|
540
|
+
),
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
class AgentStateStore(Protocol):
|
|
545
|
+
def capture_agent_state(self, cursor: TraceCursor) -> object: ...
|
|
546
|
+
|
|
547
|
+
def restore_agent_state(self, state_key: object) -> None: ...
|
|
548
|
+
|
|
549
|
+
def release_agent_state(self, state_key: object) -> None: ...
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
class IsolatedAgentStateStore(AgentStateStore, Protocol):
|
|
553
|
+
def fork_agent_state(self, state_key: object, child_id: str) -> object: ...
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
class ReplayAdapterV2(Protocol):
|
|
557
|
+
def capture_replay_state(
|
|
558
|
+
self,
|
|
559
|
+
request: CaptureReplayStateRequest,
|
|
560
|
+
) -> AdapterStateLease: ...
|
|
561
|
+
|
|
562
|
+
def restore_replay_state(
|
|
563
|
+
self,
|
|
564
|
+
binding_key: object,
|
|
565
|
+
request: RestoreReplayStateRequest,
|
|
566
|
+
) -> RestoreReceipt: ...
|
|
567
|
+
|
|
568
|
+
def validate_replay_equivalence(
|
|
569
|
+
self,
|
|
570
|
+
binding_key: object,
|
|
571
|
+
receipt: RestoreReceipt,
|
|
572
|
+
) -> EquivalenceReport: ...
|
|
573
|
+
|
|
574
|
+
def release_replay_state(self, binding_key: object) -> None: ...
|
|
575
|
+
|
|
576
|
+
|
|
577
|
+
class IsolatedReplayChildAdapter(ReplayAdapterV2, Protocol):
|
|
578
|
+
def fork_replay_child(
|
|
579
|
+
self,
|
|
580
|
+
binding_key: object,
|
|
581
|
+
request: ForkReplayChildRequest,
|
|
582
|
+
) -> ReplayChildLease: ...
|
|
583
|
+
|
|
584
|
+
def validate_replay_child_equivalence(
|
|
585
|
+
self,
|
|
586
|
+
child_key: object,
|
|
587
|
+
receipt: RestoreReceipt,
|
|
588
|
+
) -> EquivalenceReport: ...
|
|
589
|
+
|
|
590
|
+
def release_replay_child(self, child_key: object) -> None: ...
|
|
591
|
+
|
|
592
|
+
|
|
593
|
+
class RiskAwareReplayChildAdapter(IsolatedReplayChildAdapter, Protocol):
|
|
594
|
+
def assess_replay_child_intent(
|
|
595
|
+
self,
|
|
596
|
+
child_key: object,
|
|
597
|
+
intent: Mapping[str, Any],
|
|
598
|
+
) -> ReplayRiskAssessment: ...
|
|
599
|
+
|
|
600
|
+
def assess_replay_child_outcome(
|
|
601
|
+
self,
|
|
602
|
+
child_key: object,
|
|
603
|
+
intent: Mapping[str, Any],
|
|
604
|
+
outcome: Any,
|
|
605
|
+
) -> ReplayRiskAssessment: ...
|
|
606
|
+
|
|
607
|
+
|
|
608
|
+
@dataclass(frozen=True)
|
|
609
|
+
class ReplayPoint:
|
|
610
|
+
replay_point_id: str
|
|
611
|
+
cursor: TraceCursor
|
|
612
|
+
fidelity: ReplayFidelity
|
|
613
|
+
captured_projection_digest: str | None
|
|
614
|
+
lifecycle: ReplayPointLifecycle
|
|
615
|
+
created_event_id: str
|
|
616
|
+
replaces_replay_point_id: str | None = None
|
|
617
|
+
|
|
618
|
+
def to_record(self) -> dict[str, Any]:
|
|
619
|
+
"""Return the public-safe record; runtime keys intentionally do not exist."""
|
|
620
|
+
|
|
621
|
+
return {
|
|
622
|
+
"replay_point_id": self.replay_point_id,
|
|
623
|
+
"trace_id": self.cursor.trace_id,
|
|
624
|
+
"branch_id": self.cursor.branch_id,
|
|
625
|
+
"after_event_id": self.cursor.after_event_id,
|
|
626
|
+
"next_sequence": self.cursor.next_sequence,
|
|
627
|
+
"agent_history_index": self.cursor.agent_cursor.history_index,
|
|
628
|
+
"agent_history_digest": self.cursor.agent_cursor.history_digest,
|
|
629
|
+
"agent_context_digest": self.cursor.agent_cursor.context_digest,
|
|
630
|
+
"fidelity": self.fidelity.value,
|
|
631
|
+
"captured_projection_digest": self.captured_projection_digest,
|
|
632
|
+
"lifecycle": self.lifecycle.value,
|
|
633
|
+
"created_event_id": self.created_event_id,
|
|
634
|
+
"replaces_replay_point_id": self.replaces_replay_point_id,
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
def _validate_equivalence_contract(
|
|
639
|
+
point: ReplayPoint,
|
|
640
|
+
receipt: RestoreReceipt,
|
|
641
|
+
report: EquivalenceReport,
|
|
642
|
+
minimum: ReplayFidelity,
|
|
643
|
+
) -> bool:
|
|
644
|
+
if report.achieved_fidelity != receipt.achieved_fidelity:
|
|
645
|
+
raise ValueError("restore receipt and equivalence report fidelity disagree")
|
|
646
|
+
if (
|
|
647
|
+
receipt.restored_projection_digest is not None
|
|
648
|
+
and report.observed_digest is not None
|
|
649
|
+
and receipt.restored_projection_digest != report.observed_digest
|
|
650
|
+
):
|
|
651
|
+
raise ValueError(
|
|
652
|
+
"restore receipt and equivalence report observed digests disagree"
|
|
653
|
+
)
|
|
654
|
+
if (
|
|
655
|
+
point.captured_projection_digest is not None
|
|
656
|
+
and report.expected_digest != point.captured_projection_digest
|
|
657
|
+
):
|
|
658
|
+
raise ValueError(
|
|
659
|
+
"equivalence report expected digest does not match the captured ReplayPoint"
|
|
660
|
+
)
|
|
661
|
+
if report.equivalent and any(not check.passed for check in report.checks):
|
|
662
|
+
raise ValueError("equivalence report cannot pass while a declared check fails")
|
|
663
|
+
if report.achieved_fidelity == ReplayFidelity.EXACT:
|
|
664
|
+
if (
|
|
665
|
+
report.expected_digest is None
|
|
666
|
+
or report.observed_digest is None
|
|
667
|
+
or report.expected_digest != report.observed_digest
|
|
668
|
+
):
|
|
669
|
+
raise ValueError("exact replay requires equal non-empty state digests")
|
|
670
|
+
trusted_fidelity = fidelity_meets(
|
|
671
|
+
report.achieved_fidelity,
|
|
672
|
+
ReplayFidelity.SEMANTIC_EQUIVALENT,
|
|
673
|
+
)
|
|
674
|
+
if (
|
|
675
|
+
report.achieved_fidelity == ReplayFidelity.SEMANTIC_EQUIVALENT
|
|
676
|
+
and not report.checks
|
|
677
|
+
):
|
|
678
|
+
raise ValueError(
|
|
679
|
+
"semantic replay requires at least one explicit equivalence check"
|
|
680
|
+
)
|
|
681
|
+
promotion_eligible = bool(
|
|
682
|
+
report.equivalent
|
|
683
|
+
and trusted_fidelity
|
|
684
|
+
and fidelity_meets(report.achieved_fidelity, minimum)
|
|
685
|
+
)
|
|
686
|
+
if trusted_fidelity and not report.equivalent:
|
|
687
|
+
raise ValueError("exact or semantic replay requires verified state equivalence")
|
|
688
|
+
return promotion_eligible
|
|
689
|
+
|
|
690
|
+
|
|
691
|
+
@dataclass
|
|
692
|
+
class _RuntimeReplayBinding:
|
|
693
|
+
point: ReplayPoint
|
|
694
|
+
agent_state_key: object
|
|
695
|
+
adapter_binding_key: object | None
|
|
696
|
+
agent_state_store: AgentStateStore
|
|
697
|
+
adapter: ReplayAdapterV2
|
|
698
|
+
agent_released: bool = False
|
|
699
|
+
adapter_released: bool = False
|
|
700
|
+
replay_lock: threading.Lock = field(default_factory=threading.Lock)
|
|
701
|
+
|
|
702
|
+
|
|
703
|
+
class ReplayPointRegistry:
|
|
704
|
+
"""Atomically bind a trace cursor to Core and Adapter runtime state."""
|
|
705
|
+
|
|
706
|
+
def __init__(
|
|
707
|
+
self,
|
|
708
|
+
*,
|
|
709
|
+
id_factory: Callable[[], str] | None = None,
|
|
710
|
+
event_id_factory: Callable[[], str] | None = None,
|
|
711
|
+
metrics: ReplayMetricLedger | None = None,
|
|
712
|
+
) -> None:
|
|
713
|
+
self._id_factory = id_factory or (lambda: f"replay-{uuid.uuid4().hex}")
|
|
714
|
+
self._event_id_factory = event_id_factory or (
|
|
715
|
+
lambda: f"event-{uuid.uuid4().hex}"
|
|
716
|
+
)
|
|
717
|
+
self._entries: dict[str, _RuntimeReplayBinding] = {}
|
|
718
|
+
self._lock = threading.RLock()
|
|
719
|
+
self._metrics = metrics or ReplayMetricLedger()
|
|
720
|
+
|
|
721
|
+
def capture_at_head(
|
|
722
|
+
self,
|
|
723
|
+
trace: TraceLog,
|
|
724
|
+
agent_cursor: AgentCursor,
|
|
725
|
+
*,
|
|
726
|
+
agent_state_store: AgentStateStore,
|
|
727
|
+
adapter: ReplayAdapterV2,
|
|
728
|
+
replaces_replay_point_id: str | None = None,
|
|
729
|
+
) -> ReplayPoint:
|
|
730
|
+
if replayable_episode_v2_mode(adapter) is None:
|
|
731
|
+
raise TypeError(
|
|
732
|
+
"adapter does not implement the Replayable Episode V2 protocol"
|
|
733
|
+
)
|
|
734
|
+
capture_started = time.perf_counter()
|
|
735
|
+
self._metrics.increment("capture_attempt_count")
|
|
736
|
+
replay_point_id = _required_text(self._id_factory(), "replay_point_id")
|
|
737
|
+
event_id = _required_text(self._event_id_factory(), "event_id")
|
|
738
|
+
if replaces_replay_point_id is not None:
|
|
739
|
+
replaces_replay_point_id = _required_text(
|
|
740
|
+
replaces_replay_point_id,
|
|
741
|
+
"replaces_replay_point_id",
|
|
742
|
+
)
|
|
743
|
+
self._metrics.increment("replacement_capture_attempt_count")
|
|
744
|
+
agent_state_key: object | None = None
|
|
745
|
+
lease: AdapterStateLease | None = None
|
|
746
|
+
with trace.locked_cursor(agent_cursor) as cursor:
|
|
747
|
+
with self._lock:
|
|
748
|
+
if replay_point_id in self._entries:
|
|
749
|
+
raise ValueError(
|
|
750
|
+
f"replay_point_id is duplicated: {replay_point_id!r}"
|
|
751
|
+
)
|
|
752
|
+
try:
|
|
753
|
+
replaced_point: ReplayPoint | None = None
|
|
754
|
+
if replaces_replay_point_id is not None:
|
|
755
|
+
with self._lock:
|
|
756
|
+
try:
|
|
757
|
+
replaced_point = self._entries[
|
|
758
|
+
replaces_replay_point_id
|
|
759
|
+
].point
|
|
760
|
+
except KeyError as error:
|
|
761
|
+
raise KeyError(
|
|
762
|
+
"unknown replaces_replay_point_id: "
|
|
763
|
+
f"{replaces_replay_point_id}"
|
|
764
|
+
) from error
|
|
765
|
+
if replaced_point.lifecycle != ReplayPointLifecycle.RELEASED:
|
|
766
|
+
raise ValueError(
|
|
767
|
+
"a replacement ReplayPoint requires the replaced point "
|
|
768
|
+
"to be released"
|
|
769
|
+
)
|
|
770
|
+
if replaced_point.cursor.trace_id != cursor.trace_id:
|
|
771
|
+
raise ValueError(
|
|
772
|
+
"a replacement ReplayPoint must remain in the same trace"
|
|
773
|
+
)
|
|
774
|
+
if replaced_point.cursor.branch_id == cursor.branch_id:
|
|
775
|
+
raise ValueError(
|
|
776
|
+
"a replacement ReplayPoint must be captured on a new branch"
|
|
777
|
+
)
|
|
778
|
+
agent_state_key = agent_state_store.capture_agent_state(cursor)
|
|
779
|
+
if agent_state_key is None:
|
|
780
|
+
raise TypeError(
|
|
781
|
+
"capture_agent_state must return an opaque state key"
|
|
782
|
+
)
|
|
783
|
+
lease = adapter.capture_replay_state(
|
|
784
|
+
CaptureReplayStateRequest(
|
|
785
|
+
replay_point_id=replay_point_id,
|
|
786
|
+
cursor=cursor,
|
|
787
|
+
)
|
|
788
|
+
)
|
|
789
|
+
if not isinstance(lease, AdapterStateLease):
|
|
790
|
+
raise TypeError(
|
|
791
|
+
"capture_replay_state must return an AdapterStateLease"
|
|
792
|
+
)
|
|
793
|
+
if (
|
|
794
|
+
replaced_point is not None
|
|
795
|
+
and replaced_point.captured_projection_digest is not None
|
|
796
|
+
and lease.projection_digest is not None
|
|
797
|
+
and replaced_point.captured_projection_digest
|
|
798
|
+
!= lease.projection_digest
|
|
799
|
+
):
|
|
800
|
+
self._metrics.increment("replacement_projection_mismatch_count")
|
|
801
|
+
raise ValueError(
|
|
802
|
+
"replacement ReplayPoint projection does not match the "
|
|
803
|
+
"released point"
|
|
804
|
+
)
|
|
805
|
+
event = TraceEvent(
|
|
806
|
+
event_id=event_id,
|
|
807
|
+
trace_id=trace.trace_id,
|
|
808
|
+
branch_id=trace.branch_id,
|
|
809
|
+
sequence=cursor.next_sequence,
|
|
810
|
+
parent_event_id=cursor.after_event_id,
|
|
811
|
+
kind=TraceEventKind.LIFECYCLE,
|
|
812
|
+
replay_point_id=replay_point_id,
|
|
813
|
+
public_payload={
|
|
814
|
+
"operation": "replay_point_captured",
|
|
815
|
+
"fidelity": lease.fidelity.value,
|
|
816
|
+
"cursor_sequence": cursor.next_sequence,
|
|
817
|
+
"replaces_replay_point_id": replaces_replay_point_id,
|
|
818
|
+
},
|
|
819
|
+
)
|
|
820
|
+
point = ReplayPoint(
|
|
821
|
+
replay_point_id=replay_point_id,
|
|
822
|
+
cursor=cursor,
|
|
823
|
+
fidelity=lease.fidelity,
|
|
824
|
+
captured_projection_digest=lease.projection_digest,
|
|
825
|
+
lifecycle=ReplayPointLifecycle.READY,
|
|
826
|
+
created_event_id=event.event_id,
|
|
827
|
+
replaces_replay_point_id=replaces_replay_point_id,
|
|
828
|
+
)
|
|
829
|
+
entry = _RuntimeReplayBinding(
|
|
830
|
+
point=point,
|
|
831
|
+
agent_state_key=agent_state_key,
|
|
832
|
+
adapter_binding_key=lease.binding_key,
|
|
833
|
+
agent_state_store=agent_state_store,
|
|
834
|
+
adapter=adapter,
|
|
835
|
+
adapter_released=lease.binding_key is None,
|
|
836
|
+
)
|
|
837
|
+
with self._lock:
|
|
838
|
+
if replay_point_id in self._entries:
|
|
839
|
+
raise ValueError(
|
|
840
|
+
f"replay_point_id is duplicated: {replay_point_id!r}"
|
|
841
|
+
)
|
|
842
|
+
trace._validate_append_unlocked(event)
|
|
843
|
+
trace._append_unlocked(event)
|
|
844
|
+
self._entries[replay_point_id] = entry
|
|
845
|
+
self._metrics.increment("capture_success_count")
|
|
846
|
+
if replaces_replay_point_id is not None:
|
|
847
|
+
self._metrics.increment("replacement_capture_success_count")
|
|
848
|
+
return point
|
|
849
|
+
except Exception as error:
|
|
850
|
+
self._metrics.increment("capture_failure_count")
|
|
851
|
+
if replaces_replay_point_id is not None:
|
|
852
|
+
self._metrics.increment("replacement_capture_failure_count")
|
|
853
|
+
cleanup_error: Exception | None = None
|
|
854
|
+
compensation_needed = bool(
|
|
855
|
+
(lease is not None and lease.binding_key is not None)
|
|
856
|
+
or agent_state_key is not None
|
|
857
|
+
)
|
|
858
|
+
if compensation_needed:
|
|
859
|
+
self._metrics.increment("capture_compensation_count")
|
|
860
|
+
if lease is not None and lease.binding_key is not None:
|
|
861
|
+
try:
|
|
862
|
+
adapter.release_replay_state(lease.binding_key)
|
|
863
|
+
except Exception as caught:
|
|
864
|
+
cleanup_error = caught
|
|
865
|
+
self._metrics.increment("orphaned_adapter_binding_count")
|
|
866
|
+
if agent_state_key is not None:
|
|
867
|
+
try:
|
|
868
|
+
agent_state_store.release_agent_state(agent_state_key)
|
|
869
|
+
except Exception as caught:
|
|
870
|
+
cleanup_error = cleanup_error or caught
|
|
871
|
+
self._metrics.increment("orphaned_agent_state_count")
|
|
872
|
+
if cleanup_error is not None:
|
|
873
|
+
self._metrics.increment("capture_compensation_failure_count")
|
|
874
|
+
raise error from cleanup_error
|
|
875
|
+
raise
|
|
876
|
+
finally:
|
|
877
|
+
self._metrics.add_seconds(
|
|
878
|
+
"capture_minutes",
|
|
879
|
+
time.perf_counter() - capture_started,
|
|
880
|
+
)
|
|
881
|
+
|
|
882
|
+
def get(self, replay_point_id: str) -> ReplayPoint:
|
|
883
|
+
with self._lock:
|
|
884
|
+
try:
|
|
885
|
+
return self._entries[replay_point_id].point
|
|
886
|
+
except KeyError as error:
|
|
887
|
+
raise KeyError(f"unknown replay_point_id: {replay_point_id}") from error
|
|
888
|
+
|
|
889
|
+
def records(self) -> tuple[dict[str, Any], ...]:
|
|
890
|
+
with self._lock:
|
|
891
|
+
return tuple(entry.point.to_record() for entry in self._entries.values())
|
|
892
|
+
|
|
893
|
+
def metrics(self) -> dict[str, Any]:
|
|
894
|
+
payload = self._metrics.snapshot()
|
|
895
|
+
with self._lock:
|
|
896
|
+
payload.update(
|
|
897
|
+
{
|
|
898
|
+
"active_replay_point_count": sum(
|
|
899
|
+
entry.point.lifecycle != ReplayPointLifecycle.RELEASED
|
|
900
|
+
for entry in self._entries.values()
|
|
901
|
+
),
|
|
902
|
+
"active_adapter_binding_count": sum(
|
|
903
|
+
not entry.adapter_released for entry in self._entries.values()
|
|
904
|
+
),
|
|
905
|
+
"failed_replay_point_count": sum(
|
|
906
|
+
entry.point.lifecycle == ReplayPointLifecycle.FAILED
|
|
907
|
+
for entry in self._entries.values()
|
|
908
|
+
),
|
|
909
|
+
}
|
|
910
|
+
)
|
|
911
|
+
return payload
|
|
912
|
+
|
|
913
|
+
def select_before(
|
|
914
|
+
self,
|
|
915
|
+
*,
|
|
916
|
+
trace_id: str,
|
|
917
|
+
branch_id: str,
|
|
918
|
+
first_affected_sequence: int,
|
|
919
|
+
minimum_fidelity: ReplayFidelity,
|
|
920
|
+
) -> ReplayPoint:
|
|
921
|
+
"""Choose the nearest ready point before the first affected event.
|
|
922
|
+
|
|
923
|
+
A point meeting the requested fidelity wins. If none does, the
|
|
924
|
+
nearest best-effort point may still support an observed-only replay.
|
|
925
|
+
Non-replayable markers are never selected.
|
|
926
|
+
"""
|
|
927
|
+
|
|
928
|
+
trace_id = _required_text(trace_id, "trace_id")
|
|
929
|
+
branch_id = _required_text(branch_id, "branch_id")
|
|
930
|
+
affected_sequence = int(first_affected_sequence)
|
|
931
|
+
minimum = ReplayFidelity(minimum_fidelity)
|
|
932
|
+
with self._lock:
|
|
933
|
+
candidates = [
|
|
934
|
+
entry.point
|
|
935
|
+
for entry in self._entries.values()
|
|
936
|
+
if entry.point.lifecycle == ReplayPointLifecycle.READY
|
|
937
|
+
and entry.point.cursor.trace_id == trace_id
|
|
938
|
+
and entry.point.cursor.branch_id == branch_id
|
|
939
|
+
and entry.point.cursor.next_sequence <= affected_sequence
|
|
940
|
+
and entry.point.fidelity != ReplayFidelity.NON_REPLAYABLE
|
|
941
|
+
]
|
|
942
|
+
if not candidates:
|
|
943
|
+
raise LookupError(
|
|
944
|
+
"no replayable ReplayPoint exists before the affected event"
|
|
945
|
+
)
|
|
946
|
+
trusted = [
|
|
947
|
+
point for point in candidates if fidelity_meets(point.fidelity, minimum)
|
|
948
|
+
]
|
|
949
|
+
pool = trusted or candidates
|
|
950
|
+
selected = max(pool, key=lambda point: point.cursor.next_sequence)
|
|
951
|
+
self._metrics.increment("replay_point_selection_count")
|
|
952
|
+
if not trusted:
|
|
953
|
+
self._metrics.increment("replay_point_fidelity_fallback_count")
|
|
954
|
+
elif any(
|
|
955
|
+
point.cursor.next_sequence > selected.cursor.next_sequence
|
|
956
|
+
and not fidelity_meets(point.fidelity, minimum)
|
|
957
|
+
for point in candidates
|
|
958
|
+
):
|
|
959
|
+
self._metrics.increment("replay_point_fidelity_filtered_selection_count")
|
|
960
|
+
return selected
|
|
961
|
+
|
|
962
|
+
def run_from(
|
|
963
|
+
self,
|
|
964
|
+
replay_point_id: str,
|
|
965
|
+
*,
|
|
966
|
+
target_branch_id: str,
|
|
967
|
+
minimum_fidelity: ReplayFidelity,
|
|
968
|
+
execute: Callable[[ReplayRestoreResult], Any],
|
|
969
|
+
) -> tuple[ReplayRestoreResult, Any]:
|
|
970
|
+
"""Restore and execute one suffix while holding the point's serial lock."""
|
|
971
|
+
|
|
972
|
+
target_branch_id = _required_text(target_branch_id, "target_branch_id")
|
|
973
|
+
minimum = ReplayFidelity(minimum_fidelity)
|
|
974
|
+
replay_started = time.perf_counter()
|
|
975
|
+
self._metrics.increment("replay_attempt_count")
|
|
976
|
+
try:
|
|
977
|
+
with self._lock:
|
|
978
|
+
try:
|
|
979
|
+
entry = self._entries[replay_point_id]
|
|
980
|
+
except KeyError as error:
|
|
981
|
+
raise KeyError(
|
|
982
|
+
f"unknown replay_point_id: {replay_point_id}"
|
|
983
|
+
) from error
|
|
984
|
+
if entry.point.lifecycle != ReplayPointLifecycle.READY:
|
|
985
|
+
raise RuntimeError(
|
|
986
|
+
"ReplayPoint must be ready before restore: "
|
|
987
|
+
f"{entry.point.lifecycle.value}"
|
|
988
|
+
)
|
|
989
|
+
if entry.point.fidelity == ReplayFidelity.NON_REPLAYABLE:
|
|
990
|
+
raise RuntimeError("non_replayable ReplayPoints cannot be restored")
|
|
991
|
+
if entry.adapter_binding_key is None:
|
|
992
|
+
raise RuntimeError("replayable ReplayPoint has no Adapter binding")
|
|
993
|
+
|
|
994
|
+
with entry.replay_lock:
|
|
995
|
+
with self._lock:
|
|
996
|
+
if entry.point.lifecycle != ReplayPointLifecycle.READY:
|
|
997
|
+
raise RuntimeError(
|
|
998
|
+
"ReplayPoint stopped being ready before restore: "
|
|
999
|
+
f"{entry.point.lifecycle.value}"
|
|
1000
|
+
)
|
|
1001
|
+
request = RestoreReplayStateRequest(
|
|
1002
|
+
replay_point_id=replay_point_id,
|
|
1003
|
+
target_branch_id=target_branch_id,
|
|
1004
|
+
minimum_fidelity=minimum,
|
|
1005
|
+
)
|
|
1006
|
+
restore_started = time.perf_counter()
|
|
1007
|
+
self._metrics.increment("restore_attempt_count")
|
|
1008
|
+
try:
|
|
1009
|
+
receipt = entry.adapter.restore_replay_state(
|
|
1010
|
+
entry.adapter_binding_key,
|
|
1011
|
+
request,
|
|
1012
|
+
)
|
|
1013
|
+
if not isinstance(receipt, RestoreReceipt):
|
|
1014
|
+
raise TypeError(
|
|
1015
|
+
"restore_replay_state must return a RestoreReceipt"
|
|
1016
|
+
)
|
|
1017
|
+
if receipt.achieved_fidelity == ReplayFidelity.NON_REPLAYABLE:
|
|
1018
|
+
raise RuntimeError(
|
|
1019
|
+
"Adapter reported non_replayable while restoring a ReplayPoint"
|
|
1020
|
+
)
|
|
1021
|
+
if not fidelity_meets(
|
|
1022
|
+
entry.point.fidelity,
|
|
1023
|
+
receipt.achieved_fidelity,
|
|
1024
|
+
):
|
|
1025
|
+
raise ValueError(
|
|
1026
|
+
"restore achieved a fidelity stronger than the captured "
|
|
1027
|
+
"ReplayPoint"
|
|
1028
|
+
)
|
|
1029
|
+
except Exception:
|
|
1030
|
+
self._metrics.increment("restore_failure_count")
|
|
1031
|
+
raise
|
|
1032
|
+
else:
|
|
1033
|
+
self._metrics.increment("restore_success_count")
|
|
1034
|
+
if receipt.prefix_reused is True:
|
|
1035
|
+
self._metrics.increment("prefix_reuse_verified_count")
|
|
1036
|
+
elif receipt.prefix_reused is False:
|
|
1037
|
+
self._metrics.increment("prefix_reconstruction_count")
|
|
1038
|
+
else:
|
|
1039
|
+
self._metrics.increment("prefix_reuse_unknown_count")
|
|
1040
|
+
finally:
|
|
1041
|
+
restore_minutes = (time.perf_counter() - restore_started) / 60.0
|
|
1042
|
+
self._metrics.add_seconds(
|
|
1043
|
+
"restore_minutes",
|
|
1044
|
+
time.perf_counter() - restore_started,
|
|
1045
|
+
)
|
|
1046
|
+
|
|
1047
|
+
equivalence_started = time.perf_counter()
|
|
1048
|
+
self._metrics.increment("equivalence_attempt_count")
|
|
1049
|
+
try:
|
|
1050
|
+
report = entry.adapter.validate_replay_equivalence(
|
|
1051
|
+
entry.adapter_binding_key,
|
|
1052
|
+
receipt,
|
|
1053
|
+
)
|
|
1054
|
+
if not isinstance(report, EquivalenceReport):
|
|
1055
|
+
raise TypeError(
|
|
1056
|
+
"validate_replay_equivalence must return an "
|
|
1057
|
+
"EquivalenceReport"
|
|
1058
|
+
)
|
|
1059
|
+
promotion_eligible = _validate_equivalence_contract(
|
|
1060
|
+
entry.point,
|
|
1061
|
+
receipt,
|
|
1062
|
+
report,
|
|
1063
|
+
minimum,
|
|
1064
|
+
)
|
|
1065
|
+
trusted_fidelity = fidelity_meets(
|
|
1066
|
+
report.achieved_fidelity,
|
|
1067
|
+
ReplayFidelity.SEMANTIC_EQUIVALENT,
|
|
1068
|
+
)
|
|
1069
|
+
except Exception:
|
|
1070
|
+
self._metrics.increment("equivalence_failure_count")
|
|
1071
|
+
raise
|
|
1072
|
+
else:
|
|
1073
|
+
if report.equivalent and trusted_fidelity:
|
|
1074
|
+
self._metrics.increment("equivalence_verified_count")
|
|
1075
|
+
else:
|
|
1076
|
+
self._metrics.increment("equivalence_unverified_count")
|
|
1077
|
+
finally:
|
|
1078
|
+
equivalence_minutes = (
|
|
1079
|
+
time.perf_counter() - equivalence_started
|
|
1080
|
+
) / 60.0
|
|
1081
|
+
self._metrics.add_seconds(
|
|
1082
|
+
"equivalence_minutes",
|
|
1083
|
+
time.perf_counter() - equivalence_started,
|
|
1084
|
+
)
|
|
1085
|
+
self._metrics.observe_fidelity(report.achieved_fidelity.value)
|
|
1086
|
+
entry.agent_state_store.restore_agent_state(entry.agent_state_key)
|
|
1087
|
+
restore = ReplayRestoreResult(
|
|
1088
|
+
replay_point_id=replay_point_id,
|
|
1089
|
+
target_branch_id=target_branch_id,
|
|
1090
|
+
achieved_fidelity=report.achieved_fidelity,
|
|
1091
|
+
equivalent=report.equivalent,
|
|
1092
|
+
promotion_eligible=promotion_eligible,
|
|
1093
|
+
prefix_reused=receipt.prefix_reused,
|
|
1094
|
+
report=report,
|
|
1095
|
+
restore_minutes=restore_minutes,
|
|
1096
|
+
equivalence_minutes=equivalence_minutes,
|
|
1097
|
+
suffix_minutes=0.0,
|
|
1098
|
+
replay_wall_minutes=0.0,
|
|
1099
|
+
)
|
|
1100
|
+
suffix_started = time.perf_counter()
|
|
1101
|
+
self._metrics.increment("suffix_attempt_count")
|
|
1102
|
+
suffix_error: Exception | None = None
|
|
1103
|
+
finalization_error: Exception | None = None
|
|
1104
|
+
try:
|
|
1105
|
+
outcome = execute(restore)
|
|
1106
|
+
except Exception as error:
|
|
1107
|
+
suffix_error = error
|
|
1108
|
+
finally:
|
|
1109
|
+
finalizer = getattr(
|
|
1110
|
+
entry.adapter,
|
|
1111
|
+
"finalize_replay_suffix",
|
|
1112
|
+
None,
|
|
1113
|
+
)
|
|
1114
|
+
if callable(finalizer):
|
|
1115
|
+
self._metrics.increment("suffix_finalization_attempt_count")
|
|
1116
|
+
try:
|
|
1117
|
+
finalizer(
|
|
1118
|
+
entry.adapter_binding_key,
|
|
1119
|
+
request,
|
|
1120
|
+
suffix_succeeded=suffix_error is None,
|
|
1121
|
+
)
|
|
1122
|
+
except Exception as error:
|
|
1123
|
+
finalization_error = error
|
|
1124
|
+
self._metrics.increment("suffix_finalization_failure_count")
|
|
1125
|
+
else:
|
|
1126
|
+
self._metrics.increment("suffix_finalization_success_count")
|
|
1127
|
+
suffix_minutes = (time.perf_counter() - suffix_started) / 60.0
|
|
1128
|
+
self._metrics.add_seconds(
|
|
1129
|
+
"suffix_minutes",
|
|
1130
|
+
time.perf_counter() - suffix_started,
|
|
1131
|
+
)
|
|
1132
|
+
if suffix_error is not None:
|
|
1133
|
+
self._metrics.increment("suffix_failure_count")
|
|
1134
|
+
if finalization_error is not None:
|
|
1135
|
+
with self._lock:
|
|
1136
|
+
entry.point = replace(
|
|
1137
|
+
entry.point,
|
|
1138
|
+
lifecycle=ReplayPointLifecycle.FAILED,
|
|
1139
|
+
)
|
|
1140
|
+
self._metrics.increment("replay_point_quarantine_count")
|
|
1141
|
+
raise suffix_error from finalization_error
|
|
1142
|
+
raise suffix_error
|
|
1143
|
+
if finalization_error is not None:
|
|
1144
|
+
self._metrics.increment("suffix_failure_count")
|
|
1145
|
+
with self._lock:
|
|
1146
|
+
entry.point = replace(
|
|
1147
|
+
entry.point,
|
|
1148
|
+
lifecycle=ReplayPointLifecycle.FAILED,
|
|
1149
|
+
)
|
|
1150
|
+
self._metrics.increment("replay_point_quarantine_count")
|
|
1151
|
+
raise finalization_error
|
|
1152
|
+
self._metrics.increment("suffix_success_count")
|
|
1153
|
+
replay_wall_minutes = (time.perf_counter() - replay_started) / 60.0
|
|
1154
|
+
restore = replace(
|
|
1155
|
+
restore,
|
|
1156
|
+
suffix_minutes=suffix_minutes,
|
|
1157
|
+
replay_wall_minutes=replay_wall_minutes,
|
|
1158
|
+
)
|
|
1159
|
+
self._metrics.increment("replay_success_count")
|
|
1160
|
+
return restore, outcome
|
|
1161
|
+
except Exception:
|
|
1162
|
+
self._metrics.increment("replay_failure_count")
|
|
1163
|
+
raise
|
|
1164
|
+
finally:
|
|
1165
|
+
self._metrics.add_seconds(
|
|
1166
|
+
"replay_wall_minutes",
|
|
1167
|
+
time.perf_counter() - replay_started,
|
|
1168
|
+
)
|
|
1169
|
+
|
|
1170
|
+
def run_child_group_from(
|
|
1171
|
+
self,
|
|
1172
|
+
replay_point_id: str,
|
|
1173
|
+
tasks: Sequence[ReplayChildTask],
|
|
1174
|
+
*,
|
|
1175
|
+
parallel: bool = False,
|
|
1176
|
+
max_workers: int | None = None,
|
|
1177
|
+
) -> tuple[ReplayChildExecutionResult, ...]:
|
|
1178
|
+
from .child_replay_runtime import run_child_group_from
|
|
1179
|
+
|
|
1180
|
+
return run_child_group_from(
|
|
1181
|
+
self,
|
|
1182
|
+
replay_point_id,
|
|
1183
|
+
tasks,
|
|
1184
|
+
parallel=parallel,
|
|
1185
|
+
max_workers=max_workers,
|
|
1186
|
+
)
|
|
1187
|
+
|
|
1188
|
+
def run_child_group_settled_from(
|
|
1189
|
+
self,
|
|
1190
|
+
replay_point_id: str,
|
|
1191
|
+
tasks: Sequence[ReplayChildTask],
|
|
1192
|
+
*,
|
|
1193
|
+
parallel: bool = False,
|
|
1194
|
+
max_workers: int | None = None,
|
|
1195
|
+
) -> tuple[ReplayChildExecutionResult | ReplayChildExecutionFailure, ...]:
|
|
1196
|
+
"""Preserve safe sibling results when isolated candidates fail safely."""
|
|
1197
|
+
|
|
1198
|
+
from .child_replay_runtime import run_child_group_from
|
|
1199
|
+
|
|
1200
|
+
return run_child_group_from(
|
|
1201
|
+
self,
|
|
1202
|
+
replay_point_id,
|
|
1203
|
+
tasks,
|
|
1204
|
+
parallel=parallel,
|
|
1205
|
+
max_workers=max_workers,
|
|
1206
|
+
settle_failures=True,
|
|
1207
|
+
)
|
|
1208
|
+
|
|
1209
|
+
def release(self, replay_point_id: str) -> ReplayPoint:
|
|
1210
|
+
release_started = time.perf_counter()
|
|
1211
|
+
self._metrics.increment("release_attempt_count")
|
|
1212
|
+
try:
|
|
1213
|
+
with self._lock:
|
|
1214
|
+
try:
|
|
1215
|
+
entry = self._entries[replay_point_id]
|
|
1216
|
+
except KeyError as error:
|
|
1217
|
+
raise KeyError(
|
|
1218
|
+
f"unknown replay_point_id: {replay_point_id}"
|
|
1219
|
+
) from error
|
|
1220
|
+
if entry.point.lifecycle == ReplayPointLifecycle.RELEASED:
|
|
1221
|
+
self._metrics.increment("release_success_count")
|
|
1222
|
+
return entry.point
|
|
1223
|
+
entry.point = replace(
|
|
1224
|
+
entry.point,
|
|
1225
|
+
lifecycle=ReplayPointLifecycle.RELEASING,
|
|
1226
|
+
)
|
|
1227
|
+
|
|
1228
|
+
adapter_error: Exception | None = None
|
|
1229
|
+
agent_error: Exception | None = None
|
|
1230
|
+
with entry.replay_lock:
|
|
1231
|
+
if not entry.adapter_released and entry.adapter_binding_key is not None:
|
|
1232
|
+
try:
|
|
1233
|
+
entry.adapter.release_replay_state(entry.adapter_binding_key)
|
|
1234
|
+
entry.adapter_released = True
|
|
1235
|
+
except Exception as error:
|
|
1236
|
+
adapter_error = error
|
|
1237
|
+
if not entry.agent_released:
|
|
1238
|
+
try:
|
|
1239
|
+
entry.agent_state_store.release_agent_state(
|
|
1240
|
+
entry.agent_state_key
|
|
1241
|
+
)
|
|
1242
|
+
entry.agent_released = True
|
|
1243
|
+
except Exception as error:
|
|
1244
|
+
agent_error = error
|
|
1245
|
+
|
|
1246
|
+
with self._lock:
|
|
1247
|
+
entry.point = replace(
|
|
1248
|
+
entry.point,
|
|
1249
|
+
lifecycle=(
|
|
1250
|
+
ReplayPointLifecycle.RELEASED
|
|
1251
|
+
if entry.adapter_released and entry.agent_released
|
|
1252
|
+
else ReplayPointLifecycle.FAILED
|
|
1253
|
+
),
|
|
1254
|
+
)
|
|
1255
|
+
point = entry.point
|
|
1256
|
+
if adapter_error is not None:
|
|
1257
|
+
if agent_error is not None:
|
|
1258
|
+
raise adapter_error from agent_error
|
|
1259
|
+
raise adapter_error
|
|
1260
|
+
if agent_error is not None:
|
|
1261
|
+
raise agent_error
|
|
1262
|
+
self._metrics.increment("release_success_count")
|
|
1263
|
+
return point
|
|
1264
|
+
except Exception:
|
|
1265
|
+
self._metrics.increment("release_failure_count")
|
|
1266
|
+
raise
|
|
1267
|
+
finally:
|
|
1268
|
+
self._metrics.add_seconds(
|
|
1269
|
+
"release_minutes",
|
|
1270
|
+
time.perf_counter() - release_started,
|
|
1271
|
+
)
|