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,1124 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import threading
|
|
5
|
+
import time
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any, Callable, Mapping, Sequence
|
|
9
|
+
|
|
10
|
+
from .episode_runtime import (
|
|
11
|
+
RECORD_KIND_STANDALONE,
|
|
12
|
+
build_episode_execution_units,
|
|
13
|
+
build_router_probes,
|
|
14
|
+
combine_execution_metrics,
|
|
15
|
+
execute_episode_group,
|
|
16
|
+
item_concurrency_keys,
|
|
17
|
+
recoverable_episode_mode,
|
|
18
|
+
summarize_execution_records,
|
|
19
|
+
)
|
|
20
|
+
from .harness_checkpoint import (
|
|
21
|
+
HARNESS_CHECKPOINT_SCHEMA_VERSION,
|
|
22
|
+
build_arm_checkpoint,
|
|
23
|
+
load_arm_checkpoint,
|
|
24
|
+
write_arm_checkpoint,
|
|
25
|
+
)
|
|
26
|
+
from .router_state import (
|
|
27
|
+
advance_epoch,
|
|
28
|
+
family_routing_terms,
|
|
29
|
+
initial_router_state,
|
|
30
|
+
observe_epoch,
|
|
31
|
+
)
|
|
32
|
+
from .worker_branch_plan import build_explore_worker_branch_plan
|
|
33
|
+
|
|
34
|
+
"""Software-agnostic exploration harness runtime.
|
|
35
|
+
|
|
36
|
+
This module is the generic half of a two-layer design:
|
|
37
|
+
|
|
38
|
+
- **This layer (LoopX)** owns scheduling and bookkeeping: independent
|
|
39
|
+
wall-clock budget arms, a shared work queue with concurrency keys, the
|
|
40
|
+
first-seen novelty ledger with adapter-injected flag weights, router-state
|
|
41
|
+
feedback, frontier *scheduling* (when to spend speculative slots and on
|
|
42
|
+
which seed families), anytime checkpoints, and generic metric aggregation.
|
|
43
|
+
|
|
44
|
+
- **The experiment layer** (driven by a Claude/Codex session) owns every
|
|
45
|
+
domain fact: how to execute a work item, what the observation keys mean,
|
|
46
|
+
how failures are classified, and -- crucially -- WHAT the speculative
|
|
47
|
+
variants are. Variant strategy is authored by the agent as a data file
|
|
48
|
+
(the variant catalog), never generated here.
|
|
49
|
+
|
|
50
|
+
Hard rules, in the spirit of the wider LoopX design:
|
|
51
|
+
- this module never calls an LLM and never imports domain vocabulary;
|
|
52
|
+
- variant intelligence travels through files: the harness EMITS
|
|
53
|
+
``frontier_requests_<arm>.json`` (seed text + seen-observation digest) and
|
|
54
|
+
CONSUMES ``variant_catalog.json`` entries the agent wrote after reading
|
|
55
|
+
those requests -- the same packet-out / decision-in contract the LoopX
|
|
56
|
+
planner uses;
|
|
57
|
+
- novelty is only ever credited from observation keys and weighted flags the
|
|
58
|
+
adapter reports; specs carry intent, not novelty.
|
|
59
|
+
|
|
60
|
+
Adapter protocol (duck-typed; see the experiment layer for a reference
|
|
61
|
+
implementation):
|
|
62
|
+
|
|
63
|
+
- ``list_seed_items() -> list[dict]``: fresh seed work items each epoch,
|
|
64
|
+
each with ``item_id``, ``text``, ``family``; optional ``payload``.
|
|
65
|
+
- ``execute(item, *, run_root, arm, epoch, branch_id) -> dict``: run one work
|
|
66
|
+
item and return an observation record with ``item_id``, ``family``,
|
|
67
|
+
``observation_keys`` (list[str]), ``weighted_flags`` ({name: weight} for
|
|
68
|
+
true flags), ``accepted`` (bool), ``duration_minutes`` (float),
|
|
69
|
+
``retryable_infra_error`` (bool), plus any adapter-private fields.
|
|
70
|
+
- ``compile_variant(spec, seed_item) -> dict``: turn an agent-authored
|
|
71
|
+
variant spec into an executable work item (``is_variant`` True,
|
|
72
|
+
``variant_spec`` attached, own ``concurrency_key``).
|
|
73
|
+
- ``flag_weights`` is implicit: the adapter reports weights per record, so
|
|
74
|
+
the harness never holds a domain weight table.
|
|
75
|
+
- ``item_failure_policy`` may be set to ``"fatal"`` on the adapter to make
|
|
76
|
+
execution exceptions propagate; the default ``"record"`` policy emits a
|
|
77
|
+
zero-value structured observation and keeps independent lanes running.
|
|
78
|
+
- Adapters that can checkpoint an expensive shared execution prefix may
|
|
79
|
+
optionally implement all three recoverable-episode methods:
|
|
80
|
+
``prepare_episode_group(seed_item, episode_items, **context)`` returns
|
|
81
|
+
``{handle, prefix_record, checkpoint_ref?}``, ``execute_episode(handle,
|
|
82
|
+
item, **context)`` returns a suffix-only observation, and
|
|
83
|
+
``release_episode_group(handle, **context)`` releases adapter-owned state.
|
|
84
|
+
Returning ``None`` from prepare is an explicit, side-effect-free fallback
|
|
85
|
+
to legacy ``execute`` for that group (the only case where an episode
|
|
86
|
+
adapter needs ``execute`` at all). The adapter owns all restore mechanics.
|
|
87
|
+
Distinct groups run concurrently on separate workers, so the three methods
|
|
88
|
+
must be thread-safe across groups; concurrently active handles must not alias
|
|
89
|
+
mutable state, while safe sequential or immutable reuse remains valid.
|
|
90
|
+
Malformed grouping inputs (empty or duplicate seed/variant ids, id
|
|
91
|
+
collisions, or variants naming an unknown seed) degrade through the item
|
|
92
|
+
failure policy like any execution error: structured records under ``record``,
|
|
93
|
+
or a pre-dispatch raised error under ``fatal``.
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
HARNESS_ARM_SCHEMA_VERSION = "loopx_explore_harness_arm_v0"
|
|
98
|
+
VARIANT_CATALOG_SCHEMA_VERSION = "loopx_explore_variant_catalog_v0"
|
|
99
|
+
FRONTIER_REQUESTS_SCHEMA_VERSION = "loopx_explore_frontier_requests_v0"
|
|
100
|
+
HARNESS_RUNTIME_POLICY_SCHEMA_VERSION = "loopx_explore_harness_runtime_policy_v0"
|
|
101
|
+
|
|
102
|
+
class ItemFailurePolicy(str, Enum):
|
|
103
|
+
RECORD = "record"
|
|
104
|
+
FATAL = "fatal"
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
ITEM_FAILURE_POLICY_RECORD = ItemFailurePolicy.RECORD.value
|
|
108
|
+
ITEM_FAILURE_POLICY_FATAL = ItemFailurePolicy.FATAL.value
|
|
109
|
+
ITEM_FAILURE_POLICIES = {policy.value for policy in ItemFailurePolicy}
|
|
110
|
+
|
|
111
|
+
DEFAULT_OBSERVATION_WEIGHT = 1.0
|
|
112
|
+
_MIN_DURATION_MINUTES = 0.05
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _now_perf_minutes(start: float) -> float:
|
|
116
|
+
return (time.perf_counter() - start) / 60.0
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _write_json(path: Path, payload: Any) -> None:
|
|
120
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
121
|
+
path.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _read_json(path: Path) -> Any:
|
|
125
|
+
if not path.exists():
|
|
126
|
+
return None
|
|
127
|
+
try:
|
|
128
|
+
return json.loads(path.read_text(encoding="utf-8-sig"))
|
|
129
|
+
except json.JSONDecodeError:
|
|
130
|
+
return None
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _normalize_item_failure_policy(value: Any) -> str:
|
|
134
|
+
if isinstance(value, ItemFailurePolicy):
|
|
135
|
+
return value.value
|
|
136
|
+
policy = str(value or ITEM_FAILURE_POLICY_RECORD).strip().lower().replace("_", "-")
|
|
137
|
+
if policy not in ITEM_FAILURE_POLICIES:
|
|
138
|
+
raise ValueError(
|
|
139
|
+
"item_failure_policy must be one of: "
|
|
140
|
+
+ ", ".join(sorted(ITEM_FAILURE_POLICIES))
|
|
141
|
+
)
|
|
142
|
+
return policy
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _adapter_error_record(
|
|
146
|
+
item: Mapping[str, Any],
|
|
147
|
+
error: Exception,
|
|
148
|
+
*,
|
|
149
|
+
duration_minutes: float,
|
|
150
|
+
) -> dict[str, Any]:
|
|
151
|
+
message = " ".join(str(error).split())
|
|
152
|
+
spec = item.get("variant_spec")
|
|
153
|
+
return {
|
|
154
|
+
"item_id": item.get("item_id"),
|
|
155
|
+
"family": item.get("family"),
|
|
156
|
+
"is_variant": bool(item.get("is_variant")),
|
|
157
|
+
# The error factory must never raise itself: a malformed variant_spec
|
|
158
|
+
# would otherwise escalate one recordable failure into a lost lane.
|
|
159
|
+
"variant_spec_id": (
|
|
160
|
+
spec.get("spec_id")
|
|
161
|
+
if item.get("is_variant") and isinstance(spec, Mapping)
|
|
162
|
+
else None
|
|
163
|
+
),
|
|
164
|
+
"execution_status": "adapter_error",
|
|
165
|
+
"item_failure_policy": ITEM_FAILURE_POLICY_RECORD,
|
|
166
|
+
"accepted": False,
|
|
167
|
+
"observation_keys": [],
|
|
168
|
+
"weighted_flags": {},
|
|
169
|
+
"duration_minutes": max(_MIN_DURATION_MINUTES, float(duration_minutes)),
|
|
170
|
+
"retryable_infra_error": bool(
|
|
171
|
+
getattr(error, "retryable_infra_error", False)
|
|
172
|
+
),
|
|
173
|
+
"adapter_error": {
|
|
174
|
+
"schema_version": "loopx_explore_adapter_error_v0",
|
|
175
|
+
"type": type(error).__name__,
|
|
176
|
+
"message": message[:240],
|
|
177
|
+
},
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
class NoveltyLedger:
|
|
182
|
+
"""First-seen ledger: raw observation keys global, flags family-scoped.
|
|
183
|
+
|
|
184
|
+
Raw keys are ledgered globally so relabeling an item under a different
|
|
185
|
+
family can never re-mint an already-seen key; weighted flags are
|
|
186
|
+
family-scoped facts. Weights come from the record itself (adapter-owned).
|
|
187
|
+
"""
|
|
188
|
+
|
|
189
|
+
def __init__(self) -> None:
|
|
190
|
+
self.seen: set[str] = set()
|
|
191
|
+
|
|
192
|
+
def raw_value(self, record: Mapping[str, Any]) -> float:
|
|
193
|
+
keys = record.get("observation_keys") or []
|
|
194
|
+
flags = record.get("weighted_flags") or {}
|
|
195
|
+
return DEFAULT_OBSERVATION_WEIGHT * len(keys) + sum(
|
|
196
|
+
float(weight) for weight in flags.values()
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
def credit(self, record: Mapping[str, Any]) -> dict[str, float]:
|
|
200
|
+
family = str(record.get("family") or "general")
|
|
201
|
+
novel_value = 0.0
|
|
202
|
+
novel_keys = 0
|
|
203
|
+
for key in record.get("observation_keys") or []:
|
|
204
|
+
key = str(key)
|
|
205
|
+
if key not in self.seen:
|
|
206
|
+
self.seen.add(key)
|
|
207
|
+
novel_value += DEFAULT_OBSERVATION_WEIGHT
|
|
208
|
+
novel_keys += 1
|
|
209
|
+
for flag, weight in (record.get("weighted_flags") or {}).items():
|
|
210
|
+
pseudo = f"flag:{family}:{flag}"
|
|
211
|
+
if pseudo not in self.seen:
|
|
212
|
+
self.seen.add(pseudo)
|
|
213
|
+
novel_value += float(weight)
|
|
214
|
+
return {"novel_value": novel_value, "novel_observation_keys": novel_keys}
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def run_queue_epoch(
|
|
218
|
+
items: Sequence[Mapping[str, Any]],
|
|
219
|
+
*,
|
|
220
|
+
execute: Callable[..., dict[str, Any] | Sequence[dict[str, Any]]],
|
|
221
|
+
worker_count: int,
|
|
222
|
+
run_root: Path,
|
|
223
|
+
arm: str,
|
|
224
|
+
epoch: int,
|
|
225
|
+
stagger_seconds: float = 0.0,
|
|
226
|
+
item_failure_policy: str = ITEM_FAILURE_POLICY_RECORD,
|
|
227
|
+
) -> list[dict[str, Any]]:
|
|
228
|
+
"""Drain one epoch's work items through a shared pull queue.
|
|
229
|
+
|
|
230
|
+
Items may declare one ``concurrency_key`` or several ``concurrency_keys``.
|
|
231
|
+
Any overlap with an in-flight item waits until all keys free up (the generic
|
|
232
|
+
form of "same write scope must serialize"); disjoint items run concurrently
|
|
233
|
+
up to ``worker_count`` pullers. Returns per-worker lane records.
|
|
234
|
+
"""
|
|
235
|
+
|
|
236
|
+
failure_policy = _normalize_item_failure_policy(item_failure_policy)
|
|
237
|
+
lock = threading.Lock()
|
|
238
|
+
pending: list[dict[str, Any]] = [dict(item) for item in items]
|
|
239
|
+
in_flight: set[str] = set()
|
|
240
|
+
|
|
241
|
+
def pull_next() -> dict[str, Any] | None:
|
|
242
|
+
with lock:
|
|
243
|
+
for index, item in enumerate(pending):
|
|
244
|
+
keys = item_concurrency_keys(item)
|
|
245
|
+
if any(key in in_flight for key in keys):
|
|
246
|
+
continue
|
|
247
|
+
pending.pop(index)
|
|
248
|
+
in_flight.update(keys)
|
|
249
|
+
return item
|
|
250
|
+
return None
|
|
251
|
+
|
|
252
|
+
def worker(worker_index: int) -> dict[str, Any]:
|
|
253
|
+
branch_id = f"queue_worker_{worker_index + 1:02d}"
|
|
254
|
+
started_at = time.perf_counter()
|
|
255
|
+
if worker_index and stagger_seconds > 0:
|
|
256
|
+
time.sleep(min(30.0, worker_index * stagger_seconds))
|
|
257
|
+
results: list[dict[str, Any]] = []
|
|
258
|
+
while True:
|
|
259
|
+
item = pull_next()
|
|
260
|
+
if item is None:
|
|
261
|
+
with lock:
|
|
262
|
+
if not pending:
|
|
263
|
+
break
|
|
264
|
+
time.sleep(0.5)
|
|
265
|
+
continue
|
|
266
|
+
keys = item_concurrency_keys(item)
|
|
267
|
+
item_started = time.perf_counter()
|
|
268
|
+
try:
|
|
269
|
+
outcome = execute(
|
|
270
|
+
item, run_root=run_root, arm=arm, epoch=epoch, branch_id=branch_id
|
|
271
|
+
)
|
|
272
|
+
if isinstance(outcome, Mapping):
|
|
273
|
+
batch = [dict(outcome)]
|
|
274
|
+
elif isinstance(outcome, Sequence) and not isinstance(
|
|
275
|
+
outcome, (str, bytes)
|
|
276
|
+
):
|
|
277
|
+
batch = []
|
|
278
|
+
for candidate in outcome:
|
|
279
|
+
if not isinstance(candidate, Mapping):
|
|
280
|
+
raise TypeError(
|
|
281
|
+
"queue execute sequences must contain dict observation records"
|
|
282
|
+
)
|
|
283
|
+
batch.append(dict(candidate))
|
|
284
|
+
else:
|
|
285
|
+
raise TypeError(
|
|
286
|
+
"queue execute must return a dict or sequence of dict observations"
|
|
287
|
+
)
|
|
288
|
+
if not batch:
|
|
289
|
+
raise TypeError("queue execute must return at least one observation")
|
|
290
|
+
for record in batch:
|
|
291
|
+
record.setdefault("item_id", item.get("item_id"))
|
|
292
|
+
record.setdefault("family", item.get("family"))
|
|
293
|
+
record.setdefault("is_variant", bool(item.get("is_variant")))
|
|
294
|
+
if item.get("is_variant"):
|
|
295
|
+
record.setdefault(
|
|
296
|
+
"variant_spec_id",
|
|
297
|
+
(item.get("variant_spec") or {}).get("spec_id"),
|
|
298
|
+
)
|
|
299
|
+
except Exception as error:
|
|
300
|
+
if failure_policy == ITEM_FAILURE_POLICY_FATAL:
|
|
301
|
+
raise
|
|
302
|
+
batch = [
|
|
303
|
+
_adapter_error_record(
|
|
304
|
+
item,
|
|
305
|
+
error,
|
|
306
|
+
duration_minutes=_now_perf_minutes(item_started),
|
|
307
|
+
)
|
|
308
|
+
]
|
|
309
|
+
finally:
|
|
310
|
+
if keys:
|
|
311
|
+
with lock:
|
|
312
|
+
in_flight.difference_update(keys)
|
|
313
|
+
results.extend(batch)
|
|
314
|
+
return {
|
|
315
|
+
"branch_id": branch_id,
|
|
316
|
+
"launch_index": worker_index,
|
|
317
|
+
"wall_minutes": round(_now_perf_minutes(started_at), 3),
|
|
318
|
+
"item_ids": [r.get("item_id") for r in results],
|
|
319
|
+
"families": [r.get("family") for r in results],
|
|
320
|
+
"results": results,
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
thread_count = max(1, min(int(worker_count), len(pending) or 1))
|
|
324
|
+
import concurrent.futures
|
|
325
|
+
|
|
326
|
+
with concurrent.futures.ThreadPoolExecutor(max_workers=thread_count) as pool:
|
|
327
|
+
futures = [pool.submit(worker, index) for index in range(thread_count)]
|
|
328
|
+
lanes = [future.result() for future in concurrent.futures.as_completed(futures)]
|
|
329
|
+
lanes.sort(key=lambda lane: str(lane.get("branch_id")))
|
|
330
|
+
return lanes
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def _frontier_gain(router_state: Mapping[str, Any] | None, family: str, *, epoch: int) -> float:
|
|
334
|
+
"""Expected value of spending a speculative slot near this family.
|
|
335
|
+
|
|
336
|
+
Generic signals only: whether the base loop has probed the family yet,
|
|
337
|
+
its residual novelty pressure, and a small age bonus. Field names track
|
|
338
|
+
the router_state family schema; the lookup accepts both the raw family
|
|
339
|
+
id and adapter-prefixed forms (e.g. 'scope:artifacts/<family>').
|
|
340
|
+
"""
|
|
341
|
+
|
|
342
|
+
stats: Mapping[str, Any] = {}
|
|
343
|
+
families = (router_state or {}).get("families") if isinstance(router_state, Mapping) else None
|
|
344
|
+
if isinstance(families, Mapping):
|
|
345
|
+
exact = families.get(family)
|
|
346
|
+
if isinstance(exact, Mapping):
|
|
347
|
+
stats = exact
|
|
348
|
+
else:
|
|
349
|
+
suffix = "/" + str(family)
|
|
350
|
+
for key, value in families.items():
|
|
351
|
+
if isinstance(value, Mapping) and str(key).endswith(suffix):
|
|
352
|
+
stats = value
|
|
353
|
+
break
|
|
354
|
+
score = 1.0
|
|
355
|
+
runs = float(stats.get("runs") or 0.0)
|
|
356
|
+
if runs <= 0:
|
|
357
|
+
score += 1.25
|
|
358
|
+
else:
|
|
359
|
+
novelty_rate = stats.get("novelty_rate")
|
|
360
|
+
if novelty_rate is not None:
|
|
361
|
+
score += min(1.25, max(0.0, float(novelty_rate)) * 1.25)
|
|
362
|
+
accept_rate = stats.get("accept_rate_ema")
|
|
363
|
+
if accept_rate is not None and float(accept_rate) <= 0.5:
|
|
364
|
+
score += 0.25
|
|
365
|
+
score += min(0.5, max(0, int(epoch) - 1) * 0.03)
|
|
366
|
+
return round(score, 4)
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
class VariantCatalog:
|
|
370
|
+
"""Agent-authored variant specs + per-arm consumption bookkeeping.
|
|
371
|
+
|
|
372
|
+
Catalog file schema (written by the experiment agent, never by code):
|
|
373
|
+
``{"schema_version": ..., "specs": [{"spec_id", "seed_family",
|
|
374
|
+
"intent", "ops": [...adapter-defined...], "key_prefix",
|
|
375
|
+
"seed_item_id"?, "priority"?}]}``. The harness treats ``ops`` as opaque.
|
|
376
|
+
"""
|
|
377
|
+
|
|
378
|
+
def __init__(self, catalog_path: Path, consumption_path: Path) -> None:
|
|
379
|
+
self.catalog_path = Path(catalog_path)
|
|
380
|
+
self.consumption_path = Path(consumption_path)
|
|
381
|
+
consumed = _read_json(self.consumption_path)
|
|
382
|
+
self.consumed: set[str] = set(consumed or [])
|
|
383
|
+
|
|
384
|
+
def _specs(self) -> list[dict[str, Any]]:
|
|
385
|
+
payload = _read_json(self.catalog_path)
|
|
386
|
+
specs = (payload or {}).get("specs") if isinstance(payload, Mapping) else None
|
|
387
|
+
return [dict(spec) for spec in specs or [] if spec.get("spec_id")]
|
|
388
|
+
|
|
389
|
+
def pending_for(self, family: str) -> list[dict[str, Any]]:
|
|
390
|
+
return [
|
|
391
|
+
spec
|
|
392
|
+
for spec in self._specs()
|
|
393
|
+
if str(spec.get("seed_family") or "") == str(family)
|
|
394
|
+
and str(spec.get("spec_id")) not in self.consumed
|
|
395
|
+
]
|
|
396
|
+
|
|
397
|
+
def pending_count(self) -> int:
|
|
398
|
+
return len([s for s in self._specs() if str(s.get("spec_id")) not in self.consumed])
|
|
399
|
+
|
|
400
|
+
def consume(self, spec_id: str) -> None:
|
|
401
|
+
self.consumed.add(str(spec_id))
|
|
402
|
+
_write_json(self.consumption_path, sorted(self.consumed))
|
|
403
|
+
|
|
404
|
+
def restore_consumed(self, spec_ids: Sequence[Any]) -> None:
|
|
405
|
+
self.consumed = {str(spec_id) for spec_id in spec_ids if str(spec_id).strip()}
|
|
406
|
+
_write_json(self.consumption_path, sorted(self.consumed))
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def select_frontier_work(
|
|
410
|
+
seed_items: Sequence[Mapping[str, Any]],
|
|
411
|
+
*,
|
|
412
|
+
catalog: VariantCatalog,
|
|
413
|
+
adapter: Any,
|
|
414
|
+
router_state: Mapping[str, Any] | None,
|
|
415
|
+
epoch: int,
|
|
416
|
+
max_lanes: int,
|
|
417
|
+
) -> tuple[list[dict[str, Any]], dict[str, Any]]:
|
|
418
|
+
"""Pick up to ``max_lanes`` agent-authored variant specs to execute.
|
|
419
|
+
|
|
420
|
+
Seeds are ranked by the generic frontier gain with an epoch-rotating
|
|
421
|
+
tie-break (multiplicative permutation over a prime modulus) so equal
|
|
422
|
+
gains spread across families over epochs instead of freezing on an
|
|
423
|
+
alphabetical prefix. Spec choice within a family follows the agent's
|
|
424
|
+
``priority`` then catalog order. The harness only ever schedules specs;
|
|
425
|
+
it cannot invent them.
|
|
426
|
+
"""
|
|
427
|
+
|
|
428
|
+
import hashlib
|
|
429
|
+
|
|
430
|
+
def _hash(value: str) -> int:
|
|
431
|
+
return int(hashlib.sha1(value.encode("utf-8")).hexdigest()[:8], 16)
|
|
432
|
+
|
|
433
|
+
by_family: dict[str, Mapping[str, Any]] = {}
|
|
434
|
+
for item in seed_items:
|
|
435
|
+
family = str(item.get("family") or "")
|
|
436
|
+
if family and family not in by_family:
|
|
437
|
+
by_family[family] = item
|
|
438
|
+
ranked = sorted(
|
|
439
|
+
by_family.items(),
|
|
440
|
+
key=lambda pair: (
|
|
441
|
+
-_frontier_gain(router_state, pair[0], epoch=epoch),
|
|
442
|
+
(_hash(pair[0]) * (2 * int(epoch) + 1)) % 1_000_003,
|
|
443
|
+
pair[0],
|
|
444
|
+
),
|
|
445
|
+
)
|
|
446
|
+
selected: list[dict[str, Any]] = []
|
|
447
|
+
audit_specs: list[dict[str, Any]] = []
|
|
448
|
+
for family, seed in ranked:
|
|
449
|
+
if len(selected) >= max(0, int(max_lanes)):
|
|
450
|
+
break
|
|
451
|
+
pending = sorted(
|
|
452
|
+
catalog.pending_for(family),
|
|
453
|
+
key=lambda spec: (-float(spec.get("priority") or 0.0), str(spec.get("spec_id"))),
|
|
454
|
+
)
|
|
455
|
+
if not pending:
|
|
456
|
+
continue
|
|
457
|
+
spec = pending[0]
|
|
458
|
+
item = adapter.compile_variant(spec, dict(seed))
|
|
459
|
+
item.setdefault("is_variant", True)
|
|
460
|
+
item.setdefault("variant_spec", spec)
|
|
461
|
+
item.setdefault("family", family)
|
|
462
|
+
item.setdefault("seed_item_id", seed.get("item_id"))
|
|
463
|
+
item.setdefault(
|
|
464
|
+
"concurrency_key", f"{family}:variant:{spec.get('spec_id')}"
|
|
465
|
+
)
|
|
466
|
+
catalog.consume(str(spec.get("spec_id")))
|
|
467
|
+
selected.append(item)
|
|
468
|
+
audit_specs.append(
|
|
469
|
+
{
|
|
470
|
+
"spec_id": spec.get("spec_id"),
|
|
471
|
+
"seed_family": family,
|
|
472
|
+
"intent": spec.get("intent"),
|
|
473
|
+
"gain": _frontier_gain(router_state, family, epoch=epoch),
|
|
474
|
+
}
|
|
475
|
+
)
|
|
476
|
+
audit = {
|
|
477
|
+
"requested_lanes": int(max_lanes),
|
|
478
|
+
"admitted": len(selected),
|
|
479
|
+
"catalog_pending_after": catalog.pending_count(),
|
|
480
|
+
"specs": audit_specs,
|
|
481
|
+
}
|
|
482
|
+
return selected, audit
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
def emit_frontier_requests(
|
|
486
|
+
path: Path,
|
|
487
|
+
*,
|
|
488
|
+
arm_key: str,
|
|
489
|
+
epoch: int,
|
|
490
|
+
seed_items: Sequence[Mapping[str, Any]],
|
|
491
|
+
ledger: NoveltyLedger,
|
|
492
|
+
catalog: VariantCatalog,
|
|
493
|
+
router_state: Mapping[str, Any] | None,
|
|
494
|
+
per_family_key_cap: int = 24,
|
|
495
|
+
) -> None:
|
|
496
|
+
"""Publish what the harness NEEDS from the experiment agent.
|
|
497
|
+
|
|
498
|
+
The agent (a Claude/Codex session driving the experiment) reads this
|
|
499
|
+
packet -- seed text plus a digest of already-seen observation keys and
|
|
500
|
+
exhausted specs -- and appends new specs to the variant catalog. This is
|
|
501
|
+
the LoopX interaction pattern: packet out, decision in, no LLM calls
|
|
502
|
+
from code.
|
|
503
|
+
"""
|
|
504
|
+
|
|
505
|
+
families: dict[str, dict[str, Any]] = {}
|
|
506
|
+
for item in seed_items:
|
|
507
|
+
family = str(item.get("family") or "")
|
|
508
|
+
if not family or family in families:
|
|
509
|
+
continue
|
|
510
|
+
seen_for_family = sorted(
|
|
511
|
+
key
|
|
512
|
+
for key in ledger.seen
|
|
513
|
+
if not key.startswith("flag:") or key.startswith(f"flag:{family}:")
|
|
514
|
+
)[: max(1, per_family_key_cap)]
|
|
515
|
+
families[family] = {
|
|
516
|
+
"family": family,
|
|
517
|
+
"seed_item_id": item.get("item_id"),
|
|
518
|
+
"seed_text": item.get("text"),
|
|
519
|
+
"pending_spec_count": len(catalog.pending_for(family)),
|
|
520
|
+
"frontier_gain": _frontier_gain(router_state, family, epoch=epoch),
|
|
521
|
+
"seen_keys_sample": seen_for_family,
|
|
522
|
+
}
|
|
523
|
+
_write_json(
|
|
524
|
+
path,
|
|
525
|
+
{
|
|
526
|
+
"schema_version": FRONTIER_REQUESTS_SCHEMA_VERSION,
|
|
527
|
+
"arm": arm_key,
|
|
528
|
+
"epoch": int(epoch),
|
|
529
|
+
"total_seen_keys": len(ledger.seen),
|
|
530
|
+
"catalog_pending_total": catalog.pending_count(),
|
|
531
|
+
"families": sorted(families.values(), key=lambda f: -float(f["frontier_gain"])),
|
|
532
|
+
"how_to_respond": (
|
|
533
|
+
"Append specs to the variant catalog file: {spec_id, seed_family, "
|
|
534
|
+
"intent, ops (adapter-defined), key_prefix, priority}. The harness "
|
|
535
|
+
"schedules them by frontier gain; it never invents specs."
|
|
536
|
+
),
|
|
537
|
+
},
|
|
538
|
+
)
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
def plan_round_robin(seed_items: Sequence[Mapping[str, Any]], **_: Any) -> list[dict[str, Any]]:
|
|
542
|
+
"""Baseline planner: every seed, catalog untouched, no ordering signal."""
|
|
543
|
+
|
|
544
|
+
return [dict(item) for item in seed_items]
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
def plan_router_no_prune(
|
|
548
|
+
seed_items: Sequence[Mapping[str, Any]],
|
|
549
|
+
*,
|
|
550
|
+
goal_id: str,
|
|
551
|
+
agent_id: str,
|
|
552
|
+
worker_count: int,
|
|
553
|
+
router_state: Mapping[str, Any] | None,
|
|
554
|
+
load_profile: Mapping[str, Any] | None,
|
|
555
|
+
harness_profile: str = "moe-router",
|
|
556
|
+
branch_fill_policy: str = "confident-prefix",
|
|
557
|
+
) -> list[dict[str, Any]]:
|
|
558
|
+
"""Router-ordered no-prune planner over generic seed items.
|
|
559
|
+
|
|
560
|
+
Uses the LoopX worker-branch planner for VALUE ORDERING only, then
|
|
561
|
+
appends every unselected seed as a ranked tail -- sorting without
|
|
562
|
+
pruning: an executable seed never loses its slot to low confidence.
|
|
563
|
+
"""
|
|
564
|
+
|
|
565
|
+
by_id: dict[str, dict[str, Any]] = {}
|
|
566
|
+
duplicate_items: list[dict[str, Any]] = []
|
|
567
|
+
for item in seed_items:
|
|
568
|
+
item_id = str(item.get("item_id"))
|
|
569
|
+
if item_id in by_id:
|
|
570
|
+
duplicate_items.append(dict(item))
|
|
571
|
+
else:
|
|
572
|
+
by_id[item_id] = dict(item)
|
|
573
|
+
todos = [
|
|
574
|
+
{
|
|
575
|
+
"todo_id": item_id,
|
|
576
|
+
"text": str(item.get("text") or item.get("item_id")),
|
|
577
|
+
"status": "open",
|
|
578
|
+
"task_class": "advancement_task",
|
|
579
|
+
"required_write_scopes": [f"artifacts/{item.get('family')}/**"],
|
|
580
|
+
"index": index,
|
|
581
|
+
}
|
|
582
|
+
for index, (item_id, item) in enumerate(by_id.items())
|
|
583
|
+
]
|
|
584
|
+
plan = build_explore_worker_branch_plan(
|
|
585
|
+
goal_id=goal_id,
|
|
586
|
+
todos=todos,
|
|
587
|
+
agent_id=agent_id,
|
|
588
|
+
# This standalone benchmark arm opts in explicitly and consumes value
|
|
589
|
+
# ordering only; spawn stays disallowed, so the plan carries no
|
|
590
|
+
# suggested claim/lease commands.
|
|
591
|
+
orchestration={"explore_harness": {"enabled": True}},
|
|
592
|
+
worker_width=worker_count,
|
|
593
|
+
harness_profile=harness_profile,
|
|
594
|
+
branch_fill_policy=branch_fill_policy,
|
|
595
|
+
router_state=router_state,
|
|
596
|
+
load_profile=load_profile,
|
|
597
|
+
)
|
|
598
|
+
ordered: list[dict[str, Any]] = []
|
|
599
|
+
used: set[str] = set()
|
|
600
|
+
for branch in plan.get("selected_worker_branches") or []:
|
|
601
|
+
for todo_id in branch.get("todo_ids") or []:
|
|
602
|
+
todo_id = str(todo_id)
|
|
603
|
+
if todo_id in by_id and todo_id not in used:
|
|
604
|
+
ordered.append(by_id[todo_id])
|
|
605
|
+
used.add(todo_id)
|
|
606
|
+
for branch in plan.get("rejected_worker_branches") or []:
|
|
607
|
+
for todo_id in branch.get("todo_ids") or []:
|
|
608
|
+
todo_id = str(todo_id)
|
|
609
|
+
if todo_id in by_id and todo_id not in used:
|
|
610
|
+
ordered.append(by_id[todo_id])
|
|
611
|
+
used.add(todo_id)
|
|
612
|
+
for item_id, item in by_id.items():
|
|
613
|
+
if item_id not in used:
|
|
614
|
+
ordered.append(item)
|
|
615
|
+
used.add(item_id)
|
|
616
|
+
# Preserve malformed duplicates for the episode grouping boundary. The
|
|
617
|
+
# planner still reasons over unique ids, but no-prune means it must not
|
|
618
|
+
# silently erase an input that record/fatal policy needs to classify.
|
|
619
|
+
ordered.extend(duplicate_items)
|
|
620
|
+
return ordered
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
def _arm_runtime_signature(
|
|
624
|
+
adapter: Any,
|
|
625
|
+
*,
|
|
626
|
+
arm_key: str,
|
|
627
|
+
worker_count: int,
|
|
628
|
+
use_router: bool,
|
|
629
|
+
frontier_max_lanes: int,
|
|
630
|
+
variant_catalog_enabled: bool,
|
|
631
|
+
goal_id: str,
|
|
632
|
+
agent_id: str,
|
|
633
|
+
item_failure_policy: str,
|
|
634
|
+
episode_mode: str | None,
|
|
635
|
+
) -> dict[str, Any]:
|
|
636
|
+
adapter_type = f"{type(adapter).__module__}.{type(adapter).__qualname__}"
|
|
637
|
+
signature = {
|
|
638
|
+
"arm_key": str(arm_key),
|
|
639
|
+
"adapter_type": adapter_type,
|
|
640
|
+
"worker_count": max(1, int(worker_count)),
|
|
641
|
+
"use_router": bool(use_router),
|
|
642
|
+
"frontier_max_lanes": max(0, int(frontier_max_lanes)),
|
|
643
|
+
"variant_catalog_enabled": bool(variant_catalog_enabled),
|
|
644
|
+
"goal_id": str(goal_id),
|
|
645
|
+
"agent_id": str(agent_id),
|
|
646
|
+
"item_failure_policy": item_failure_policy,
|
|
647
|
+
}
|
|
648
|
+
# Missing keys in legacy manifests read as None, so old standalone arms
|
|
649
|
+
# remain compatible while a recoverable arm cannot resume as standalone.
|
|
650
|
+
signature["episode_mode"] = episode_mode
|
|
651
|
+
return signature
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
def run_budget_arm(
|
|
655
|
+
adapter: Any,
|
|
656
|
+
*,
|
|
657
|
+
arm_key: str,
|
|
658
|
+
run_root: Path,
|
|
659
|
+
budget_minutes: float,
|
|
660
|
+
worker_count: int,
|
|
661
|
+
max_epochs: int = 1000,
|
|
662
|
+
use_router: bool = False,
|
|
663
|
+
frontier_max_lanes: int = 0,
|
|
664
|
+
variant_catalog_path: Path | None = None,
|
|
665
|
+
goal_id: str = "exploration",
|
|
666
|
+
agent_id: str = "harness",
|
|
667
|
+
stagger_seconds: float = 0.0,
|
|
668
|
+
duration_guard_factor: float = 1.15,
|
|
669
|
+
duration_guard_recent: int = 3,
|
|
670
|
+
resumable: bool = False,
|
|
671
|
+
resume: bool = False,
|
|
672
|
+
checkpoint_path: Path | None = None,
|
|
673
|
+
item_failure_policy: str | None = None,
|
|
674
|
+
) -> dict[str, Any]:
|
|
675
|
+
"""Run ONE arm alone against its wall-clock budget; return arm payload.
|
|
676
|
+
|
|
677
|
+
``use_router=False`` is the blind baseline (round-robin sweep, no
|
|
678
|
+
variants). ``use_router=True`` adds router-ordered no-prune planning,
|
|
679
|
+
router-state feedback, and -- when a variant catalog path is given --
|
|
680
|
+
agent-authored frontier variants appended to each epoch's queue. Restart
|
|
681
|
+
is opt-in and only trusts the validated epoch-boundary checkpoint manifest;
|
|
682
|
+
rolling progress remains an observability surface.
|
|
683
|
+
"""
|
|
684
|
+
|
|
685
|
+
run_root = Path(run_root)
|
|
686
|
+
failure_policy = _normalize_item_failure_policy(
|
|
687
|
+
item_failure_policy
|
|
688
|
+
if item_failure_policy is not None
|
|
689
|
+
else getattr(adapter, "item_failure_policy", None)
|
|
690
|
+
)
|
|
691
|
+
episode_mode = recoverable_episode_mode(adapter)
|
|
692
|
+
checkpoint_enabled = bool(resumable or resume or checkpoint_path is not None)
|
|
693
|
+
checkpoint_file = (
|
|
694
|
+
Path(checkpoint_path)
|
|
695
|
+
if checkpoint_path is not None
|
|
696
|
+
else run_root / f"arm_checkpoint_{arm_key}.json"
|
|
697
|
+
)
|
|
698
|
+
runtime_signature = _arm_runtime_signature(
|
|
699
|
+
adapter,
|
|
700
|
+
arm_key=arm_key,
|
|
701
|
+
worker_count=worker_count,
|
|
702
|
+
use_router=use_router,
|
|
703
|
+
frontier_max_lanes=frontier_max_lanes,
|
|
704
|
+
variant_catalog_enabled=variant_catalog_path is not None,
|
|
705
|
+
goal_id=goal_id,
|
|
706
|
+
agent_id=agent_id,
|
|
707
|
+
item_failure_policy=failure_policy,
|
|
708
|
+
episode_mode=episode_mode,
|
|
709
|
+
)
|
|
710
|
+
ledger = NoveltyLedger()
|
|
711
|
+
router_state = initial_router_state() if use_router else None
|
|
712
|
+
load_profile: Mapping[str, Any] | None = None
|
|
713
|
+
catalog = (
|
|
714
|
+
VariantCatalog(
|
|
715
|
+
Path(variant_catalog_path),
|
|
716
|
+
run_root / f"variant_consumption_{arm_key}.json",
|
|
717
|
+
)
|
|
718
|
+
if variant_catalog_path
|
|
719
|
+
else None
|
|
720
|
+
)
|
|
721
|
+
epochs: list[dict[str, Any]] = []
|
|
722
|
+
checkpoints: list[dict[str, Any]] = []
|
|
723
|
+
coverage_first_seen: dict[str, float] = {}
|
|
724
|
+
raw_cum = 0.0
|
|
725
|
+
novel_cum = 0.0
|
|
726
|
+
variant_records_cum = 0
|
|
727
|
+
next_epoch = 1
|
|
728
|
+
elapsed_offset = 0.0
|
|
729
|
+
if resume:
|
|
730
|
+
state = load_arm_checkpoint(
|
|
731
|
+
checkpoint_file,
|
|
732
|
+
expected_signature=runtime_signature,
|
|
733
|
+
)
|
|
734
|
+
epochs = [dict(epoch) for epoch in state["epochs"]]
|
|
735
|
+
checkpoints = [dict(checkpoint) for checkpoint in state["checkpoints"]]
|
|
736
|
+
ledger.seen = {str(key) for key in state["novelty_seen"]}
|
|
737
|
+
router_state = (
|
|
738
|
+
dict(state["router_state"])
|
|
739
|
+
if isinstance(state.get("router_state"), Mapping)
|
|
740
|
+
else None
|
|
741
|
+
)
|
|
742
|
+
load_profile = (
|
|
743
|
+
dict(state["load_profile"])
|
|
744
|
+
if isinstance(state.get("load_profile"), Mapping)
|
|
745
|
+
else None
|
|
746
|
+
)
|
|
747
|
+
coverage_first_seen = {
|
|
748
|
+
str(family): float(minutes)
|
|
749
|
+
for family, minutes in state["coverage_first_seen"].items()
|
|
750
|
+
}
|
|
751
|
+
raw_cum = float(state.get("raw_value_total") or 0.0)
|
|
752
|
+
novel_cum = float(state.get("novel_value_total") or 0.0)
|
|
753
|
+
variant_records_cum = int(state.get("variant_records_total") or 0)
|
|
754
|
+
next_epoch = int(state["next_epoch"])
|
|
755
|
+
elapsed_offset = float(state.get("elapsed_minutes") or 0.0)
|
|
756
|
+
if catalog is not None:
|
|
757
|
+
catalog.restore_consumed(state["catalog_consumed"])
|
|
758
|
+
elif checkpoint_enabled and catalog is not None:
|
|
759
|
+
# Reusing a run_root is not an implicit restart contract. A fresh arm
|
|
760
|
+
# starts with fresh consumption; only resume restores prior attempts.
|
|
761
|
+
catalog.restore_consumed([])
|
|
762
|
+
if not resume:
|
|
763
|
+
checkpoint_file.unlink(missing_ok=True)
|
|
764
|
+
execution_metric_fields = set(combine_execution_metrics([]))
|
|
765
|
+
historical_execution_metrics: list[dict[str, Any]] = []
|
|
766
|
+
for completed_epoch in epochs:
|
|
767
|
+
stored_metrics = completed_epoch.get("execution_metrics")
|
|
768
|
+
if (
|
|
769
|
+
isinstance(stored_metrics, Mapping)
|
|
770
|
+
and execution_metric_fields.issubset(stored_metrics)
|
|
771
|
+
):
|
|
772
|
+
historical_execution_metrics.append(dict(stored_metrics))
|
|
773
|
+
continue
|
|
774
|
+
historical_records = [
|
|
775
|
+
record
|
|
776
|
+
for lane in completed_epoch.get("lanes") or []
|
|
777
|
+
for record in lane.get("results") or []
|
|
778
|
+
]
|
|
779
|
+
if episode_mode is None:
|
|
780
|
+
historical_records = [
|
|
781
|
+
{
|
|
782
|
+
**record,
|
|
783
|
+
"execution_group_id": None,
|
|
784
|
+
"record_kind": RECORD_KIND_STANDALONE,
|
|
785
|
+
}
|
|
786
|
+
for record in historical_records
|
|
787
|
+
]
|
|
788
|
+
historical_execution_metrics.append(
|
|
789
|
+
summarize_execution_records(historical_records)
|
|
790
|
+
)
|
|
791
|
+
execution_metrics_cum = combine_execution_metrics(
|
|
792
|
+
historical_execution_metrics
|
|
793
|
+
)
|
|
794
|
+
stop_reason = "max_epochs"
|
|
795
|
+
started = time.perf_counter()
|
|
796
|
+
for epoch in range(next_epoch, int(max_epochs) + 1):
|
|
797
|
+
elapsed = elapsed_offset + _now_perf_minutes(started)
|
|
798
|
+
remaining = float(budget_minutes) - elapsed
|
|
799
|
+
if epochs and remaining <= 0:
|
|
800
|
+
stop_reason = "budget_exhausted"
|
|
801
|
+
break
|
|
802
|
+
if epochs and duration_guard_factor > 0:
|
|
803
|
+
recent = epochs[-max(1, int(duration_guard_recent)) :]
|
|
804
|
+
estimated = max(
|
|
805
|
+
float(entry.get("epoch_wall_minutes") or 0.0) for entry in recent
|
|
806
|
+
) * float(duration_guard_factor)
|
|
807
|
+
if remaining < estimated:
|
|
808
|
+
stop_reason = "duration_guard"
|
|
809
|
+
break
|
|
810
|
+
seed_items = [dict(item) for item in adapter.list_seed_items()]
|
|
811
|
+
if not seed_items:
|
|
812
|
+
stop_reason = "seed_queue_empty"
|
|
813
|
+
break
|
|
814
|
+
if episode_mode is not None and failure_policy == ITEM_FAILURE_POLICY_FATAL:
|
|
815
|
+
# Router planning keys by item_id and variant compilation calls
|
|
816
|
+
# adapter code, so fatal structural validation must see the raw
|
|
817
|
+
# seeds before either operation can collapse ids or cause effects.
|
|
818
|
+
build_episode_execution_units(
|
|
819
|
+
seed_items,
|
|
820
|
+
arm=arm_key,
|
|
821
|
+
epoch=epoch,
|
|
822
|
+
fatal_preflight=True,
|
|
823
|
+
)
|
|
824
|
+
if use_router:
|
|
825
|
+
queue_items = plan_router_no_prune(
|
|
826
|
+
seed_items,
|
|
827
|
+
goal_id=goal_id,
|
|
828
|
+
agent_id=agent_id,
|
|
829
|
+
worker_count=worker_count,
|
|
830
|
+
router_state=router_state,
|
|
831
|
+
load_profile=load_profile,
|
|
832
|
+
)
|
|
833
|
+
else:
|
|
834
|
+
queue_items = plan_round_robin(seed_items)
|
|
835
|
+
frontier_audit: dict[str, Any] | None = None
|
|
836
|
+
if use_router and catalog is not None and int(frontier_max_lanes) > 0:
|
|
837
|
+
variant_items, frontier_audit = select_frontier_work(
|
|
838
|
+
seed_items,
|
|
839
|
+
catalog=catalog,
|
|
840
|
+
adapter=adapter,
|
|
841
|
+
router_state=router_state,
|
|
842
|
+
epoch=epoch,
|
|
843
|
+
max_lanes=frontier_max_lanes,
|
|
844
|
+
)
|
|
845
|
+
queue_items = queue_items + variant_items
|
|
846
|
+
emit_frontier_requests(
|
|
847
|
+
run_root / f"frontier_requests_{arm_key}.json",
|
|
848
|
+
arm_key=arm_key,
|
|
849
|
+
epoch=epoch,
|
|
850
|
+
seed_items=seed_items,
|
|
851
|
+
ledger=ledger,
|
|
852
|
+
catalog=catalog,
|
|
853
|
+
router_state=router_state,
|
|
854
|
+
)
|
|
855
|
+
epoch_start = time.perf_counter()
|
|
856
|
+
execution_items = queue_items
|
|
857
|
+
if episode_mode is not None:
|
|
858
|
+
execution_items = build_episode_execution_units(
|
|
859
|
+
queue_items,
|
|
860
|
+
arm=arm_key,
|
|
861
|
+
epoch=epoch,
|
|
862
|
+
fatal_preflight=failure_policy == ITEM_FAILURE_POLICY_FATAL,
|
|
863
|
+
)
|
|
864
|
+
|
|
865
|
+
def execute_epoch(item: Mapping[str, Any], **context: Any) -> list[dict[str, Any]]:
|
|
866
|
+
return execute_episode_group(
|
|
867
|
+
item,
|
|
868
|
+
adapter=adapter,
|
|
869
|
+
failure_policy=failure_policy,
|
|
870
|
+
fatal_failure_policy=ITEM_FAILURE_POLICY_FATAL,
|
|
871
|
+
error_record_factory=_adapter_error_record,
|
|
872
|
+
**context,
|
|
873
|
+
)
|
|
874
|
+
|
|
875
|
+
else:
|
|
876
|
+
# Accessed lazily so an episode-only adapter (no legacy execute)
|
|
877
|
+
# is valid as long as prepare_episode_group never returns None.
|
|
878
|
+
execute_epoch: Callable[..., Any] = adapter.execute
|
|
879
|
+
|
|
880
|
+
lanes = run_queue_epoch(
|
|
881
|
+
execution_items,
|
|
882
|
+
execute=execute_epoch,
|
|
883
|
+
worker_count=worker_count,
|
|
884
|
+
run_root=run_root,
|
|
885
|
+
arm=arm_key,
|
|
886
|
+
epoch=epoch,
|
|
887
|
+
stagger_seconds=stagger_seconds,
|
|
888
|
+
item_failure_policy=failure_policy,
|
|
889
|
+
)
|
|
890
|
+
epoch_wall = _now_perf_minutes(epoch_start)
|
|
891
|
+
epoch_raw = 0.0
|
|
892
|
+
epoch_novel = 0.0
|
|
893
|
+
records: list[dict[str, Any]] = []
|
|
894
|
+
for lane in lanes:
|
|
895
|
+
for record in lane.get("results") or []:
|
|
896
|
+
credit = ledger.credit(record)
|
|
897
|
+
record["novel_value"] = credit["novel_value"]
|
|
898
|
+
record["raw_value"] = ledger.raw_value(record)
|
|
899
|
+
epoch_raw += record["raw_value"]
|
|
900
|
+
epoch_novel += credit["novel_value"]
|
|
901
|
+
if record.get("is_variant"):
|
|
902
|
+
variant_records_cum += 1
|
|
903
|
+
records.append(record)
|
|
904
|
+
raw_cum += epoch_raw
|
|
905
|
+
novel_cum += epoch_novel
|
|
906
|
+
metric_records = records
|
|
907
|
+
if episode_mode is None:
|
|
908
|
+
metric_records = [
|
|
909
|
+
{
|
|
910
|
+
**record,
|
|
911
|
+
"execution_group_id": None,
|
|
912
|
+
"record_kind": RECORD_KIND_STANDALONE,
|
|
913
|
+
}
|
|
914
|
+
for record in records
|
|
915
|
+
]
|
|
916
|
+
epoch_execution_metrics = summarize_execution_records(metric_records)
|
|
917
|
+
execution_metrics_cum = combine_execution_metrics(
|
|
918
|
+
[execution_metrics_cum, epoch_execution_metrics]
|
|
919
|
+
)
|
|
920
|
+
elapsed_after = elapsed_offset + _now_perf_minutes(started)
|
|
921
|
+
for record in records:
|
|
922
|
+
family = str(record.get("family") or "")
|
|
923
|
+
if family:
|
|
924
|
+
coverage_first_seen.setdefault(family, round(elapsed_after, 3))
|
|
925
|
+
if use_router:
|
|
926
|
+
if episode_mode is not None:
|
|
927
|
+
probes = build_router_probes(records)
|
|
928
|
+
else:
|
|
929
|
+
probes = [
|
|
930
|
+
{
|
|
931
|
+
"family": str(record.get("family") or "general"),
|
|
932
|
+
"duration_minutes": record.get("duration_minutes"),
|
|
933
|
+
"observation_keys": record.get("observation_keys") or [],
|
|
934
|
+
"weighted_flags": record.get("weighted_flags") or {},
|
|
935
|
+
"accepted": bool(record.get("accepted")),
|
|
936
|
+
"retryable_infra_error": bool(
|
|
937
|
+
record.get("retryable_infra_error")
|
|
938
|
+
),
|
|
939
|
+
}
|
|
940
|
+
for record in records
|
|
941
|
+
]
|
|
942
|
+
router_state = observe_epoch(router_state, epoch=epoch, probes=probes)
|
|
943
|
+
router_state = advance_epoch(
|
|
944
|
+
router_state,
|
|
945
|
+
epoch=epoch,
|
|
946
|
+
eligible_families=sorted(
|
|
947
|
+
{str(item.get("family")) for item in seed_items if item.get("family")}
|
|
948
|
+
),
|
|
949
|
+
)
|
|
950
|
+
_write_json(run_root / f"router_state_{arm_key}.json", router_state)
|
|
951
|
+
max_lane_wall = max(
|
|
952
|
+
(float(lane.get("wall_minutes") or 0.0) for lane in lanes), default=0.0
|
|
953
|
+
)
|
|
954
|
+
load_profile = {
|
|
955
|
+
"source": f"observed_epoch_{epoch:02d}_{arm_key}",
|
|
956
|
+
"parallel_wall_minutes": epoch_wall,
|
|
957
|
+
"max_branch_minutes": max_lane_wall,
|
|
958
|
+
"branch_count": len(lanes),
|
|
959
|
+
}
|
|
960
|
+
checkpoints.append(
|
|
961
|
+
{
|
|
962
|
+
"epoch": epoch,
|
|
963
|
+
"elapsed_minutes": round(elapsed_after, 3),
|
|
964
|
+
"raw_value_cum": round(raw_cum, 3),
|
|
965
|
+
"novel_value_cum": round(novel_cum, 3),
|
|
966
|
+
"coverage_count_cum": len(coverage_first_seen),
|
|
967
|
+
"variant_records_cum": variant_records_cum,
|
|
968
|
+
"requested_worker_minutes_cum": round(elapsed_after * worker_count, 3),
|
|
969
|
+
"execution_metrics_cum": dict(execution_metrics_cum),
|
|
970
|
+
}
|
|
971
|
+
)
|
|
972
|
+
epochs.append(
|
|
973
|
+
{
|
|
974
|
+
"schema_version": HARNESS_ARM_SCHEMA_VERSION,
|
|
975
|
+
"epoch": epoch,
|
|
976
|
+
"epoch_wall_minutes": round(epoch_wall, 3),
|
|
977
|
+
"queue_size": len(queue_items),
|
|
978
|
+
"execution_unit_count": len(execution_items),
|
|
979
|
+
"execution_metrics": epoch_execution_metrics,
|
|
980
|
+
"frontier": frontier_audit,
|
|
981
|
+
"lanes": lanes,
|
|
982
|
+
}
|
|
983
|
+
)
|
|
984
|
+
if checkpoint_enabled:
|
|
985
|
+
write_arm_checkpoint(
|
|
986
|
+
checkpoint_file,
|
|
987
|
+
build_arm_checkpoint(
|
|
988
|
+
arm_key=arm_key,
|
|
989
|
+
runtime_signature=runtime_signature,
|
|
990
|
+
next_epoch=epoch + 1,
|
|
991
|
+
elapsed_minutes=elapsed_after,
|
|
992
|
+
epochs=epochs,
|
|
993
|
+
checkpoints=checkpoints,
|
|
994
|
+
novelty_seen=sorted(ledger.seen),
|
|
995
|
+
router_state=router_state,
|
|
996
|
+
load_profile=load_profile,
|
|
997
|
+
catalog_consumed=sorted(catalog.consumed) if catalog else [],
|
|
998
|
+
coverage_first_seen=coverage_first_seen,
|
|
999
|
+
raw_value_total=raw_cum,
|
|
1000
|
+
novel_value_total=novel_cum,
|
|
1001
|
+
variant_records_total=variant_records_cum,
|
|
1002
|
+
),
|
|
1003
|
+
)
|
|
1004
|
+
_write_json(
|
|
1005
|
+
run_root / f"rolling_progress_{arm_key}.json",
|
|
1006
|
+
{"arm": arm_key, "completed_epochs": len(epochs), "last_checkpoint": checkpoints[-1]},
|
|
1007
|
+
)
|
|
1008
|
+
return {
|
|
1009
|
+
"schema_version": HARNESS_ARM_SCHEMA_VERSION,
|
|
1010
|
+
"arm_key": arm_key,
|
|
1011
|
+
"budget_minutes": float(budget_minutes),
|
|
1012
|
+
"elapsed_minutes": round(elapsed_offset + _now_perf_minutes(started), 3),
|
|
1013
|
+
"stop_reason": stop_reason,
|
|
1014
|
+
"epoch_count": len(epochs),
|
|
1015
|
+
"raw_value_total": round(raw_cum, 3),
|
|
1016
|
+
"novel_value_total": round(novel_cum, 3),
|
|
1017
|
+
"variant_records_total": variant_records_cum,
|
|
1018
|
+
"execution_metrics": execution_metrics_cum,
|
|
1019
|
+
"coverage_count": len(coverage_first_seen),
|
|
1020
|
+
"coverage_first_seen_minutes": coverage_first_seen,
|
|
1021
|
+
"checkpoints": checkpoints,
|
|
1022
|
+
"epochs": epochs,
|
|
1023
|
+
"resume": {
|
|
1024
|
+
"schema_version": "loopx_explore_harness_resume_v0",
|
|
1025
|
+
"enabled": checkpoint_enabled,
|
|
1026
|
+
"resumed": bool(resume),
|
|
1027
|
+
"checkpoint_schema_version": HARNESS_CHECKPOINT_SCHEMA_VERSION,
|
|
1028
|
+
"checkpoint_path": str(checkpoint_file) if checkpoint_enabled else None,
|
|
1029
|
+
"restored_epoch_count": next_epoch - 1 if resume else 0,
|
|
1030
|
+
},
|
|
1031
|
+
"runtime_policy": {
|
|
1032
|
+
"schema_version": HARNESS_RUNTIME_POLICY_SCHEMA_VERSION,
|
|
1033
|
+
"item_failure_policy": failure_policy,
|
|
1034
|
+
"item_exception_isolation": failure_policy == ITEM_FAILURE_POLICY_RECORD,
|
|
1035
|
+
"episode_execution": {
|
|
1036
|
+
"mode": episode_mode or "standalone",
|
|
1037
|
+
"adapter_owned_restore": episode_mode is not None,
|
|
1038
|
+
},
|
|
1039
|
+
"planner_guidance": {
|
|
1040
|
+
"retry_backoff": {
|
|
1041
|
+
"enforced": False,
|
|
1042
|
+
"owner": "external_runner",
|
|
1043
|
+
},
|
|
1044
|
+
"infra_cooldown": {
|
|
1045
|
+
"enforced": False,
|
|
1046
|
+
"owner": "external_runner",
|
|
1047
|
+
},
|
|
1048
|
+
},
|
|
1049
|
+
},
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
|
|
1053
|
+
def step_auc(checkpoints: Sequence[Mapping[str, Any]], *, value_key: str, horizon_minutes: float) -> float:
|
|
1054
|
+
"""Step-integrate a cumulative anytime curve to the budget horizon."""
|
|
1055
|
+
|
|
1056
|
+
auc = 0.0
|
|
1057
|
+
prev_t = 0.0
|
|
1058
|
+
prev_v = 0.0
|
|
1059
|
+
for checkpoint in checkpoints:
|
|
1060
|
+
t = min(float(checkpoint.get("elapsed_minutes") or 0.0), float(horizon_minutes))
|
|
1061
|
+
if t > prev_t:
|
|
1062
|
+
auc += prev_v * (t - prev_t)
|
|
1063
|
+
prev_t = t
|
|
1064
|
+
prev_v = float(checkpoint.get(value_key) or 0.0)
|
|
1065
|
+
if float(horizon_minutes) > prev_t:
|
|
1066
|
+
auc += prev_v * (float(horizon_minutes) - prev_t)
|
|
1067
|
+
return round(auc, 3)
|
|
1068
|
+
|
|
1069
|
+
|
|
1070
|
+
def aggregate_arms(
|
|
1071
|
+
arms: Mapping[str, Mapping[str, Any]],
|
|
1072
|
+
*,
|
|
1073
|
+
budget_minutes: float,
|
|
1074
|
+
worker_count: int,
|
|
1075
|
+
coverage_denominator: int | None = None,
|
|
1076
|
+
) -> dict[str, Any]:
|
|
1077
|
+
"""Generic two-arm (or N-arm) comparison on novelty-first metrics."""
|
|
1078
|
+
|
|
1079
|
+
summary: dict[str, Any] = {
|
|
1080
|
+
"schema_version": "loopx_explore_harness_score_v0",
|
|
1081
|
+
"budget_minutes_per_arm": float(budget_minutes),
|
|
1082
|
+
"worker_count": int(worker_count),
|
|
1083
|
+
"coverage_denominator": coverage_denominator,
|
|
1084
|
+
"arms": {},
|
|
1085
|
+
}
|
|
1086
|
+
for arm_key, arm in arms.items():
|
|
1087
|
+
checkpoints = arm.get("checkpoints") or []
|
|
1088
|
+
execution_metrics = (
|
|
1089
|
+
dict(arm.get("execution_metrics") or {})
|
|
1090
|
+
if isinstance(arm.get("execution_metrics"), Mapping)
|
|
1091
|
+
else {}
|
|
1092
|
+
)
|
|
1093
|
+
effective_compute = float(
|
|
1094
|
+
execution_metrics.get("effective_compute_minutes") or 0.0
|
|
1095
|
+
)
|
|
1096
|
+
summary["arms"][arm_key] = {
|
|
1097
|
+
"novel_value_endpoint": arm.get("novel_value_total"),
|
|
1098
|
+
"novel_value_auc": step_auc(
|
|
1099
|
+
checkpoints, value_key="novel_value_cum", horizon_minutes=budget_minutes
|
|
1100
|
+
),
|
|
1101
|
+
"raw_value_total": arm.get("raw_value_total"),
|
|
1102
|
+
"coverage_count": arm.get("coverage_count"),
|
|
1103
|
+
"variant_records_total": arm.get("variant_records_total"),
|
|
1104
|
+
"epoch_count": arm.get("epoch_count"),
|
|
1105
|
+
"elapsed_minutes": arm.get("elapsed_minutes"),
|
|
1106
|
+
"stop_reason": arm.get("stop_reason"),
|
|
1107
|
+
"execution_metrics": execution_metrics,
|
|
1108
|
+
"novel_value_per_requested_worker_minute": round(
|
|
1109
|
+
float(arm.get("novel_value_total") or 0.0)
|
|
1110
|
+
/ max(0.001, float(budget_minutes) * worker_count),
|
|
1111
|
+
4,
|
|
1112
|
+
),
|
|
1113
|
+
"novel_value_per_effective_compute_minute": (
|
|
1114
|
+
round(
|
|
1115
|
+
float(arm.get("novel_value_total") or 0.0)
|
|
1116
|
+
/ effective_compute,
|
|
1117
|
+
4,
|
|
1118
|
+
)
|
|
1119
|
+
if effective_compute > 0
|
|
1120
|
+
else None
|
|
1121
|
+
),
|
|
1122
|
+
"anytime_curve": checkpoints,
|
|
1123
|
+
}
|
|
1124
|
+
return summary
|