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,1578 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from .agent_scope_frontier import (
|
|
8
|
+
AGENT_LANE_FRONTIER_HINT_SCHEMA_VERSION,
|
|
9
|
+
AgentLaneFrontierHintDecision,
|
|
10
|
+
AgentScopeFrontierAction,
|
|
11
|
+
agent_scope_frontier_action as _agent_scope_frontier_action,
|
|
12
|
+
build_agent_scope_frontier_payload,
|
|
13
|
+
)
|
|
14
|
+
from ..todos.decision_scope import todo_gate_relation, todo_gate_relation_blocks_agent
|
|
15
|
+
from ..work_items.work_lane import (
|
|
16
|
+
work_lane_contract_is_due_monitor_attempt,
|
|
17
|
+
work_lane_contract_requires_current_agent_attempt,
|
|
18
|
+
)
|
|
19
|
+
from ..todos.contract import (
|
|
20
|
+
TODO_STATUS_OPEN,
|
|
21
|
+
TODO_TASK_CLASS_ADVANCEMENT,
|
|
22
|
+
TODO_TASK_CLASS_MONITOR,
|
|
23
|
+
normalize_todo_blocks_agent,
|
|
24
|
+
normalize_todo_bound_agent,
|
|
25
|
+
normalize_todo_claimed_by,
|
|
26
|
+
normalize_todo_excluded_agents,
|
|
27
|
+
normalize_todo_global_gate,
|
|
28
|
+
normalize_todo_id,
|
|
29
|
+
normalize_todo_status,
|
|
30
|
+
normalize_todo_task_class,
|
|
31
|
+
)
|
|
32
|
+
from ..todos.handoff_gate import HandoffGateState
|
|
33
|
+
from ..todos.deferred_resume import todo_summary_blocked_successor_items
|
|
34
|
+
from ..todos.projection import (
|
|
35
|
+
todo_item_claimed_by_agent_or_unclaimed,
|
|
36
|
+
todo_item_excludes_agent,
|
|
37
|
+
todo_item_is_actionable_open,
|
|
38
|
+
todo_item_is_deferred,
|
|
39
|
+
todo_item_task_class,
|
|
40
|
+
todo_projection_sort_key,
|
|
41
|
+
)
|
|
42
|
+
from ..todos.summary_item import compact_todo_summary_item
|
|
43
|
+
from ..todos.user_gate import (
|
|
44
|
+
open_todo_count as _open_todo_count,
|
|
45
|
+
open_user_gate_todo_items as _open_user_gate_todo_items,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
_ACTION_SCOPE_STOPWORDS = {
|
|
49
|
+
"a",
|
|
50
|
+
"an",
|
|
51
|
+
"and",
|
|
52
|
+
"approve",
|
|
53
|
+
"approved",
|
|
54
|
+
"approval",
|
|
55
|
+
"before",
|
|
56
|
+
"blocked",
|
|
57
|
+
"continue",
|
|
58
|
+
"decide",
|
|
59
|
+
"decision",
|
|
60
|
+
"for",
|
|
61
|
+
"from",
|
|
62
|
+
"gate",
|
|
63
|
+
"gated",
|
|
64
|
+
"goal",
|
|
65
|
+
"harness",
|
|
66
|
+
"internal",
|
|
67
|
+
"material",
|
|
68
|
+
"next",
|
|
69
|
+
"open",
|
|
70
|
+
"owner",
|
|
71
|
+
"p0",
|
|
72
|
+
"p1",
|
|
73
|
+
"p2",
|
|
74
|
+
"p3",
|
|
75
|
+
"provide",
|
|
76
|
+
"read",
|
|
77
|
+
"registered",
|
|
78
|
+
"safe",
|
|
79
|
+
"should",
|
|
80
|
+
"sync",
|
|
81
|
+
"the",
|
|
82
|
+
"todo",
|
|
83
|
+
"until",
|
|
84
|
+
"user",
|
|
85
|
+
"whether",
|
|
86
|
+
"with",
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
AGENT_TASK_SCOPE = "goal_all_read_claimed_run_global_read_v0"
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _agent_identity_has_scoped_lane(agent_identity: dict[str, Any] | None) -> bool:
|
|
93
|
+
return bool(
|
|
94
|
+
isinstance(agent_identity, dict)
|
|
95
|
+
and normalize_todo_claimed_by(agent_identity.get("agent_id"))
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _attach_agent_identity_contracts(
|
|
100
|
+
*,
|
|
101
|
+
payload: dict[str, Any],
|
|
102
|
+
agent_identity: dict[str, Any] | None,
|
|
103
|
+
) -> dict[str, Any]:
|
|
104
|
+
if not agent_identity:
|
|
105
|
+
return payload
|
|
106
|
+
payload["agent_identity"] = agent_identity
|
|
107
|
+
payload["task_scope"] = AGENT_TASK_SCOPE
|
|
108
|
+
return payload
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _todo_task_class(item: dict[str, Any]) -> str:
|
|
112
|
+
return todo_item_task_class(item)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _todo_projection_sort_key(item: dict[str, Any]) -> tuple[int, int, int, str]:
|
|
116
|
+
return todo_projection_sort_key(item)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _monitor_debt_projection_sort_key(item: dict[str, Any]) -> tuple[Any, ...]:
|
|
120
|
+
priority, *remainder = _todo_projection_sort_key(item)
|
|
121
|
+
advancement_first = 0 if _todo_task_class(item) == TODO_TASK_CLASS_ADVANCEMENT else 1
|
|
122
|
+
return (priority, advancement_first, *remainder)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _todo_item_is_actionable_open(item: dict[str, Any]) -> bool:
|
|
126
|
+
return todo_item_is_actionable_open(item)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def agent_scope_item_claimed_by(item: dict[str, Any]) -> str | None:
|
|
130
|
+
return normalize_todo_claimed_by(item.get("claimed_by"))
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def agent_scope_item_blocks_agent(item: dict[str, Any]) -> str | None:
|
|
134
|
+
return normalize_todo_blocks_agent(item.get("blocks_agent"))
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def agent_scope_item_bound_agent(item: dict[str, Any]) -> str | None:
|
|
138
|
+
return normalize_todo_bound_agent(item.get("bound_agent"))
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def agent_scope_item_excluded_agents(item: dict[str, Any]) -> list[str]:
|
|
142
|
+
return normalize_todo_excluded_agents(item.get("excluded_agents"))
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def agent_scope_item_claimed_by_agent_or_unclaimed(
|
|
146
|
+
item: dict[str, Any],
|
|
147
|
+
*,
|
|
148
|
+
agent_id: str | None,
|
|
149
|
+
) -> bool:
|
|
150
|
+
return todo_item_claimed_by_agent_or_unclaimed(item, agent_id=agent_id)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def agent_scope_item_matches_agent_or_unclaimed(
|
|
154
|
+
item: dict[str, Any],
|
|
155
|
+
*,
|
|
156
|
+
agent_id: str | None,
|
|
157
|
+
) -> bool:
|
|
158
|
+
normalized_agent_id = normalize_todo_claimed_by(agent_id)
|
|
159
|
+
if not normalized_agent_id:
|
|
160
|
+
return True
|
|
161
|
+
if todo_item_excludes_agent(item, agent_id=normalized_agent_id):
|
|
162
|
+
return False
|
|
163
|
+
return agent_scope_item_claimed_by_agent_or_unclaimed(
|
|
164
|
+
item,
|
|
165
|
+
agent_id=normalized_agent_id,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def agent_scope_count_advancement_items(
|
|
170
|
+
items: Any,
|
|
171
|
+
*,
|
|
172
|
+
claimed_by: str | None = None,
|
|
173
|
+
) -> int:
|
|
174
|
+
if not isinstance(items, list):
|
|
175
|
+
return 0
|
|
176
|
+
normalized_claimed_by = (
|
|
177
|
+
"__unclaimed__"
|
|
178
|
+
if claimed_by == "__unclaimed__"
|
|
179
|
+
else normalize_todo_claimed_by(claimed_by)
|
|
180
|
+
if claimed_by is not None
|
|
181
|
+
else None
|
|
182
|
+
)
|
|
183
|
+
count = 0
|
|
184
|
+
for item in items:
|
|
185
|
+
if not isinstance(item, dict):
|
|
186
|
+
continue
|
|
187
|
+
if not _todo_item_is_actionable_open(item):
|
|
188
|
+
continue
|
|
189
|
+
if _todo_task_class(item) != TODO_TASK_CLASS_ADVANCEMENT:
|
|
190
|
+
continue
|
|
191
|
+
item_claimed_by = agent_scope_item_claimed_by(item)
|
|
192
|
+
if normalized_claimed_by == "__unclaimed__":
|
|
193
|
+
if item_claimed_by:
|
|
194
|
+
continue
|
|
195
|
+
elif normalized_claimed_by is not None and item_claimed_by != normalized_claimed_by:
|
|
196
|
+
continue
|
|
197
|
+
count += 1
|
|
198
|
+
return count
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def _protocol_action_text(value: Any, *, limit: int = 220) -> str | None:
|
|
202
|
+
if value is None:
|
|
203
|
+
return None
|
|
204
|
+
text = " ".join(str(value).strip().split())
|
|
205
|
+
if not text:
|
|
206
|
+
return None
|
|
207
|
+
if len(text) <= limit:
|
|
208
|
+
return text
|
|
209
|
+
return text[: max(0, limit - 3)].rstrip() + "..."
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _action_scope_tokens_from_text(text: str) -> set[str]:
|
|
213
|
+
return {
|
|
214
|
+
token
|
|
215
|
+
for token in re.findall(
|
|
216
|
+
r"[a-z0-9]+",
|
|
217
|
+
text.lower().replace("_", " ").replace("-", " "),
|
|
218
|
+
)
|
|
219
|
+
if len(token) > 1 and token not in _ACTION_SCOPE_STOPWORDS
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def _todo_action_kind_tokens(item: dict[str, Any]) -> set[str]:
|
|
224
|
+
return _action_scope_tokens_from_text(str(item.get("action_kind") or ""))
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def _todo_action_scope_tokens(item: dict[str, Any]) -> set[str]:
|
|
228
|
+
text = " ".join(
|
|
229
|
+
str(value or "")
|
|
230
|
+
for value in (item.get("action_kind"), item.get("title"), item.get("text"))
|
|
231
|
+
if str(value or "").strip()
|
|
232
|
+
)
|
|
233
|
+
return _action_scope_tokens_from_text(text)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _todo_gate_relation(gate: dict[str, Any], agent_item: dict[str, Any]) -> dict[str, Any] | None:
|
|
237
|
+
return todo_gate_relation(gate, agent_item)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def _user_gate_blocks_agent_item(gate: dict[str, Any], agent_item: dict[str, Any]) -> bool:
|
|
241
|
+
if normalize_todo_global_gate(gate.get("global_gate")):
|
|
242
|
+
return True
|
|
243
|
+
relation = _todo_gate_relation(gate, agent_item)
|
|
244
|
+
if relation:
|
|
245
|
+
return todo_gate_relation_blocks_agent(relation)
|
|
246
|
+
|
|
247
|
+
gate_action_tokens = _todo_action_kind_tokens(gate)
|
|
248
|
+
agent_action_tokens = _todo_action_kind_tokens(agent_item)
|
|
249
|
+
if gate_action_tokens and agent_action_tokens:
|
|
250
|
+
return bool(gate_action_tokens & agent_action_tokens)
|
|
251
|
+
if agent_action_tokens:
|
|
252
|
+
return False
|
|
253
|
+
|
|
254
|
+
gate_tokens = _todo_action_scope_tokens(gate)
|
|
255
|
+
agent_tokens = _todo_action_scope_tokens(agent_item)
|
|
256
|
+
if not gate_tokens or not agent_tokens:
|
|
257
|
+
return False
|
|
258
|
+
if gate_action_tokens:
|
|
259
|
+
return len(gate_action_tokens & agent_tokens) >= 2
|
|
260
|
+
return len(gate_tokens & agent_tokens) >= 3
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def _todo_item_claimed_by_agent_or_unclaimed(item: dict[str, Any], *, agent_id: str) -> bool:
|
|
264
|
+
return agent_scope_item_claimed_by_agent_or_unclaimed(item, agent_id=agent_id)
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def _agent_scope_selectable_todo_item(
|
|
268
|
+
item: dict[str, Any],
|
|
269
|
+
*,
|
|
270
|
+
agent_identity: dict[str, Any] | None,
|
|
271
|
+
) -> bool:
|
|
272
|
+
if not isinstance(agent_identity, dict):
|
|
273
|
+
return True
|
|
274
|
+
agent_id = normalize_todo_claimed_by(agent_identity.get("agent_id"))
|
|
275
|
+
if not agent_id:
|
|
276
|
+
return True
|
|
277
|
+
if todo_item_excludes_agent(item, agent_id=agent_id):
|
|
278
|
+
return False
|
|
279
|
+
return _todo_item_claimed_by_agent_or_unclaimed(item, agent_id=agent_id)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def _agent_scope_filter_user_gate_items(
|
|
283
|
+
open_items: list[dict[str, Any]],
|
|
284
|
+
*,
|
|
285
|
+
agent_identity: dict[str, Any] | None,
|
|
286
|
+
) -> tuple[list[dict[str, Any]], list[dict[str, Any]], dict[str, Any] | None]:
|
|
287
|
+
if not isinstance(agent_identity, dict):
|
|
288
|
+
return open_items, [], None
|
|
289
|
+
agent_id = normalize_todo_claimed_by(agent_identity.get("agent_id"))
|
|
290
|
+
if not agent_id:
|
|
291
|
+
return open_items, [], None
|
|
292
|
+
|
|
293
|
+
current_agent_items: list[dict[str, Any]] = []
|
|
294
|
+
other_agent_scoped_items: list[dict[str, Any]] = []
|
|
295
|
+
for item in open_items:
|
|
296
|
+
if normalize_todo_global_gate(item.get("global_gate")):
|
|
297
|
+
current_agent_items.append(item)
|
|
298
|
+
continue
|
|
299
|
+
blocks_agent = agent_scope_item_blocks_agent(item)
|
|
300
|
+
if blocks_agent:
|
|
301
|
+
if blocks_agent != agent_id:
|
|
302
|
+
other_agent_scoped_items.append(item)
|
|
303
|
+
continue
|
|
304
|
+
current_agent_items.append(item)
|
|
305
|
+
continue
|
|
306
|
+
claimed_by = agent_scope_item_claimed_by(item)
|
|
307
|
+
if claimed_by and claimed_by != agent_id:
|
|
308
|
+
other_agent_scoped_items.append(item)
|
|
309
|
+
continue
|
|
310
|
+
current_agent_items.append(item)
|
|
311
|
+
if not other_agent_scoped_items:
|
|
312
|
+
return open_items, [], None
|
|
313
|
+
|
|
314
|
+
return (
|
|
315
|
+
current_agent_items,
|
|
316
|
+
other_agent_scoped_items,
|
|
317
|
+
{
|
|
318
|
+
"schema_version": "agent_scoped_user_gate_filter_v0",
|
|
319
|
+
"agent_id": agent_id,
|
|
320
|
+
"policy": (
|
|
321
|
+
"user todos scoped to another agent by blocks_agent or claimed_by "
|
|
322
|
+
"remain visible but do not block this agent's quota lane"
|
|
323
|
+
),
|
|
324
|
+
"current_agent_blocking_open_count": len(current_agent_items),
|
|
325
|
+
"other_agent_scoped_open_count": len(other_agent_scoped_items),
|
|
326
|
+
},
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def _agent_scope_filter_user_action_items(
|
|
331
|
+
open_items: list[dict[str, Any]],
|
|
332
|
+
*,
|
|
333
|
+
agent_identity: dict[str, Any] | None,
|
|
334
|
+
) -> tuple[list[dict[str, Any]], list[dict[str, Any]], dict[str, Any] | None]:
|
|
335
|
+
if not isinstance(agent_identity, dict):
|
|
336
|
+
return open_items, [], None
|
|
337
|
+
agent_id = normalize_todo_claimed_by(agent_identity.get("agent_id"))
|
|
338
|
+
if not agent_id:
|
|
339
|
+
return open_items, [], None
|
|
340
|
+
|
|
341
|
+
current_agent_items: list[dict[str, Any]] = []
|
|
342
|
+
other_agent_items: list[dict[str, Any]] = []
|
|
343
|
+
for item in open_items:
|
|
344
|
+
# claimed_by is a read-only compatibility fallback for user actions
|
|
345
|
+
# written before bound_agent became a first-class routing relation.
|
|
346
|
+
bound_agent = agent_scope_item_bound_agent(item) or agent_scope_item_claimed_by(item)
|
|
347
|
+
if bound_agent and bound_agent != agent_id:
|
|
348
|
+
other_agent_items.append(item)
|
|
349
|
+
continue
|
|
350
|
+
current_agent_items.append(item)
|
|
351
|
+
if not other_agent_items:
|
|
352
|
+
return open_items, [], None
|
|
353
|
+
return (
|
|
354
|
+
current_agent_items,
|
|
355
|
+
other_agent_items,
|
|
356
|
+
{
|
|
357
|
+
"schema_version": "agent_scoped_user_action_filter_v0",
|
|
358
|
+
"agent_id": agent_id,
|
|
359
|
+
"policy": (
|
|
360
|
+
"user actions bound to another agent remain diagnostic-only and "
|
|
361
|
+
"must not enter this agent's reminder channel"
|
|
362
|
+
),
|
|
363
|
+
"current_agent_user_action_open_count": len(current_agent_items),
|
|
364
|
+
"other_agent_bound_user_action_open_count": len(other_agent_items),
|
|
365
|
+
},
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def _scoped_user_gate_fallback(
|
|
370
|
+
user_todo_summary: dict[str, Any] | None,
|
|
371
|
+
agent_todo_summary: dict[str, Any] | None,
|
|
372
|
+
*,
|
|
373
|
+
capability_gate: dict[str, Any] | None = None,
|
|
374
|
+
allow_unrelated_gate: bool = False,
|
|
375
|
+
monitor_debt_backoff_active: bool = False,
|
|
376
|
+
) -> dict[str, Any] | None:
|
|
377
|
+
gates = _open_user_gate_todo_items(user_todo_summary)
|
|
378
|
+
if not gates or not isinstance(agent_todo_summary, dict):
|
|
379
|
+
return None
|
|
380
|
+
|
|
381
|
+
due_monitor_candidates = (
|
|
382
|
+
agent_todo_summary.get("monitor_due_items")
|
|
383
|
+
if isinstance(agent_todo_summary.get("monitor_due_items"), list)
|
|
384
|
+
else []
|
|
385
|
+
)
|
|
386
|
+
ready_deferred_candidates = (
|
|
387
|
+
agent_todo_summary.get("deferred_resume_candidates")
|
|
388
|
+
if isinstance(agent_todo_summary.get("deferred_resume_candidates"), list)
|
|
389
|
+
else []
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
# An empty capability projection is authoritative for advancement work.
|
|
393
|
+
# Due monitors are projected separately from advancement candidates and are
|
|
394
|
+
# already current-agent scoped, due, and capability-runnable here.
|
|
395
|
+
capability_candidates = (
|
|
396
|
+
capability_gate.get("runnable_candidates")
|
|
397
|
+
if isinstance(capability_gate, dict)
|
|
398
|
+
else None
|
|
399
|
+
)
|
|
400
|
+
if isinstance(capability_candidates, list):
|
|
401
|
+
executable_items = [
|
|
402
|
+
*due_monitor_candidates,
|
|
403
|
+
*capability_candidates,
|
|
404
|
+
*ready_deferred_candidates,
|
|
405
|
+
]
|
|
406
|
+
else:
|
|
407
|
+
advancement_items = (
|
|
408
|
+
agent_todo_summary.get("executable_backlog_items")
|
|
409
|
+
if isinstance(agent_todo_summary.get("executable_backlog_items"), list)
|
|
410
|
+
else agent_todo_summary.get("first_executable_items")
|
|
411
|
+
if isinstance(agent_todo_summary.get("first_executable_items"), list)
|
|
412
|
+
else []
|
|
413
|
+
)
|
|
414
|
+
executable_items = [
|
|
415
|
+
*due_monitor_candidates,
|
|
416
|
+
*advancement_items,
|
|
417
|
+
*ready_deferred_candidates,
|
|
418
|
+
]
|
|
419
|
+
executable_items = [item for item in executable_items if isinstance(item, dict)]
|
|
420
|
+
deduped_executable_items: list[dict[str, Any]] = []
|
|
421
|
+
seen_todo_ids: set[str] = set()
|
|
422
|
+
for item in executable_items:
|
|
423
|
+
todo_id = normalize_todo_id(item.get("todo_id"))
|
|
424
|
+
if todo_id and todo_id in seen_todo_ids:
|
|
425
|
+
continue
|
|
426
|
+
if todo_id:
|
|
427
|
+
seen_todo_ids.add(todo_id)
|
|
428
|
+
deduped_executable_items.append(item)
|
|
429
|
+
executable_items = sorted(
|
|
430
|
+
deduped_executable_items,
|
|
431
|
+
key=(
|
|
432
|
+
_monitor_debt_projection_sort_key
|
|
433
|
+
if monitor_debt_backoff_active
|
|
434
|
+
else _todo_projection_sort_key
|
|
435
|
+
),
|
|
436
|
+
)
|
|
437
|
+
claim_scope = (
|
|
438
|
+
agent_todo_summary.get("claim_scope")
|
|
439
|
+
if isinstance(agent_todo_summary.get("claim_scope"), dict)
|
|
440
|
+
else None
|
|
441
|
+
)
|
|
442
|
+
if claim_scope:
|
|
443
|
+
agent_id = normalize_todo_claimed_by(claim_scope.get("agent_id"))
|
|
444
|
+
executable_items = [
|
|
445
|
+
item
|
|
446
|
+
for item in executable_items
|
|
447
|
+
if agent_scope_item_claimed_by_agent_or_unclaimed(item, agent_id=agent_id)
|
|
448
|
+
]
|
|
449
|
+
blocked_items: list[dict[str, Any]] = []
|
|
450
|
+
selected: dict[str, Any] | None = None
|
|
451
|
+
blocking_gate: dict[str, Any] | None = None
|
|
452
|
+
for item in executable_items:
|
|
453
|
+
matching_gate = next(
|
|
454
|
+
(gate for gate in gates if _user_gate_blocks_agent_item(gate, item)),
|
|
455
|
+
None,
|
|
456
|
+
)
|
|
457
|
+
if matching_gate:
|
|
458
|
+
blocking_gate = blocking_gate or matching_gate
|
|
459
|
+
text = str(item.get("text") or "").strip()
|
|
460
|
+
blocked_item = compact_todo_summary_item(item, text=text)
|
|
461
|
+
relation = _todo_gate_relation(matching_gate, item)
|
|
462
|
+
if relation:
|
|
463
|
+
blocked_item["todo_gate_relation"] = relation
|
|
464
|
+
blocked_items.append(blocked_item)
|
|
465
|
+
continue
|
|
466
|
+
if selected is None:
|
|
467
|
+
selected = item
|
|
468
|
+
|
|
469
|
+
if selected is None:
|
|
470
|
+
return None
|
|
471
|
+
if not blocking_gate and not allow_unrelated_gate:
|
|
472
|
+
return None
|
|
473
|
+
|
|
474
|
+
selected_text = str(selected.get("text") or "").strip()
|
|
475
|
+
gate_to_surface = blocking_gate or gates[0]
|
|
476
|
+
selected_item = compact_todo_summary_item(selected, text=selected_text)
|
|
477
|
+
selected_is_deferred_replan = (
|
|
478
|
+
todo_item_is_deferred(selected) and selected.get("resume_ready") is True
|
|
479
|
+
)
|
|
480
|
+
if selected_is_deferred_replan:
|
|
481
|
+
selected_item["fallback_kind"] = "deferred_successor_replan"
|
|
482
|
+
selected_relation = _todo_gate_relation(gate_to_surface, selected)
|
|
483
|
+
if selected_relation:
|
|
484
|
+
selected_item["todo_gate_relation"] = selected_relation
|
|
485
|
+
gate_text = str(gate_to_surface.get("text") or "").strip()
|
|
486
|
+
reason = (
|
|
487
|
+
"an open user_gate blocks a scoped agent todo, but a non-dependent "
|
|
488
|
+
"executable fallback remains available"
|
|
489
|
+
if blocking_gate
|
|
490
|
+
else (
|
|
491
|
+
"an open user_gate blocks a different action scope, but the ready "
|
|
492
|
+
"deferred successor can be replanned independently"
|
|
493
|
+
if selected_is_deferred_replan
|
|
494
|
+
else "an open user_gate blocks a different action scope, but the selected "
|
|
495
|
+
"executable agent todo is non-dependent and safe to advance"
|
|
496
|
+
)
|
|
497
|
+
)
|
|
498
|
+
return {
|
|
499
|
+
"schema_version": "scoped_user_gate_fallback_v0",
|
|
500
|
+
"kind": "scoped_user_gate_fallback",
|
|
501
|
+
"notify_user": True,
|
|
502
|
+
"requires_user_action": True,
|
|
503
|
+
"reason": reason,
|
|
504
|
+
"blocked_user_gate": compact_todo_summary_item(gate_to_surface, text=gate_text),
|
|
505
|
+
"blocked_agent_items": blocked_items[:3],
|
|
506
|
+
"selected_executable": selected_item,
|
|
507
|
+
"recommended_action": (
|
|
508
|
+
"Notify the user about the scoped gate; then reopen, supersede, or "
|
|
509
|
+
"record a no-follow-up rationale for the selected ready deferred "
|
|
510
|
+
"successor, spending only after validated writeback."
|
|
511
|
+
if selected_is_deferred_replan
|
|
512
|
+
else "Notify the user about the scoped gate; then execute the selected "
|
|
513
|
+
"non-gated fallback and spend only after validated writeback."
|
|
514
|
+
),
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
def _first_executable_todo_text(agent_todo_summary: dict[str, Any] | None) -> str | None:
|
|
519
|
+
if not isinstance(agent_todo_summary, dict):
|
|
520
|
+
return None
|
|
521
|
+
items = (
|
|
522
|
+
agent_todo_summary.get("first_executable_items")
|
|
523
|
+
if isinstance(agent_todo_summary.get("first_executable_items"), list)
|
|
524
|
+
else []
|
|
525
|
+
)
|
|
526
|
+
for item in items:
|
|
527
|
+
if not isinstance(item, dict):
|
|
528
|
+
continue
|
|
529
|
+
if not _todo_item_is_actionable_open(item):
|
|
530
|
+
continue
|
|
531
|
+
if _todo_task_class(item) != TODO_TASK_CLASS_ADVANCEMENT:
|
|
532
|
+
continue
|
|
533
|
+
text = _protocol_action_text(item.get("text"), limit=320)
|
|
534
|
+
if text:
|
|
535
|
+
return text
|
|
536
|
+
return None
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
def _first_monitor_todo_text(agent_todo_summary: dict[str, Any] | None) -> str | None:
|
|
540
|
+
if not isinstance(agent_todo_summary, dict):
|
|
541
|
+
return None
|
|
542
|
+
for key in ("monitor_due_items", "monitor_open_items"):
|
|
543
|
+
items = agent_todo_summary.get(key) if isinstance(agent_todo_summary.get(key), list) else []
|
|
544
|
+
for item in items:
|
|
545
|
+
if not isinstance(item, dict):
|
|
546
|
+
continue
|
|
547
|
+
if not _todo_item_is_actionable_open(item):
|
|
548
|
+
continue
|
|
549
|
+
if _todo_task_class(item) != TODO_TASK_CLASS_MONITOR:
|
|
550
|
+
continue
|
|
551
|
+
text = _protocol_action_text(item.get("text"), limit=320)
|
|
552
|
+
if text:
|
|
553
|
+
return text
|
|
554
|
+
return None
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
def _agent_scoped_user_todo_override(
|
|
558
|
+
*,
|
|
559
|
+
state: str,
|
|
560
|
+
item: dict[str, Any],
|
|
561
|
+
user_todo_summary: dict[str, Any] | None,
|
|
562
|
+
agent_todo_summary: dict[str, Any] | None,
|
|
563
|
+
agent_identity: dict[str, Any] | None,
|
|
564
|
+
) -> dict[str, Any] | None:
|
|
565
|
+
if state != "operator_gate":
|
|
566
|
+
return None
|
|
567
|
+
if not isinstance(agent_identity, dict):
|
|
568
|
+
return None
|
|
569
|
+
if not isinstance(user_todo_summary, dict):
|
|
570
|
+
return None
|
|
571
|
+
# A non-blocking user_action may still need a reminder without preserving
|
|
572
|
+
# an operator gate inherited from a different agent's user_gate.
|
|
573
|
+
if _open_user_gate_todo_items(user_todo_summary):
|
|
574
|
+
return None
|
|
575
|
+
other_gate_count = _open_todo_count(
|
|
576
|
+
{"open_count": user_todo_summary.get("other_agent_scoped_open_count")}
|
|
577
|
+
)
|
|
578
|
+
if item.get("operator_question") or item.get("missing_gates"):
|
|
579
|
+
return None
|
|
580
|
+
selected_action = _first_executable_todo_text(agent_todo_summary)
|
|
581
|
+
if not selected_action:
|
|
582
|
+
selected_action = _first_monitor_todo_text(agent_todo_summary)
|
|
583
|
+
agent_id = normalize_todo_claimed_by(agent_identity.get("agent_id"))
|
|
584
|
+
if other_gate_count > 0:
|
|
585
|
+
if not selected_action:
|
|
586
|
+
return None
|
|
587
|
+
return {
|
|
588
|
+
"schema_version": "agent_scoped_user_gate_override_v0",
|
|
589
|
+
"kind": "agent_scoped_user_gate_override",
|
|
590
|
+
"agent_id": agent_id,
|
|
591
|
+
"from_state": "operator_gate",
|
|
592
|
+
"to_state": "eligible",
|
|
593
|
+
"other_agent_scoped_open_count": other_gate_count,
|
|
594
|
+
"selected_action": selected_action,
|
|
595
|
+
"reason": (
|
|
596
|
+
"the open user gate is scoped to another agent via blocks_agent or "
|
|
597
|
+
"claimed_by; this agent still has an executable in-scope todo"
|
|
598
|
+
),
|
|
599
|
+
}
|
|
600
|
+
other_action_count = _open_todo_count(
|
|
601
|
+
{
|
|
602
|
+
"open_count": user_todo_summary.get(
|
|
603
|
+
"other_agent_bound_user_action_open_count"
|
|
604
|
+
)
|
|
605
|
+
}
|
|
606
|
+
)
|
|
607
|
+
if other_action_count <= 0:
|
|
608
|
+
return None
|
|
609
|
+
result: dict[str, Any] = {
|
|
610
|
+
"schema_version": "agent_scoped_user_action_override_v0",
|
|
611
|
+
"kind": "agent_scoped_user_action_override",
|
|
612
|
+
"agent_id": agent_id,
|
|
613
|
+
"from_state": "operator_gate",
|
|
614
|
+
"to_state": "eligible" if selected_action else "waiting",
|
|
615
|
+
"other_agent_bound_user_action_open_count": other_action_count,
|
|
616
|
+
"reason": (
|
|
617
|
+
"the only open user actions are bound to another agent; they remain "
|
|
618
|
+
"diagnostic-only for this lane and cannot create an operator gate"
|
|
619
|
+
),
|
|
620
|
+
"quota_patch": {
|
|
621
|
+
"safe_bypass_allowed": False,
|
|
622
|
+
"safe_bypass_kind": None,
|
|
623
|
+
"blocked_action_scope": None,
|
|
624
|
+
},
|
|
625
|
+
"item_patch": {
|
|
626
|
+
"status": "active_state_agent_todo" if selected_action else "agent_scope_wait",
|
|
627
|
+
"waiting_on": "codex" if selected_action else "agent_scope",
|
|
628
|
+
"recommended_action": (
|
|
629
|
+
selected_action or "wait for work or a user action bound to this agent"
|
|
630
|
+
),
|
|
631
|
+
},
|
|
632
|
+
}
|
|
633
|
+
if selected_action:
|
|
634
|
+
result["selected_action"] = selected_action
|
|
635
|
+
return result
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
def _count_advancement_items(items: Any, *, claimed_by: str | None = None) -> int:
|
|
639
|
+
return agent_scope_count_advancement_items(items, claimed_by=claimed_by)
|
|
640
|
+
|
|
641
|
+
|
|
642
|
+
def _selectable_unclaimed_advancement_count(
|
|
643
|
+
agent_todo_summary: dict[str, Any],
|
|
644
|
+
) -> int:
|
|
645
|
+
executable_items = agent_todo_summary.get("executable_backlog_items")
|
|
646
|
+
source_items = (
|
|
647
|
+
executable_items
|
|
648
|
+
if isinstance(executable_items, list)
|
|
649
|
+
else agent_todo_summary.get("unclaimed_priority_open_items")
|
|
650
|
+
)
|
|
651
|
+
return _count_advancement_items(source_items, claimed_by="__unclaimed__")
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
def _first_compact_todo_id(items: Any) -> str | None:
|
|
655
|
+
if not isinstance(items, list):
|
|
656
|
+
return None
|
|
657
|
+
for item in items:
|
|
658
|
+
if not isinstance(item, dict):
|
|
659
|
+
continue
|
|
660
|
+
todo_id = normalize_todo_id(item.get("todo_id"))
|
|
661
|
+
if todo_id:
|
|
662
|
+
return todo_id
|
|
663
|
+
return None
|
|
664
|
+
|
|
665
|
+
|
|
666
|
+
def _agent_lane_frontier_hint(
|
|
667
|
+
*,
|
|
668
|
+
goal_id: str,
|
|
669
|
+
agent_identity: dict[str, Any] | None,
|
|
670
|
+
agent_todo_summary: dict[str, Any] | None,
|
|
671
|
+
agent_lane_next_action: dict[str, Any] | None,
|
|
672
|
+
agent_scope_frontier: dict[str, Any] | None,
|
|
673
|
+
work_lane_contract: dict[str, Any] | None,
|
|
674
|
+
) -> dict[str, Any] | None:
|
|
675
|
+
if not _agent_identity_has_scoped_lane(agent_identity):
|
|
676
|
+
return None
|
|
677
|
+
agent_id = normalize_todo_claimed_by(agent_identity.get("agent_id"))
|
|
678
|
+
if not agent_id:
|
|
679
|
+
return None
|
|
680
|
+
|
|
681
|
+
def build_hint(
|
|
682
|
+
decision: AgentLaneFrontierHintDecision,
|
|
683
|
+
*,
|
|
684
|
+
source: str,
|
|
685
|
+
reason_code: str,
|
|
686
|
+
target_todo_id: str | None = None,
|
|
687
|
+
quiet_noop_allowed: bool,
|
|
688
|
+
next_cli_action: str | None = None,
|
|
689
|
+
) -> dict[str, Any]:
|
|
690
|
+
hint: dict[str, Any] = {
|
|
691
|
+
"schema_version": AGENT_LANE_FRONTIER_HINT_SCHEMA_VERSION,
|
|
692
|
+
"decision": decision.value,
|
|
693
|
+
"agent_id": agent_id,
|
|
694
|
+
"agent_model": "peer_v1",
|
|
695
|
+
"source": source,
|
|
696
|
+
"reason_code": reason_code,
|
|
697
|
+
"quiet_noop_allowed": quiet_noop_allowed,
|
|
698
|
+
"uses_structured_frontier": True,
|
|
699
|
+
}
|
|
700
|
+
if target_todo_id:
|
|
701
|
+
hint["target_todo_id"] = target_todo_id
|
|
702
|
+
if next_cli_action:
|
|
703
|
+
hint["next_cli_action"] = next_cli_action
|
|
704
|
+
return hint
|
|
705
|
+
|
|
706
|
+
if isinstance(agent_lane_next_action, dict):
|
|
707
|
+
selected_by = str(agent_lane_next_action.get("selected_by") or "")
|
|
708
|
+
claim_required = agent_lane_next_action.get("claim_required_before_work") is True
|
|
709
|
+
target_todo_id = normalize_todo_id(agent_lane_next_action.get("todo_id"))
|
|
710
|
+
if selected_by == "unclaimed_todo" or claim_required:
|
|
711
|
+
action = None
|
|
712
|
+
if target_todo_id:
|
|
713
|
+
action = (
|
|
714
|
+
f"loopx todo claim --goal-id {goal_id} --todo-id {target_todo_id} "
|
|
715
|
+
f"--claimed-by {agent_id} --agent-id {agent_id}"
|
|
716
|
+
)
|
|
717
|
+
return build_hint(
|
|
718
|
+
AgentLaneFrontierHintDecision.CLAIM_UNOWNED_IN_SCOPE,
|
|
719
|
+
source="agent_lane_next_action",
|
|
720
|
+
reason_code="unclaimed_advancement_selected",
|
|
721
|
+
target_todo_id=target_todo_id,
|
|
722
|
+
quiet_noop_allowed=False,
|
|
723
|
+
next_cli_action=action,
|
|
724
|
+
)
|
|
725
|
+
|
|
726
|
+
if work_lane_contract_is_due_monitor_attempt(work_lane_contract):
|
|
727
|
+
return None
|
|
728
|
+
if work_lane_contract_requires_current_agent_attempt(work_lane_contract):
|
|
729
|
+
return None
|
|
730
|
+
|
|
731
|
+
frontier = agent_scope_frontier if isinstance(agent_scope_frontier, dict) else {}
|
|
732
|
+
frontier_action = _agent_scope_frontier_action(frontier.get("action"))
|
|
733
|
+
if frontier_action == AgentScopeFrontierAction.SUCCESSOR_REPLAN_REQUIRED:
|
|
734
|
+
cleared_todo_id = _first_compact_todo_id(
|
|
735
|
+
frontier.get("cleared_without_successor_handoff_gates")
|
|
736
|
+
)
|
|
737
|
+
if cleared_todo_id:
|
|
738
|
+
return build_hint(
|
|
739
|
+
AgentLaneFrontierHintDecision.RECORD_NO_FOLLOWUP,
|
|
740
|
+
source="agent_scope_frontier",
|
|
741
|
+
reason_code="cleared_handoff_without_successor",
|
|
742
|
+
target_todo_id=cleared_todo_id,
|
|
743
|
+
quiet_noop_allowed=False,
|
|
744
|
+
next_cli_action=(
|
|
745
|
+
f"loopx todo complete --goal-id {goal_id} --todo-id {cleared_todo_id} "
|
|
746
|
+
f"--agent-id {agent_id} --no-follow-up "
|
|
747
|
+
"--evidence '<public-safe rationale>'"
|
|
748
|
+
),
|
|
749
|
+
)
|
|
750
|
+
monitor_candidates = (
|
|
751
|
+
frontier.get("monitor_blocked_resume_candidates")
|
|
752
|
+
if isinstance(frontier.get("monitor_blocked_resume_candidates"), list)
|
|
753
|
+
else []
|
|
754
|
+
)
|
|
755
|
+
monitor_candidate = (
|
|
756
|
+
monitor_candidates[0]
|
|
757
|
+
if monitor_candidates and isinstance(monitor_candidates[0], dict)
|
|
758
|
+
else {}
|
|
759
|
+
)
|
|
760
|
+
monitor_blocker_id = normalize_todo_id(
|
|
761
|
+
monitor_candidate.get("blocking_monitor_todo_id")
|
|
762
|
+
)
|
|
763
|
+
if monitor_candidate:
|
|
764
|
+
target_todo_id = normalize_todo_id(monitor_candidate.get("todo_id"))
|
|
765
|
+
return build_hint(
|
|
766
|
+
AgentLaneFrontierHintDecision.ADD_NEXT_ADVANCEMENT,
|
|
767
|
+
source="agent_scope_frontier",
|
|
768
|
+
reason_code="resume_blocked_by_open_monitor",
|
|
769
|
+
target_todo_id=target_todo_id or monitor_blocker_id,
|
|
770
|
+
quiet_noop_allowed=False,
|
|
771
|
+
next_cli_action=(
|
|
772
|
+
f"loopx todo complete --goal-id {goal_id} --todo-id {monitor_blocker_id} "
|
|
773
|
+
f"--agent-id {agent_id} --evidence '<validated gate evidence>'"
|
|
774
|
+
if monitor_blocker_id
|
|
775
|
+
else None
|
|
776
|
+
),
|
|
777
|
+
)
|
|
778
|
+
deferred_todo_id = _first_compact_todo_id(frontier.get("deferred_resume_candidates"))
|
|
779
|
+
route_todo_id = _first_compact_todo_id(
|
|
780
|
+
frontier.get("route_continuation_replan_candidates")
|
|
781
|
+
)
|
|
782
|
+
route_candidates = (
|
|
783
|
+
frontier.get("route_continuation_replan_candidates")
|
|
784
|
+
if isinstance(frontier.get("route_continuation_replan_candidates"), list)
|
|
785
|
+
else []
|
|
786
|
+
)
|
|
787
|
+
route_candidate = (
|
|
788
|
+
route_candidates[0]
|
|
789
|
+
if route_candidates and isinstance(route_candidates[0], dict)
|
|
790
|
+
else {}
|
|
791
|
+
)
|
|
792
|
+
route_target = (
|
|
793
|
+
route_todo_id
|
|
794
|
+
or normalize_todo_id(route_candidate.get("route_id"))
|
|
795
|
+
or normalize_todo_id(route_candidate.get("route_key"))
|
|
796
|
+
)
|
|
797
|
+
if route_candidate:
|
|
798
|
+
return build_hint(
|
|
799
|
+
AgentLaneFrontierHintDecision.ADD_NEXT_ADVANCEMENT,
|
|
800
|
+
source="agent_scope_frontier",
|
|
801
|
+
reason_code="route_continuation_replan_required",
|
|
802
|
+
target_todo_id=route_target,
|
|
803
|
+
quiet_noop_allowed=False,
|
|
804
|
+
next_cli_action=(
|
|
805
|
+
f"loopx todo add --goal-id {goal_id} --role agent "
|
|
806
|
+
"--text '<public-safe route continuation advancement todo>'"
|
|
807
|
+
),
|
|
808
|
+
)
|
|
809
|
+
return build_hint(
|
|
810
|
+
AgentLaneFrontierHintDecision.ADD_NEXT_ADVANCEMENT,
|
|
811
|
+
source="agent_scope_frontier",
|
|
812
|
+
reason_code="successor_replan_required",
|
|
813
|
+
target_todo_id=deferred_todo_id,
|
|
814
|
+
quiet_noop_allowed=False,
|
|
815
|
+
)
|
|
816
|
+
|
|
817
|
+
if frontier_action in {
|
|
818
|
+
AgentScopeFrontierAction.AGENT_SCOPE_WAIT,
|
|
819
|
+
AgentScopeFrontierAction.REASSIGNMENT_REQUIRED,
|
|
820
|
+
}:
|
|
821
|
+
blocked_successor_todo_id = _first_compact_todo_id(
|
|
822
|
+
frontier.get("blocked_successor_wait_candidates")
|
|
823
|
+
)
|
|
824
|
+
if blocked_successor_todo_id:
|
|
825
|
+
return build_hint(
|
|
826
|
+
AgentLaneFrontierHintDecision.QUIET_NOOP_BLOCKER,
|
|
827
|
+
source="agent_scope_frontier",
|
|
828
|
+
reason_code="blocked_successor_resume_pending",
|
|
829
|
+
target_todo_id=blocked_successor_todo_id,
|
|
830
|
+
quiet_noop_allowed=True,
|
|
831
|
+
)
|
|
832
|
+
blocker_todo_id = _first_compact_todo_id(frontier.get("blocking_handoff_gates"))
|
|
833
|
+
if not blocker_todo_id:
|
|
834
|
+
blocker_todo_id = _first_compact_todo_id(frontier.get("other_agent_claimed_items"))
|
|
835
|
+
if blocker_todo_id:
|
|
836
|
+
return build_hint(
|
|
837
|
+
AgentLaneFrontierHintDecision.QUIET_NOOP_BLOCKER,
|
|
838
|
+
source="agent_scope_frontier",
|
|
839
|
+
reason_code="blocked_by_other_agent_frontier",
|
|
840
|
+
target_todo_id=blocker_todo_id,
|
|
841
|
+
quiet_noop_allowed=True,
|
|
842
|
+
)
|
|
843
|
+
return build_hint(
|
|
844
|
+
AgentLaneFrontierHintDecision.ADD_NEXT_ADVANCEMENT,
|
|
845
|
+
source="agent_scope_frontier",
|
|
846
|
+
reason_code="no_current_agent_advancement_todo",
|
|
847
|
+
quiet_noop_allowed=True,
|
|
848
|
+
)
|
|
849
|
+
|
|
850
|
+
if frontier_action == AgentScopeFrontierAction.AGENT_SCOPE_EXHAUSTED:
|
|
851
|
+
return build_hint(
|
|
852
|
+
AgentLaneFrontierHintDecision.ADD_NEXT_ADVANCEMENT,
|
|
853
|
+
source="agent_scope_frontier",
|
|
854
|
+
reason_code="agent_scope_exhausted",
|
|
855
|
+
quiet_noop_allowed=True,
|
|
856
|
+
)
|
|
857
|
+
|
|
858
|
+
if not isinstance(agent_todo_summary, dict):
|
|
859
|
+
return None
|
|
860
|
+
current_advancement_count = int(
|
|
861
|
+
agent_todo_summary.get("current_agent_claimed_advancement_count") or 0
|
|
862
|
+
)
|
|
863
|
+
current_monitor_count = int(agent_todo_summary.get("current_agent_claimed_monitor_count") or 0)
|
|
864
|
+
unclaimed_count = _selectable_unclaimed_advancement_count(agent_todo_summary)
|
|
865
|
+
lane = str(work_lane_contract.get("lane") or "") if isinstance(work_lane_contract, dict) else ""
|
|
866
|
+
if current_advancement_count == 0 and unclaimed_count == 0 and current_monitor_count > 0:
|
|
867
|
+
return build_hint(
|
|
868
|
+
AgentLaneFrontierHintDecision.QUIET_NOOP_BLOCKER,
|
|
869
|
+
source="agent_todo_summary",
|
|
870
|
+
reason_code="only_current_agent_monitor_work_remains",
|
|
871
|
+
quiet_noop_allowed=lane != TODO_TASK_CLASS_ADVANCEMENT,
|
|
872
|
+
)
|
|
873
|
+
return None
|
|
874
|
+
|
|
875
|
+
|
|
876
|
+
def _agent_scope_deferred_resume_candidates(
|
|
877
|
+
agent_todo_summary: dict[str, Any],
|
|
878
|
+
*,
|
|
879
|
+
agent_id: str,
|
|
880
|
+
) -> list[dict[str, Any]]:
|
|
881
|
+
candidates: list[dict[str, Any]] = []
|
|
882
|
+
for key in (
|
|
883
|
+
"current_agent_deferred_resume_candidates",
|
|
884
|
+
"unclaimed_deferred_resume_candidates",
|
|
885
|
+
):
|
|
886
|
+
value = agent_todo_summary.get(key)
|
|
887
|
+
if isinstance(value, list):
|
|
888
|
+
candidates.extend(item for item in value if isinstance(item, dict))
|
|
889
|
+
if not candidates:
|
|
890
|
+
value = agent_todo_summary.get("deferred_resume_candidates")
|
|
891
|
+
if isinstance(value, list):
|
|
892
|
+
for item in value:
|
|
893
|
+
if not isinstance(item, dict):
|
|
894
|
+
continue
|
|
895
|
+
if not agent_scope_item_claimed_by_agent_or_unclaimed(
|
|
896
|
+
item,
|
|
897
|
+
agent_id=agent_id,
|
|
898
|
+
):
|
|
899
|
+
continue
|
|
900
|
+
candidates.append(item)
|
|
901
|
+
|
|
902
|
+
unique: list[dict[str, Any]] = []
|
|
903
|
+
seen: set[str] = set()
|
|
904
|
+
for item in candidates:
|
|
905
|
+
if todo_item_excludes_agent(item, agent_id=agent_id):
|
|
906
|
+
continue
|
|
907
|
+
if item.get("resume_ready") is not True:
|
|
908
|
+
continue
|
|
909
|
+
if not todo_item_is_deferred(item):
|
|
910
|
+
continue
|
|
911
|
+
identity = str(item.get("todo_id") or item.get("index") or item.get("text") or "")
|
|
912
|
+
if identity in seen:
|
|
913
|
+
continue
|
|
914
|
+
seen.add(identity)
|
|
915
|
+
unique.append(compact_todo_summary_item(item, text=str(item.get("text") or "").strip()))
|
|
916
|
+
return sorted(unique, key=_todo_projection_sort_key)
|
|
917
|
+
|
|
918
|
+
|
|
919
|
+
def _agent_scope_monitor_blocked_resume_candidates(
|
|
920
|
+
agent_todo_summary: dict[str, Any] | None,
|
|
921
|
+
*,
|
|
922
|
+
agent_id: str | None,
|
|
923
|
+
) -> list[dict[str, Any]]:
|
|
924
|
+
if not isinstance(agent_todo_summary, dict):
|
|
925
|
+
return []
|
|
926
|
+
candidates: list[dict[str, Any]] = []
|
|
927
|
+
if agent_id:
|
|
928
|
+
for key in (
|
|
929
|
+
"current_agent_monitor_blocked_resume_candidates",
|
|
930
|
+
"unclaimed_monitor_blocked_resume_candidates",
|
|
931
|
+
):
|
|
932
|
+
value = agent_todo_summary.get(key)
|
|
933
|
+
if isinstance(value, list):
|
|
934
|
+
candidates.extend(item for item in value if isinstance(item, dict))
|
|
935
|
+
else:
|
|
936
|
+
value = agent_todo_summary.get("monitor_blocked_resume_candidates")
|
|
937
|
+
if isinstance(value, list):
|
|
938
|
+
candidates.extend(item for item in value if isinstance(item, dict))
|
|
939
|
+
|
|
940
|
+
unique: list[dict[str, Any]] = []
|
|
941
|
+
seen: set[str] = set()
|
|
942
|
+
for item in candidates:
|
|
943
|
+
if todo_item_excludes_agent(item, agent_id=agent_id):
|
|
944
|
+
continue
|
|
945
|
+
if _todo_task_class(item) != TODO_TASK_CLASS_ADVANCEMENT:
|
|
946
|
+
continue
|
|
947
|
+
if item.get("resume_ready") is not False:
|
|
948
|
+
continue
|
|
949
|
+
condition = item.get("resume_condition") if isinstance(item.get("resume_condition"), dict) else {}
|
|
950
|
+
if normalize_todo_status(condition.get("target_status")) != TODO_STATUS_OPEN:
|
|
951
|
+
continue
|
|
952
|
+
target_todo_id = normalize_todo_id(
|
|
953
|
+
item.get("blocking_monitor_todo_id")
|
|
954
|
+
or condition.get("target_todo_id")
|
|
955
|
+
or condition.get("target")
|
|
956
|
+
)
|
|
957
|
+
target_task_class = normalize_todo_task_class(
|
|
958
|
+
condition.get("target_task_class"),
|
|
959
|
+
text="",
|
|
960
|
+
)
|
|
961
|
+
if target_task_class != TODO_TASK_CLASS_MONITOR and not target_todo_id:
|
|
962
|
+
continue
|
|
963
|
+
identity = str(item.get("todo_id") or item.get("index") or item.get("text") or "")
|
|
964
|
+
if identity in seen:
|
|
965
|
+
continue
|
|
966
|
+
seen.add(identity)
|
|
967
|
+
compact = compact_todo_summary_item(item, text=str(item.get("text") or "").strip())
|
|
968
|
+
if target_todo_id:
|
|
969
|
+
compact["blocking_monitor_todo_id"] = target_todo_id
|
|
970
|
+
unique.append(compact)
|
|
971
|
+
return sorted(unique, key=_todo_projection_sort_key)
|
|
972
|
+
|
|
973
|
+
|
|
974
|
+
def _agent_scope_cleared_without_successor_handoff_gates(
|
|
975
|
+
agent_todo_summary: dict[str, Any],
|
|
976
|
+
*,
|
|
977
|
+
agent_id: str,
|
|
978
|
+
) -> list[dict[str, Any]]:
|
|
979
|
+
unique = _agent_scope_handoff_gates_by_state(
|
|
980
|
+
agent_todo_summary,
|
|
981
|
+
agent_id=agent_id,
|
|
982
|
+
gate_state=HandoffGateState.CLEARED_WITHOUT_SUCCESSOR.value,
|
|
983
|
+
preferred_key="current_agent_cleared_without_successor_handoff_gates",
|
|
984
|
+
)
|
|
985
|
+
return sorted(
|
|
986
|
+
unique,
|
|
987
|
+
key=lambda item: (
|
|
988
|
+
-int(item.get("index") or 0),
|
|
989
|
+
str(item.get("todo_id") or ""),
|
|
990
|
+
),
|
|
991
|
+
)
|
|
992
|
+
|
|
993
|
+
|
|
994
|
+
def _agent_scope_handoff_gates_by_state(
|
|
995
|
+
agent_todo_summary: dict[str, Any],
|
|
996
|
+
*,
|
|
997
|
+
agent_id: str | None,
|
|
998
|
+
gate_state: str,
|
|
999
|
+
preferred_key: str,
|
|
1000
|
+
) -> list[dict[str, Any]]:
|
|
1001
|
+
if not agent_id:
|
|
1002
|
+
return []
|
|
1003
|
+
gates = agent_todo_summary.get(preferred_key)
|
|
1004
|
+
if not isinstance(gates, list):
|
|
1005
|
+
gates = agent_todo_summary.get("handoff_gates")
|
|
1006
|
+
if not isinstance(gates, list):
|
|
1007
|
+
return []
|
|
1008
|
+
selected: list[dict[str, Any]] = []
|
|
1009
|
+
seen: set[str] = set()
|
|
1010
|
+
for item in gates:
|
|
1011
|
+
if not isinstance(item, dict):
|
|
1012
|
+
continue
|
|
1013
|
+
if agent_id not in agent_scope_item_excluded_agents(item):
|
|
1014
|
+
continue
|
|
1015
|
+
if item.get("gate_state") != gate_state:
|
|
1016
|
+
continue
|
|
1017
|
+
identity = str(item.get("todo_id") or item.get("index") or item.get("text") or "")
|
|
1018
|
+
if identity in seen:
|
|
1019
|
+
continue
|
|
1020
|
+
seen.add(identity)
|
|
1021
|
+
compact = dict(item)
|
|
1022
|
+
compact["text"] = str(item.get("text") or "").strip()
|
|
1023
|
+
selected.append(compact)
|
|
1024
|
+
return selected
|
|
1025
|
+
|
|
1026
|
+
|
|
1027
|
+
def agent_scope_blocking_handoff_gates(
|
|
1028
|
+
agent_todo_summary: dict[str, Any],
|
|
1029
|
+
*,
|
|
1030
|
+
agent_id: str | None,
|
|
1031
|
+
) -> list[dict[str, Any]]:
|
|
1032
|
+
selected = _agent_scope_handoff_gates_by_state(
|
|
1033
|
+
agent_todo_summary,
|
|
1034
|
+
agent_id=agent_id,
|
|
1035
|
+
gate_state=HandoffGateState.BLOCKING.value,
|
|
1036
|
+
preferred_key="current_agent_handoff_gates",
|
|
1037
|
+
)
|
|
1038
|
+
return sorted(selected, key=_todo_projection_sort_key)
|
|
1039
|
+
|
|
1040
|
+
|
|
1041
|
+
def _agent_scope_blocking_handoff_gates(
|
|
1042
|
+
agent_todo_summary: dict[str, Any],
|
|
1043
|
+
*,
|
|
1044
|
+
agent_id: str,
|
|
1045
|
+
) -> list[dict[str, Any]]:
|
|
1046
|
+
return agent_scope_blocking_handoff_gates(agent_todo_summary, agent_id=agent_id)
|
|
1047
|
+
|
|
1048
|
+
|
|
1049
|
+
def _route_continuation_candidate_matches_agent(
|
|
1050
|
+
item: dict[str, Any],
|
|
1051
|
+
*,
|
|
1052
|
+
agent_id: str,
|
|
1053
|
+
) -> bool:
|
|
1054
|
+
return agent_scope_item_matches_agent_or_unclaimed(item, agent_id=agent_id)
|
|
1055
|
+
|
|
1056
|
+
|
|
1057
|
+
def _agent_scope_route_continuation_replan_candidates(
|
|
1058
|
+
agent_todo_summary: dict[str, Any],
|
|
1059
|
+
*,
|
|
1060
|
+
agent_id: str,
|
|
1061
|
+
) -> list[dict[str, Any]]:
|
|
1062
|
+
candidates: list[dict[str, Any]] = []
|
|
1063
|
+
for key in (
|
|
1064
|
+
"current_agent_route_continuation_replan_candidates",
|
|
1065
|
+
"unclaimed_route_continuation_replan_candidates",
|
|
1066
|
+
):
|
|
1067
|
+
value = agent_todo_summary.get(key)
|
|
1068
|
+
if isinstance(value, list):
|
|
1069
|
+
candidates.extend(item for item in value if isinstance(item, dict))
|
|
1070
|
+
if not candidates:
|
|
1071
|
+
value = agent_todo_summary.get("route_continuation_replan_candidates")
|
|
1072
|
+
if isinstance(value, list):
|
|
1073
|
+
for item in value:
|
|
1074
|
+
if not isinstance(item, dict):
|
|
1075
|
+
continue
|
|
1076
|
+
if not _route_continuation_candidate_matches_agent(item, agent_id=agent_id):
|
|
1077
|
+
continue
|
|
1078
|
+
candidates.append(item)
|
|
1079
|
+
|
|
1080
|
+
unique: list[dict[str, Any]] = []
|
|
1081
|
+
seen: set[str] = set()
|
|
1082
|
+
for item in candidates:
|
|
1083
|
+
if not _route_continuation_candidate_matches_agent(
|
|
1084
|
+
item,
|
|
1085
|
+
agent_id=agent_id,
|
|
1086
|
+
):
|
|
1087
|
+
continue
|
|
1088
|
+
if item.get("route_continuation_replan_required") is False:
|
|
1089
|
+
continue
|
|
1090
|
+
identity = str(
|
|
1091
|
+
item.get("todo_id")
|
|
1092
|
+
or item.get("route_id")
|
|
1093
|
+
or item.get("route_key")
|
|
1094
|
+
or item.get("index")
|
|
1095
|
+
or item.get("text")
|
|
1096
|
+
or ""
|
|
1097
|
+
)
|
|
1098
|
+
if not identity or identity in seen:
|
|
1099
|
+
continue
|
|
1100
|
+
seen.add(identity)
|
|
1101
|
+
compact = compact_todo_summary_item(item, text=str(item.get("text") or "").strip())
|
|
1102
|
+
compact["route_continuation_replan_required"] = True
|
|
1103
|
+
if item.get("route_continuation_reason") is not None:
|
|
1104
|
+
compact["route_continuation_reason"] = item.get("route_continuation_reason")
|
|
1105
|
+
if item.get("route_id") is not None:
|
|
1106
|
+
compact["route_id"] = item.get("route_id")
|
|
1107
|
+
if item.get("route_key") is not None:
|
|
1108
|
+
compact["route_key"] = item.get("route_key")
|
|
1109
|
+
unique.append(compact)
|
|
1110
|
+
return sorted(unique, key=_todo_projection_sort_key)
|
|
1111
|
+
|
|
1112
|
+
|
|
1113
|
+
@dataclass(frozen=True)
|
|
1114
|
+
class _AgentScopeNoCandidateContext:
|
|
1115
|
+
agent_id: str
|
|
1116
|
+
summary: dict[str, Any]
|
|
1117
|
+
current_agent_count: int
|
|
1118
|
+
unclaimed_count: int
|
|
1119
|
+
has_advancement_contract: bool
|
|
1120
|
+
|
|
1121
|
+
@property
|
|
1122
|
+
def candidate_counts(self) -> dict[str, int]:
|
|
1123
|
+
return {
|
|
1124
|
+
"current_agent_claimed_advancement_count": self.current_agent_count,
|
|
1125
|
+
"unclaimed_advancement_count": self.unclaimed_count,
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
|
|
1129
|
+
def _selected_candidate_priority_frontier(
|
|
1130
|
+
*,
|
|
1131
|
+
agent_id: str,
|
|
1132
|
+
summary: dict[str, Any],
|
|
1133
|
+
selected: dict[str, Any],
|
|
1134
|
+
) -> dict[str, Any] | None:
|
|
1135
|
+
candidates = _agent_scope_deferred_resume_candidates(summary, agent_id=agent_id)
|
|
1136
|
+
if not candidates:
|
|
1137
|
+
return None
|
|
1138
|
+
first = candidates[0]
|
|
1139
|
+
if _todo_projection_sort_key(first)[0] >= _todo_projection_sort_key(selected)[0]:
|
|
1140
|
+
return None
|
|
1141
|
+
candidate_todo_id = str(first.get("todo_id") or "").strip() or "<todo_id>"
|
|
1142
|
+
selected_todo_id = str(selected.get("todo_id") or "").strip() or "<open_todo_id>"
|
|
1143
|
+
return build_agent_scope_frontier_payload(
|
|
1144
|
+
agent_id=agent_id,
|
|
1145
|
+
action=AgentScopeFrontierAction.SUCCESSOR_REPLAN_REQUIRED,
|
|
1146
|
+
quiet_noop_allowed=False,
|
|
1147
|
+
spend_policy="spend once after validated priority successor replan/todo writeback",
|
|
1148
|
+
reason=(
|
|
1149
|
+
f"ready deferred successor {candidate_todo_id} has higher priority than "
|
|
1150
|
+
f"selected open advancement {selected_todo_id}; its lifecycle must be "
|
|
1151
|
+
"resolved before lower-priority delivery"
|
|
1152
|
+
),
|
|
1153
|
+
recommended_action=(
|
|
1154
|
+
"Run a bounded successor replan before lower-priority delivery: reopen, "
|
|
1155
|
+
f"supersede, or record a no-follow-up rationale for {candidate_todo_id}."
|
|
1156
|
+
),
|
|
1157
|
+
requires_replan=True,
|
|
1158
|
+
candidate_counts={
|
|
1159
|
+
"deferred_resume_candidate_count": len(candidates),
|
|
1160
|
+
"preempted_open_candidate_count": 1,
|
|
1161
|
+
},
|
|
1162
|
+
extra_fields={
|
|
1163
|
+
"deferred_resume_candidates": candidates[:3],
|
|
1164
|
+
"preempted_open_candidate": compact_todo_summary_item(
|
|
1165
|
+
selected,
|
|
1166
|
+
text=str(selected.get("text") or "").strip(),
|
|
1167
|
+
),
|
|
1168
|
+
"priority_preemption": True,
|
|
1169
|
+
},
|
|
1170
|
+
)
|
|
1171
|
+
|
|
1172
|
+
|
|
1173
|
+
def _agent_scope_advancement_counts(
|
|
1174
|
+
summary: dict[str, Any],
|
|
1175
|
+
*,
|
|
1176
|
+
agent_id: str,
|
|
1177
|
+
) -> tuple[int, int]:
|
|
1178
|
+
current = max(
|
|
1179
|
+
int(summary.get("current_agent_claimed_advancement_count") or 0),
|
|
1180
|
+
_count_advancement_items(
|
|
1181
|
+
summary.get("current_agent_claimed_advancement_items"),
|
|
1182
|
+
claimed_by=agent_id,
|
|
1183
|
+
),
|
|
1184
|
+
)
|
|
1185
|
+
unclaimed = _selectable_unclaimed_advancement_count(summary)
|
|
1186
|
+
executable_items = summary.get("executable_backlog_items")
|
|
1187
|
+
if isinstance(executable_items, list):
|
|
1188
|
+
current = max(
|
|
1189
|
+
current,
|
|
1190
|
+
_count_advancement_items(executable_items, claimed_by=agent_id),
|
|
1191
|
+
)
|
|
1192
|
+
unclaimed = _count_advancement_items(
|
|
1193
|
+
executable_items,
|
|
1194
|
+
claimed_by="__unclaimed__",
|
|
1195
|
+
)
|
|
1196
|
+
return current, unclaimed
|
|
1197
|
+
|
|
1198
|
+
|
|
1199
|
+
def _monitor_blocked_resume_frontier(
|
|
1200
|
+
context: _AgentScopeNoCandidateContext,
|
|
1201
|
+
) -> dict[str, Any] | None:
|
|
1202
|
+
candidates = _agent_scope_monitor_blocked_resume_candidates(
|
|
1203
|
+
context.summary,
|
|
1204
|
+
agent_id=context.agent_id,
|
|
1205
|
+
)
|
|
1206
|
+
if not candidates:
|
|
1207
|
+
return None
|
|
1208
|
+
first = candidates[0]
|
|
1209
|
+
candidate_id = str(first.get("todo_id") or "").strip() or "<todo_id>"
|
|
1210
|
+
monitor_id = (
|
|
1211
|
+
str(first.get("blocking_monitor_todo_id") or "").strip()
|
|
1212
|
+
or "<monitor_todo_id>"
|
|
1213
|
+
)
|
|
1214
|
+
return build_agent_scope_frontier_payload(
|
|
1215
|
+
agent_id=context.agent_id,
|
|
1216
|
+
action=AgentScopeFrontierAction.SUCCESSOR_REPLAN_REQUIRED,
|
|
1217
|
+
quiet_noop_allowed=False,
|
|
1218
|
+
spend_policy="spend once after validated standing-monitor gate repair/todo writeback",
|
|
1219
|
+
reason=(
|
|
1220
|
+
f"current agent {context.agent_id} has advancement todo {candidate_id} gated "
|
|
1221
|
+
f"by open continuous_monitor {monitor_id}; a standing monitor cannot be the "
|
|
1222
|
+
"todo_done prerequisite for autonomous continuation"
|
|
1223
|
+
),
|
|
1224
|
+
recommended_action=(
|
|
1225
|
+
"Run a bounded gate-model repair before quiet wait: close/supersede "
|
|
1226
|
+
f"{monitor_id} after validated evidence, or rewrite {candidate_id} to use "
|
|
1227
|
+
"a non-blocking monitor contract before delivery."
|
|
1228
|
+
),
|
|
1229
|
+
requires_replan=True,
|
|
1230
|
+
candidate_counts={
|
|
1231
|
+
**context.candidate_counts,
|
|
1232
|
+
"monitor_blocked_resume_candidate_count": len(candidates),
|
|
1233
|
+
},
|
|
1234
|
+
extra_fields={"monitor_blocked_resume_candidates": candidates[:3]},
|
|
1235
|
+
)
|
|
1236
|
+
|
|
1237
|
+
|
|
1238
|
+
def _deferred_resume_frontier(
|
|
1239
|
+
context: _AgentScopeNoCandidateContext,
|
|
1240
|
+
) -> dict[str, Any] | None:
|
|
1241
|
+
candidates = _agent_scope_deferred_resume_candidates(
|
|
1242
|
+
context.summary,
|
|
1243
|
+
agent_id=context.agent_id,
|
|
1244
|
+
)
|
|
1245
|
+
if not candidates:
|
|
1246
|
+
return None
|
|
1247
|
+
candidate_id = str(candidates[0].get("todo_id") or "").strip() or "<todo_id>"
|
|
1248
|
+
return build_agent_scope_frontier_payload(
|
|
1249
|
+
agent_id=context.agent_id,
|
|
1250
|
+
action=AgentScopeFrontierAction.SUCCESSOR_REPLAN_REQUIRED,
|
|
1251
|
+
quiet_noop_allowed=False,
|
|
1252
|
+
spend_policy="spend once after validated successor replan/todo writeback",
|
|
1253
|
+
reason=(
|
|
1254
|
+
f"current agent {context.agent_id} has no open current/unclaimed advancement "
|
|
1255
|
+
"candidate, but a deferred successor resume condition is satisfied"
|
|
1256
|
+
),
|
|
1257
|
+
recommended_action=(
|
|
1258
|
+
"Run a bounded successor replan before delivery: reopen, supersede, or "
|
|
1259
|
+
f"record a no-follow-up rationale for {candidate_id}."
|
|
1260
|
+
),
|
|
1261
|
+
requires_replan=True,
|
|
1262
|
+
candidate_counts={
|
|
1263
|
+
**context.candidate_counts,
|
|
1264
|
+
"deferred_resume_candidate_count": len(candidates),
|
|
1265
|
+
},
|
|
1266
|
+
extra_fields={"deferred_resume_candidates": candidates[:3]},
|
|
1267
|
+
)
|
|
1268
|
+
|
|
1269
|
+
|
|
1270
|
+
def _blocked_successor_wait_frontier(
|
|
1271
|
+
context: _AgentScopeNoCandidateContext,
|
|
1272
|
+
) -> dict[str, Any] | None:
|
|
1273
|
+
candidates = todo_summary_blocked_successor_items(
|
|
1274
|
+
context.summary,
|
|
1275
|
+
agent_id=context.agent_id,
|
|
1276
|
+
)
|
|
1277
|
+
if not candidates:
|
|
1278
|
+
return None
|
|
1279
|
+
first = candidates[0]
|
|
1280
|
+
candidate_id = str(first.get("todo_id") or "").strip() or "<todo_id>"
|
|
1281
|
+
resume_when = str(first.get("resume_when") or "").strip()
|
|
1282
|
+
return build_agent_scope_frontier_payload(
|
|
1283
|
+
agent_id=context.agent_id,
|
|
1284
|
+
action=AgentScopeFrontierAction.AGENT_SCOPE_WAIT,
|
|
1285
|
+
quiet_noop_allowed=True,
|
|
1286
|
+
spend_policy="no quota spend while an exact successor resume condition is pending",
|
|
1287
|
+
reason=(
|
|
1288
|
+
f"current agent {context.agent_id} has exact blocked successor {candidate_id} "
|
|
1289
|
+
f"waiting on {resume_when}; another successor replan would duplicate the "
|
|
1290
|
+
"existing route"
|
|
1291
|
+
),
|
|
1292
|
+
recommended_action=(
|
|
1293
|
+
f"Keep {context.agent_id} active but quiet until {resume_when} becomes ready; "
|
|
1294
|
+
"then resume ordinary todo/deferred-successor routing automatically."
|
|
1295
|
+
),
|
|
1296
|
+
requires_replan=False,
|
|
1297
|
+
candidate_counts={
|
|
1298
|
+
**context.candidate_counts,
|
|
1299
|
+
"blocked_successor_wait_count": len(candidates),
|
|
1300
|
+
},
|
|
1301
|
+
extra_fields={"blocked_successor_wait_candidates": candidates[:3]},
|
|
1302
|
+
)
|
|
1303
|
+
|
|
1304
|
+
|
|
1305
|
+
def _route_continuation_frontier(
|
|
1306
|
+
context: _AgentScopeNoCandidateContext,
|
|
1307
|
+
) -> dict[str, Any] | None:
|
|
1308
|
+
candidates = _agent_scope_route_continuation_replan_candidates(
|
|
1309
|
+
context.summary,
|
|
1310
|
+
agent_id=context.agent_id,
|
|
1311
|
+
)
|
|
1312
|
+
if not candidates:
|
|
1313
|
+
return None
|
|
1314
|
+
first = candidates[0]
|
|
1315
|
+
route_label = (
|
|
1316
|
+
str(
|
|
1317
|
+
first.get("route_id")
|
|
1318
|
+
or first.get("route_key")
|
|
1319
|
+
or first.get("todo_id")
|
|
1320
|
+
or ""
|
|
1321
|
+
).strip()
|
|
1322
|
+
or "<route>"
|
|
1323
|
+
)
|
|
1324
|
+
return build_agent_scope_frontier_payload(
|
|
1325
|
+
agent_id=context.agent_id,
|
|
1326
|
+
action=AgentScopeFrontierAction.SUCCESSOR_REPLAN_REQUIRED,
|
|
1327
|
+
quiet_noop_allowed=False,
|
|
1328
|
+
spend_policy="spend once after validated route continuation replan/todo writeback",
|
|
1329
|
+
reason=(
|
|
1330
|
+
f"current agent {context.agent_id} has no open current/unclaimed advancement "
|
|
1331
|
+
"candidate, but the route continuation projection requires a successor "
|
|
1332
|
+
f"replan for {route_label}"
|
|
1333
|
+
),
|
|
1334
|
+
recommended_action=(
|
|
1335
|
+
"Run a bounded route continuation replan before quiet wait: create or claim "
|
|
1336
|
+
f"the next concrete {context.agent_id}/unclaimed advancement todo for "
|
|
1337
|
+
f"{route_label}, or record an explicit no-follow-up rationale."
|
|
1338
|
+
),
|
|
1339
|
+
requires_replan=True,
|
|
1340
|
+
candidate_counts={
|
|
1341
|
+
**context.candidate_counts,
|
|
1342
|
+
"route_continuation_replan_candidate_count": len(candidates),
|
|
1343
|
+
},
|
|
1344
|
+
extra_fields={"route_continuation_replan_candidates": candidates[:3]},
|
|
1345
|
+
)
|
|
1346
|
+
|
|
1347
|
+
|
|
1348
|
+
def _blocking_handoff_frontier(
|
|
1349
|
+
context: _AgentScopeNoCandidateContext,
|
|
1350
|
+
) -> dict[str, Any] | None:
|
|
1351
|
+
gates = _agent_scope_blocking_handoff_gates(
|
|
1352
|
+
context.summary,
|
|
1353
|
+
agent_id=context.agent_id,
|
|
1354
|
+
)
|
|
1355
|
+
if not gates:
|
|
1356
|
+
return None
|
|
1357
|
+
claimants = sorted(
|
|
1358
|
+
{
|
|
1359
|
+
claimed_by
|
|
1360
|
+
for item in gates
|
|
1361
|
+
for claimed_by in [agent_scope_item_claimed_by(item)]
|
|
1362
|
+
if claimed_by
|
|
1363
|
+
}
|
|
1364
|
+
)
|
|
1365
|
+
owner = ", ".join(claimants) or "the owning agent"
|
|
1366
|
+
return build_agent_scope_frontier_payload(
|
|
1367
|
+
agent_id=context.agent_id,
|
|
1368
|
+
action=AgentScopeFrontierAction.AGENT_SCOPE_WAIT,
|
|
1369
|
+
quiet_noop_allowed=True,
|
|
1370
|
+
spend_policy="no quota spend while the current agent has no in-scope runnable candidate",
|
|
1371
|
+
reason=(
|
|
1372
|
+
f"current agent {context.agent_id} has no current/unclaimed advancement "
|
|
1373
|
+
f"candidate; blocking handoff work is claimed by {owner}"
|
|
1374
|
+
),
|
|
1375
|
+
recommended_action=(
|
|
1376
|
+
f"Keep {context.agent_id} active but quiet: wait for {owner} to finish the "
|
|
1377
|
+
"blocking handoff, reassign it, or create a concrete current-agent/"
|
|
1378
|
+
"unclaimed advancement todo before delivery."
|
|
1379
|
+
),
|
|
1380
|
+
candidate_counts={
|
|
1381
|
+
**context.candidate_counts,
|
|
1382
|
+
"blocking_handoff_gate_count": len(gates),
|
|
1383
|
+
},
|
|
1384
|
+
extra_fields={
|
|
1385
|
+
"other_claimants": claimants,
|
|
1386
|
+
"blocking_handoff_claimants": claimants,
|
|
1387
|
+
"blocking_handoff_gates": gates[:3],
|
|
1388
|
+
},
|
|
1389
|
+
)
|
|
1390
|
+
|
|
1391
|
+
|
|
1392
|
+
def _cleared_handoff_frontier(
|
|
1393
|
+
context: _AgentScopeNoCandidateContext,
|
|
1394
|
+
) -> dict[str, Any] | None:
|
|
1395
|
+
gates = _agent_scope_cleared_without_successor_handoff_gates(
|
|
1396
|
+
context.summary,
|
|
1397
|
+
agent_id=context.agent_id,
|
|
1398
|
+
)
|
|
1399
|
+
if not gates:
|
|
1400
|
+
return None
|
|
1401
|
+
first = gates[0]
|
|
1402
|
+
blocker_id = str(first.get("todo_id") or "").strip() or "<todo_id>"
|
|
1403
|
+
unblocks_id = normalize_todo_id(first.get("unblocks_todo_id"))
|
|
1404
|
+
target_text = f" for unblocked todo {unblocks_id}" if unblocks_id else ""
|
|
1405
|
+
return build_agent_scope_frontier_payload(
|
|
1406
|
+
agent_id=context.agent_id,
|
|
1407
|
+
action=AgentScopeFrontierAction.SUCCESSOR_REPLAN_REQUIRED,
|
|
1408
|
+
quiet_noop_allowed=False,
|
|
1409
|
+
spend_policy="spend once after validated successor replan/todo writeback",
|
|
1410
|
+
reason=(
|
|
1411
|
+
f"current agent {context.agent_id} has no open current/unclaimed advancement "
|
|
1412
|
+
f"candidate, but blocking handoff {blocker_id}{target_text} is already done "
|
|
1413
|
+
"without a projected successor"
|
|
1414
|
+
),
|
|
1415
|
+
recommended_action=(
|
|
1416
|
+
"Run a bounded successor replan before quiet wait: reopen or supersede "
|
|
1417
|
+
f"{blocker_id} into a concrete {context.agent_id}/unclaimed advancement "
|
|
1418
|
+
"todo, or record an explicit no-follow-up rationale."
|
|
1419
|
+
),
|
|
1420
|
+
requires_replan=True,
|
|
1421
|
+
candidate_counts={
|
|
1422
|
+
**context.candidate_counts,
|
|
1423
|
+
"cleared_without_successor_handoff_count": len(gates),
|
|
1424
|
+
},
|
|
1425
|
+
extra_fields={"cleared_without_successor_handoff_gates": gates[:3]},
|
|
1426
|
+
)
|
|
1427
|
+
|
|
1428
|
+
|
|
1429
|
+
def _other_agent_or_exhausted_frontier(
|
|
1430
|
+
context: _AgentScopeNoCandidateContext,
|
|
1431
|
+
) -> dict[str, Any] | None:
|
|
1432
|
+
claimed_items = context.summary.get("claimed_advancement_open_items")
|
|
1433
|
+
claimed_items = claimed_items if isinstance(claimed_items, list) else []
|
|
1434
|
+
other_items = [
|
|
1435
|
+
item
|
|
1436
|
+
for item in claimed_items
|
|
1437
|
+
if isinstance(item, dict)
|
|
1438
|
+
and _todo_item_is_actionable_open(item)
|
|
1439
|
+
and _todo_task_class(item) == TODO_TASK_CLASS_ADVANCEMENT
|
|
1440
|
+
and agent_scope_item_claimed_by(item) not in {None, "", context.agent_id}
|
|
1441
|
+
]
|
|
1442
|
+
if not context.has_advancement_contract and not other_items:
|
|
1443
|
+
return None
|
|
1444
|
+
other_claimants = sorted(
|
|
1445
|
+
{
|
|
1446
|
+
claimed_by
|
|
1447
|
+
for item in other_items
|
|
1448
|
+
for claimed_by in [agent_scope_item_claimed_by(item)]
|
|
1449
|
+
if claimed_by
|
|
1450
|
+
}
|
|
1451
|
+
)
|
|
1452
|
+
blocking_claimants = sorted(
|
|
1453
|
+
{
|
|
1454
|
+
claimed_by
|
|
1455
|
+
for item in other_items
|
|
1456
|
+
if context.agent_id in agent_scope_item_excluded_agents(item)
|
|
1457
|
+
for claimed_by in [agent_scope_item_claimed_by(item)]
|
|
1458
|
+
if claimed_by
|
|
1459
|
+
}
|
|
1460
|
+
)
|
|
1461
|
+
if blocking_claimants:
|
|
1462
|
+
action = AgentScopeFrontierAction.AGENT_SCOPE_WAIT
|
|
1463
|
+
owner = ", ".join(blocking_claimants)
|
|
1464
|
+
reason = (
|
|
1465
|
+
f"current agent {context.agent_id} has no current/unclaimed advancement "
|
|
1466
|
+
f"candidate; blocking handoff work is claimed by {owner}"
|
|
1467
|
+
)
|
|
1468
|
+
recommended_action = (
|
|
1469
|
+
f"Keep {context.agent_id} active but quiet: wait for {owner} to finish the "
|
|
1470
|
+
"blocking handoff, reassign it, or create a concrete current-agent/"
|
|
1471
|
+
"unclaimed advancement todo before delivery."
|
|
1472
|
+
)
|
|
1473
|
+
elif other_items:
|
|
1474
|
+
action = AgentScopeFrontierAction.REASSIGNMENT_REQUIRED
|
|
1475
|
+
owner = ", ".join(other_claimants) or "the owning agent"
|
|
1476
|
+
reason = (
|
|
1477
|
+
f"current agent {context.agent_id} has no current/unclaimed advancement "
|
|
1478
|
+
f"candidate; visible advancement work is claimed by {owner}"
|
|
1479
|
+
)
|
|
1480
|
+
recommended_action = (
|
|
1481
|
+
f"Keep {context.agent_id} active but quiet: wait for {owner} to finish, "
|
|
1482
|
+
"reassign, or create a concrete current-agent/unclaimed advancement todo "
|
|
1483
|
+
"before delivery."
|
|
1484
|
+
)
|
|
1485
|
+
else:
|
|
1486
|
+
action = AgentScopeFrontierAction.AGENT_SCOPE_EXHAUSTED
|
|
1487
|
+
reason = (
|
|
1488
|
+
f"current agent {context.agent_id} has no projected current/unclaimed "
|
|
1489
|
+
"advancement candidate despite a goal-level advancement lane"
|
|
1490
|
+
)
|
|
1491
|
+
recommended_action = (
|
|
1492
|
+
f"Keep {context.agent_id} active but quiet until LoopX projects a concrete "
|
|
1493
|
+
"current-agent or unclaimed advancement todo, or another peer transfers work."
|
|
1494
|
+
)
|
|
1495
|
+
claim_scope = context.summary.get("claim_scope")
|
|
1496
|
+
claim_scope = claim_scope if isinstance(claim_scope, dict) else {}
|
|
1497
|
+
return build_agent_scope_frontier_payload(
|
|
1498
|
+
agent_id=context.agent_id,
|
|
1499
|
+
action=action,
|
|
1500
|
+
quiet_noop_allowed=True,
|
|
1501
|
+
spend_policy="no quota spend while the current agent has no in-scope runnable candidate",
|
|
1502
|
+
reason=reason,
|
|
1503
|
+
recommended_action=recommended_action,
|
|
1504
|
+
candidate_counts={
|
|
1505
|
+
**context.candidate_counts,
|
|
1506
|
+
"other_agent_claimed_advancement_count": len(other_items),
|
|
1507
|
+
"other_agent_claimed_open_count": int(
|
|
1508
|
+
claim_scope.get("other_agent_claimed_open_count") or 0
|
|
1509
|
+
),
|
|
1510
|
+
},
|
|
1511
|
+
extra_fields={
|
|
1512
|
+
"other_claimants": other_claimants,
|
|
1513
|
+
"blocking_handoff_claimants": blocking_claimants,
|
|
1514
|
+
"other_agent_claimed_items": [
|
|
1515
|
+
compact_todo_summary_item(item, text=str(item.get("text") or "").strip())
|
|
1516
|
+
for item in other_items[:3]
|
|
1517
|
+
],
|
|
1518
|
+
},
|
|
1519
|
+
)
|
|
1520
|
+
|
|
1521
|
+
|
|
1522
|
+
_AGENT_SCOPE_NO_CANDIDATE_RULES = (
|
|
1523
|
+
_monitor_blocked_resume_frontier,
|
|
1524
|
+
_deferred_resume_frontier,
|
|
1525
|
+
_blocked_successor_wait_frontier,
|
|
1526
|
+
_route_continuation_frontier,
|
|
1527
|
+
_blocking_handoff_frontier,
|
|
1528
|
+
_cleared_handoff_frontier,
|
|
1529
|
+
_other_agent_or_exhausted_frontier,
|
|
1530
|
+
)
|
|
1531
|
+
|
|
1532
|
+
|
|
1533
|
+
def _agent_scope_no_candidate_frontier(
|
|
1534
|
+
*,
|
|
1535
|
+
agent_identity: dict[str, Any] | None,
|
|
1536
|
+
agent_todo_summary: dict[str, Any] | None,
|
|
1537
|
+
agent_lane_next_action: dict[str, Any] | None,
|
|
1538
|
+
work_lane_contract: dict[str, Any] | None,
|
|
1539
|
+
candidate_should_run: bool,
|
|
1540
|
+
) -> dict[str, Any] | None:
|
|
1541
|
+
if not candidate_should_run or not _agent_identity_has_scoped_lane(agent_identity):
|
|
1542
|
+
return None
|
|
1543
|
+
agent_id = normalize_todo_claimed_by(agent_identity.get("agent_id"))
|
|
1544
|
+
if not agent_id or not isinstance(agent_todo_summary, dict):
|
|
1545
|
+
return None
|
|
1546
|
+
if work_lane_contract_is_due_monitor_attempt(work_lane_contract):
|
|
1547
|
+
return None
|
|
1548
|
+
if isinstance(agent_lane_next_action, dict):
|
|
1549
|
+
return _selected_candidate_priority_frontier(
|
|
1550
|
+
agent_id=agent_id,
|
|
1551
|
+
summary=agent_todo_summary,
|
|
1552
|
+
selected=agent_lane_next_action,
|
|
1553
|
+
)
|
|
1554
|
+
if work_lane_contract_requires_current_agent_attempt(work_lane_contract):
|
|
1555
|
+
return None
|
|
1556
|
+
|
|
1557
|
+
current_count, unclaimed_count = _agent_scope_advancement_counts(
|
|
1558
|
+
agent_todo_summary,
|
|
1559
|
+
agent_id=agent_id,
|
|
1560
|
+
)
|
|
1561
|
+
if current_count > 0 or unclaimed_count > 0:
|
|
1562
|
+
return None
|
|
1563
|
+
context = _AgentScopeNoCandidateContext(
|
|
1564
|
+
agent_id=agent_id,
|
|
1565
|
+
summary=agent_todo_summary,
|
|
1566
|
+
current_agent_count=current_count,
|
|
1567
|
+
unclaimed_count=unclaimed_count,
|
|
1568
|
+
has_advancement_contract=bool(
|
|
1569
|
+
isinstance(work_lane_contract, dict)
|
|
1570
|
+
and work_lane_contract.get("lane") == TODO_TASK_CLASS_ADVANCEMENT
|
|
1571
|
+
and work_lane_contract.get("must_attempt_work") is True
|
|
1572
|
+
),
|
|
1573
|
+
)
|
|
1574
|
+
for rule in _AGENT_SCOPE_NO_CANDIDATE_RULES:
|
|
1575
|
+
frontier = rule(context)
|
|
1576
|
+
if frontier is not None:
|
|
1577
|
+
return frontier
|
|
1578
|
+
return None
|