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,212 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""PreToolUse policy gate for goal-mode — OPTIONAL deterministic hardening.
|
|
3
|
+
|
|
4
|
+
In the native-`/loop` design, Claude Code's `/loop` is the runtime and loopx's
|
|
5
|
+
MCP `should_run` is the per-tick gate. This hook is NOT installed by default —
|
|
6
|
+
it is opt-in (`install.py --harden` / `connect.py --harden`), project-scoped, and
|
|
7
|
+
adds a per-TOOL-CALL gate on top of the loop:
|
|
8
|
+
|
|
9
|
+
- should_run gate (fail-closed) -> may the agent spend this turn right now?
|
|
10
|
+
- Edit/Write -> write_scope -> deny file edits outside the goal's scope
|
|
11
|
+
- Bash -> destructive denylist -> deny obviously dangerous commands
|
|
12
|
+
|
|
13
|
+
It only acts in a project where loopx is armed (a `.claude/loop.md` exists).
|
|
14
|
+
|
|
15
|
+
Limitation (by design): Bash is gated only by a denylist, so a determined shell
|
|
16
|
+
command can still write outside write_scope or reach the network. For STRONG
|
|
17
|
+
isolation (untrusted code / unattended high-stakes), run Claude Code inside a dev
|
|
18
|
+
container or VM, which contains the whole process at the environment level.
|
|
19
|
+
|
|
20
|
+
Behavior (only while goal-mode is ARMED for the event's project):
|
|
21
|
+
- no goal here / goal-mode off -> {} (no-op; normal permission flow)
|
|
22
|
+
- read-only/safe tools -> allow
|
|
23
|
+
- should_run == false (quota/gate closed) -> deny
|
|
24
|
+
- should_run probe unavailable (error) -> deny for non-read-only tools (FAIL-CLOSED)
|
|
25
|
+
- should_run == true:
|
|
26
|
+
* Edit/Write outside write_scope -> deny
|
|
27
|
+
* Edit/Write within write_scope -> allow
|
|
28
|
+
* destructive Bash -> deny
|
|
29
|
+
* Bash / other -> allow
|
|
30
|
+
|
|
31
|
+
Fail-closed: if the deterministic ``should_run`` probe cannot be reached, a
|
|
32
|
+
non-read-only tool is denied rather than allowed, so a flaky/broken control
|
|
33
|
+
plane pauses delivery instead of running ungated. Read-only tools stay allowed.
|
|
34
|
+
|
|
35
|
+
Identity-aware: when the active goal registered an agent (loopx
|
|
36
|
+
``coordination.registered_agents``), the context carries ``agent_id`` and this
|
|
37
|
+
hook forwards it as ``--agent-id`` to ``loopx quota should-run``. Without it
|
|
38
|
+
loopx returns ``automation_prompt_upgrade_required`` and should_run=false — the
|
|
39
|
+
Claude Code analogue of Codex's identity-scoped heartbeat prompt.
|
|
40
|
+
"""
|
|
41
|
+
from __future__ import annotations
|
|
42
|
+
|
|
43
|
+
import json
|
|
44
|
+
import shutil
|
|
45
|
+
import subprocess
|
|
46
|
+
import sys
|
|
47
|
+
from pathlib import Path
|
|
48
|
+
|
|
49
|
+
# Goal context is resolved PER PROJECT from the event's cwd via the registry, and
|
|
50
|
+
# "armed" = the project's .claude/loop.md exists (see goal_state.py). The registry
|
|
51
|
+
# is the single source of truth; there is no separate active-state file.
|
|
52
|
+
from goal_state import active_context
|
|
53
|
+
|
|
54
|
+
# Read-only tools are always allowed under goal-mode. Edit/Write are scoped to
|
|
55
|
+
# write_scope; Bash is gated by a destructive-command denylist. (No OS sandbox in
|
|
56
|
+
# this design — the hook is the whole gate; see the module docstring.)
|
|
57
|
+
# NOTE: `Task` is deliberately NOT here. It can launch a subagent that performs
|
|
58
|
+
# writes, so allowing it unconditionally would bypass the gate when
|
|
59
|
+
# should_run=false. It must go through the gate: denied when should_run=false,
|
|
60
|
+
# otherwise deferred to Claude Code's normal permission flow (unknown tool).
|
|
61
|
+
READONLY_TOOLS = {"Read", "Glob", "Grep", "NotebookRead", "TodoWrite", "WebFetch", "WebSearch", "ToolSearch"}
|
|
62
|
+
WRITE_TOOLS = {"Edit", "Write", "MultiEdit", "NotebookEdit"}
|
|
63
|
+
DESTRUCTIVE = (
|
|
64
|
+
"rm -rf", "rm -fr", "mkfs", "dd if=", ":(){", "shutdown", "reboot",
|
|
65
|
+
"git push --force", "git reset --hard", "> /dev/sd", "format ",
|
|
66
|
+
)
|
|
67
|
+
CLAUDE_RUNTIME_PROFILE_ARGS = ["--runtime-profile", "claude_code"]
|
|
68
|
+
CLAUDE_LEGACY_SCHEDULER_ARGS = [
|
|
69
|
+
"--host-surface", "claude_code",
|
|
70
|
+
"--scheduler-owner", "agent_cli_loop",
|
|
71
|
+
"--execution-mode", "interactive",
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def within(path: str, root: str) -> bool:
|
|
76
|
+
"""True if `path` resolves to `root` or a descendant of it."""
|
|
77
|
+
try:
|
|
78
|
+
p = Path(path).resolve()
|
|
79
|
+
r = Path(root).resolve()
|
|
80
|
+
return r == p or r in p.parents
|
|
81
|
+
except Exception:
|
|
82
|
+
return False
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _gh_prefix():
|
|
86
|
+
_exe = shutil.which("loopx")
|
|
87
|
+
return [_exe] if _exe else [__import__("sys").executable, "-m", "loopx.cli"]
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def emit(decision=None, reason=""):
|
|
91
|
+
if decision is None:
|
|
92
|
+
sys.stdout.write("{}") # no-op: defer to normal permission flow
|
|
93
|
+
return
|
|
94
|
+
sys.stdout.write(json.dumps({
|
|
95
|
+
"hookSpecificOutput": {
|
|
96
|
+
"hookEventName": "PreToolUse",
|
|
97
|
+
"permissionDecision": decision,
|
|
98
|
+
"permissionDecisionReason": reason,
|
|
99
|
+
}
|
|
100
|
+
}))
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _runtime_profile_flag_is_unsupported(out: subprocess.CompletedProcess) -> bool:
|
|
104
|
+
diagnostic = str(out.stderr or "").lower()
|
|
105
|
+
return (
|
|
106
|
+
out.returncode == 2
|
|
107
|
+
and "--runtime-profile" in diagnostic
|
|
108
|
+
and (
|
|
109
|
+
"unrecognized arguments" in diagnostic
|
|
110
|
+
or "invalid choice" in diagnostic
|
|
111
|
+
)
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def should_run(registry, goal_id, agent_id=None) -> bool | None:
|
|
116
|
+
"""Return True/False from loopx quota should-run, or None if unknown."""
|
|
117
|
+
if not goal_id:
|
|
118
|
+
return None
|
|
119
|
+
cmd = list(_gh_prefix())
|
|
120
|
+
if registry:
|
|
121
|
+
cmd += ["--registry", registry]
|
|
122
|
+
cmd += ["--format", "json", "quota", "should-run", "--goal-id", goal_id]
|
|
123
|
+
if agent_id:
|
|
124
|
+
cmd += ["--agent-id", agent_id]
|
|
125
|
+
try:
|
|
126
|
+
out = subprocess.run(
|
|
127
|
+
[*cmd, *CLAUDE_RUNTIME_PROFILE_ARGS],
|
|
128
|
+
capture_output=True,
|
|
129
|
+
text=True,
|
|
130
|
+
timeout=10,
|
|
131
|
+
)
|
|
132
|
+
if _runtime_profile_flag_is_unsupported(out):
|
|
133
|
+
out = subprocess.run(
|
|
134
|
+
[*cmd, *CLAUDE_LEGACY_SCHEDULER_ARGS],
|
|
135
|
+
capture_output=True,
|
|
136
|
+
text=True,
|
|
137
|
+
timeout=10,
|
|
138
|
+
)
|
|
139
|
+
except Exception:
|
|
140
|
+
return None # caller fails CLOSED on None for non-read-only tools
|
|
141
|
+
try:
|
|
142
|
+
data = json.loads(out.stdout or "{}")
|
|
143
|
+
except (json.JSONDecodeError, TypeError, ValueError):
|
|
144
|
+
return None
|
|
145
|
+
should_run_value = data.get("should_run")
|
|
146
|
+
return should_run_value if isinstance(should_run_value, bool) else None
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def decide(ev: dict) -> dict:
|
|
150
|
+
"""Pure policy: given a PreToolUse event, return the hookSpecificOutput dict
|
|
151
|
+
(or {} for no-op). Reused by the CLI hook AND the Agent SDK in-process hook.
|
|
152
|
+
|
|
153
|
+
The hook is the whole gate (no OS sandbox): should_run (fail-closed) +
|
|
154
|
+
Edit/Write write_scope + a destructive-Bash denylist."""
|
|
155
|
+
# Claude Code feeds the session's working directory on the event; resolve the
|
|
156
|
+
# goal from THAT project's registry, and gate only while goal-mode is armed
|
|
157
|
+
# (the project's .claude/loop.md exists). Unrelated/unarmed projects -> no-op.
|
|
158
|
+
cwd = ev.get("cwd") or (ev.get("workspace") or {}).get("current_dir")
|
|
159
|
+
ctx = active_context(cwd)
|
|
160
|
+
if not ctx:
|
|
161
|
+
return {} # no goal here, or goal-mode off -> defer to normal flow
|
|
162
|
+
|
|
163
|
+
goal_id = ctx.get("goal_id")
|
|
164
|
+
registry = ctx.get("registry")
|
|
165
|
+
agent_id = ctx.get("agent_id")
|
|
166
|
+
scope = ctx.get("write_scope") or []
|
|
167
|
+
tool = ev.get("tool_name", "")
|
|
168
|
+
ti = ev.get("tool_input", {}) or {}
|
|
169
|
+
|
|
170
|
+
def d(decision, reason):
|
|
171
|
+
return {"hookSpecificOutput": {
|
|
172
|
+
"hookEventName": "PreToolUse",
|
|
173
|
+
"permissionDecision": decision,
|
|
174
|
+
"permissionDecisionReason": reason}}
|
|
175
|
+
|
|
176
|
+
if tool in READONLY_TOOLS:
|
|
177
|
+
return d("allow", "read-only/safe tool under goal-mode")
|
|
178
|
+
|
|
179
|
+
sr = should_run(registry, goal_id, agent_id) # True / False / None
|
|
180
|
+
if sr is False:
|
|
181
|
+
return d("deny", f"goal '{goal_id}' should_run=false (quota/gate closed)")
|
|
182
|
+
if sr is None:
|
|
183
|
+
# deterministic should_run probe unavailable -> fail closed (non-read-only tool)
|
|
184
|
+
return d("deny", "loopx should_run probe unavailable — failing closed under goal-mode")
|
|
185
|
+
|
|
186
|
+
# should_run == True. Scope the Edit/Write file tools to write_scope, and
|
|
187
|
+
# gate Bash with the destructive denylist (the hook is the whole gate).
|
|
188
|
+
if tool in WRITE_TOOLS:
|
|
189
|
+
fp = ti.get("file_path") or ti.get("notebook_path") or ""
|
|
190
|
+
if scope and not any(within(fp, s) for s in scope):
|
|
191
|
+
return d("deny", f"'{fp}' outside goal write_scope {scope}")
|
|
192
|
+
return d("allow", "write within goal scope")
|
|
193
|
+
if tool == "Bash":
|
|
194
|
+
low = (ti.get("command") or "").lower()
|
|
195
|
+
if any(tok in low for tok in DESTRUCTIVE):
|
|
196
|
+
return d("deny", "destructive command blocked by goal policy")
|
|
197
|
+
return d("allow", "bash permitted under goal policy")
|
|
198
|
+
return {} # unknown tool: defer to normal flow
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def main():
|
|
202
|
+
raw = sys.stdin.read() or "{}"
|
|
203
|
+
try:
|
|
204
|
+
ev = json.loads(raw)
|
|
205
|
+
except Exception:
|
|
206
|
+
ev = {}
|
|
207
|
+
result = decide(ev)
|
|
208
|
+
sys.stdout.write(json.dumps(result) if result else "{}")
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
if __name__ == "__main__":
|
|
212
|
+
main()
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Goal-mode context resolution — registry-driven, mirroring the Codex model.
|
|
3
|
+
|
|
4
|
+
LoopX's registry (``.loopx/registry.json``) is the single source of truth for the
|
|
5
|
+
project's goal (goal_id / agent / scope), exactly as on Codex. We resolve it by
|
|
6
|
+
walking up from cwd to the nearest ``.loopx/`` (or legacy ``.goal-harness/``)
|
|
7
|
+
registry — so two sessions in different projects are independent, and two
|
|
8
|
+
sessions in the SAME project share the one goal.
|
|
9
|
+
|
|
10
|
+
"Goal-mode ON / armed" for a project = a ``.claude/loop.md`` exists (written by
|
|
11
|
+
``/loopx <task>``). That file is the per-iteration protocol that Claude Code's
|
|
12
|
+
native ``/loop`` runs; its presence means loopx is driving this project. The
|
|
13
|
+
OPTIONAL PreToolUse hook + statusline gate on this, so they only act where loopx
|
|
14
|
+
is actually active. ``LOOPX_GOAL_FORCE=1`` forces armed=true for tests.
|
|
15
|
+
"""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import json
|
|
19
|
+
import os
|
|
20
|
+
import re
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
REGISTRY_DIRS = (".loopx", ".goal-harness") # prefer loopx; fall back to legacy
|
|
24
|
+
|
|
25
|
+
_ARMED_RE = re.compile(r"<!--\s*loopx:armed\s*(\{.*?\})\s*-->")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def find_registry(cwd) -> Path | None:
|
|
29
|
+
"""Nearest ancestor of cwd (inclusive) holding a registry.json, else None."""
|
|
30
|
+
try:
|
|
31
|
+
cur = Path(cwd).resolve()
|
|
32
|
+
except Exception:
|
|
33
|
+
return None
|
|
34
|
+
for d in [cur, *cur.parents]:
|
|
35
|
+
for sub in REGISTRY_DIRS:
|
|
36
|
+
cand = d / sub / "registry.json"
|
|
37
|
+
if cand.exists():
|
|
38
|
+
return cand
|
|
39
|
+
return None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def project_root_for(cwd) -> Path | None:
|
|
43
|
+
reg = find_registry(cwd)
|
|
44
|
+
return reg.parent.parent if reg else None
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _agent_of(goal: dict):
|
|
48
|
+
coord = goal.get("coordination") or {}
|
|
49
|
+
for entry in coord.get("registered_agents") or []:
|
|
50
|
+
if isinstance(entry, dict):
|
|
51
|
+
val = entry.get("id") or entry.get("agent_id") or entry.get("name")
|
|
52
|
+
if val:
|
|
53
|
+
return str(val)
|
|
54
|
+
elif entry:
|
|
55
|
+
return str(entry)
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def loop_md_path(project_root) -> Path:
|
|
60
|
+
return Path(project_root) / ".claude" / "loop.md"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def read_armed_goal(project_root) -> dict | None:
|
|
64
|
+
"""The goal_id/agent_id that `/loopx` armed for THIS project, parsed from the
|
|
65
|
+
`loopx:armed` marker it writes at the top of `.claude/loop.md`. None if the
|
|
66
|
+
project isn't armed or the marker is absent/unparseable.
|
|
67
|
+
|
|
68
|
+
This is what lets goal_context resolve the *armed* goal rather than guessing
|
|
69
|
+
by registry order, so a multi-goal repo gates/claims/completes the goal the
|
|
70
|
+
user actually set up."""
|
|
71
|
+
if not project_root:
|
|
72
|
+
return None
|
|
73
|
+
try:
|
|
74
|
+
text = loop_md_path(project_root).read_text(encoding="utf-8")
|
|
75
|
+
except Exception:
|
|
76
|
+
return None
|
|
77
|
+
m = _ARMED_RE.search(text)
|
|
78
|
+
if not m:
|
|
79
|
+
return None
|
|
80
|
+
try:
|
|
81
|
+
d = json.loads(m.group(1))
|
|
82
|
+
except Exception:
|
|
83
|
+
return None
|
|
84
|
+
return d if isinstance(d, dict) and d.get("goal_id") else None
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def goal_context(cwd) -> dict | None:
|
|
88
|
+
"""The current project's goal, read live from the registry. None if no goal.
|
|
89
|
+
|
|
90
|
+
Returns goal_id, registry path, agent_id, write_scope (the goal's repo), and
|
|
91
|
+
project_root — everything the hooks / MCP / statusline need. When the project
|
|
92
|
+
is armed (`.claude/loop.md` carries a `loopx:armed` marker), the marker's goal
|
|
93
|
+
is authoritative; otherwise we fall back to the first registry goal."""
|
|
94
|
+
reg = find_registry(cwd)
|
|
95
|
+
if not reg:
|
|
96
|
+
return None
|
|
97
|
+
try:
|
|
98
|
+
data = json.loads(reg.read_text(encoding="utf-8"))
|
|
99
|
+
except Exception:
|
|
100
|
+
return None
|
|
101
|
+
goals = data.get("goals") or []
|
|
102
|
+
if not goals:
|
|
103
|
+
return None
|
|
104
|
+
root = reg.parent.parent
|
|
105
|
+
# Prefer the goal /loopx armed for this project; only fall back to registry
|
|
106
|
+
# order when there is no armed marker or it points at a goal not in the registry.
|
|
107
|
+
armed = read_armed_goal(root)
|
|
108
|
+
chosen = None
|
|
109
|
+
armed_agent = None
|
|
110
|
+
if armed:
|
|
111
|
+
chosen = next((g for g in goals if g.get("id") == armed.get("goal_id")), None)
|
|
112
|
+
if chosen is not None:
|
|
113
|
+
armed_agent = armed.get("agent_id")
|
|
114
|
+
if chosen is None:
|
|
115
|
+
chosen = goals[0]
|
|
116
|
+
repo = chosen.get("repo")
|
|
117
|
+
scope = [str(repo).replace("\\", "/")] if repo else [str(root).replace("\\", "/")]
|
|
118
|
+
return {
|
|
119
|
+
"goal_id": chosen.get("id"),
|
|
120
|
+
"registry": str(reg).replace("\\", "/"),
|
|
121
|
+
"agent_id": armed_agent or _agent_of(chosen),
|
|
122
|
+
"write_scope": scope,
|
|
123
|
+
"project_root": str(root).replace("\\", "/"),
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def is_armed(project_root) -> bool:
|
|
128
|
+
"""Armed = the project has a `.claude/loop.md` (loopx is driving), or forced."""
|
|
129
|
+
if os.environ.get("LOOPX_GOAL_FORCE") == "1":
|
|
130
|
+
return True
|
|
131
|
+
return bool(project_root) and loop_md_path(project_root).exists()
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def active_context(cwd) -> dict | None:
|
|
135
|
+
"""goal_context for cwd, but only when goal-mode is armed; else None."""
|
|
136
|
+
ctx = goal_context(cwd)
|
|
137
|
+
if not ctx or not is_armed(ctx.get("project_root")):
|
|
138
|
+
return None
|
|
139
|
+
return ctx
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Minimal MCP server exposing loopx state to the agent.
|
|
3
|
+
|
|
4
|
+
Tools: list_todos, claim_task, complete_task, should_run.
|
|
5
|
+
Each shells out to the loopx CLI (deterministic control plane).
|
|
6
|
+
|
|
7
|
+
Requires: pip install mcp
|
|
8
|
+
Config (registry/goal/agent) is resolved PER PROJECT from this process's cwd via
|
|
9
|
+
the `.loopx/registry.json` (see goal_state.py): each Claude Code session launches
|
|
10
|
+
its own stdio MCP subprocess inheriting the session's working directory, so the
|
|
11
|
+
goal scoping is per-project — no global state shared across sessions.
|
|
12
|
+
|
|
13
|
+
These tools are the loopx control-plane protocol that native `/loop` drives each
|
|
14
|
+
iteration (via `.claude/loop.md`): should_run -> claim_task -> bounded work ->
|
|
15
|
+
verify -> complete_task (which also spends one quota slot after writeback).
|
|
16
|
+
"""
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import json
|
|
20
|
+
import shutil
|
|
21
|
+
import subprocess
|
|
22
|
+
import sys
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
from mcp.server.fastmcp import FastMCP
|
|
27
|
+
except Exception as e: # pragma: no cover
|
|
28
|
+
raise SystemExit("MCP SDK not installed. Run: pip install mcp\n" + str(e))
|
|
29
|
+
|
|
30
|
+
# share the registry-driven resolver with the hooks (sibling hooks/ dir)
|
|
31
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "hooks"))
|
|
32
|
+
from goal_state import goal_context
|
|
33
|
+
|
|
34
|
+
mcp = FastMCP("loopx")
|
|
35
|
+
CLAUDE_RUNTIME_PROFILE_ARGS = ["--runtime-profile", "claude_code"]
|
|
36
|
+
CLAUDE_LEGACY_SCHEDULER_ARGS = [
|
|
37
|
+
"--host-surface", "claude_code",
|
|
38
|
+
"--scheduler-owner", "agent_cli_loop",
|
|
39
|
+
"--execution-mode", "interactive",
|
|
40
|
+
]
|
|
41
|
+
def _gh_prefix():
|
|
42
|
+
_exe = shutil.which("loopx")
|
|
43
|
+
return [_exe] if _exe else [__import__("sys").executable, "-m", "loopx.cli"]
|
|
44
|
+
|
|
45
|
+
def _state() -> dict:
|
|
46
|
+
# registry-derived context for THIS project (cwd) — the control-plane tools
|
|
47
|
+
# work whenever a goal exists, independent of the optional hook/loop.md.
|
|
48
|
+
return goal_context(Path.cwd()) or {}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _ctx():
|
|
52
|
+
st = _state()
|
|
53
|
+
return st.get("goal_id"), st.get("registry")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _agent_id():
|
|
57
|
+
return _state().get("agent_id")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _runtime_profile_flag_is_unsupported(out: subprocess.CompletedProcess) -> bool:
|
|
61
|
+
diagnostic = str(out.stderr or "").lower()
|
|
62
|
+
return (
|
|
63
|
+
out.returncode == 2
|
|
64
|
+
and "--runtime-profile" in diagnostic
|
|
65
|
+
and (
|
|
66
|
+
"unrecognized arguments" in diagnostic
|
|
67
|
+
or "invalid choice" in diagnostic
|
|
68
|
+
)
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _gh(args: list[str], *, legacy_args: list[str] | None = None) -> str:
|
|
73
|
+
goal_id, registry = _ctx()
|
|
74
|
+
cmd = list(_gh_prefix())
|
|
75
|
+
if registry:
|
|
76
|
+
cmd += ["--registry", registry]
|
|
77
|
+
out = subprocess.run([*cmd, *args], capture_output=True, text=True, timeout=30)
|
|
78
|
+
if (
|
|
79
|
+
legacy_args is not None
|
|
80
|
+
and _runtime_profile_flag_is_unsupported(out)
|
|
81
|
+
):
|
|
82
|
+
out = subprocess.run(
|
|
83
|
+
[*cmd, *legacy_args], capture_output=True, text=True, timeout=30
|
|
84
|
+
)
|
|
85
|
+
return (out.stdout or "") + (("\n" + out.stderr) if out.returncode else "")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _should_run_args(goal_id: str, agent_id: str | None) -> tuple[list[str], list[str]]:
|
|
89
|
+
common = ["--format", "json", "quota", "should-run", "--goal-id", goal_id]
|
|
90
|
+
if agent_id:
|
|
91
|
+
common += ["--agent-id", agent_id]
|
|
92
|
+
return (
|
|
93
|
+
[*common, *CLAUDE_RUNTIME_PROFILE_ARGS],
|
|
94
|
+
[*common, *CLAUDE_LEGACY_SCHEDULER_ARGS],
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _no_goal_msg() -> str:
|
|
99
|
+
return json.dumps({"error": "goal-mode is not active in this project; run /loopx <task> first"})
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@mcp.tool()
|
|
103
|
+
def should_run() -> str:
|
|
104
|
+
"""Whether the active goal should run now (quota/gate)."""
|
|
105
|
+
gid, _ = _ctx()
|
|
106
|
+
if not gid:
|
|
107
|
+
return _no_goal_msg()
|
|
108
|
+
args, legacy_args = _should_run_args(gid, _agent_id())
|
|
109
|
+
return _gh(args, legacy_args=legacy_args)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@mcp.tool()
|
|
113
|
+
def list_todos() -> str:
|
|
114
|
+
"""List the active goal's open agent todos (with todo_id + claimed_by)."""
|
|
115
|
+
gid, _ = _ctx()
|
|
116
|
+
if not gid:
|
|
117
|
+
return _no_goal_msg()
|
|
118
|
+
args, legacy_args = _should_run_args(gid, _agent_id())
|
|
119
|
+
return _gh(args, legacy_args=legacy_args)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@mcp.tool()
|
|
123
|
+
def claim_task(todo_id: str, agent_id: str) -> str:
|
|
124
|
+
"""Soft-claim a todo for an agent."""
|
|
125
|
+
gid, _ = _ctx()
|
|
126
|
+
return _gh(["todo", "claim", "--goal-id", gid, "--todo-id", todo_id, "--claimed-by", agent_id])
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@mcp.tool()
|
|
130
|
+
def complete_task(
|
|
131
|
+
todo_id: str,
|
|
132
|
+
agent_id: str,
|
|
133
|
+
evidence: str,
|
|
134
|
+
next_agent_todo: str = "",
|
|
135
|
+
task_lease_idempotency_key: str = "",
|
|
136
|
+
) -> str:
|
|
137
|
+
"""Complete a todo (with evidence), then spend one quota slot.
|
|
138
|
+
|
|
139
|
+
Mirrors the Codex heartbeat contract: spend exactly once AFTER validated
|
|
140
|
+
writeback. The slot is accounted only when the completion reports ok, with
|
|
141
|
+
source=heartbeat so it lines up with Codex spend accounting."""
|
|
142
|
+
gid, _ = _ctx()
|
|
143
|
+
args = ["todo", "complete", "--goal-id", gid, "--todo-id", todo_id,
|
|
144
|
+
"--claimed-by", agent_id, "--evidence", evidence]
|
|
145
|
+
if task_lease_idempotency_key:
|
|
146
|
+
args += ["--task-lease-idempotency-key", task_lease_idempotency_key]
|
|
147
|
+
if next_agent_todo:
|
|
148
|
+
# Don't hard-code a next claimer. With --next-agent-todo and no
|
|
149
|
+
# --next-claimed-by, LoopX assigns the new todo using its own completion
|
|
150
|
+
# semantics — to the completing agent for same-agent non-delivery, or to
|
|
151
|
+
# an explicitly claimed peer for an independent handoff.
|
|
152
|
+
# Hard-coding an unregistered id
|
|
153
|
+
# like `cc-controller` would fail registration.
|
|
154
|
+
args += ["--next-agent-todo", next_agent_todo]
|
|
155
|
+
out = _gh(args)
|
|
156
|
+
# spend-slot only on a validated completion (ok), matching "spend after writeback"
|
|
157
|
+
if "ok: `True`" in out or "ok=True" in out or '"ok": true' in out.lower():
|
|
158
|
+
spend = ["quota", "spend-slot", "--goal-id", gid, "--slots", "1",
|
|
159
|
+
"--source", "heartbeat", "--execute"]
|
|
160
|
+
if agent_id:
|
|
161
|
+
spend += ["--agent-id", agent_id]
|
|
162
|
+
out += "\n--- spend-slot ---\n" + _gh(spend)
|
|
163
|
+
return out
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
if __name__ == "__main__":
|
|
167
|
+
mcp.run()
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Connect a project to loopx for Claude Code (PROJECT-scoped).
|
|
3
|
+
|
|
4
|
+
Thin wrapper: (1) `loopx connect` the repo, (2) mark `agent_backends += claude`
|
|
5
|
+
in the project registry, (3) install the Claude adapter at PROJECT scope (the
|
|
6
|
+
loopx MCP + the `/loopx` command into this project's `.claude/`) by delegating to
|
|
7
|
+
install.py. No global config, no OS sandbox. The run loop is native `/loop`.
|
|
8
|
+
|
|
9
|
+
python connect.py --project DIR --goal-id G [--objective "…"] [--harden]
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import argparse
|
|
14
|
+
import json
|
|
15
|
+
import shutil
|
|
16
|
+
import subprocess
|
|
17
|
+
import sys
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
PLUGIN_ROOT = Path(__file__).resolve().parents[1]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _gh_prefix():
|
|
24
|
+
exe = shutil.which("loopx")
|
|
25
|
+
return [exe] if exe else [sys.executable, "-m", "loopx.cli"]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def main():
|
|
29
|
+
ap = argparse.ArgumentParser()
|
|
30
|
+
ap.add_argument("--project", required=True)
|
|
31
|
+
ap.add_argument("--goal-id", required=True)
|
|
32
|
+
ap.add_argument("--objective", default=None)
|
|
33
|
+
ap.add_argument("--registry", default=None)
|
|
34
|
+
ap.add_argument("--harden", action="store_true",
|
|
35
|
+
help="also install the optional PreToolUse should_run gate + statusline (project scope)")
|
|
36
|
+
ap.add_argument("--dry-run", action="store_true")
|
|
37
|
+
a = ap.parse_args()
|
|
38
|
+
proj = Path(a.project).resolve()
|
|
39
|
+
dry = a.dry_run
|
|
40
|
+
|
|
41
|
+
# 1. connect the repo to loopx (optional, when an objective is given)
|
|
42
|
+
if a.objective:
|
|
43
|
+
cmd = list(_gh_prefix()) + (["--registry", a.registry] if a.registry else []) + \
|
|
44
|
+
["connect", "--goal-id", a.goal_id, "--objective", a.objective,
|
|
45
|
+
"--state-file", f".claude/goals/{a.goal_id}/ACTIVE_GOAL_STATE.md"]
|
|
46
|
+
print("[connect]", " ".join(cmd))
|
|
47
|
+
if not dry:
|
|
48
|
+
try:
|
|
49
|
+
rc = subprocess.run(cmd, cwd=str(proj), timeout=120)
|
|
50
|
+
except Exception as e:
|
|
51
|
+
print(f"[connect] FAILED to run loopx connect: {e}")
|
|
52
|
+
sys.exit(1)
|
|
53
|
+
if rc.returncode != 0:
|
|
54
|
+
# Fail closed: a non-zero connect (e.g. a global route collision)
|
|
55
|
+
# must stop the helper rather than print a misleading "next steps".
|
|
56
|
+
print(f"[connect] FAILED (exit {rc.returncode}) — not continuing. "
|
|
57
|
+
"Resolve the error above and re-run.")
|
|
58
|
+
sys.exit(rc.returncode)
|
|
59
|
+
|
|
60
|
+
# 2. mark agent_backends += claude in the project registry (additive)
|
|
61
|
+
if a.registry:
|
|
62
|
+
reg = Path(a.registry)
|
|
63
|
+
else:
|
|
64
|
+
reg = proj / ".loopx" / "registry.json"
|
|
65
|
+
if not reg.exists() and (proj / ".goal-harness" / "registry.json").exists():
|
|
66
|
+
reg = proj / ".goal-harness" / "registry.json"
|
|
67
|
+
if reg.exists():
|
|
68
|
+
try:
|
|
69
|
+
data = json.loads(reg.read_text(encoding="utf-8"))
|
|
70
|
+
data.setdefault("agent_backends", [])
|
|
71
|
+
if "claude" not in data["agent_backends"]:
|
|
72
|
+
data["agent_backends"].append("claude")
|
|
73
|
+
print(f"[registry] mark agent_backends += claude ({reg})")
|
|
74
|
+
if not dry:
|
|
75
|
+
reg.write_text(json.dumps(data, indent=2), encoding="utf-8")
|
|
76
|
+
except Exception as e:
|
|
77
|
+
print(" (registry annotate skipped:", e, ")")
|
|
78
|
+
else:
|
|
79
|
+
print(f"[registry] {reg} not found yet (run with --objective to connect first)")
|
|
80
|
+
|
|
81
|
+
# 3. install the Claude adapter at PROJECT scope (MCP + /loopx command [+ --harden])
|
|
82
|
+
installer = PLUGIN_ROOT / "scripts" / "install.py"
|
|
83
|
+
inst = [sys.executable, str(installer), "--scope", "project", "--project", str(proj)]
|
|
84
|
+
if a.harden:
|
|
85
|
+
inst.append("--harden")
|
|
86
|
+
if dry:
|
|
87
|
+
inst.append("--dry-run")
|
|
88
|
+
print("[install]", " ".join(inst))
|
|
89
|
+
ri = subprocess.run(inst, cwd=str(proj))
|
|
90
|
+
if ri.returncode != 0:
|
|
91
|
+
# Fail closed: don't claim success / print next steps if the adapter
|
|
92
|
+
# install failed.
|
|
93
|
+
print(f"[install] FAILED (exit {ri.returncode}).")
|
|
94
|
+
sys.exit(ri.returncode)
|
|
95
|
+
|
|
96
|
+
print("\nNext: open Claude Code in the project, then:")
|
|
97
|
+
print(" /loopx <task> # set the goal, write .claude/loop.md, do the first segment")
|
|
98
|
+
print(" /loop # drive the loop (native; loopx should_run gates each tick)")
|
|
99
|
+
print(" /loopx off # remove the loop protocol")
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
if __name__ == "__main__":
|
|
103
|
+
main()
|