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,991 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from .control_plane.scheduler.execution_context import (
|
|
6
|
+
GENERIC_CLI_OUTER_CONTROLLER_SCHEDULER_CONTEXT,
|
|
7
|
+
scheduler_execution_context_for_turn,
|
|
8
|
+
)
|
|
9
|
+
from .control_plane.turn_driver.driver import SUPPORTED_HOSTS
|
|
10
|
+
from .host_loop_activation import _identity_state
|
|
11
|
+
from .project_prompt import render_quota_guard_command, shell_arg
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
SCHEMA_VERSION = "host_mode_plan_v0"
|
|
15
|
+
|
|
16
|
+
# Host-mode selector modes. These are user/operator choices that map onto the
|
|
17
|
+
# shipped connector catalog and LoopX Turn contract; they are not a second
|
|
18
|
+
# runtime authority.
|
|
19
|
+
MODE_VISIBLE_TUI = "visible_tui"
|
|
20
|
+
MODE_ISOLATED_HEADLESS_TURN = "isolated_headless_turn"
|
|
21
|
+
MODE_IM_GATEWAY = "im_gateway"
|
|
22
|
+
MODE_SHELL_SERVICE = "shell_service"
|
|
23
|
+
MODE_HYBRID_HANDOFF = "hybrid_handoff"
|
|
24
|
+
|
|
25
|
+
CAP_VISIBLE_SESSION = "visible_session"
|
|
26
|
+
CAP_LOOPX_TURN = "loopx_turn"
|
|
27
|
+
CAP_TYPED_HOST_ADAPTER = "typed_host_adapter"
|
|
28
|
+
CAP_INDEPENDENT_VALIDATOR = "independent_validator"
|
|
29
|
+
CAP_CHAT_GATEWAY = "chat_gateway"
|
|
30
|
+
CAP_SERVICE_TIMER = "service_timer"
|
|
31
|
+
CAP_SHELL = "shell"
|
|
32
|
+
|
|
33
|
+
INTENT_WATCH_EACH_TURN = "watch_each_turn"
|
|
34
|
+
INTENT_CONTINUE_WITHOUT_UI = "continue_without_ui"
|
|
35
|
+
INTENT_INTAKE_FROM_CHAT = "intake_from_chat"
|
|
36
|
+
INTENT_TIMER_KEEPALIVE = "timer_keepalive"
|
|
37
|
+
INTENT_ESCALATE_BETWEEN_MODES = "escalate_between_modes"
|
|
38
|
+
|
|
39
|
+
_SHARED_PROOFS = [
|
|
40
|
+
"scoped_agent_identity",
|
|
41
|
+
"quota_guard_before_run",
|
|
42
|
+
"turn_envelope_or_connector_contract_preserved",
|
|
43
|
+
"readiness_probe",
|
|
44
|
+
]
|
|
45
|
+
_TURN_PROOFS = [
|
|
46
|
+
"loopx_turn_plan_preview",
|
|
47
|
+
"typed_host_adapter",
|
|
48
|
+
"independent_validation",
|
|
49
|
+
"validated_writeback_before_quota_spend",
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
_MODE_METADATA: dict[str, dict[str, Any]] = {
|
|
53
|
+
MODE_VISIBLE_TUI: {
|
|
54
|
+
"intent": INTENT_WATCH_EACH_TURN,
|
|
55
|
+
"connector_id": "codex_cli_tui",
|
|
56
|
+
"turn_host": "codex-cli",
|
|
57
|
+
"turn_execution_mode": "interactive-visible",
|
|
58
|
+
"scheduler_owner": "agent_cli_loop",
|
|
59
|
+
"required_capabilities": [CAP_VISIBLE_SESSION],
|
|
60
|
+
"summary": "Keep work in a visible Codex/agent TUI so the user can watch or steer each turn.",
|
|
61
|
+
"extra_proofs": ["visible_surface_preserved", "clear_gate_handling"],
|
|
62
|
+
},
|
|
63
|
+
MODE_ISOLATED_HEADLESS_TURN: {
|
|
64
|
+
"intent": INTENT_CONTINUE_WITHOUT_UI,
|
|
65
|
+
"connector_id": "loopx_turn",
|
|
66
|
+
"turn_host": "generic-cli",
|
|
67
|
+
"turn_execution_mode": "isolated-headless",
|
|
68
|
+
"scheduler_owner": "outer_controller",
|
|
69
|
+
"required_capabilities": [CAP_LOOPX_TURN, CAP_TYPED_HOST_ADAPTER, CAP_INDEPENDENT_VALIDATOR],
|
|
70
|
+
"summary": "Use the shipped LoopX Turn path for bounded headless execution with typed results.",
|
|
71
|
+
"extra_proofs": [*_TURN_PROOFS, "opaque_session_handle_not_public"],
|
|
72
|
+
},
|
|
73
|
+
MODE_IM_GATEWAY: {
|
|
74
|
+
"intent": INTENT_INTAKE_FROM_CHAT,
|
|
75
|
+
"connector_id": "http_webhook",
|
|
76
|
+
"turn_host": None,
|
|
77
|
+
"turn_execution_mode": None,
|
|
78
|
+
"scheduler_owner": None,
|
|
79
|
+
"required_capabilities": [CAP_CHAT_GATEWAY],
|
|
80
|
+
"summary": "Use a chat/webhook gateway only for durable intake, then hand execution to LoopX state.",
|
|
81
|
+
"extra_proofs": ["durable_todo_intake", "text_or_card_fallback", "source_boundary"],
|
|
82
|
+
},
|
|
83
|
+
MODE_SHELL_SERVICE: {
|
|
84
|
+
"intent": INTENT_TIMER_KEEPALIVE,
|
|
85
|
+
"connector_id": "shell_worker",
|
|
86
|
+
"turn_host": "generic-cli",
|
|
87
|
+
"turn_execution_mode": "isolated-headless",
|
|
88
|
+
"scheduler_owner": "outer_controller",
|
|
89
|
+
"required_capabilities": [CAP_SERVICE_TIMER, CAP_SHELL, CAP_LOOPX_TURN, CAP_TYPED_HOST_ADAPTER, CAP_INDEPENDENT_VALIDATOR],
|
|
90
|
+
"summary": "Let a shell, cron, launchd, or service timer wake bounded LoopX Turn previews/runs.",
|
|
91
|
+
"extra_proofs": [*_TURN_PROOFS, "scheduler_hint_backoff", "no_spend_quiet_monitor"],
|
|
92
|
+
},
|
|
93
|
+
MODE_HYBRID_HANDOFF: {
|
|
94
|
+
"intent": INTENT_ESCALATE_BETWEEN_MODES,
|
|
95
|
+
"connector_id": "hybrid_handoff",
|
|
96
|
+
"turn_host": None,
|
|
97
|
+
"turn_execution_mode": None,
|
|
98
|
+
"scheduler_owner": None,
|
|
99
|
+
"required_capabilities": [],
|
|
100
|
+
"summary": "Plan an explicit transition between visible, intake, timer, and headless Turn modes.",
|
|
101
|
+
"extra_proofs": [
|
|
102
|
+
"explicit_transition_event",
|
|
103
|
+
"target_mode_readiness",
|
|
104
|
+
"shared_agent_id_writeback",
|
|
105
|
+
"visible_escalation_for_user_gate",
|
|
106
|
+
],
|
|
107
|
+
},
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
CANONICAL_MODES = list(_MODE_METADATA)
|
|
111
|
+
SUPPORTED_INTENTS = [meta["intent"] for meta in _MODE_METADATA.values()]
|
|
112
|
+
SUPPORTED_HOST_CAPABILITIES = [
|
|
113
|
+
CAP_VISIBLE_SESSION,
|
|
114
|
+
CAP_LOOPX_TURN,
|
|
115
|
+
CAP_TYPED_HOST_ADAPTER,
|
|
116
|
+
CAP_INDEPENDENT_VALIDATOR,
|
|
117
|
+
CAP_CHAT_GATEWAY,
|
|
118
|
+
CAP_SERVICE_TIMER,
|
|
119
|
+
CAP_SHELL,
|
|
120
|
+
]
|
|
121
|
+
_INTENT_PRIMARY_MODE = {meta["intent"]: mode for mode, meta in _MODE_METADATA.items()}
|
|
122
|
+
|
|
123
|
+
# Host identities that map to a concrete visible Turn host. Visible TUI mapping
|
|
124
|
+
# requires an explicit host identity so Codex CLI, Claude Code, and generic CLI
|
|
125
|
+
# sessions keep their real host binding instead of a hard-coded Codex default.
|
|
126
|
+
SUPPORTED_TURN_HOST_IDENTITIES = sorted(SUPPORTED_HOSTS)
|
|
127
|
+
|
|
128
|
+
# Typed host identity -> runtime connector catalog id. Only identities with a
|
|
129
|
+
# registered catalog connector may emit a host-specific visible mapping; any
|
|
130
|
+
# other identity fails closed instead of fabricating a connector id.
|
|
131
|
+
VISIBLE_HOST_CONNECTOR_IDS: dict[str, str] = {
|
|
132
|
+
"codex-cli": "codex_cli_tui",
|
|
133
|
+
"claude-code": "claude_code_loop",
|
|
134
|
+
# OpenCode runs its visible goal loop through the generic-cli Turn host;
|
|
135
|
+
# parity lives in the selector/catalog mapping, not a new Turn host kind.
|
|
136
|
+
"generic-cli": "opencode_goal_loop",
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
# Host identities that are valid visible selections but map to the OpenCode
|
|
140
|
+
# goal loop connector rather than their own Turn host kind.
|
|
141
|
+
VISIBLE_OPENCODE_ALIASES: dict[str, str] = {
|
|
142
|
+
"opencode": "generic-cli",
|
|
143
|
+
"open-code": "generic-cli",
|
|
144
|
+
"opencode2": "generic-cli",
|
|
145
|
+
"opencode-2": "generic-cli",
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
# Pi runs its visible goal loop through the generic-cli Turn host, same as
|
|
149
|
+
# OpenCode, but keeps its own goal-loop connector identity.
|
|
150
|
+
VISIBLE_PI_ALIASES: dict[str, str] = {
|
|
151
|
+
"pi": "generic-cli",
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
# Connector id override for source host identities that alias onto the
|
|
155
|
+
# generic-cli Turn host. The normalized Turn host stays generic-cli while the
|
|
156
|
+
# visible connector keeps naming the real host loop.
|
|
157
|
+
VISIBLE_CONNECTOR_OVERRIDES: dict[str, str] = {
|
|
158
|
+
"pi": "pi_goal_loop",
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
CAPABILITY_GUIDANCE = {
|
|
163
|
+
CAP_VISIBLE_SESSION: "A visible session is required so the user can watch, steer, or take over safely.",
|
|
164
|
+
CAP_LOOPX_TURN: "LoopX Turn support is required before headless execution can be trusted.",
|
|
165
|
+
CAP_TYPED_HOST_ADAPTER: "A typed host adapter is required so the host returns one machine-checkable result instead of free-form prose.",
|
|
166
|
+
CAP_INDEPENDENT_VALIDATOR: "An independent validator is required so LoopX does not trust the host's own completion claim.",
|
|
167
|
+
CAP_CHAT_GATEWAY: "A chat or webhook gateway is required before work can be created from an external intake surface.",
|
|
168
|
+
CAP_SERVICE_TIMER: "A service timer, cron, launchd, or equivalent wake mechanism is required for timer keepalive.",
|
|
169
|
+
CAP_SHELL: "Shell execution is required so the host can run LoopX preview and guard commands.",
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class HostModePlanError(ValueError):
|
|
174
|
+
"""Raised for unknown/empty intent, capability, or host selector input."""
|
|
175
|
+
|
|
176
|
+
def __init__(self, *, reason: str, field: str, suggestions: list[str] | None = None) -> None:
|
|
177
|
+
self.reason = reason
|
|
178
|
+
self.field = field
|
|
179
|
+
self.suggestions = suggestions or []
|
|
180
|
+
super().__init__(reason)
|
|
181
|
+
|
|
182
|
+
def to_payload(self) -> dict[str, Any]:
|
|
183
|
+
return {
|
|
184
|
+
"ok": False,
|
|
185
|
+
"schema_version": "host_mode_plan_error_v0",
|
|
186
|
+
"error_kind": "invalid_workflow_intent_or_capability",
|
|
187
|
+
"field": self.field,
|
|
188
|
+
"reason": self.reason,
|
|
189
|
+
"suggestions": self.suggestions,
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _normalize_tokens(values: Any) -> list[str]:
|
|
194
|
+
if values is None:
|
|
195
|
+
return []
|
|
196
|
+
if isinstance(values, str):
|
|
197
|
+
values = [values]
|
|
198
|
+
tokens: list[str] = []
|
|
199
|
+
for value in values:
|
|
200
|
+
token = str(value or "").strip().lower().replace("-", "_")
|
|
201
|
+
if token and token not in tokens:
|
|
202
|
+
tokens.append(token)
|
|
203
|
+
return tokens
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _normalize_supported(values: Any, *, field: str, supported: list[str], required: bool) -> list[str]:
|
|
207
|
+
tokens = _normalize_tokens(values)
|
|
208
|
+
if required and not tokens:
|
|
209
|
+
raise HostModePlanError(reason=f"{field} is required", field=field, suggestions=supported)
|
|
210
|
+
unknown = [token for token in tokens if token not in supported]
|
|
211
|
+
if unknown:
|
|
212
|
+
raise HostModePlanError(
|
|
213
|
+
reason=f"unsupported {field}: {', '.join(unknown)}",
|
|
214
|
+
field=field,
|
|
215
|
+
suggestions=supported,
|
|
216
|
+
)
|
|
217
|
+
return tokens
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def _missing_mode_capabilities(mode: str, host_capabilities: list[str]) -> list[str]:
|
|
221
|
+
if mode == MODE_HYBRID_HANDOFF:
|
|
222
|
+
return []
|
|
223
|
+
required = _MODE_METADATA[mode]["required_capabilities"]
|
|
224
|
+
return [capability for capability in required if capability not in host_capabilities]
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def _ready_concrete_modes(
|
|
228
|
+
host_capabilities: list[str], host_identity: str | None = None
|
|
229
|
+
) -> list[str]:
|
|
230
|
+
return [
|
|
231
|
+
candidate
|
|
232
|
+
for candidate in CANONICAL_MODES
|
|
233
|
+
if candidate != MODE_HYBRID_HANDOFF
|
|
234
|
+
and not _missing_mode_capabilities(candidate, host_capabilities)
|
|
235
|
+
and not (
|
|
236
|
+
candidate == MODE_VISIBLE_TUI
|
|
237
|
+
and host_identity not in VISIBLE_HOST_CONNECTOR_IDS
|
|
238
|
+
)
|
|
239
|
+
]
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def _mode_capability_ready(
|
|
243
|
+
mode: str, host_capabilities: list[str], host_identity: str | None = None
|
|
244
|
+
) -> bool:
|
|
245
|
+
if mode == MODE_VISIBLE_TUI and host_identity not in VISIBLE_HOST_CONNECTOR_IDS:
|
|
246
|
+
# Without an explicit catalog-registered host identity, a generic
|
|
247
|
+
# visible_session cannot establish which host is present; fail closed.
|
|
248
|
+
return False
|
|
249
|
+
if mode == MODE_HYBRID_HANDOFF:
|
|
250
|
+
return len(_ready_concrete_modes(host_capabilities, host_identity)) >= 2
|
|
251
|
+
return not _missing_mode_capabilities(mode, host_capabilities)
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def _turn_plan_command(
|
|
255
|
+
*,
|
|
256
|
+
goal_id: str,
|
|
257
|
+
agent_id: str | None,
|
|
258
|
+
mode: str,
|
|
259
|
+
cli_bin: str,
|
|
260
|
+
available_capabilities: list[str] | None,
|
|
261
|
+
host_identity: str | None,
|
|
262
|
+
) -> str | None:
|
|
263
|
+
meta = _MODE_METADATA[mode]
|
|
264
|
+
turn_host = meta.get("turn_host")
|
|
265
|
+
execution_mode = meta.get("turn_execution_mode")
|
|
266
|
+
if not turn_host or not execution_mode:
|
|
267
|
+
return None
|
|
268
|
+
if mode == MODE_VISIBLE_TUI:
|
|
269
|
+
# Visible mode requires an explicit, catalog-registered host identity.
|
|
270
|
+
# Without it, a coarse `visible_session` capability cannot distinguish
|
|
271
|
+
# Codex CLI, Claude Code, or OpenCode, so claiming any concrete host
|
|
272
|
+
# would fabricate attribution.
|
|
273
|
+
if not host_identity:
|
|
274
|
+
raise HostModePlanError(
|
|
275
|
+
reason=(
|
|
276
|
+
"visible_tui requires an explicit host_identity; a generic "
|
|
277
|
+
"visible_session capability cannot identify the actual host"
|
|
278
|
+
),
|
|
279
|
+
field="host_identity",
|
|
280
|
+
suggestions=sorted(VISIBLE_HOST_CONNECTOR_IDS),
|
|
281
|
+
)
|
|
282
|
+
if host_identity not in VISIBLE_HOST_CONNECTOR_IDS:
|
|
283
|
+
raise HostModePlanError(
|
|
284
|
+
reason=(
|
|
285
|
+
f"host_identity {host_identity!r} has no registered catalog "
|
|
286
|
+
"connector for visible mode"
|
|
287
|
+
),
|
|
288
|
+
field="host_identity",
|
|
289
|
+
suggestions=sorted(VISIBLE_HOST_CONNECTOR_IDS),
|
|
290
|
+
)
|
|
291
|
+
turn_host = host_identity
|
|
292
|
+
if turn_host not in SUPPORTED_HOSTS:
|
|
293
|
+
raise HostModePlanError(
|
|
294
|
+
reason=f"unsupported Turn host mapped by {mode}: {turn_host}",
|
|
295
|
+
field="mode",
|
|
296
|
+
suggestions=CANONICAL_MODES,
|
|
297
|
+
)
|
|
298
|
+
agent_arg = f" --agent-id {shell_arg(agent_id)}" if agent_id else ""
|
|
299
|
+
capability_args = "".join(
|
|
300
|
+
f" --available-capability {shell_arg(capability)}"
|
|
301
|
+
for capability in (available_capabilities or [])
|
|
302
|
+
)
|
|
303
|
+
scheduler_owner = meta.get("scheduler_owner")
|
|
304
|
+
scheduler_arg = f" --scheduler-owner {shell_arg(scheduler_owner)}" if scheduler_owner else ""
|
|
305
|
+
return (
|
|
306
|
+
f"{shell_arg(cli_bin)} turn plan --goal-id {shell_arg(goal_id)}{agent_arg} "
|
|
307
|
+
f"--host {shell_arg(turn_host)} --execution-mode {shell_arg(execution_mode)}"
|
|
308
|
+
f"{scheduler_arg}{capability_args}"
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def _scheduler_context(mode: str, host_identity: str | None = None) -> dict[str, Any] | None:
|
|
313
|
+
meta = _MODE_METADATA[mode]
|
|
314
|
+
turn_host = meta.get("turn_host")
|
|
315
|
+
execution_mode = meta.get("turn_execution_mode")
|
|
316
|
+
if mode == MODE_VISIBLE_TUI:
|
|
317
|
+
if host_identity in VISIBLE_HOST_CONNECTOR_IDS:
|
|
318
|
+
turn_host = host_identity
|
|
319
|
+
else:
|
|
320
|
+
# No honest host binding exists without an explicit registered
|
|
321
|
+
# identity; do not project a fabricated host context.
|
|
322
|
+
return None
|
|
323
|
+
if not turn_host or not execution_mode:
|
|
324
|
+
if mode == MODE_IM_GATEWAY:
|
|
325
|
+
return None
|
|
326
|
+
if mode == MODE_HYBRID_HANDOFF:
|
|
327
|
+
return None
|
|
328
|
+
resolution = scheduler_execution_context_for_turn(
|
|
329
|
+
host=str(turn_host),
|
|
330
|
+
execution_mode=str(execution_mode),
|
|
331
|
+
scheduler_owner=meta.get("scheduler_owner"),
|
|
332
|
+
)
|
|
333
|
+
return resolution.projection()
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def _mode_quota_guard(
|
|
337
|
+
*,
|
|
338
|
+
mode: str,
|
|
339
|
+
goal_id: str,
|
|
340
|
+
agent_id: str | None,
|
|
341
|
+
cli_bin: str,
|
|
342
|
+
available_capabilities: list[str] | None,
|
|
343
|
+
host_identity: str | None = None,
|
|
344
|
+
) -> str:
|
|
345
|
+
scheduler_context = _scheduler_context(mode, host_identity)
|
|
346
|
+
runtime_profile = None
|
|
347
|
+
if mode == MODE_IM_GATEWAY:
|
|
348
|
+
runtime_profile = None
|
|
349
|
+
elif scheduler_context is None and mode == MODE_HYBRID_HANDOFF:
|
|
350
|
+
scheduler_context = GENERIC_CLI_OUTER_CONTROLLER_SCHEDULER_CONTEXT
|
|
351
|
+
return render_quota_guard_command(
|
|
352
|
+
goal_id,
|
|
353
|
+
cli_bin=cli_bin,
|
|
354
|
+
agent_id=agent_id,
|
|
355
|
+
available_capabilities=available_capabilities,
|
|
356
|
+
runtime_profile=runtime_profile,
|
|
357
|
+
scheduler_execution_context=scheduler_context,
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def _blocking_reasons(
|
|
362
|
+
mode: str, host_capabilities: list[str], host_identity: str | None = None
|
|
363
|
+
) -> list[str]:
|
|
364
|
+
if mode == MODE_HYBRID_HANDOFF:
|
|
365
|
+
ready_modes = _ready_concrete_modes(host_capabilities, host_identity)
|
|
366
|
+
if len(ready_modes) >= 2:
|
|
367
|
+
return []
|
|
368
|
+
return [
|
|
369
|
+
"Hybrid handoff requires at least two concrete ready modes so work can move between runtime surfaces safely."
|
|
370
|
+
]
|
|
371
|
+
return [
|
|
372
|
+
CAPABILITY_GUIDANCE[capability]
|
|
373
|
+
for capability in _missing_mode_capabilities(mode, host_capabilities)
|
|
374
|
+
]
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def _recommended_next_steps(
|
|
378
|
+
*,
|
|
379
|
+
mode: str,
|
|
380
|
+
host_capabilities: list[str],
|
|
381
|
+
turn_plan_command: str | None,
|
|
382
|
+
quota_guard_command: str,
|
|
383
|
+
host_identity: str | None = None,
|
|
384
|
+
) -> list[dict[str, Any]]:
|
|
385
|
+
missing = _missing_mode_capabilities(mode, host_capabilities)
|
|
386
|
+
steps: list[dict[str, Any]] = []
|
|
387
|
+
if mode == MODE_HYBRID_HANDOFF:
|
|
388
|
+
ready_modes = _ready_concrete_modes(host_capabilities, host_identity)
|
|
389
|
+
if len(ready_modes) < 2:
|
|
390
|
+
steps.append(
|
|
391
|
+
{
|
|
392
|
+
"step": 1,
|
|
393
|
+
"kind": "stop",
|
|
394
|
+
"action": "Do not plan a hybrid handoff yet; make at least two concrete host modes ready first.",
|
|
395
|
+
"command": None,
|
|
396
|
+
"no_spend": True,
|
|
397
|
+
}
|
|
398
|
+
)
|
|
399
|
+
else:
|
|
400
|
+
steps.append(
|
|
401
|
+
{
|
|
402
|
+
"step": 1,
|
|
403
|
+
"kind": "preview",
|
|
404
|
+
"action": "Preview the target mode before changing host surfaces.",
|
|
405
|
+
"command": quota_guard_command,
|
|
406
|
+
"no_spend": True,
|
|
407
|
+
}
|
|
408
|
+
)
|
|
409
|
+
steps.append(
|
|
410
|
+
{
|
|
411
|
+
"step": len(steps) + 1,
|
|
412
|
+
"kind": "handoff_gate",
|
|
413
|
+
"action": "Escalate back to visible_tui whenever a user gate, ambiguous decision, missing validator, or risky action appears.",
|
|
414
|
+
"command": None,
|
|
415
|
+
"no_spend": True,
|
|
416
|
+
}
|
|
417
|
+
)
|
|
418
|
+
return steps
|
|
419
|
+
|
|
420
|
+
if missing:
|
|
421
|
+
steps.append(
|
|
422
|
+
{
|
|
423
|
+
"step": 1,
|
|
424
|
+
"kind": "stop",
|
|
425
|
+
"action": "Stop before attempting this host mode; fill the missing host capabilities first.",
|
|
426
|
+
"command": None,
|
|
427
|
+
"no_spend": True,
|
|
428
|
+
}
|
|
429
|
+
)
|
|
430
|
+
for capability in missing:
|
|
431
|
+
steps.append(
|
|
432
|
+
{
|
|
433
|
+
"step": len(steps) + 1,
|
|
434
|
+
"kind": "capability_gap",
|
|
435
|
+
"capability": capability,
|
|
436
|
+
"action": CAPABILITY_GUIDANCE[capability],
|
|
437
|
+
"command": None,
|
|
438
|
+
"no_spend": True,
|
|
439
|
+
}
|
|
440
|
+
)
|
|
441
|
+
steps.append(
|
|
442
|
+
{
|
|
443
|
+
"step": len(steps) + 1,
|
|
444
|
+
"kind": "repreview",
|
|
445
|
+
"action": "Re-run loopx host-mode-plan after the missing capabilities are configured.",
|
|
446
|
+
"command": None,
|
|
447
|
+
"no_spend": True,
|
|
448
|
+
}
|
|
449
|
+
)
|
|
450
|
+
return steps
|
|
451
|
+
|
|
452
|
+
if mode == MODE_IM_GATEWAY:
|
|
453
|
+
steps.append(
|
|
454
|
+
{
|
|
455
|
+
"step": 1,
|
|
456
|
+
"kind": "intake",
|
|
457
|
+
"action": "Create durable work from the chat or webhook intake surface; the gateway itself does not execute.",
|
|
458
|
+
"command": None,
|
|
459
|
+
"no_spend": True,
|
|
460
|
+
}
|
|
461
|
+
)
|
|
462
|
+
steps.append(
|
|
463
|
+
{
|
|
464
|
+
"step": 2,
|
|
465
|
+
"kind": "state_preview",
|
|
466
|
+
"action": "Confirm current LoopX state is safe to proceed after intake is recorded.",
|
|
467
|
+
"command": quota_guard_command,
|
|
468
|
+
"no_spend": True,
|
|
469
|
+
}
|
|
470
|
+
)
|
|
471
|
+
steps.append(
|
|
472
|
+
{
|
|
473
|
+
"step": 3,
|
|
474
|
+
"kind": "handoff_gate",
|
|
475
|
+
"action": "Choose an execution mode for the durable work: isolated_headless_turn for unattended runs, visible_tui when a user gate or ambiguous decision appears.",
|
|
476
|
+
"command": None,
|
|
477
|
+
"no_spend": True,
|
|
478
|
+
}
|
|
479
|
+
)
|
|
480
|
+
return steps
|
|
481
|
+
|
|
482
|
+
steps.append(
|
|
483
|
+
{
|
|
484
|
+
"step": 1,
|
|
485
|
+
"kind": "state_preview",
|
|
486
|
+
"action": "Confirm current LoopX state is safe to proceed before host setup.",
|
|
487
|
+
"command": quota_guard_command,
|
|
488
|
+
"no_spend": True,
|
|
489
|
+
}
|
|
490
|
+
)
|
|
491
|
+
if turn_plan_command:
|
|
492
|
+
steps.append(
|
|
493
|
+
{
|
|
494
|
+
"step": len(steps) + 1,
|
|
495
|
+
"kind": "turn_preview",
|
|
496
|
+
"action": "Preview the bounded run before launching or resuming any host execution.",
|
|
497
|
+
"command": turn_plan_command,
|
|
498
|
+
"no_spend": True,
|
|
499
|
+
}
|
|
500
|
+
)
|
|
501
|
+
steps.append(
|
|
502
|
+
{
|
|
503
|
+
"step": len(steps) + 1,
|
|
504
|
+
"kind": "handoff_gate",
|
|
505
|
+
"action": "Escalate back to visible_tui whenever a user gate, ambiguous decision, missing validator, or risky action appears.",
|
|
506
|
+
"command": None,
|
|
507
|
+
"no_spend": True,
|
|
508
|
+
}
|
|
509
|
+
)
|
|
510
|
+
return steps
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
def _build_mode_option(
|
|
514
|
+
*,
|
|
515
|
+
mode: str,
|
|
516
|
+
goal_id: str,
|
|
517
|
+
agent_id: str | None,
|
|
518
|
+
host_capabilities: list[str],
|
|
519
|
+
cli_bin: str,
|
|
520
|
+
available_capabilities: list[str] | None,
|
|
521
|
+
host_identity: str | None,
|
|
522
|
+
connector_override_identity: str | None = None,
|
|
523
|
+
) -> dict[str, Any]:
|
|
524
|
+
meta = _MODE_METADATA[mode]
|
|
525
|
+
visible_unresolved = (
|
|
526
|
+
mode == MODE_VISIBLE_TUI and host_identity not in VISIBLE_HOST_CONNECTOR_IDS
|
|
527
|
+
)
|
|
528
|
+
turn_plan_command = (
|
|
529
|
+
None
|
|
530
|
+
if visible_unresolved
|
|
531
|
+
else _turn_plan_command(
|
|
532
|
+
goal_id=goal_id,
|
|
533
|
+
agent_id=agent_id,
|
|
534
|
+
mode=mode,
|
|
535
|
+
cli_bin=cli_bin,
|
|
536
|
+
available_capabilities=available_capabilities,
|
|
537
|
+
host_identity=host_identity,
|
|
538
|
+
)
|
|
539
|
+
)
|
|
540
|
+
quota_guard_command = _mode_quota_guard(
|
|
541
|
+
mode=mode,
|
|
542
|
+
goal_id=goal_id,
|
|
543
|
+
agent_id=agent_id,
|
|
544
|
+
cli_bin=cli_bin,
|
|
545
|
+
available_capabilities=available_capabilities,
|
|
546
|
+
host_identity=host_identity if host_identity in VISIBLE_HOST_CONNECTOR_IDS else None,
|
|
547
|
+
)
|
|
548
|
+
if mode == MODE_VISIBLE_TUI:
|
|
549
|
+
connector_id = VISIBLE_CONNECTOR_OVERRIDES.get(
|
|
550
|
+
connector_override_identity or ""
|
|
551
|
+
) or VISIBLE_HOST_CONNECTOR_IDS.get(host_identity or "")
|
|
552
|
+
effective_turn_host = host_identity if connector_id else None
|
|
553
|
+
host_resolution = (
|
|
554
|
+
"resolved"
|
|
555
|
+
if connector_id
|
|
556
|
+
else (
|
|
557
|
+
"identity_required"
|
|
558
|
+
if host_identity is None
|
|
559
|
+
else "unregistered_host_identity"
|
|
560
|
+
)
|
|
561
|
+
)
|
|
562
|
+
else:
|
|
563
|
+
connector_id = meta["connector_id"]
|
|
564
|
+
effective_turn_host = meta.get("turn_host")
|
|
565
|
+
host_resolution = "resolved"
|
|
566
|
+
recommended_next_steps = (
|
|
567
|
+
[
|
|
568
|
+
{
|
|
569
|
+
"step": 1,
|
|
570
|
+
"kind": "stop",
|
|
571
|
+
"action": (
|
|
572
|
+
"Re-run with --host-identity set to a catalog-registered visible host "
|
|
573
|
+
"(" + ", ".join(sorted(VISIBLE_HOST_CONNECTOR_IDS)) + ")."
|
|
574
|
+
),
|
|
575
|
+
"command": None,
|
|
576
|
+
"no_spend": True,
|
|
577
|
+
}
|
|
578
|
+
]
|
|
579
|
+
if visible_unresolved
|
|
580
|
+
else _recommended_next_steps(
|
|
581
|
+
mode=mode,
|
|
582
|
+
host_capabilities=host_capabilities,
|
|
583
|
+
turn_plan_command=turn_plan_command,
|
|
584
|
+
quota_guard_command=quota_guard_command,
|
|
585
|
+
host_identity=host_identity,
|
|
586
|
+
)
|
|
587
|
+
)
|
|
588
|
+
return {
|
|
589
|
+
"mode": mode,
|
|
590
|
+
"summary": meta["summary"],
|
|
591
|
+
"connector_id": connector_id,
|
|
592
|
+
"host_resolution": host_resolution,
|
|
593
|
+
"host_identity": host_identity if mode == MODE_VISIBLE_TUI else None,
|
|
594
|
+
"capability_ready": _mode_capability_ready(mode, host_capabilities, host_identity),
|
|
595
|
+
"required_host_capabilities": list(meta["required_capabilities"]),
|
|
596
|
+
"missing_host_capabilities": _missing_mode_capabilities(mode, host_capabilities),
|
|
597
|
+
"blocking_reasons": (
|
|
598
|
+
_blocking_reasons(mode, host_capabilities, host_identity)
|
|
599
|
+
if not (mode == MODE_VISIBLE_TUI and visible_unresolved)
|
|
600
|
+
else [
|
|
601
|
+
(
|
|
602
|
+
"visible_tui requires an explicit host_identity "
|
|
603
|
+
"(--host-identity); a generic visible_session capability "
|
|
604
|
+
"cannot identify the actual host."
|
|
605
|
+
)
|
|
606
|
+
if host_identity is None
|
|
607
|
+
else (
|
|
608
|
+
f"host_identity {host_identity!r} has no registered catalog "
|
|
609
|
+
"connector for visible mode."
|
|
610
|
+
)
|
|
611
|
+
]
|
|
612
|
+
),
|
|
613
|
+
"recommended_next_steps": recommended_next_steps,
|
|
614
|
+
"turn_mapping": {
|
|
615
|
+
"host": effective_turn_host,
|
|
616
|
+
"execution_mode": meta.get("turn_execution_mode"),
|
|
617
|
+
"scheduler_owner": meta.get("scheduler_owner"),
|
|
618
|
+
"plan_command": turn_plan_command,
|
|
619
|
+
},
|
|
620
|
+
"scheduler_execution_context": _scheduler_context(mode, host_identity),
|
|
621
|
+
"quota_guard_command": quota_guard_command,
|
|
622
|
+
"required_proofs": [*_SHARED_PROOFS, *meta["extra_proofs"]],
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
def _transitions(
|
|
627
|
+
*,
|
|
628
|
+
goal_id: str,
|
|
629
|
+
agent_id: str | None,
|
|
630
|
+
cli_bin: str,
|
|
631
|
+
available_capabilities: list[str] | None,
|
|
632
|
+
host_identity: str | None,
|
|
633
|
+
) -> list[dict[str, Any]]:
|
|
634
|
+
def target_turn_command(to_mode: str) -> str | None:
|
|
635
|
+
try:
|
|
636
|
+
return _turn_plan_command(
|
|
637
|
+
goal_id=goal_id,
|
|
638
|
+
agent_id=agent_id,
|
|
639
|
+
mode=to_mode,
|
|
640
|
+
cli_bin=cli_bin,
|
|
641
|
+
available_capabilities=available_capabilities,
|
|
642
|
+
host_identity=host_identity,
|
|
643
|
+
)
|
|
644
|
+
except HostModePlanError:
|
|
645
|
+
# A visible target without a catalog-registered identity has no
|
|
646
|
+
# honest preview command; emit none rather than a fabricated host.
|
|
647
|
+
return None
|
|
648
|
+
def transition(
|
|
649
|
+
transition_id: str,
|
|
650
|
+
from_mode: str,
|
|
651
|
+
to_mode: str,
|
|
652
|
+
trigger: str,
|
|
653
|
+
target_readiness: list[str],
|
|
654
|
+
) -> dict[str, Any]:
|
|
655
|
+
return {
|
|
656
|
+
"transition": transition_id,
|
|
657
|
+
"from_mode": from_mode,
|
|
658
|
+
"to_mode": to_mode,
|
|
659
|
+
"trigger": trigger,
|
|
660
|
+
"preserves_agent_id": True,
|
|
661
|
+
"spends_quota": False,
|
|
662
|
+
"target_readiness": target_readiness,
|
|
663
|
+
"target_turn_plan_command": target_turn_command(to_mode),
|
|
664
|
+
"guard_command": _mode_quota_guard(
|
|
665
|
+
mode=to_mode,
|
|
666
|
+
goal_id=goal_id,
|
|
667
|
+
agent_id=agent_id,
|
|
668
|
+
cli_bin=cli_bin,
|
|
669
|
+
available_capabilities=available_capabilities,
|
|
670
|
+
host_identity=host_identity if host_identity in VISIBLE_HOST_CONNECTOR_IDS else None,
|
|
671
|
+
),
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
return [
|
|
675
|
+
transition(
|
|
676
|
+
"visible_bootstrap_to_isolated_headless_turn",
|
|
677
|
+
MODE_VISIBLE_TUI,
|
|
678
|
+
MODE_ISOLATED_HEADLESS_TURN,
|
|
679
|
+
"the user closes the visible session but wants bounded work to continue",
|
|
680
|
+
[CAP_LOOPX_TURN, CAP_TYPED_HOST_ADAPTER, CAP_INDEPENDENT_VALIDATOR],
|
|
681
|
+
),
|
|
682
|
+
transition(
|
|
683
|
+
"isolated_headless_turn_to_visible_tui_escalation",
|
|
684
|
+
MODE_ISOLATED_HEADLESS_TURN,
|
|
685
|
+
MODE_VISIBLE_TUI,
|
|
686
|
+
"a user gate or ambiguous decision requires visible steering",
|
|
687
|
+
[CAP_VISIBLE_SESSION, "clear_gate_handling"],
|
|
688
|
+
),
|
|
689
|
+
transition(
|
|
690
|
+
"im_gateway_to_isolated_headless_turn",
|
|
691
|
+
MODE_IM_GATEWAY,
|
|
692
|
+
MODE_ISOLATED_HEADLESS_TURN,
|
|
693
|
+
"chat intake has created durable work that can run through LoopX Turn",
|
|
694
|
+
[CAP_LOOPX_TURN, CAP_TYPED_HOST_ADAPTER, CAP_INDEPENDENT_VALIDATOR],
|
|
695
|
+
),
|
|
696
|
+
transition(
|
|
697
|
+
"shell_service_to_visible_tui_escalation",
|
|
698
|
+
MODE_SHELL_SERVICE,
|
|
699
|
+
MODE_VISIBLE_TUI,
|
|
700
|
+
"timer-owned work hits a user gate, missing validator, or risky action",
|
|
701
|
+
[CAP_VISIBLE_SESSION, "clear_gate_handling"],
|
|
702
|
+
),
|
|
703
|
+
]
|
|
704
|
+
|
|
705
|
+
|
|
706
|
+
def build_host_mode_plan(
|
|
707
|
+
*,
|
|
708
|
+
goal_id: str,
|
|
709
|
+
user_intent: Any,
|
|
710
|
+
host_capabilities: Any = None,
|
|
711
|
+
agent_id: str | None = None,
|
|
712
|
+
registered_agents: list[str] | None = None,
|
|
713
|
+
cli_bin: str = "loopx",
|
|
714
|
+
available_capabilities: list[str] | None = None,
|
|
715
|
+
host_identity: str | None = None,
|
|
716
|
+
) -> dict[str, Any]:
|
|
717
|
+
"""Build a read-only host-mode selector plan on top of LoopX Turn.
|
|
718
|
+
|
|
719
|
+
This selector chooses the user-facing host mode and prints the matching
|
|
720
|
+
connector/Turn preview commands. LoopX Turn, quota, todo projection, and
|
|
721
|
+
run history remain authoritative; the selector never launches a host, writes
|
|
722
|
+
state, validates work, or spends quota.
|
|
723
|
+
"""
|
|
724
|
+
|
|
725
|
+
intents = _normalize_supported(
|
|
726
|
+
user_intent, field="user_intent", supported=SUPPORTED_INTENTS, required=True
|
|
727
|
+
)
|
|
728
|
+
caps = _normalize_supported(
|
|
729
|
+
host_capabilities,
|
|
730
|
+
field="host_capabilities",
|
|
731
|
+
supported=SUPPORTED_HOST_CAPABILITIES,
|
|
732
|
+
required=False,
|
|
733
|
+
)
|
|
734
|
+
available = _normalize_tokens(available_capabilities)
|
|
735
|
+
normalized_host_identity = None
|
|
736
|
+
connector_override_identity = None
|
|
737
|
+
if host_identity:
|
|
738
|
+
# Host identities are Turn host kinds and already use dashes
|
|
739
|
+
# (codex-cli, claude-code, generic-cli); normalize case without
|
|
740
|
+
# converting dashes to underscores. OpenCode and Pi alias onto the
|
|
741
|
+
# generic-cli Turn host while keeping their own connector identity.
|
|
742
|
+
raw_identity = str(host_identity).strip().lower()
|
|
743
|
+
candidate = VISIBLE_OPENCODE_ALIASES.get(raw_identity, raw_identity)
|
|
744
|
+
candidate = VISIBLE_PI_ALIASES.get(candidate, candidate)
|
|
745
|
+
if candidate not in SUPPORTED_TURN_HOST_IDENTITIES:
|
|
746
|
+
raise HostModePlanError(
|
|
747
|
+
reason=f"unsupported host_identity: {candidate}",
|
|
748
|
+
field="host_identity",
|
|
749
|
+
suggestions=SUPPORTED_TURN_HOST_IDENTITIES,
|
|
750
|
+
)
|
|
751
|
+
normalized_host_identity = candidate
|
|
752
|
+
connector_override_identity = (
|
|
753
|
+
raw_identity if raw_identity in VISIBLE_CONNECTOR_OVERRIDES else None
|
|
754
|
+
)
|
|
755
|
+
# Host-mode planning continues an existing goal; fresh identity creation
|
|
756
|
+
# belongs to the onboarding entry points that opt into that policy.
|
|
757
|
+
identity = _identity_state(
|
|
758
|
+
agent_id=agent_id,
|
|
759
|
+
registered_agents=registered_agents,
|
|
760
|
+
fresh_agent_default=False,
|
|
761
|
+
)
|
|
762
|
+
raw_scoped = identity.get("selected_agent_id")
|
|
763
|
+
scoped_agent_id = str(raw_scoped) if raw_scoped else None
|
|
764
|
+
|
|
765
|
+
primary_mode = _INTENT_PRIMARY_MODE[intents[0]]
|
|
766
|
+
ordered_modes = [primary_mode] + [mode for mode in CANONICAL_MODES if mode != primary_mode]
|
|
767
|
+
mode_options = [
|
|
768
|
+
_build_mode_option(
|
|
769
|
+
mode=mode,
|
|
770
|
+
goal_id=goal_id,
|
|
771
|
+
agent_id=scoped_agent_id,
|
|
772
|
+
host_capabilities=caps,
|
|
773
|
+
cli_bin=cli_bin,
|
|
774
|
+
available_capabilities=available,
|
|
775
|
+
host_identity=normalized_host_identity,
|
|
776
|
+
connector_override_identity=connector_override_identity,
|
|
777
|
+
)
|
|
778
|
+
for mode in ordered_modes
|
|
779
|
+
]
|
|
780
|
+
selected = mode_options[0]
|
|
781
|
+
|
|
782
|
+
return {
|
|
783
|
+
"ok": True,
|
|
784
|
+
"schema_version": SCHEMA_VERSION,
|
|
785
|
+
"mode": "dry_run_host_mode_selector",
|
|
786
|
+
"agent_model": "peer_v1",
|
|
787
|
+
"goal_id": goal_id,
|
|
788
|
+
"agent_id": scoped_agent_id,
|
|
789
|
+
"user_intent": intents,
|
|
790
|
+
"host_capabilities": caps,
|
|
791
|
+
"host_identity": normalized_host_identity,
|
|
792
|
+
"selected_mode": primary_mode,
|
|
793
|
+
"selected_connector_id": selected["connector_id"],
|
|
794
|
+
"selected_turn_mapping": selected["turn_mapping"],
|
|
795
|
+
"selected_capability_ready": selected["capability_ready"],
|
|
796
|
+
"selected_missing_host_capabilities": selected["missing_host_capabilities"],
|
|
797
|
+
"selected_blocking_reasons": selected["blocking_reasons"],
|
|
798
|
+
"operator_next_steps": selected["recommended_next_steps"],
|
|
799
|
+
"mode_options": mode_options,
|
|
800
|
+
"identity_contract": identity,
|
|
801
|
+
"guard_command": selected["quota_guard_command"],
|
|
802
|
+
"next_preview_command": (
|
|
803
|
+
selected["turn_mapping"].get("plan_command")
|
|
804
|
+
or selected["quota_guard_command"]
|
|
805
|
+
if not (
|
|
806
|
+
primary_mode == MODE_VISIBLE_TUI
|
|
807
|
+
and normalized_host_identity not in VISIBLE_HOST_CONNECTOR_IDS
|
|
808
|
+
)
|
|
809
|
+
else selected["quota_guard_command"]
|
|
810
|
+
),
|
|
811
|
+
"no_spend_policy": {
|
|
812
|
+
"selector_preview": True,
|
|
813
|
+
"turn_plan_preview": True,
|
|
814
|
+
"quiet_monitor_skip": True,
|
|
815
|
+
"cadence_only_change": True,
|
|
816
|
+
"readiness_or_final_check": True,
|
|
817
|
+
"spends_only_after_validated_delivery_writeback": True,
|
|
818
|
+
},
|
|
819
|
+
"turn_contract": {
|
|
820
|
+
"schema_version": "loopx_turn_v0",
|
|
821
|
+
"role": "execution_authority_for_headless_modes",
|
|
822
|
+
"plan_command_required_before_host_launch": True,
|
|
823
|
+
"host_result_must_be_typed": True,
|
|
824
|
+
"independent_validation_required": True,
|
|
825
|
+
"writeback_before_quota_spend": True,
|
|
826
|
+
"raw_session_or_transcript_publication_allowed": False,
|
|
827
|
+
},
|
|
828
|
+
"transitions": _transitions(
|
|
829
|
+
goal_id=goal_id,
|
|
830
|
+
agent_id=scoped_agent_id,
|
|
831
|
+
cli_bin=cli_bin,
|
|
832
|
+
available_capabilities=available,
|
|
833
|
+
host_identity=normalized_host_identity,
|
|
834
|
+
),
|
|
835
|
+
"boundary": {
|
|
836
|
+
"selector_is_authoritative": False,
|
|
837
|
+
"turn_envelope_is_authoritative_for_execution": True,
|
|
838
|
+
"starts_process": False,
|
|
839
|
+
"writes_state": False,
|
|
840
|
+
"spends_quota": False,
|
|
841
|
+
"infers_production_permission": False,
|
|
842
|
+
"infers_credential_access": False,
|
|
843
|
+
"infers_destructive_authority": False,
|
|
844
|
+
"adapter_neutral": True,
|
|
845
|
+
},
|
|
846
|
+
"truth_contract": {
|
|
847
|
+
"source_of_truth": [
|
|
848
|
+
"loopx_turn_v0",
|
|
849
|
+
"turn_envelope",
|
|
850
|
+
"registry",
|
|
851
|
+
"quota_should_run",
|
|
852
|
+
"todo_projection",
|
|
853
|
+
"run_history",
|
|
854
|
+
"runtime_connector_catalog",
|
|
855
|
+
],
|
|
856
|
+
"selector_is_authoritative": False,
|
|
857
|
+
"recompute_rule": "Recompute before each host setup or handoff.",
|
|
858
|
+
},
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
|
|
862
|
+
def render_host_mode_plan_markdown(payload: dict[str, Any]) -> str:
|
|
863
|
+
if not payload.get("ok"):
|
|
864
|
+
return "\n".join(
|
|
865
|
+
[
|
|
866
|
+
"# LoopX Host Mode Plan Error",
|
|
867
|
+
"",
|
|
868
|
+
f"- ok: `{payload.get('ok')}`",
|
|
869
|
+
f"- error_kind: `{payload.get('error_kind')}`",
|
|
870
|
+
f"- field: `{payload.get('field')}`",
|
|
871
|
+
f"- reason: {payload.get('reason')}",
|
|
872
|
+
f"- suggestions: `{', '.join(payload.get('suggestions') or [])}`",
|
|
873
|
+
]
|
|
874
|
+
)
|
|
875
|
+
identity = payload.get("identity_contract") if isinstance(payload.get("identity_contract"), dict) else {}
|
|
876
|
+
lines = [
|
|
877
|
+
"# LoopX Host Mode Plan",
|
|
878
|
+
"",
|
|
879
|
+
"This is a read-only selector on top of LoopX Turn and the runtime connector catalog.",
|
|
880
|
+
"It chooses a host mode and prints preview commands; it does not execute work or write state.",
|
|
881
|
+
"",
|
|
882
|
+
f"- schema_version: `{payload.get('schema_version')}`",
|
|
883
|
+
f"- mode: `{payload.get('mode')}`",
|
|
884
|
+
f"- goal_id: `{payload.get('goal_id')}`",
|
|
885
|
+
f"- agent_id: `{payload.get('agent_id')}`",
|
|
886
|
+
f"- selected_mode: `{payload.get('selected_mode')}`",
|
|
887
|
+
f"- selected_connector_id: `{payload.get('selected_connector_id') or 'null'}`",
|
|
888
|
+
f"- selected_capability_ready: `{payload.get('selected_capability_ready')}`",
|
|
889
|
+
f"- host_identity: `{payload.get('host_identity')}`",
|
|
890
|
+
f"- selected_missing_host_capabilities: `{', '.join(payload.get('selected_missing_host_capabilities') or [])}`",
|
|
891
|
+
f"- user_intent: `{', '.join(payload.get('user_intent') or [])}`",
|
|
892
|
+
f"- host_capabilities: `{', '.join(payload.get('host_capabilities') or [])}`",
|
|
893
|
+
"",
|
|
894
|
+
]
|
|
895
|
+
selected_ready = payload.get("selected_capability_ready") is True
|
|
896
|
+
if selected_ready:
|
|
897
|
+
lines.extend(
|
|
898
|
+
[
|
|
899
|
+
"## Next Preview Command",
|
|
900
|
+
"",
|
|
901
|
+
"```bash",
|
|
902
|
+
str(payload.get("next_preview_command") or ""),
|
|
903
|
+
"```",
|
|
904
|
+
"",
|
|
905
|
+
]
|
|
906
|
+
)
|
|
907
|
+
else:
|
|
908
|
+
lines.extend(
|
|
909
|
+
[
|
|
910
|
+
"## Not Ready Yet",
|
|
911
|
+
"",
|
|
912
|
+
"Do not run this host mode yet. Fill the gaps below first; the preview command becomes meaningful only after the mode is ready.",
|
|
913
|
+
"",
|
|
914
|
+
]
|
|
915
|
+
)
|
|
916
|
+
lines.extend(
|
|
917
|
+
[
|
|
918
|
+
"## Runtime Modes",
|
|
919
|
+
"",
|
|
920
|
+
"| mode | connector | ready | Turn host | execution mode | scheduler owner | summary |",
|
|
921
|
+
"| --- | --- | --- | --- | --- | --- | --- |",
|
|
922
|
+
]
|
|
923
|
+
)
|
|
924
|
+
for option in payload.get("mode_options") or []:
|
|
925
|
+
if not isinstance(option, dict):
|
|
926
|
+
continue
|
|
927
|
+
turn = option.get("turn_mapping") if isinstance(option.get("turn_mapping"), dict) else {}
|
|
928
|
+
lines.append(
|
|
929
|
+
"| "
|
|
930
|
+
f"`{option.get('mode')}` | "
|
|
931
|
+
f"`{option.get('connector_id') or 'null'}` | "
|
|
932
|
+
f"`{option.get('capability_ready')}` | "
|
|
933
|
+
f"`{turn.get('host')}` | "
|
|
934
|
+
f"`{turn.get('execution_mode')}` | "
|
|
935
|
+
f"`{turn.get('scheduler_owner')}` | "
|
|
936
|
+
f"{option.get('summary')} |"
|
|
937
|
+
)
|
|
938
|
+
selected_mode = payload.get("selected_mode")
|
|
939
|
+
selected_option = next(
|
|
940
|
+
(
|
|
941
|
+
option
|
|
942
|
+
for option in payload.get("mode_options") or []
|
|
943
|
+
if isinstance(option, dict) and option.get("mode") == selected_mode
|
|
944
|
+
),
|
|
945
|
+
None,
|
|
946
|
+
)
|
|
947
|
+
if selected_option:
|
|
948
|
+
lines.extend(["", "## Selected Mode Required Proofs", ""])
|
|
949
|
+
lines.extend(f"- {proof}" for proof in selected_option.get("required_proofs") or [])
|
|
950
|
+
if payload.get("selected_blocking_reasons"):
|
|
951
|
+
lines.extend(["", "## Blocking Reasons", ""])
|
|
952
|
+
lines.extend(f"- {reason}" for reason in payload.get("selected_blocking_reasons") or [])
|
|
953
|
+
if payload.get("operator_next_steps"):
|
|
954
|
+
lines.extend(["", "## Operator Next Steps", ""])
|
|
955
|
+
for step in payload.get("operator_next_steps") or []:
|
|
956
|
+
if not isinstance(step, dict):
|
|
957
|
+
continue
|
|
958
|
+
line = f"{step.get('step')}. [{step.get('kind')}] {step.get('action')}"
|
|
959
|
+
if step.get("command"):
|
|
960
|
+
line += f" — `{step.get('command')}`"
|
|
961
|
+
lines.append(line)
|
|
962
|
+
lines.extend(["", "## Handoffs", ""])
|
|
963
|
+
for transition in payload.get("transitions") or []:
|
|
964
|
+
if isinstance(transition, dict):
|
|
965
|
+
lines.append(
|
|
966
|
+
f"- `{transition.get('transition')}`: "
|
|
967
|
+
f"`{transition.get('from_mode')}` -> `{transition.get('to_mode')}`; "
|
|
968
|
+
f"target readiness: `{', '.join(transition.get('target_readiness') or [])}`"
|
|
969
|
+
)
|
|
970
|
+
if identity.get("action_required"):
|
|
971
|
+
lines.extend(
|
|
972
|
+
[
|
|
973
|
+
"",
|
|
974
|
+
"## Identity Gate",
|
|
975
|
+
"",
|
|
976
|
+
f"- reason: {identity.get('reason')}",
|
|
977
|
+
f"- required_cli_arg: `{identity.get('required_cli_arg')}`",
|
|
978
|
+
]
|
|
979
|
+
)
|
|
980
|
+
lines.extend(
|
|
981
|
+
[
|
|
982
|
+
"",
|
|
983
|
+
"## Why This Helps",
|
|
984
|
+
"",
|
|
985
|
+
"- makes visible, headless, intake, timer, and hybrid host choices explicit;",
|
|
986
|
+
"- maps headless execution to `loopx turn plan` instead of inventing a parallel runner;",
|
|
987
|
+
"- keeps scoped identity, no-spend previews, independent validation, and quota spend order visible before setup;",
|
|
988
|
+
"- gives operators a safe escalation path back to a visible session for user gates.",
|
|
989
|
+
]
|
|
990
|
+
)
|
|
991
|
+
return "\n".join(lines)
|