agentevolve-optimizer 0.5.0__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.
- agent_evolve/__init__.py +722 -0
- agent_evolve/agentic.py +2800 -0
- agent_evolve/api.py +767 -0
- agent_evolve/application/__init__.py +1580 -0
- agent_evolve/application/action_allocation.py +744 -0
- agent_evolve/application/action_allocation_frame.py +347 -0
- agent_evolve/application/action_allocation_frame_commit.py +185 -0
- agent_evolve/application/action_allocation_frame_commit_v3.py +184 -0
- agent_evolve/application/action_allocation_frame_v3.py +338 -0
- agent_evolve/application/action_archive_value.py +497 -0
- agent_evolve/application/action_evidence_consistency.py +455 -0
- agent_evolve/application/action_forecast_partitioning.py +1471 -0
- agent_evolve/application/action_metric_projection.py +211 -0
- agent_evolve/application/action_role_value.py +680 -0
- agent_evolve/application/action_score_authorities.py +363 -0
- agent_evolve/application/action_structural_signature.py +116 -0
- agent_evolve/application/action_target_realization.py +402 -0
- agent_evolve/application/agentic_evolution.py +7734 -0
- agent_evolve/application/agentic_portfolio_residual_expert.py +835 -0
- agent_evolve/application/anchor_residual_identification.py +463 -0
- agent_evolve/application/archive_conditioned_action_target.py +208 -0
- agent_evolve/application/artifact_journal.py +246 -0
- agent_evolve/application/artifact_replay.py +347 -0
- agent_evolve/application/budgeted_optimizer.py +1828 -0
- agent_evolve/application/calibrated_campaign.py +485 -0
- agent_evolve/application/calibrated_current_prefix_forecast_opportunity.py +322 -0
- agent_evolve/application/calibrated_positive_gain_opportunity.py +1581 -0
- agent_evolve/application/campaign_capacity_recourse.py +254 -0
- agent_evolve/application/campaign_contextual_outcomes.py +119 -0
- agent_evolve/application/campaign_diagnostic_blocks.py +930 -0
- agent_evolve/application/campaign_evidence_registry.py +262 -0
- agent_evolve/application/campaign_execution.py +2537 -0
- agent_evolve/application/campaign_generation_audit.py +942 -0
- agent_evolve/application/campaign_learning.py +1812 -0
- agent_evolve/application/campaign_learning_runtime.py +1977 -0
- agent_evolve/application/campaign_search_phase.py +227 -0
- agent_evolve/application/campaign_selector_context_extension.py +220 -0
- agent_evolve/application/campaign_variation_envelope.py +649 -0
- agent_evolve/application/campaign_variation_trace.py +451 -0
- agent_evolve/application/candidate_archive_consequence.py +128 -0
- agent_evolve/application/causal_opportunity_portfolio_gate.py +385 -0
- agent_evolve/application/composite_outcome_updater.py +145 -0
- agent_evolve/application/composition_portfolio_selection.py +363 -0
- agent_evolve/application/concurrent_stage.py +144 -0
- agent_evolve/application/contextual_action_allocation.py +181 -0
- agent_evolve/application/contextual_campaign_outcomes.py +267 -0
- agent_evolve/application/contextual_campaign_planning.py +1366 -0
- agent_evolve/application/contextual_delayed_credit.py +651 -0
- agent_evolve/application/contextual_search_controller.py +2374 -0
- agent_evolve/application/current_prefix_forecast_opportunity.py +714 -0
- agent_evolve/application/decision_metric_projection.py +112 -0
- agent_evolve/application/derived_action_semantics.py +129 -0
- agent_evolve/application/detailed_evaluation.py +449 -0
- agent_evolve/application/earned_lineage.py +1011 -0
- agent_evolve/application/effective_choice_audit.py +484 -0
- agent_evolve/application/empirical_consequence_calibration.py +908 -0
- agent_evolve/application/evaluation_accounting.py +325 -0
- agent_evolve/application/evaluation_cache.py +199 -0
- agent_evolve/application/evaluation_escrow.py +547 -0
- agent_evolve/application/evaluation_recourse.py +253 -0
- agent_evolve/application/event_recorder.py +151 -0
- agent_evolve/application/evolution_campaign.py +1840 -0
- agent_evolve/application/executable_hypothesis.py +323 -0
- agent_evolve/application/factorial_branch_pilot.py +772 -0
- agent_evolve/application/finite_acquisition_capacity_recourse.py +672 -0
- agent_evolve/application/finite_acquisition_residual_expert.py +373 -0
- agent_evolve/application/finite_acquisition_variation_envelope.py +802 -0
- agent_evolve/application/finite_action_hypothesis_semantics.py +446 -0
- agent_evolve/application/finite_action_selection.py +188 -0
- agent_evolve/application/finite_action_set.py +306 -0
- agent_evolve/application/finite_action_transition.py +537 -0
- agent_evolve/application/finite_variation_eligibility.py +296 -0
- agent_evolve/application/forecast_geometry_portfolio.py +799 -0
- agent_evolve/application/forecast_opportunity_shadow_calibration.py +316 -0
- agent_evolve/application/front_proximity_admission.py +311 -0
- agent_evolve/application/front_proximity_parent_basis.py +458 -0
- agent_evolve/application/frozen_hurdle_score.py +659 -0
- agent_evolve/application/g3_causal_screen.py +2257 -0
- agent_evolve/application/g3_causal_validation.py +1046 -0
- agent_evolve/application/g3_postseal_curation.py +818 -0
- agent_evolve/application/gated_agentic_generator.py +205 -0
- agent_evolve/application/generation_feedback.py +293 -0
- agent_evolve/application/generative_proposal_journal.py +185 -0
- agent_evolve/application/geometry_conditional_elasticity.py +453 -0
- agent_evolve/application/global_wave_action_allocation.py +1151 -0
- agent_evolve/application/head_mass_conditional_seat.py +268 -0
- agent_evolve/application/identifiable_reflection_evidence.py +1147 -0
- agent_evolve/application/identifiable_reflection_learning.py +395 -0
- agent_evolve/application/identifiable_reflection_request.py +364 -0
- agent_evolve/application/in_memory_residual_archive.py +341 -0
- agent_evolve/application/insight_memory.py +1804 -0
- agent_evolve/application/live_runtime_manifest.py +758 -0
- agent_evolve/application/llm_task_queue.py +769 -0
- agent_evolve/application/matched_finite_action_block.py +409 -0
- agent_evolve/application/materialized_action_broker.py +2328 -0
- agent_evolve/application/materialized_action_constraints.py +83 -0
- agent_evolve/application/materialized_variation.py +211 -0
- agent_evolve/application/multi_option_evolution.py +1536 -0
- agent_evolve/application/outcome_adaptive_action_racing.py +2827 -0
- agent_evolve/application/outcome_adaptive_residual_campaign_runtime.py +580 -0
- agent_evolve/application/outcome_adaptive_residual_portfolio_evolution.py +3671 -0
- agent_evolve/application/outcome_conditioned_portfolio_selection.py +1374 -0
- agent_evolve/application/outcome_relation.py +193 -0
- agent_evolve/application/paired_allocation_comparison.py +241 -0
- agent_evolve/application/paired_block_schedule.py +127 -0
- agent_evolve/application/parent_measurement.py +226 -0
- agent_evolve/application/pareto_archive.py +811 -0
- agent_evolve/application/portfolio_campaign_runtime.py +4739 -0
- agent_evolve/application/portfolio_evolution.py +2950 -0
- agent_evolve/application/portfolio_hypothesis_observations.py +814 -0
- agent_evolve/application/portfolio_memory_attribution.py +581 -0
- agent_evolve/application/portfolio_memory_dose.py +788 -0
- agent_evolve/application/portfolio_memory_matched_control.py +938 -0
- agent_evolve/application/portfolio_memory_transfer.py +297 -0
- agent_evolve/application/portfolio_optimization_memory.py +363 -0
- agent_evolve/application/portfolio_outcome_feedback.py +1613 -0
- agent_evolve/application/portfolio_projection.py +335 -0
- agent_evolve/application/portfolio_recombination.py +2032 -0
- agent_evolve/application/post_evolution_reflection.py +834 -0
- agent_evolve/application/postcommit_rank_authority.py +245 -0
- agent_evolve/application/precommitted_portfolio_racing.py +2762 -0
- agent_evolve/application/prequential_archive_opportunity_calibration.py +1154 -0
- agent_evolve/application/prequential_residual_exploration.py +343 -0
- agent_evolve/application/prequential_score_portfolio.py +954 -0
- agent_evolve/application/projections.py +292 -0
- agent_evolve/application/protected_action_committee.py +1027 -0
- agent_evolve/application/protected_branch_pilot.py +376 -0
- agent_evolve/application/protected_current_prefix_forecast_opportunity.py +552 -0
- agent_evolve/application/provider_replay.py +910 -0
- agent_evolve/application/rank_balanced_causal_pilot.py +1372 -0
- agent_evolve/application/recombination_residual_expert.py +403 -0
- agent_evolve/application/reflection_workflow.py +571 -0
- agent_evolve/application/region_conditional_credit.py +911 -0
- agent_evolve/application/residual_campaign_runtime.py +531 -0
- agent_evolve/application/residual_headroom_campaign_runtime.py +459 -0
- agent_evolve/application/residual_headroom_ledger.py +1544 -0
- agent_evolve/application/residual_learning_transaction.py +396 -0
- agent_evolve/application/residual_portfolio_evolution.py +1228 -0
- agent_evolve/application/residual_reachability.py +749 -0
- agent_evolve/application/residual_stage_credit.py +499 -0
- agent_evolve/application/same_prefix_paired_audit.py +1580 -0
- agent_evolve/application/semantic_coverage_score_portfolio.py +838 -0
- agent_evolve/application/sequential_lineage_allocation.py +1017 -0
- agent_evolve/application/sequential_market_replay.py +1395 -0
- agent_evolve/application/sequential_residual_campaign_runtime.py +305 -0
- agent_evolve/application/sequential_residual_portfolio_evolution.py +940 -0
- agent_evolve/application/single_score_action_allocation.py +299 -0
- agent_evolve/application/source_exposure_allocation.py +906 -0
- agent_evolve/application/staged_memory.py +210 -0
- agent_evolve/application/stratified_cold_start_allocation.py +732 -0
- agent_evolve/application/support_guarded_hurdle_score.py +549 -0
- agent_evolve/application/target_conditioned_action_forecast.py +595 -0
- agent_evolve/application/target_conditioned_campaign.py +566 -0
- agent_evolve/application/treatment_assignment.py +201 -0
- agent_evolve/application/trusted_objective_evidence.py +217 -0
- agent_evolve/application/two_stage_action_evolution.py +1131 -0
- agent_evolve/application/v8lite_allocation_policy.py +1083 -0
- agent_evolve/application/v9_candidate_policy.py +1303 -0
- agent_evolve/bootstrap.py +108 -0
- agent_evolve/campaign_presets.py +517 -0
- agent_evolve/campaign_profiles.py +452 -0
- agent_evolve/campaign_variation_topology.py +288 -0
- agent_evolve/campaign_workload.py +950 -0
- agent_evolve/cli.py +797 -0
- agent_evolve/contract.py +241 -0
- agent_evolve/core/__init__.py +91 -0
- agent_evolve/core/action_semantics.py +411 -0
- agent_evolve/core/authored.py +105 -0
- agent_evolve/core/formatting.py +286 -0
- agent_evolve/core/optimization_semantics.py +324 -0
- agent_evolve/core/problem.py +167 -0
- agent_evolve/core/results.py +323 -0
- agent_evolve/core/stats.py +70 -0
- agent_evolve/core/telemetry.py +100 -0
- agent_evolve/domain/__init__.py +89 -0
- agent_evolve/domain/artifact.py +162 -0
- agent_evolve/domain/durable_text.py +68 -0
- agent_evolve/domain/event.py +1454 -0
- agent_evolve/domain/finite_action_set.py +426 -0
- agent_evolve/domain/finite_variation.py +526 -0
- agent_evolve/domain/generative_emission.py +559 -0
- agent_evolve/domain/ids.py +163 -0
- agent_evolve/domain/inline_text.py +106 -0
- agent_evolve/domain/insight.py +27 -0
- agent_evolve/domain/lineage.py +737 -0
- agent_evolve/domain/llm_task_queue.py +960 -0
- agent_evolve/domain/outcome.py +96 -0
- agent_evolve/domain/patch.py +854 -0
- agent_evolve/domain/typed_json.py +542 -0
- agent_evolve/domain/variation_space.py +158 -0
- agent_evolve/driver.py +1014 -0
- agent_evolve/harness/__init__.py +29 -0
- agent_evolve/harness/base.py +242 -0
- agent_evolve/harness/directives.py +163 -0
- agent_evolve/harness/generative_seal.py +479 -0
- agent_evolve/harness/registry.py +41 -0
- agent_evolve/infrastructure/__init__.py +39 -0
- agent_evolve/infrastructure/artifacts/__init__.py +6 -0
- agent_evolve/infrastructure/artifacts/_verification.py +67 -0
- agent_evolve/infrastructure/artifacts/filesystem.py +343 -0
- agent_evolve/infrastructure/artifacts/in_memory.py +73 -0
- agent_evolve/infrastructure/asyncio_runtime.py +109 -0
- agent_evolve/infrastructure/authored_runtime.py +188 -0
- agent_evolve/infrastructure/authored_worker.py +171 -0
- agent_evolve/infrastructure/clock.py +53 -0
- agent_evolve/infrastructure/events/__init__.py +6 -0
- agent_evolve/infrastructure/events/_validation.py +89 -0
- agent_evolve/infrastructure/events/in_memory.py +56 -0
- agent_evolve/infrastructure/events/jsonl.py +193 -0
- agent_evolve/infrastructure/exception_provenance.py +215 -0
- agent_evolve/infrastructure/ids.py +118 -0
- agent_evolve/infrastructure/lineage_codec.py +1836 -0
- agent_evolve/infrastructure/outcome_adaptive_phase_journal.py +170 -0
- agent_evolve/infrastructure/residual_headroom_journal.py +221 -0
- agent_evolve/infrastructure/resource_lease.py +370 -0
- agent_evolve/infrastructure/sanitization/__init__.py +8 -0
- agent_evolve/infrastructure/sanitization/strict_json.py +484 -0
- agent_evolve/infrastructure/sequential_phase_journal.py +170 -0
- agent_evolve/infrastructure/stream_liveness.py +383 -0
- agent_evolve/infrastructure/subprocess_boundary.py +136 -0
- agent_evolve/integrations/__init__.py +1 -0
- agent_evolve/integrations/botorch/__init__.py +28 -0
- agent_evolve/integrations/botorch/finite_qlognehvi.py +190 -0
- agent_evolve/integrations/botorch/finite_qlognehvi_batch.py +155 -0
- agent_evolve/integrations/botorch/finite_qlognehvi_batch_identity.py +20 -0
- agent_evolve/integrations/botorch/finite_qlognehvi_batch_worker.py +55 -0
- agent_evolve/integrations/botorch/finite_qlognehvi_identity.py +22 -0
- agent_evolve/integrations/botorch/finite_qlognehvi_worker.py +55 -0
- agent_evolve/integrations/botorch/subprocess_qlognehvi.py +261 -0
- agent_evolve/integrations/botorch/subprocess_qlognehvi_batch.py +273 -0
- agent_evolve/integrations/completion.py +242 -0
- agent_evolve/integrations/pydantic_ai/__init__.py +441 -0
- agent_evolve/integrations/pydantic_ai/action_forecast.py +1068 -0
- agent_evolve/integrations/pydantic_ai/agentic_generator.py +2308 -0
- agent_evolve/integrations/pydantic_ai/async_generator.py +1604 -0
- agent_evolve/integrations/pydantic_ai/boundary_codec.py +1526 -0
- agent_evolve/integrations/pydantic_ai/calibrated_portfolio_campaign.py +756 -0
- agent_evolve/integrations/pydantic_ai/calibrated_portfolio_selection.py +7537 -0
- agent_evolve/integrations/pydantic_ai/campaign_acquisition.py +609 -0
- agent_evolve/integrations/pydantic_ai/execution_binding.py +138 -0
- agent_evolve/integrations/pydantic_ai/forecast_geometry_action_committee.py +217 -0
- agent_evolve/integrations/pydantic_ai/harness.py +159 -0
- agent_evolve/integrations/pydantic_ai/heterogeneous_model_execution.py +306 -0
- agent_evolve/integrations/pydantic_ai/hierarchical_residual_adaptive_semantic_view.py +179 -0
- agent_evolve/integrations/pydantic_ai/json_schema_dialect.py +108 -0
- agent_evolve/integrations/pydantic_ai/materialized_hierarchical_residual_expert.py +952 -0
- agent_evolve/integrations/pydantic_ai/materialized_portfolio_judge.py +520 -0
- agent_evolve/integrations/pydantic_ai/model_execution_profile.py +659 -0
- agent_evolve/integrations/pydantic_ai/outbound_request_manifest.py +1170 -0
- agent_evolve/integrations/pydantic_ai/portable_residual_consequence_features.py +575 -0
- agent_evolve/integrations/pydantic_ai/portfolio_selection.py +422 -0
- agent_evolve/integrations/pydantic_ai/progress_aware_openrouter.py +416 -0
- agent_evolve/integrations/pydantic_ai/provider_attempt_join.py +1523 -0
- agent_evolve/integrations/pydantic_ai/provider_free_calibrated_runner.py +607 -0
- agent_evolve/integrations/pydantic_ai/queued_runner.py +2634 -0
- agent_evolve/integrations/pydantic_ai/reconciled_residual_reachability.py +1417 -0
- agent_evolve/integrations/pydantic_ai/residual_forecast_geometry.py +445 -0
- agent_evolve/integrations/pydantic_ai/residual_reachability.py +674 -0
- agent_evolve/integrations/pydantic_ai/residual_semantic_cells.py +239 -0
- agent_evolve/integrations/pydantic_ai/sealed_output_replay.py +1068 -0
- agent_evolve/integrations/pydantic_ai/semantic_coverage_residual_portfolio.py +770 -0
- agent_evolve/integrations/pydantic_ai/semantic_decision_replay.py +383 -0
- agent_evolve/integrations/pydantic_ai/support_adaptive_residual_portfolio.py +135 -0
- agent_evolve/integrations/pydantic_ai/trusted_residual_prompt_context.py +143 -0
- agent_evolve/integrations/pydantic_ai/validated_openrouter_model.py +107 -0
- agent_evolve/integrations/pymoo_adapter.py +242 -0
- agent_evolve/policies/__init__.py +17 -0
- agent_evolve/policies/check.py +469 -0
- agent_evolve/policies/emit_scaffold.py +451 -0
- agent_evolve/policies/feedback/__init__.py +37 -0
- agent_evolve/policies/feedback/held_out_asn.py +1325 -0
- agent_evolve/policies/genetic.py +607 -0
- agent_evolve/policies/llm_backoff.py +183 -0
- agent_evolve/policies/llm_chooser.py +226 -0
- agent_evolve/policies/llm_generator.py +1760 -0
- agent_evolve/policies/llm_init.py +267 -0
- agent_evolve/policies/llm_operator.py +109 -0
- agent_evolve/policies/llm_prior.py +194 -0
- agent_evolve/policies/llm_surrogate.py +334 -0
- agent_evolve/policies/measurement_evidence.py +704 -0
- agent_evolve/policies/memory/__init__.py +223 -0
- agent_evolve/policies/memory/balanced_subset_blocks.py +707 -0
- agent_evolve/policies/memory/compatibility_matching.py +593 -0
- agent_evolve/policies/memory/global_falsification.py +1841 -0
- agent_evolve/policies/memory/prompt_shape.py +503 -0
- agent_evolve/policies/memory/randomized_subset.py +714 -0
- agent_evolve/policies/memory/staged_causal.py +1270 -0
- agent_evolve/policies/memory/treatment_compliance.py +759 -0
- agent_evolve/policies/objective_resolution/__init__.py +17 -0
- agent_evolve/policies/objective_resolution/fixed_grid.py +364 -0
- agent_evolve/policies/operator_portfolio.py +407 -0
- agent_evolve/policies/reguidance.py +1133 -0
- agent_evolve/policies/reward/__init__.py +83 -0
- agent_evolve/policies/reward/affine_candidate_consequence.py +156 -0
- agent_evolve/policies/reward/affine_candidate_consequence_3d.py +159 -0
- agent_evolve/policies/reward/affine_hypervolume.py +490 -0
- agent_evolve/policies/reward/affine_hypervolume_3d.py +567 -0
- agent_evolve/policies/reward/contextual_marginal_utility.py +318 -0
- agent_evolve/policies/reward/frozen_archive.py +360 -0
- agent_evolve/policies/reward/frozen_wave_archive.py +368 -0
- agent_evolve/policies/search_state.py +208 -0
- agent_evolve/policies/selection/__init__.py +345 -0
- agent_evolve/policies/selection/acquisition_certified_slate.py +684 -0
- agent_evolve/policies/selection/affine_frontier_context.py +330 -0
- agent_evolve/policies/selection/affine_frontier_target.py +473 -0
- agent_evolve/policies/selection/archive_elite.py +1346 -0
- agent_evolve/policies/selection/calibrated_portfolio_binding.py +640 -0
- agent_evolve/policies/selection/calibrated_slate.py +1394 -0
- agent_evolve/policies/selection/calibrated_slate_codec.py +579 -0
- agent_evolve/policies/selection/common_candidate_pool.py +685 -0
- agent_evolve/policies/selection/diagnostic_sampling.py +319 -0
- agent_evolve/policies/selection/disjoint_pairs.py +479 -0
- agent_evolve/policies/selection/elite_explorer.py +719 -0
- agent_evolve/policies/selection/finite_action.py +187 -0
- agent_evolve/policies/selection/finite_option_prompt_projection.py +377 -0
- agent_evolve/policies/selection/finite_palette_evidence.py +247 -0
- agent_evolve/policies/selection/forecast_calibration.py +922 -0
- agent_evolve/policies/selection/frontier_probe_slate.py +814 -0
- agent_evolve/policies/selection/frozen_archive_pairs.py +762 -0
- agent_evolve/policies/selection/full_support_slate.py +91 -0
- agent_evolve/policies/selection/meaningful_direction.py +240 -0
- agent_evolve/policies/selection/memory_dose_feasibility.py +259 -0
- agent_evolve/policies/selection/model_anchored_slate.py +826 -0
- agent_evolve/policies/selection/phenotype_recourse.py +979 -0
- agent_evolve/policies/selection/proposal_support.py +368 -0
- agent_evolve/policies/selection/random_portfolio.py +254 -0
- agent_evolve/policies/selection/regret_bounded_slate.py +1084 -0
- agent_evolve/policies/selection/residual_frontier.py +463 -0
- agent_evolve/policies/selection/residual_frontier_target.py +605 -0
- agent_evolve/policies/selection/structural_posterior_slate.py +1571 -0
- agent_evolve/policies/selection/target_conditioned_allocator.py +648 -0
- agent_evolve/policies/selection/target_conditioned_features.py +812 -0
- agent_evolve/policies/selection/target_conditioned_prequential.py +1527 -0
- agent_evolve/policies/selection/task_keyed_palette.py +906 -0
- agent_evolve/policies/semantics.py +147 -0
- agent_evolve/policies/structure.py +362 -0
- agent_evolve/policies/structured_output_budget.py +62 -0
- agent_evolve/policies/surrogate.py +696 -0
- agent_evolve/policies/variation/__init__.py +1 -0
- agent_evolve/policies/variation/compositional_finite_catalog.py +426 -0
- agent_evolve/policies/variation/crossover_inheritance.py +575 -0
- agent_evolve/policies/variation/disjoint_recombination.py +611 -0
- agent_evolve/policies/variation/exact_composition_capacity.py +214 -0
- agent_evolve/policies/variation/exact_parent_crossover.py +950 -0
- agent_evolve/policies/variation/multiscale_restart_catalog.py +372 -0
- agent_evolve/policies/variation/source_union_finite_catalog.py +403 -0
- agent_evolve/policies/variation/typed_patch.py +1981 -0
- agent_evolve/policies/weighted_prior.py +394 -0
- agent_evolve/ports/__init__.py +383 -0
- agent_evolve/ports/action_allocation.py +733 -0
- agent_evolve/ports/action_allocation_frame.py +1153 -0
- agent_evolve/ports/action_allocation_frame_commit.py +294 -0
- agent_evolve/ports/action_allocation_frame_commit_v3.py +432 -0
- agent_evolve/ports/action_allocation_frame_v3.py +995 -0
- agent_evolve/ports/action_forecast.py +1568 -0
- agent_evolve/ports/action_metric_projection.py +165 -0
- agent_evolve/ports/agentic_generator.py +1561 -0
- agent_evolve/ports/archive_context.py +136 -0
- agent_evolve/ports/artifact_sanitizer.py +44 -0
- agent_evolve/ports/artifact_store.py +225 -0
- agent_evolve/ports/clock.py +13 -0
- agent_evolve/ports/contextual_search_allocation.py +827 -0
- agent_evolve/ports/decision_metric_projection.py +258 -0
- agent_evolve/ports/event_store.py +55 -0
- agent_evolve/ports/executable_hypothesis.py +557 -0
- agent_evolve/ports/finite_acquisition.py +377 -0
- agent_evolve/ports/finite_acquisition_batch.py +296 -0
- agent_evolve/ports/finite_acquisition_batch_json.py +164 -0
- agent_evolve/ports/finite_acquisition_json.py +247 -0
- agent_evolve/ports/finite_acquisition_space.py +168 -0
- agent_evolve/ports/finite_action_selection.py +348 -0
- agent_evolve/ports/finite_action_set.py +256 -0
- agent_evolve/ports/frontier_target.py +396 -0
- agent_evolve/ports/generation_failure.py +43 -0
- agent_evolve/ports/hard_feasibility.py +233 -0
- agent_evolve/ports/id_factory.py +34 -0
- agent_evolve/ports/llm_task_queue.py +93 -0
- agent_evolve/ports/objective_resolution.py +419 -0
- agent_evolve/ports/paired_allocation_comparison.py +401 -0
- agent_evolve/ports/paired_block_schedule.py +475 -0
- agent_evolve/ports/parent_measurement.py +336 -0
- agent_evolve/ports/portfolio_memory_dose.py +643 -0
- agent_evolve/ports/portfolio_selection.py +3169 -0
- agent_evolve/ports/postcommit_rank_authority.py +467 -0
- agent_evolve/ports/presented_action_evidence.py +794 -0
- agent_evolve/ports/resource_lease.py +162 -0
- agent_evolve/ports/structured_generator.py +734 -0
- agent_evolve/ports/structured_output_budget.py +120 -0
- agent_evolve/ports/subprocess_boundary.py +138 -0
- agent_evolve/ports/treatment_assignment.py +466 -0
- agent_evolve/ports/variation_catalog.py +76 -0
- agent_evolve/ports/variation_source.py +226 -0
- agent_evolve/proposal_mode.py +157 -0
- agent_evolve/proposers/__init__.py +10 -0
- agent_evolve/proposers/random_proposer.py +188 -0
- agent_evolve/provider_accounting.py +163 -0
- agent_evolve/py.typed +0 -0
- agent_evolve/reference_method.py +1570 -0
- agent_evolve/session/__init__.py +11 -0
- agent_evolve/session/authorship.py +864 -0
- agent_evolve/session/evaluate.py +236 -0
- agent_evolve/session/fidelity.py +237 -0
- agent_evolve/session/genetic_loop.py +742 -0
- agent_evolve/session/loop.py +803 -0
- agent_evolve/session/screening.py +671 -0
- agent_evolve/settings.py +376 -0
- agent_evolve/workload_kit.py +368 -0
- agent_evolve/workload_prompt.py +398 -0
- agentevolve_optimizer-0.5.0.dist-info/METADATA +599 -0
- agentevolve_optimizer-0.5.0.dist-info/RECORD +414 -0
- agentevolve_optimizer-0.5.0.dist-info/WHEEL +5 -0
- agentevolve_optimizer-0.5.0.dist-info/entry_points.txt +2 -0
- agentevolve_optimizer-0.5.0.dist-info/licenses/LICENSE +21 -0
- agentevolve_optimizer-0.5.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1151 @@
|
|
|
1
|
+
"""Wave-level allocation across concurrent evolutionary parent lanes.
|
|
2
|
+
|
|
3
|
+
Lane-local selection duplicates protected exploration roles and can spend the
|
|
4
|
+
same wave on structurally equivalent actions from different parents. This
|
|
5
|
+
module coordinates only after every lane has independently produced its sealed
|
|
6
|
+
finite action set and consequence forecasts. It owns no workload adapter and
|
|
7
|
+
never sees evaluator outcomes from the current or a future wave.
|
|
8
|
+
|
|
9
|
+
The policy gives a nonterminal wave one global bridge and one global probe,
|
|
10
|
+
uses the remaining capacity for exploitation, preserves every lane's hard
|
|
11
|
+
portfolio contract, and compares both lane orderings with and without
|
|
12
|
+
cross-lane redundancy avoidance. Existing deterministic allocators remain the
|
|
13
|
+
hard-feasibility authority for every candidate slate.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import asyncio
|
|
19
|
+
import hashlib
|
|
20
|
+
import itertools
|
|
21
|
+
import json
|
|
22
|
+
import math
|
|
23
|
+
from dataclasses import dataclass, field, replace
|
|
24
|
+
from typing import Protocol, runtime_checkable
|
|
25
|
+
|
|
26
|
+
from agent_evolve.application.action_role_value import (
|
|
27
|
+
RoleFactorizedActionPortfolioUtility,
|
|
28
|
+
)
|
|
29
|
+
from agent_evolve.application.action_structural_signature import (
|
|
30
|
+
ActionStructuralSignature,
|
|
31
|
+
action_structural_signatures_by_option,
|
|
32
|
+
)
|
|
33
|
+
from agent_evolve.application.contextual_action_allocation import (
|
|
34
|
+
contextual_action_arm_count_constraints,
|
|
35
|
+
contextual_allocation_realization,
|
|
36
|
+
)
|
|
37
|
+
from agent_evolve.application.target_conditioned_action_forecast import (
|
|
38
|
+
TargetConditionedActionForecastPlan,
|
|
39
|
+
allocate_target_conditioned_actions,
|
|
40
|
+
build_target_conditioned_action_utility,
|
|
41
|
+
)
|
|
42
|
+
from agent_evolve.domain.typed_json import FrozenJsonObject, freeze_json
|
|
43
|
+
from agent_evolve.ports.action_allocation import ActionAllocationResult
|
|
44
|
+
from agent_evolve.ports.action_forecast import ResolvedActionForecastBatch
|
|
45
|
+
from agent_evolve.ports.contextual_search_allocation import (
|
|
46
|
+
ContextualPortfolioAllocationContract,
|
|
47
|
+
)
|
|
48
|
+
from agent_evolve.ports.portfolio_selection import PortfolioSelectionRequest
|
|
49
|
+
|
|
50
|
+
GLOBAL_WAVE_ACTION_ALLOCATION_POLICY_ID = "global_role_balanced_wave"
|
|
51
|
+
GLOBAL_WAVE_ACTION_ALLOCATION_POLICY_VERSION = 7
|
|
52
|
+
GLOBAL_WAVE_ACTION_ALLOCATION_POLICY_DEFINITION_SHA256 = hashlib.sha256(
|
|
53
|
+
b"agent-evolve:global-role-balanced-wave:v7;"
|
|
54
|
+
b"scope=concurrent-parent-wave;"
|
|
55
|
+
b"nonterminal-roles=one-bridge-one-probe-remainder-exploit;"
|
|
56
|
+
b"protected-role-placement=exhaustive-small-finite-set;"
|
|
57
|
+
b"memory-search=bounded-primary-then-exhaustive-feasibility-fallback;"
|
|
58
|
+
b"eligible-universe=plan-authenticated-forecast-contract;"
|
|
59
|
+
b"hard-feasibility=existing-lane-allocator-plus-contextual-arm-marginals;"
|
|
60
|
+
b"contextual-count-recourse=minimum-l1-materialized-feasible-projection-"
|
|
61
|
+
b"only-after-exact-search-exhaustion;"
|
|
62
|
+
b"cross-lane-diversity=family-path-signature-plus-p50-objective-cell;"
|
|
63
|
+
b"search=bounded-memory-pairings-times-role-placements-times-lane-rotations;"
|
|
64
|
+
b"current-future-outcomes=false;workload-model-provider-branches=false"
|
|
65
|
+
).hexdigest()
|
|
66
|
+
_RECEIPT_DOMAIN = b"agent-evolve:global-wave-action-allocation:v1\x00"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _canonical_json(value: object) -> bytes:
|
|
70
|
+
return json.dumps(
|
|
71
|
+
value,
|
|
72
|
+
allow_nan=False,
|
|
73
|
+
ensure_ascii=True,
|
|
74
|
+
separators=(",", ":"),
|
|
75
|
+
sort_keys=True,
|
|
76
|
+
).encode("ascii", errors="strict")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _object(value: dict[str, object]) -> FrozenJsonObject:
|
|
80
|
+
frozen = freeze_json(value)
|
|
81
|
+
if type(frozen) is not FrozenJsonObject: # pragma: no cover - closed root.
|
|
82
|
+
raise AssertionError("global allocation audit did not freeze to an object")
|
|
83
|
+
return frozen
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
MemoryAssignment = tuple[tuple[str, str], ...]
|
|
87
|
+
RoleSlots = tuple[int, int, int]
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@dataclass(frozen=True, slots=True)
|
|
91
|
+
class GlobalWaveActionAllocationLane:
|
|
92
|
+
"""One fully forecast lane submitted to the wave-level trusted policy."""
|
|
93
|
+
|
|
94
|
+
generation: int
|
|
95
|
+
request: PortfolioSelectionRequest
|
|
96
|
+
plan: TargetConditionedActionForecastPlan
|
|
97
|
+
forecasts: ResolvedActionForecastBatch
|
|
98
|
+
utility_mode: str
|
|
99
|
+
target_kind: str
|
|
100
|
+
memory_assignments: tuple[MemoryAssignment, ...]
|
|
101
|
+
risk_aversion: float
|
|
102
|
+
diversity_weight: float
|
|
103
|
+
beam_width: int
|
|
104
|
+
contextual_allocation: ContextualPortfolioAllocationContract | None = None
|
|
105
|
+
|
|
106
|
+
def __post_init__(self) -> None:
|
|
107
|
+
if type(self.generation) is not int or self.generation <= 0:
|
|
108
|
+
raise ValueError("generation must be a positive exact integer")
|
|
109
|
+
if type(self.request) is not PortfolioSelectionRequest:
|
|
110
|
+
raise TypeError("request must be an exact PortfolioSelectionRequest")
|
|
111
|
+
self.request.__post_init__()
|
|
112
|
+
if type(self.plan) is not TargetConditionedActionForecastPlan:
|
|
113
|
+
raise TypeError("plan must be exact")
|
|
114
|
+
self.plan.__post_init__()
|
|
115
|
+
if type(self.forecasts) is not ResolvedActionForecastBatch:
|
|
116
|
+
raise TypeError("forecasts must be exact")
|
|
117
|
+
self.plan.assess(self.forecasts)
|
|
118
|
+
if type(self.utility_mode) is not str or not self.utility_mode:
|
|
119
|
+
raise ValueError("utility_mode must be non-empty")
|
|
120
|
+
if type(self.target_kind) is not str or not self.target_kind:
|
|
121
|
+
raise ValueError("target_kind must be non-empty")
|
|
122
|
+
if type(self.memory_assignments) is not tuple or not self.memory_assignments:
|
|
123
|
+
raise ValueError("memory_assignments must be a non-empty tuple")
|
|
124
|
+
for assignment in self.memory_assignments:
|
|
125
|
+
if (
|
|
126
|
+
type(assignment) is not tuple
|
|
127
|
+
or assignment != tuple(sorted(set(assignment)))
|
|
128
|
+
or any(
|
|
129
|
+
type(item) is not tuple
|
|
130
|
+
or len(item) != 2
|
|
131
|
+
or any(type(value) is not str or not value for value in item)
|
|
132
|
+
for item in assignment
|
|
133
|
+
)
|
|
134
|
+
):
|
|
135
|
+
raise ValueError("memory assignments must be canonical string pairs")
|
|
136
|
+
for name in ("risk_aversion", "diversity_weight"):
|
|
137
|
+
value = getattr(self, name)
|
|
138
|
+
if type(value) is not float or not math.isfinite(value) or value < 0.0:
|
|
139
|
+
raise ValueError(f"{name} must be finite and non-negative")
|
|
140
|
+
if type(self.beam_width) is not int or self.beam_width <= 0:
|
|
141
|
+
raise ValueError("beam_width must be a positive exact integer")
|
|
142
|
+
if self.contextual_allocation is not None:
|
|
143
|
+
if type(self.contextual_allocation) is not (
|
|
144
|
+
ContextualPortfolioAllocationContract
|
|
145
|
+
):
|
|
146
|
+
raise TypeError("contextual_allocation must be exact or None")
|
|
147
|
+
self.contextual_allocation.__post_init__()
|
|
148
|
+
if (
|
|
149
|
+
self.contextual_allocation.campaign_generation != self.generation
|
|
150
|
+
or self.contextual_allocation.slice_id
|
|
151
|
+
!= self.plan.campaign_target.lane_id
|
|
152
|
+
or self.contextual_allocation.evaluation_slots
|
|
153
|
+
!= self.request.portfolio_size
|
|
154
|
+
):
|
|
155
|
+
raise ValueError("contextual allocation differs from its wave lane")
|
|
156
|
+
|
|
157
|
+
@property
|
|
158
|
+
def lane_id(self) -> str:
|
|
159
|
+
# The exact frozen lane is validated at construction and again at the
|
|
160
|
+
# public policy/coordinator boundaries. Revalidating its complete
|
|
161
|
+
# forecast and finite contract on every identity lookup made the joint
|
|
162
|
+
# counterfactual search repeatedly hash the same immutable graph.
|
|
163
|
+
return self.plan.campaign_target.lane_id
|
|
164
|
+
|
|
165
|
+
@property
|
|
166
|
+
def wave_key(self) -> tuple[int, str]:
|
|
167
|
+
# See ``lane_id``: this is a pure projection of already validated,
|
|
168
|
+
# immutable fields, not a trust boundary.
|
|
169
|
+
return (
|
|
170
|
+
self.generation,
|
|
171
|
+
self.plan.campaign_target.archive_utility_snapshot_sha256,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
@dataclass(frozen=True, slots=True)
|
|
176
|
+
class GlobalWaveActionAllocationLaneResult:
|
|
177
|
+
"""One lane projection of a jointly selected wave portfolio."""
|
|
178
|
+
|
|
179
|
+
allocation: ActionAllocationResult
|
|
180
|
+
memory_assignment: MemoryAssignment
|
|
181
|
+
role_slots: RoleSlots | None
|
|
182
|
+
eligible_option_ids: tuple[str, ...] | None
|
|
183
|
+
global_receipt_sha256: str
|
|
184
|
+
audit: FrozenJsonObject
|
|
185
|
+
|
|
186
|
+
def __post_init__(self) -> None:
|
|
187
|
+
if type(self.allocation) is not ActionAllocationResult:
|
|
188
|
+
raise TypeError("allocation must be exact")
|
|
189
|
+
self.allocation.__post_init__()
|
|
190
|
+
if type(self.memory_assignment) is not tuple:
|
|
191
|
+
raise TypeError("memory_assignment must be exact")
|
|
192
|
+
if self.role_slots is not None and (
|
|
193
|
+
type(self.role_slots) is not tuple
|
|
194
|
+
or len(self.role_slots) != 3
|
|
195
|
+
or any(type(value) is not int or value < 0 for value in self.role_slots)
|
|
196
|
+
):
|
|
197
|
+
raise ValueError("role_slots must be non-negative exact integers")
|
|
198
|
+
if self.eligible_option_ids is not None and (
|
|
199
|
+
type(self.eligible_option_ids) is not tuple
|
|
200
|
+
or self.eligible_option_ids != tuple(sorted(set(self.eligible_option_ids)))
|
|
201
|
+
):
|
|
202
|
+
raise ValueError("eligible_option_ids must be canonical or None")
|
|
203
|
+
if (
|
|
204
|
+
type(self.global_receipt_sha256) is not str
|
|
205
|
+
or len(self.global_receipt_sha256) != 64
|
|
206
|
+
):
|
|
207
|
+
raise ValueError("global_receipt_sha256 must be a SHA-256 digest")
|
|
208
|
+
if (
|
|
209
|
+
type(self.audit) is not FrozenJsonObject
|
|
210
|
+
or freeze_json(self.audit) is not self.audit
|
|
211
|
+
):
|
|
212
|
+
raise TypeError("audit must be an exact frozen object")
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
@runtime_checkable
|
|
216
|
+
class GlobalWaveActionAllocationPolicy(Protocol):
|
|
217
|
+
def allocate(
|
|
218
|
+
self,
|
|
219
|
+
lanes: tuple[GlobalWaveActionAllocationLane, ...],
|
|
220
|
+
) -> dict[str, GlobalWaveActionAllocationLaneResult]: ...
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
@runtime_checkable
|
|
224
|
+
class GlobalWaveActionAllocationCoordinator(Protocol):
|
|
225
|
+
async def allocate(
|
|
226
|
+
self,
|
|
227
|
+
lane: GlobalWaveActionAllocationLane,
|
|
228
|
+
) -> GlobalWaveActionAllocationLaneResult: ...
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
@dataclass(frozen=True, slots=True)
|
|
232
|
+
class _Trial:
|
|
233
|
+
allocations: tuple[tuple[str, ActionAllocationResult], ...]
|
|
234
|
+
memory_assignments: tuple[tuple[str, MemoryAssignment], ...]
|
|
235
|
+
role_slots: tuple[tuple[str, RoleSlots | None], ...]
|
|
236
|
+
eligible_option_ids: tuple[tuple[str, tuple[str, ...] | None], ...]
|
|
237
|
+
lane_order: tuple[str, ...]
|
|
238
|
+
avoided_redundancy: bool
|
|
239
|
+
structural_collisions: int
|
|
240
|
+
near_outcome_collisions: int
|
|
241
|
+
local_utility: float
|
|
242
|
+
total_score: float
|
|
243
|
+
contextual_source_l1_deviation: int = 0
|
|
244
|
+
contextual_operator_l1_deviation: int = 0
|
|
245
|
+
contextual_projection_count: int = 0
|
|
246
|
+
|
|
247
|
+
@property
|
|
248
|
+
def tie_break(self) -> tuple[str, ...]:
|
|
249
|
+
return tuple(
|
|
250
|
+
allocation.decision.receipt_sha256 for _, allocation in self.allocations
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
@property
|
|
254
|
+
def contextual_l1_deviation(self) -> int:
|
|
255
|
+
return (
|
|
256
|
+
self.contextual_source_l1_deviation
|
|
257
|
+
+ self.contextual_operator_l1_deviation
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
@dataclass(frozen=True, slots=True)
|
|
262
|
+
class _LaneAllocationAttempt:
|
|
263
|
+
allocation: ActionAllocationResult
|
|
264
|
+
source_l1_deviation: int
|
|
265
|
+
operator_l1_deviation: int
|
|
266
|
+
contextual_counts_projected: bool
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def _memory_map(assignment: MemoryAssignment) -> dict[str, str]:
|
|
270
|
+
return dict(assignment)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def _required_option_ids(
|
|
274
|
+
lane: GlobalWaveActionAllocationLane,
|
|
275
|
+
assignment: MemoryAssignment,
|
|
276
|
+
) -> tuple[str, ...]:
|
|
277
|
+
return tuple(
|
|
278
|
+
sorted(
|
|
279
|
+
{
|
|
280
|
+
*lane.request.candidate_pool_required_option_ids,
|
|
281
|
+
*(option_id for option_id, _ in assignment),
|
|
282
|
+
}
|
|
283
|
+
)
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def _p50_cells(
|
|
288
|
+
lane: GlobalWaveActionAllocationLane,
|
|
289
|
+
) -> dict[str, tuple[tuple[str, float], ...]]:
|
|
290
|
+
aliases = {
|
|
291
|
+
value.target_metric_id: value.forecast_metric_id for value in lane.plan.aliases
|
|
292
|
+
}
|
|
293
|
+
result: dict[str, tuple[tuple[str, float], ...]] = {}
|
|
294
|
+
for forecast in lane.forecasts.forecasts:
|
|
295
|
+
metrics = {value.metric_id: value for value in forecast.metric_forecasts}
|
|
296
|
+
result[forecast.option_id] = tuple(
|
|
297
|
+
(
|
|
298
|
+
axis.metric_id,
|
|
299
|
+
axis.normalize(
|
|
300
|
+
axis.parent_value + metrics[aliases[axis.metric_id]].p50_delta
|
|
301
|
+
),
|
|
302
|
+
)
|
|
303
|
+
for axis in lane.plan.objective_target.axes
|
|
304
|
+
)
|
|
305
|
+
return result
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def _cell_distance(
|
|
309
|
+
left: tuple[tuple[str, float], ...],
|
|
310
|
+
right: tuple[tuple[str, float], ...],
|
|
311
|
+
) -> float | None:
|
|
312
|
+
if tuple(value[0] for value in left) != tuple(value[0] for value in right):
|
|
313
|
+
return None
|
|
314
|
+
return sum(abs(a[1] - b[1]) for a, b in zip(left, right, strict=True)) / len(left)
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def _role_budgets(
|
|
318
|
+
lanes: tuple[GlobalWaveActionAllocationLane, ...],
|
|
319
|
+
) -> tuple[dict[str, RoleSlots | None], ...]:
|
|
320
|
+
if any(value.utility_mode != "role_factorized" for value in lanes):
|
|
321
|
+
return ({value.lane_id: None for value in lanes},)
|
|
322
|
+
results: list[dict[str, RoleSlots | None]] = []
|
|
323
|
+
for bridge_lane in lanes:
|
|
324
|
+
for probe_lane in lanes:
|
|
325
|
+
budget: dict[str, RoleSlots | None] = {}
|
|
326
|
+
feasible = True
|
|
327
|
+
for lane in lanes:
|
|
328
|
+
bridge = int(lane.lane_id == bridge_lane.lane_id)
|
|
329
|
+
probe = int(lane.lane_id == probe_lane.lane_id)
|
|
330
|
+
exploit = lane.request.portfolio_size - bridge - probe
|
|
331
|
+
if exploit <= 0:
|
|
332
|
+
feasible = False
|
|
333
|
+
break
|
|
334
|
+
budget[lane.lane_id] = (exploit, bridge, probe)
|
|
335
|
+
if feasible and budget not in results:
|
|
336
|
+
results.append(budget)
|
|
337
|
+
if not results:
|
|
338
|
+
raise ValueError("wave capacity cannot support one global bridge and probe")
|
|
339
|
+
|
|
340
|
+
def screen(budget: dict[str, RoleSlots | None]) -> float:
|
|
341
|
+
total = 0.0
|
|
342
|
+
for lane in lanes:
|
|
343
|
+
role_slots = budget[lane.lane_id]
|
|
344
|
+
assert role_slots is not None
|
|
345
|
+
binding = build_target_conditioned_action_utility(
|
|
346
|
+
plan=lane.plan,
|
|
347
|
+
forecasts=lane.forecasts,
|
|
348
|
+
portfolio_size=lane.request.portfolio_size,
|
|
349
|
+
utility_mode="role_factorized",
|
|
350
|
+
role_slots=role_slots,
|
|
351
|
+
)
|
|
352
|
+
utility = binding.utility
|
|
353
|
+
if type(utility) is not RoleFactorizedActionPortfolioUtility:
|
|
354
|
+
raise AssertionError("role utility factory returned a foreign type")
|
|
355
|
+
exploit_slots, bridge_slots, probe_slots = role_slots
|
|
356
|
+
bridge_choices: tuple[tuple[str | None, float], ...] = (
|
|
357
|
+
((None, 0.0),)
|
|
358
|
+
if bridge_slots == 0
|
|
359
|
+
else tuple(
|
|
360
|
+
(row.option_id, row.bridge_rank_scores[1])
|
|
361
|
+
for row in utility.score_rows
|
|
362
|
+
)
|
|
363
|
+
)
|
|
364
|
+
probe_choices: tuple[tuple[str | None, float], ...] = (
|
|
365
|
+
((None, 0.0),)
|
|
366
|
+
if probe_slots == 0
|
|
367
|
+
else tuple(
|
|
368
|
+
(row.option_id, row.probe_rank_score) for row in utility.score_rows
|
|
369
|
+
)
|
|
370
|
+
)
|
|
371
|
+
rows = {value.option_id: value for value in utility.score_rows}
|
|
372
|
+
best: float | None = None
|
|
373
|
+
for bridge_id, bridge_score in bridge_choices:
|
|
374
|
+
for probe_id, probe_score in probe_choices:
|
|
375
|
+
if bridge_id is not None and bridge_id == probe_id:
|
|
376
|
+
continue
|
|
377
|
+
excluded = {value for value in (bridge_id, probe_id) if value}
|
|
378
|
+
exploit = sorted(
|
|
379
|
+
(
|
|
380
|
+
value.exploit_rank_scores[1]
|
|
381
|
+
for value in rows.values()
|
|
382
|
+
if value.option_id not in excluded
|
|
383
|
+
),
|
|
384
|
+
reverse=True,
|
|
385
|
+
)[:exploit_slots]
|
|
386
|
+
if len(exploit) != exploit_slots:
|
|
387
|
+
continue
|
|
388
|
+
value = bridge_score + probe_score + sum(exploit)
|
|
389
|
+
if best is None or value > best:
|
|
390
|
+
best = value
|
|
391
|
+
if best is None:
|
|
392
|
+
raise ValueError("role budget has no injective action assignment")
|
|
393
|
+
total += best
|
|
394
|
+
return total
|
|
395
|
+
|
|
396
|
+
ranked = sorted(
|
|
397
|
+
results,
|
|
398
|
+
key=lambda value: (
|
|
399
|
+
-screen(value),
|
|
400
|
+
tuple((lane_id, value[lane_id]) for lane_id in sorted(value)),
|
|
401
|
+
),
|
|
402
|
+
)
|
|
403
|
+
# Screening orders the tiny protected-role placement set; it must not
|
|
404
|
+
# discard alternatives before memory, patch, and family feasibility are
|
|
405
|
+
# enforced by the authoritative lane allocator. In particular, a
|
|
406
|
+
# high-scoring bridge/probe placement may be incompatible with a required
|
|
407
|
+
# memory action while another placement remains fully feasible.
|
|
408
|
+
return tuple(ranked)
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def _memory_combinations(
|
|
412
|
+
lanes: tuple[GlobalWaveActionAllocationLane, ...],
|
|
413
|
+
*,
|
|
414
|
+
per_lane_limit: int,
|
|
415
|
+
combination_limit: int,
|
|
416
|
+
) -> tuple[dict[str, MemoryAssignment], ...]:
|
|
417
|
+
candidates = tuple(lane.memory_assignments[:per_lane_limit] for lane in lanes)
|
|
418
|
+
indexed = itertools.product(*(tuple(enumerate(value)) for value in candidates))
|
|
419
|
+
rows = sorted(
|
|
420
|
+
indexed,
|
|
421
|
+
key=lambda row: (
|
|
422
|
+
sum(value[0] for value in row),
|
|
423
|
+
tuple(value[0] for value in row),
|
|
424
|
+
),
|
|
425
|
+
)[:combination_limit]
|
|
426
|
+
return tuple(
|
|
427
|
+
{
|
|
428
|
+
lane.lane_id: indexed_assignment[1]
|
|
429
|
+
for lane, indexed_assignment in zip(lanes, row, strict=True)
|
|
430
|
+
}
|
|
431
|
+
for row in rows
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
@dataclass(slots=True)
|
|
436
|
+
class GlobalRoleBalancedWaveActionAllocationPolicy:
|
|
437
|
+
"""Bounded joint search with one wave-level exploration role budget."""
|
|
438
|
+
|
|
439
|
+
structural_redundancy_penalty: float = 0.25
|
|
440
|
+
near_outcome_redundancy_penalty: float = 0.15
|
|
441
|
+
near_outcome_distance: float = 0.03
|
|
442
|
+
memory_assignments_per_lane: int = 8
|
|
443
|
+
memory_combination_limit: int = 4
|
|
444
|
+
|
|
445
|
+
def __post_init__(self) -> None:
|
|
446
|
+
for name in (
|
|
447
|
+
"structural_redundancy_penalty",
|
|
448
|
+
"near_outcome_redundancy_penalty",
|
|
449
|
+
"near_outcome_distance",
|
|
450
|
+
):
|
|
451
|
+
value = getattr(self, name)
|
|
452
|
+
if type(value) is not float or not math.isfinite(value) or value < 0.0:
|
|
453
|
+
raise ValueError(f"{name} must be finite and non-negative")
|
|
454
|
+
for name in ("memory_assignments_per_lane", "memory_combination_limit"):
|
|
455
|
+
value = getattr(self, name)
|
|
456
|
+
if type(value) is not int or value <= 0:
|
|
457
|
+
raise ValueError(f"{name} must be a positive exact integer")
|
|
458
|
+
|
|
459
|
+
def _allocate_lane(
|
|
460
|
+
self,
|
|
461
|
+
lane: GlobalWaveActionAllocationLane,
|
|
462
|
+
*,
|
|
463
|
+
assignment: MemoryAssignment,
|
|
464
|
+
role_slots: RoleSlots | None,
|
|
465
|
+
eligible_option_ids: tuple[str, ...] | None,
|
|
466
|
+
) -> ActionAllocationResult:
|
|
467
|
+
return allocate_target_conditioned_actions(
|
|
468
|
+
plan=lane.plan,
|
|
469
|
+
forecasts=lane.forecasts,
|
|
470
|
+
portfolio_size=lane.request.portfolio_size,
|
|
471
|
+
eligible_option_ids=eligible_option_ids,
|
|
472
|
+
risk_aversion=lane.risk_aversion,
|
|
473
|
+
diversity_weight=lane.diversity_weight,
|
|
474
|
+
beam_width=lane.beam_width,
|
|
475
|
+
utility_mode=lane.utility_mode,
|
|
476
|
+
required_option_ids=_required_option_ids(lane, assignment),
|
|
477
|
+
role_slots=role_slots,
|
|
478
|
+
exact_arm_count_constraints=contextual_action_arm_count_constraints(
|
|
479
|
+
# The forecast plan is the authenticated allocation universe.
|
|
480
|
+
# The selection request may intentionally retain a much larger
|
|
481
|
+
# source union for support auditing and must not be used to
|
|
482
|
+
# reconstruct constraints over the screened forecast batch.
|
|
483
|
+
finite_contract=lane.plan.request.finite_variation_contract,
|
|
484
|
+
allocation=lane.contextual_allocation,
|
|
485
|
+
portfolio_size=lane.request.portfolio_size,
|
|
486
|
+
),
|
|
487
|
+
minimum_single_path_interventions=(
|
|
488
|
+
0
|
|
489
|
+
if lane.contextual_allocation is None
|
|
490
|
+
else lane.contextual_allocation.minimum_single_path_interventions
|
|
491
|
+
),
|
|
492
|
+
minimum_disjoint_parent_patch_pairs=(
|
|
493
|
+
0
|
|
494
|
+
if lane.contextual_allocation is None
|
|
495
|
+
else lane.contextual_allocation.minimum_disjoint_parent_patch_pairs
|
|
496
|
+
),
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
@staticmethod
|
|
500
|
+
def _count_vectors(
|
|
501
|
+
target: tuple[tuple[str, int], ...],
|
|
502
|
+
*,
|
|
503
|
+
slots: int,
|
|
504
|
+
) -> tuple[tuple[tuple[str, int], ...], ...]:
|
|
505
|
+
"""Enumerate canonical exposure vectors by distance from one request."""
|
|
506
|
+
|
|
507
|
+
arm_ids = tuple(value[0] for value in target)
|
|
508
|
+
requested = dict(target)
|
|
509
|
+
rows = tuple(
|
|
510
|
+
tuple(zip(arm_ids, counts, strict=True))
|
|
511
|
+
for counts in itertools.product(range(slots + 1), repeat=len(arm_ids))
|
|
512
|
+
if sum(counts) == slots
|
|
513
|
+
)
|
|
514
|
+
return tuple(
|
|
515
|
+
sorted(
|
|
516
|
+
rows,
|
|
517
|
+
key=lambda row: (
|
|
518
|
+
sum(abs(count - requested[arm_id]) for arm_id, count in row),
|
|
519
|
+
row,
|
|
520
|
+
),
|
|
521
|
+
)
|
|
522
|
+
)
|
|
523
|
+
|
|
524
|
+
def _allocate_lane_with_count_recourse(
|
|
525
|
+
self,
|
|
526
|
+
lane: GlobalWaveActionAllocationLane,
|
|
527
|
+
*,
|
|
528
|
+
assignment: MemoryAssignment,
|
|
529
|
+
role_slots: RoleSlots | None,
|
|
530
|
+
eligible_option_ids: tuple[str, ...] | None,
|
|
531
|
+
) -> _LaneAllocationAttempt:
|
|
532
|
+
"""Project an unrealizable count request onto the nearest legal slate.
|
|
533
|
+
|
|
534
|
+
Role, memory, structural, phenotype, and evaluator constraints remain
|
|
535
|
+
hard. Only the controller's desired source/operator marginals may
|
|
536
|
+
move, and the first successful L1 shell is exhausted so forecast
|
|
537
|
+
utility breaks ties without permitting a more distant exposure vector.
|
|
538
|
+
"""
|
|
539
|
+
|
|
540
|
+
original = lane.contextual_allocation
|
|
541
|
+
if original is None:
|
|
542
|
+
return _LaneAllocationAttempt(
|
|
543
|
+
allocation=self._allocate_lane(
|
|
544
|
+
lane,
|
|
545
|
+
assignment=assignment,
|
|
546
|
+
role_slots=role_slots,
|
|
547
|
+
eligible_option_ids=eligible_option_ids,
|
|
548
|
+
),
|
|
549
|
+
source_l1_deviation=0,
|
|
550
|
+
operator_l1_deviation=0,
|
|
551
|
+
contextual_counts_projected=False,
|
|
552
|
+
)
|
|
553
|
+
|
|
554
|
+
source_rows = self._count_vectors(
|
|
555
|
+
original.source_target_counts,
|
|
556
|
+
slots=lane.request.portfolio_size,
|
|
557
|
+
)
|
|
558
|
+
operator_rows = self._count_vectors(
|
|
559
|
+
original.operator_target_counts,
|
|
560
|
+
slots=lane.request.portfolio_size,
|
|
561
|
+
)
|
|
562
|
+
requested_source = dict(original.source_target_counts)
|
|
563
|
+
requested_operator = dict(original.operator_target_counts)
|
|
564
|
+
candidates = sorted(
|
|
565
|
+
itertools.product(source_rows, operator_rows),
|
|
566
|
+
key=lambda rows: (
|
|
567
|
+
sum(
|
|
568
|
+
abs(count - requested_source[arm_id])
|
|
569
|
+
for arm_id, count in rows[0]
|
|
570
|
+
)
|
|
571
|
+
+ sum(
|
|
572
|
+
abs(count - requested_operator[arm_id])
|
|
573
|
+
for arm_id, count in rows[1]
|
|
574
|
+
),
|
|
575
|
+
rows,
|
|
576
|
+
),
|
|
577
|
+
)
|
|
578
|
+
best: tuple[ActionAllocationResult, int, int] | None = None
|
|
579
|
+
best_distance: int | None = None
|
|
580
|
+
failures: list[BaseException] = []
|
|
581
|
+
for source_counts, operator_counts in candidates:
|
|
582
|
+
source_l1 = sum(
|
|
583
|
+
abs(count - requested_source[arm_id])
|
|
584
|
+
for arm_id, count in source_counts
|
|
585
|
+
)
|
|
586
|
+
operator_l1 = sum(
|
|
587
|
+
abs(count - requested_operator[arm_id])
|
|
588
|
+
for arm_id, count in operator_counts
|
|
589
|
+
)
|
|
590
|
+
distance = source_l1 + operator_l1
|
|
591
|
+
if best_distance is not None and distance > best_distance:
|
|
592
|
+
break
|
|
593
|
+
projected_contract = replace(
|
|
594
|
+
original,
|
|
595
|
+
source_target_counts=source_counts,
|
|
596
|
+
operator_target_counts=operator_counts,
|
|
597
|
+
feasibility_witness_option_identity_sha256s=(),
|
|
598
|
+
)
|
|
599
|
+
try:
|
|
600
|
+
allocation = self._allocate_lane(
|
|
601
|
+
replace(lane, contextual_allocation=projected_contract),
|
|
602
|
+
assignment=assignment,
|
|
603
|
+
role_slots=role_slots,
|
|
604
|
+
eligible_option_ids=eligible_option_ids,
|
|
605
|
+
)
|
|
606
|
+
except (TypeError, ValueError, RuntimeError) as error:
|
|
607
|
+
failures.append(error)
|
|
608
|
+
continue
|
|
609
|
+
if best is None or (
|
|
610
|
+
allocation.decision.final_score.total_utility,
|
|
611
|
+
allocation.decision.receipt_sha256,
|
|
612
|
+
) > (
|
|
613
|
+
best[0].decision.final_score.total_utility,
|
|
614
|
+
best[0].decision.receipt_sha256,
|
|
615
|
+
):
|
|
616
|
+
best = (allocation, source_l1, operator_l1)
|
|
617
|
+
best_distance = distance
|
|
618
|
+
if best is None:
|
|
619
|
+
detail = "" if not failures else f"; {type(failures[0]).__name__}:{failures[0]}"
|
|
620
|
+
raise ValueError(
|
|
621
|
+
"contextual count projection found no hard-feasible lane slate"
|
|
622
|
+
+ detail
|
|
623
|
+
)
|
|
624
|
+
allocation, source_l1, operator_l1 = best
|
|
625
|
+
selected_option_ids = tuple(
|
|
626
|
+
value.option_id for value in allocation.decision.members
|
|
627
|
+
)
|
|
628
|
+
realization = contextual_allocation_realization(
|
|
629
|
+
finite_contract=lane.plan.request.finite_variation_contract,
|
|
630
|
+
allocation=original,
|
|
631
|
+
selected_option_ids=selected_option_ids,
|
|
632
|
+
)
|
|
633
|
+
if realization is None: # pragma: no cover - original is non-null.
|
|
634
|
+
raise AssertionError("contextual projection lost its source contract")
|
|
635
|
+
if (
|
|
636
|
+
realization.source_l1_deviation != source_l1
|
|
637
|
+
or realization.operator_l1_deviation != operator_l1
|
|
638
|
+
):
|
|
639
|
+
raise RuntimeError("projected count receipt differs from selected slate")
|
|
640
|
+
return _LaneAllocationAttempt(
|
|
641
|
+
allocation=allocation,
|
|
642
|
+
source_l1_deviation=source_l1,
|
|
643
|
+
operator_l1_deviation=operator_l1,
|
|
644
|
+
contextual_counts_projected=not realization.exact,
|
|
645
|
+
)
|
|
646
|
+
|
|
647
|
+
def _eligible_after_prior(
|
|
648
|
+
self,
|
|
649
|
+
lane: GlobalWaveActionAllocationLane,
|
|
650
|
+
*,
|
|
651
|
+
assignment: MemoryAssignment,
|
|
652
|
+
prior: tuple[
|
|
653
|
+
tuple[GlobalWaveActionAllocationLane, ActionAllocationResult], ...
|
|
654
|
+
],
|
|
655
|
+
signatures: dict[str, dict[str, ActionStructuralSignature]],
|
|
656
|
+
cells: dict[str, dict[str, tuple[tuple[str, float], ...]]],
|
|
657
|
+
) -> tuple[str, ...]:
|
|
658
|
+
required = set(_required_option_ids(lane, assignment))
|
|
659
|
+
blocked_signatures = {
|
|
660
|
+
signatures[prior_lane.lane_id][member.option_id]
|
|
661
|
+
for prior_lane, allocation in prior
|
|
662
|
+
for member in allocation.decision.members
|
|
663
|
+
}
|
|
664
|
+
prior_cells = tuple(
|
|
665
|
+
cells[prior_lane.lane_id][member.option_id]
|
|
666
|
+
for prior_lane, allocation in prior
|
|
667
|
+
for member in allocation.decision.members
|
|
668
|
+
)
|
|
669
|
+
eligible: list[str] = []
|
|
670
|
+
for forecast in lane.forecasts.forecasts:
|
|
671
|
+
option_id = forecast.option_id
|
|
672
|
+
if option_id in required:
|
|
673
|
+
eligible.append(option_id)
|
|
674
|
+
continue
|
|
675
|
+
if signatures[lane.lane_id][option_id] in blocked_signatures:
|
|
676
|
+
continue
|
|
677
|
+
cell = cells[lane.lane_id][option_id]
|
|
678
|
+
if any(
|
|
679
|
+
distance is not None and distance <= self.near_outcome_distance
|
|
680
|
+
for distance in (_cell_distance(cell, value) for value in prior_cells)
|
|
681
|
+
):
|
|
682
|
+
continue
|
|
683
|
+
eligible.append(option_id)
|
|
684
|
+
return tuple(sorted(eligible))
|
|
685
|
+
|
|
686
|
+
def _collision_counts(
|
|
687
|
+
self,
|
|
688
|
+
allocations: dict[str, ActionAllocationResult],
|
|
689
|
+
*,
|
|
690
|
+
signatures: dict[str, dict[str, ActionStructuralSignature]],
|
|
691
|
+
cells: dict[str, dict[str, tuple[tuple[str, float], ...]]],
|
|
692
|
+
) -> tuple[int, int]:
|
|
693
|
+
structural = 0
|
|
694
|
+
near = 0
|
|
695
|
+
lane_ids = tuple(sorted(allocations))
|
|
696
|
+
for left_index, left_id in enumerate(lane_ids):
|
|
697
|
+
for right_id in lane_ids[left_index + 1 :]:
|
|
698
|
+
for left in allocations[left_id].decision.members:
|
|
699
|
+
for right in allocations[right_id].decision.members:
|
|
700
|
+
if (
|
|
701
|
+
signatures[left_id][left.option_id]
|
|
702
|
+
== signatures[right_id][right.option_id]
|
|
703
|
+
):
|
|
704
|
+
structural += 1
|
|
705
|
+
distance = _cell_distance(
|
|
706
|
+
cells[left_id][left.option_id],
|
|
707
|
+
cells[right_id][right.option_id],
|
|
708
|
+
)
|
|
709
|
+
if (
|
|
710
|
+
distance is not None
|
|
711
|
+
and distance <= self.near_outcome_distance
|
|
712
|
+
):
|
|
713
|
+
near += 1
|
|
714
|
+
return structural, near
|
|
715
|
+
|
|
716
|
+
def allocate(
|
|
717
|
+
self,
|
|
718
|
+
lanes: tuple[GlobalWaveActionAllocationLane, ...],
|
|
719
|
+
) -> dict[str, GlobalWaveActionAllocationLaneResult]:
|
|
720
|
+
self.__post_init__()
|
|
721
|
+
if type(lanes) is not tuple or not lanes:
|
|
722
|
+
raise ValueError("lanes must be a non-empty exact tuple")
|
|
723
|
+
for lane in lanes:
|
|
724
|
+
if type(lane) is not GlobalWaveActionAllocationLane:
|
|
725
|
+
raise TypeError("lanes must contain exact lane requests")
|
|
726
|
+
lane.__post_init__()
|
|
727
|
+
lanes = tuple(sorted(lanes, key=lambda value: value.lane_id))
|
|
728
|
+
if len({value.lane_id for value in lanes}) != len(lanes):
|
|
729
|
+
raise ValueError("global wave cannot repeat a lane")
|
|
730
|
+
if len({value.wave_key for value in lanes}) != 1:
|
|
731
|
+
raise ValueError("global allocation lanes belong to different waves")
|
|
732
|
+
signatures = {
|
|
733
|
+
lane.lane_id: action_structural_signatures_by_option(
|
|
734
|
+
lane.plan.request.finite_variation_contract
|
|
735
|
+
)
|
|
736
|
+
for lane in lanes
|
|
737
|
+
}
|
|
738
|
+
cells = {lane.lane_id: _p50_cells(lane) for lane in lanes}
|
|
739
|
+
role_budgets = _role_budgets(lanes)
|
|
740
|
+
maximum_memory_combinations = math.prod(
|
|
741
|
+
min(len(lane.memory_assignments), self.memory_assignments_per_lane)
|
|
742
|
+
for lane in lanes
|
|
743
|
+
)
|
|
744
|
+
all_memory_combinations = _memory_combinations(
|
|
745
|
+
lanes,
|
|
746
|
+
per_lane_limit=self.memory_assignments_per_lane,
|
|
747
|
+
combination_limit=maximum_memory_combinations,
|
|
748
|
+
)
|
|
749
|
+
primary_memory_combinations = all_memory_combinations[
|
|
750
|
+
: self.memory_combination_limit
|
|
751
|
+
]
|
|
752
|
+
fallback_memory_combinations = all_memory_combinations[
|
|
753
|
+
len(primary_memory_combinations) :
|
|
754
|
+
]
|
|
755
|
+
memory_batches = (
|
|
756
|
+
primary_memory_combinations,
|
|
757
|
+
fallback_memory_combinations,
|
|
758
|
+
)
|
|
759
|
+
orders = tuple(
|
|
760
|
+
(*lanes[index:], *lanes[:index]) for index in range(len(lanes))
|
|
761
|
+
)
|
|
762
|
+
best: _Trial | None = None
|
|
763
|
+
projected_best: _Trial | None = None
|
|
764
|
+
failures: list[str] = []
|
|
765
|
+
projection_failures: list[str] = []
|
|
766
|
+
trial_count = 0
|
|
767
|
+
projection_trial_count = 0
|
|
768
|
+
projection_candidates: list[
|
|
769
|
+
tuple[
|
|
770
|
+
dict[str, RoleSlots | None],
|
|
771
|
+
dict[str, MemoryAssignment],
|
|
772
|
+
tuple[GlobalWaveActionAllocationLane, ...],
|
|
773
|
+
bool,
|
|
774
|
+
]
|
|
775
|
+
] = []
|
|
776
|
+
allocation_cache: dict[
|
|
777
|
+
tuple[
|
|
778
|
+
str,
|
|
779
|
+
MemoryAssignment,
|
|
780
|
+
RoleSlots | None,
|
|
781
|
+
tuple[str, ...] | None,
|
|
782
|
+
bool,
|
|
783
|
+
],
|
|
784
|
+
_LaneAllocationAttempt,
|
|
785
|
+
] = {}
|
|
786
|
+
fallback_memory_search_used = False
|
|
787
|
+
|
|
788
|
+
def attempt_trial(
|
|
789
|
+
*,
|
|
790
|
+
role_budget: dict[str, RoleSlots | None],
|
|
791
|
+
memory: dict[str, MemoryAssignment],
|
|
792
|
+
order: tuple[GlobalWaveActionAllocationLane, ...],
|
|
793
|
+
avoid_redundancy: bool,
|
|
794
|
+
permit_count_projection: bool,
|
|
795
|
+
) -> _Trial:
|
|
796
|
+
allocations: dict[str, ActionAllocationResult] = {}
|
|
797
|
+
eligible_by_lane: dict[str, tuple[str, ...] | None] = {}
|
|
798
|
+
prior: list[
|
|
799
|
+
tuple[GlobalWaveActionAllocationLane, ActionAllocationResult]
|
|
800
|
+
] = []
|
|
801
|
+
source_l1 = 0
|
|
802
|
+
operator_l1 = 0
|
|
803
|
+
projection_count = 0
|
|
804
|
+
for lane in order:
|
|
805
|
+
eligible = None
|
|
806
|
+
if avoid_redundancy and prior:
|
|
807
|
+
candidate_eligible = self._eligible_after_prior(
|
|
808
|
+
lane,
|
|
809
|
+
assignment=memory[lane.lane_id],
|
|
810
|
+
prior=tuple(prior),
|
|
811
|
+
signatures=signatures,
|
|
812
|
+
cells=cells,
|
|
813
|
+
)
|
|
814
|
+
if len(candidate_eligible) >= lane.request.portfolio_size:
|
|
815
|
+
eligible = candidate_eligible
|
|
816
|
+
cache_key = (
|
|
817
|
+
lane.lane_id,
|
|
818
|
+
memory[lane.lane_id],
|
|
819
|
+
role_budget[lane.lane_id],
|
|
820
|
+
eligible,
|
|
821
|
+
permit_count_projection,
|
|
822
|
+
)
|
|
823
|
+
attempt = allocation_cache.get(cache_key)
|
|
824
|
+
if attempt is None:
|
|
825
|
+
if permit_count_projection:
|
|
826
|
+
attempt = self._allocate_lane_with_count_recourse(
|
|
827
|
+
lane,
|
|
828
|
+
assignment=memory[lane.lane_id],
|
|
829
|
+
role_slots=role_budget[lane.lane_id],
|
|
830
|
+
eligible_option_ids=eligible,
|
|
831
|
+
)
|
|
832
|
+
else:
|
|
833
|
+
attempt = _LaneAllocationAttempt(
|
|
834
|
+
allocation=self._allocate_lane(
|
|
835
|
+
lane,
|
|
836
|
+
assignment=memory[lane.lane_id],
|
|
837
|
+
role_slots=role_budget[lane.lane_id],
|
|
838
|
+
eligible_option_ids=eligible,
|
|
839
|
+
),
|
|
840
|
+
source_l1_deviation=0,
|
|
841
|
+
operator_l1_deviation=0,
|
|
842
|
+
contextual_counts_projected=False,
|
|
843
|
+
)
|
|
844
|
+
allocation_cache[cache_key] = attempt
|
|
845
|
+
allocation = attempt.allocation
|
|
846
|
+
allocations[lane.lane_id] = allocation
|
|
847
|
+
eligible_by_lane[lane.lane_id] = eligible
|
|
848
|
+
prior.append((lane, allocation))
|
|
849
|
+
source_l1 += attempt.source_l1_deviation
|
|
850
|
+
operator_l1 += attempt.operator_l1_deviation
|
|
851
|
+
projection_count += attempt.contextual_counts_projected
|
|
852
|
+
structural, near = self._collision_counts(
|
|
853
|
+
allocations,
|
|
854
|
+
signatures=signatures,
|
|
855
|
+
cells=cells,
|
|
856
|
+
)
|
|
857
|
+
local = sum(
|
|
858
|
+
value.decision.final_score.total_utility
|
|
859
|
+
for value in allocations.values()
|
|
860
|
+
)
|
|
861
|
+
total = (
|
|
862
|
+
local
|
|
863
|
+
- self.structural_redundancy_penalty * structural
|
|
864
|
+
- self.near_outcome_redundancy_penalty * near
|
|
865
|
+
)
|
|
866
|
+
return _Trial(
|
|
867
|
+
allocations=tuple(sorted(allocations.items())),
|
|
868
|
+
memory_assignments=tuple(sorted(memory.items())),
|
|
869
|
+
role_slots=tuple(sorted(role_budget.items())),
|
|
870
|
+
eligible_option_ids=tuple(sorted(eligible_by_lane.items())),
|
|
871
|
+
lane_order=tuple(value.lane_id for value in order),
|
|
872
|
+
avoided_redundancy=avoid_redundancy,
|
|
873
|
+
structural_collisions=structural,
|
|
874
|
+
near_outcome_collisions=near,
|
|
875
|
+
local_utility=float(local),
|
|
876
|
+
total_score=float(total),
|
|
877
|
+
contextual_source_l1_deviation=source_l1,
|
|
878
|
+
contextual_operator_l1_deviation=operator_l1,
|
|
879
|
+
contextual_projection_count=projection_count,
|
|
880
|
+
)
|
|
881
|
+
|
|
882
|
+
for batch_index, memory_combinations in enumerate(memory_batches):
|
|
883
|
+
if not memory_combinations:
|
|
884
|
+
continue
|
|
885
|
+
if batch_index == 1:
|
|
886
|
+
fallback_memory_search_used = True
|
|
887
|
+
for role_budget in role_budgets:
|
|
888
|
+
for memory in memory_combinations:
|
|
889
|
+
for order in orders:
|
|
890
|
+
for avoid_redundancy in (False, True):
|
|
891
|
+
trial_count += 1
|
|
892
|
+
try:
|
|
893
|
+
trial = attempt_trial(
|
|
894
|
+
role_budget=role_budget,
|
|
895
|
+
memory=memory,
|
|
896
|
+
order=order,
|
|
897
|
+
avoid_redundancy=avoid_redundancy,
|
|
898
|
+
permit_count_projection=False,
|
|
899
|
+
)
|
|
900
|
+
except (TypeError, ValueError, RuntimeError) as error:
|
|
901
|
+
failures.append(f"{type(error).__name__}:{error}")
|
|
902
|
+
projection_candidates.append(
|
|
903
|
+
(
|
|
904
|
+
role_budget,
|
|
905
|
+
memory,
|
|
906
|
+
order,
|
|
907
|
+
avoid_redundancy,
|
|
908
|
+
)
|
|
909
|
+
)
|
|
910
|
+
continue
|
|
911
|
+
if best is None or (
|
|
912
|
+
trial.total_score,
|
|
913
|
+
trial.tie_break,
|
|
914
|
+
) > (
|
|
915
|
+
best.total_score,
|
|
916
|
+
best.tie_break,
|
|
917
|
+
):
|
|
918
|
+
best = trial
|
|
919
|
+
# The bounded primary set is a quality-prioritized search. The
|
|
920
|
+
# larger set is a feasibility recourse only; do not pay its cost
|
|
921
|
+
# once any primary assignment produces a legal joint slate.
|
|
922
|
+
if best is not None:
|
|
923
|
+
break
|
|
924
|
+
if best is None:
|
|
925
|
+
for (
|
|
926
|
+
role_budget,
|
|
927
|
+
memory,
|
|
928
|
+
order,
|
|
929
|
+
avoid_redundancy,
|
|
930
|
+
) in projection_candidates:
|
|
931
|
+
projection_trial_count += 1
|
|
932
|
+
try:
|
|
933
|
+
projected = attempt_trial(
|
|
934
|
+
role_budget=role_budget,
|
|
935
|
+
memory=memory,
|
|
936
|
+
order=order,
|
|
937
|
+
avoid_redundancy=avoid_redundancy,
|
|
938
|
+
permit_count_projection=True,
|
|
939
|
+
)
|
|
940
|
+
except (TypeError, ValueError, RuntimeError) as projected_error:
|
|
941
|
+
projection_failures.append(
|
|
942
|
+
f"{type(projected_error).__name__}:{projected_error}"
|
|
943
|
+
)
|
|
944
|
+
else:
|
|
945
|
+
if projected_best is None or (
|
|
946
|
+
projected.contextual_l1_deviation,
|
|
947
|
+
-projected.total_score,
|
|
948
|
+
projected.tie_break,
|
|
949
|
+
) < (
|
|
950
|
+
projected_best.contextual_l1_deviation,
|
|
951
|
+
-projected_best.total_score,
|
|
952
|
+
projected_best.tie_break,
|
|
953
|
+
):
|
|
954
|
+
projected_best = projected
|
|
955
|
+
contextual_count_recourse_used = best is None and projected_best is not None
|
|
956
|
+
if best is None:
|
|
957
|
+
best = projected_best
|
|
958
|
+
if best is None:
|
|
959
|
+
detail = "; ".join((*failures[:2], *projection_failures[:2]))
|
|
960
|
+
raise ValueError(
|
|
961
|
+
"global wave allocation found no hard-feasible joint slate"
|
|
962
|
+
+ ("; " + detail if detail else "")
|
|
963
|
+
)
|
|
964
|
+
record = {
|
|
965
|
+
"schema_version": 1,
|
|
966
|
+
"policy": {
|
|
967
|
+
"policy_id": GLOBAL_WAVE_ACTION_ALLOCATION_POLICY_ID,
|
|
968
|
+
"policy_version": GLOBAL_WAVE_ACTION_ALLOCATION_POLICY_VERSION,
|
|
969
|
+
"definition_sha256": (
|
|
970
|
+
GLOBAL_WAVE_ACTION_ALLOCATION_POLICY_DEFINITION_SHA256
|
|
971
|
+
),
|
|
972
|
+
},
|
|
973
|
+
"wave_key": {
|
|
974
|
+
"generation": lanes[0].generation,
|
|
975
|
+
"archive_utility_snapshot_sha256": lanes[0].wave_key[1],
|
|
976
|
+
},
|
|
977
|
+
"lane_order": list(best.lane_order),
|
|
978
|
+
"avoided_redundancy": best.avoided_redundancy,
|
|
979
|
+
"role_slots": {
|
|
980
|
+
lane_id: None if slots is None else list(slots)
|
|
981
|
+
for lane_id, slots in best.role_slots
|
|
982
|
+
},
|
|
983
|
+
"selected_option_ids": {
|
|
984
|
+
lane_id: [value.option_id for value in allocation.decision.members]
|
|
985
|
+
for lane_id, allocation in best.allocations
|
|
986
|
+
},
|
|
987
|
+
"eligible_option_ids": {
|
|
988
|
+
lane_id: (None if option_ids is None else list(option_ids))
|
|
989
|
+
for lane_id, option_ids in best.eligible_option_ids
|
|
990
|
+
},
|
|
991
|
+
"allocation_receipt_sha256s": {
|
|
992
|
+
lane_id: allocation.decision.receipt_sha256
|
|
993
|
+
for lane_id, allocation in best.allocations
|
|
994
|
+
},
|
|
995
|
+
"memory_assignments": {
|
|
996
|
+
lane_id: dict(assignment)
|
|
997
|
+
for lane_id, assignment in best.memory_assignments
|
|
998
|
+
},
|
|
999
|
+
"contextual_allocation_contract_sha256s": {
|
|
1000
|
+
lane.lane_id: (
|
|
1001
|
+
None
|
|
1002
|
+
if lane.contextual_allocation is None
|
|
1003
|
+
else lane.contextual_allocation.contract_sha256
|
|
1004
|
+
)
|
|
1005
|
+
for lane in lanes
|
|
1006
|
+
},
|
|
1007
|
+
"source_finite_contract_sha256s": {
|
|
1008
|
+
lane.lane_id: lane.request.finite_variation_contract.identity_sha256
|
|
1009
|
+
for lane in lanes
|
|
1010
|
+
},
|
|
1011
|
+
"allocation_finite_contract_sha256s": {
|
|
1012
|
+
lane.lane_id: (
|
|
1013
|
+
lane.plan.request.finite_variation_contract.identity_sha256
|
|
1014
|
+
)
|
|
1015
|
+
for lane in lanes
|
|
1016
|
+
},
|
|
1017
|
+
"structural_collisions": best.structural_collisions,
|
|
1018
|
+
"near_outcome_collisions": best.near_outcome_collisions,
|
|
1019
|
+
"near_outcome_distance_hex": self.near_outcome_distance.hex(),
|
|
1020
|
+
"local_utility_hex": best.local_utility.hex(),
|
|
1021
|
+
"structural_penalty_hex": self.structural_redundancy_penalty.hex(),
|
|
1022
|
+
"near_outcome_penalty_hex": self.near_outcome_redundancy_penalty.hex(),
|
|
1023
|
+
"total_score_hex": best.total_score.hex(),
|
|
1024
|
+
"trial_count": trial_count,
|
|
1025
|
+
"failed_trial_count": len(failures),
|
|
1026
|
+
"memory_combination_count": len(all_memory_combinations),
|
|
1027
|
+
"primary_memory_combination_count": len(primary_memory_combinations),
|
|
1028
|
+
"fallback_memory_search_used": fallback_memory_search_used,
|
|
1029
|
+
"contextual_count_recourse_used": contextual_count_recourse_used,
|
|
1030
|
+
"contextual_projection_deferred_until_exact_exhaustion": True,
|
|
1031
|
+
"contextual_projection_eligible_trial_count": len(
|
|
1032
|
+
projection_candidates
|
|
1033
|
+
),
|
|
1034
|
+
"contextual_projection_trial_count": projection_trial_count,
|
|
1035
|
+
"contextual_projection_failure_count": len(projection_failures),
|
|
1036
|
+
"contextual_source_l1_deviation": (
|
|
1037
|
+
best.contextual_source_l1_deviation
|
|
1038
|
+
),
|
|
1039
|
+
"contextual_operator_l1_deviation": (
|
|
1040
|
+
best.contextual_operator_l1_deviation
|
|
1041
|
+
),
|
|
1042
|
+
"contextual_projection_count": best.contextual_projection_count,
|
|
1043
|
+
}
|
|
1044
|
+
receipt = hashlib.sha256(_RECEIPT_DOMAIN + _canonical_json(record)).hexdigest()
|
|
1045
|
+
audit = _object({**record, "receipt_sha256": receipt})
|
|
1046
|
+
allocation_by_id = dict(best.allocations)
|
|
1047
|
+
memory_by_id = dict(best.memory_assignments)
|
|
1048
|
+
roles_by_id = dict(best.role_slots)
|
|
1049
|
+
eligible_by_id = dict(best.eligible_option_ids)
|
|
1050
|
+
return {
|
|
1051
|
+
lane.lane_id: GlobalWaveActionAllocationLaneResult(
|
|
1052
|
+
allocation=allocation_by_id[lane.lane_id],
|
|
1053
|
+
memory_assignment=memory_by_id[lane.lane_id],
|
|
1054
|
+
role_slots=roles_by_id[lane.lane_id],
|
|
1055
|
+
eligible_option_ids=eligible_by_id[lane.lane_id],
|
|
1056
|
+
global_receipt_sha256=receipt,
|
|
1057
|
+
audit=audit,
|
|
1058
|
+
)
|
|
1059
|
+
for lane in lanes
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
|
|
1063
|
+
@dataclass(slots=True)
|
|
1064
|
+
class BarrierGlobalWaveActionAllocationCoordinator:
|
|
1065
|
+
"""Cancellation-safe rendezvous for independently forecast parent lanes."""
|
|
1066
|
+
|
|
1067
|
+
policy: GlobalWaveActionAllocationPolicy
|
|
1068
|
+
expected_lane_count: int = 2
|
|
1069
|
+
_lock: asyncio.Lock = field(default_factory=asyncio.Lock, init=False, repr=False)
|
|
1070
|
+
_pending: dict[
|
|
1071
|
+
tuple[int, str],
|
|
1072
|
+
dict[
|
|
1073
|
+
str,
|
|
1074
|
+
tuple[
|
|
1075
|
+
GlobalWaveActionAllocationLane,
|
|
1076
|
+
asyncio.Future[GlobalWaveActionAllocationLaneResult],
|
|
1077
|
+
],
|
|
1078
|
+
],
|
|
1079
|
+
] = field(default_factory=dict, init=False, repr=False)
|
|
1080
|
+
|
|
1081
|
+
def __post_init__(self) -> None:
|
|
1082
|
+
if not isinstance(self.policy, GlobalWaveActionAllocationPolicy):
|
|
1083
|
+
raise TypeError("policy must satisfy GlobalWaveActionAllocationPolicy")
|
|
1084
|
+
if type(self.expected_lane_count) is not int or self.expected_lane_count <= 0:
|
|
1085
|
+
raise ValueError("expected_lane_count must be a positive exact integer")
|
|
1086
|
+
|
|
1087
|
+
async def allocate(
|
|
1088
|
+
self,
|
|
1089
|
+
lane: GlobalWaveActionAllocationLane,
|
|
1090
|
+
) -> GlobalWaveActionAllocationLaneResult:
|
|
1091
|
+
self.__post_init__()
|
|
1092
|
+
if type(lane) is not GlobalWaveActionAllocationLane:
|
|
1093
|
+
raise TypeError("lane must be exact")
|
|
1094
|
+
lane.__post_init__()
|
|
1095
|
+
loop = asyncio.get_running_loop()
|
|
1096
|
+
future: asyncio.Future[GlobalWaveActionAllocationLaneResult] = (
|
|
1097
|
+
loop.create_future()
|
|
1098
|
+
)
|
|
1099
|
+
key = lane.wave_key
|
|
1100
|
+
async with self._lock:
|
|
1101
|
+
bucket = self._pending.setdefault(key, {})
|
|
1102
|
+
if lane.lane_id in bucket:
|
|
1103
|
+
raise ValueError("global allocation received the same lane twice")
|
|
1104
|
+
bucket[lane.lane_id] = (lane, future)
|
|
1105
|
+
if len(bucket) > self.expected_lane_count:
|
|
1106
|
+
bucket.pop(lane.lane_id)
|
|
1107
|
+
raise ValueError("global allocation exceeded expected lane count")
|
|
1108
|
+
if len(bucket) == self.expected_lane_count:
|
|
1109
|
+
entries = tuple(bucket.values())
|
|
1110
|
+
self._pending.pop(key)
|
|
1111
|
+
try:
|
|
1112
|
+
results = self.policy.allocate(tuple(value[0] for value in entries))
|
|
1113
|
+
if set(results) != {value[0].lane_id for value in entries}:
|
|
1114
|
+
raise ValueError("global allocation result differs from cohort")
|
|
1115
|
+
except BaseException as error: # noqa: BLE001 - release all waiters.
|
|
1116
|
+
for _, pending_future in entries:
|
|
1117
|
+
if not pending_future.done():
|
|
1118
|
+
pending_future.set_exception(error)
|
|
1119
|
+
else:
|
|
1120
|
+
for pending_lane, pending_future in entries:
|
|
1121
|
+
pending_future.set_result(results[pending_lane.lane_id])
|
|
1122
|
+
try:
|
|
1123
|
+
return await asyncio.shield(future)
|
|
1124
|
+
except BaseException:
|
|
1125
|
+
async with self._lock:
|
|
1126
|
+
bucket = self._pending.get(key)
|
|
1127
|
+
if (
|
|
1128
|
+
bucket is not None
|
|
1129
|
+
and bucket.get(lane.lane_id, (None, None))[1] is future
|
|
1130
|
+
):
|
|
1131
|
+
bucket.pop(lane.lane_id)
|
|
1132
|
+
if not bucket:
|
|
1133
|
+
self._pending.pop(key)
|
|
1134
|
+
if not future.done():
|
|
1135
|
+
future.cancel()
|
|
1136
|
+
raise
|
|
1137
|
+
|
|
1138
|
+
|
|
1139
|
+
__all__ = [
|
|
1140
|
+
"GLOBAL_WAVE_ACTION_ALLOCATION_POLICY_DEFINITION_SHA256",
|
|
1141
|
+
"GLOBAL_WAVE_ACTION_ALLOCATION_POLICY_ID",
|
|
1142
|
+
"GLOBAL_WAVE_ACTION_ALLOCATION_POLICY_VERSION",
|
|
1143
|
+
"BarrierGlobalWaveActionAllocationCoordinator",
|
|
1144
|
+
"GlobalRoleBalancedWaveActionAllocationPolicy",
|
|
1145
|
+
"GlobalWaveActionAllocationCoordinator",
|
|
1146
|
+
"GlobalWaveActionAllocationLane",
|
|
1147
|
+
"GlobalWaveActionAllocationLaneResult",
|
|
1148
|
+
"GlobalWaveActionAllocationPolicy",
|
|
1149
|
+
"MemoryAssignment",
|
|
1150
|
+
"RoleSlots",
|
|
1151
|
+
]
|