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,1394 @@
|
|
|
1
|
+
"""Opt-in prior-calibrated allocation over an eight-member finite slate.
|
|
2
|
+
|
|
3
|
+
The provider-facing schema is intentionally outside this module. A caller
|
|
4
|
+
supplies sealed prediction receipts, structural evidence, and an immutable
|
|
5
|
+
prior-wave calibration snapshot. The legacy model top-k prefix remains the
|
|
6
|
+
default; calibrated mode assigns exactly four distinct engine-owned roles.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import hashlib
|
|
12
|
+
import json
|
|
13
|
+
import math
|
|
14
|
+
import re
|
|
15
|
+
from dataclasses import dataclass, replace
|
|
16
|
+
from enum import Enum
|
|
17
|
+
from itertools import permutations
|
|
18
|
+
from typing import ClassVar
|
|
19
|
+
|
|
20
|
+
from agent_evolve.domain.patch import require_sha256
|
|
21
|
+
from agent_evolve.policies.selection.forecast_calibration import (
|
|
22
|
+
ForecastCalibrationCell,
|
|
23
|
+
ForecastCalibrationScope,
|
|
24
|
+
ForecastCalibrationSnapshot,
|
|
25
|
+
ForecastConfidenceBin,
|
|
26
|
+
ForecastPredictionReceipt,
|
|
27
|
+
)
|
|
28
|
+
from agent_evolve.ports.agentic_generator import MetricEffectDirection
|
|
29
|
+
from agent_evolve.ports.portfolio_memory_dose import (
|
|
30
|
+
BoundedPortfolioMemoryDoseContract,
|
|
31
|
+
PortfolioMemoryDoseAssessment,
|
|
32
|
+
PortfolioMemoryDoseMember,
|
|
33
|
+
PortfolioMemoryDoseStage,
|
|
34
|
+
assess_evaluated_portfolio_memory_dose,
|
|
35
|
+
assess_proposed_portfolio_memory_dose,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
POLICY_ID = "trace_calibrated_four_role_slate"
|
|
40
|
+
POLICY_VERSION = 1
|
|
41
|
+
POLICY_DEFINITION_SHA256 = hashlib.sha256(
|
|
42
|
+
b"agent-evolve:trace-calibrated-four-role-slate:v1;"
|
|
43
|
+
b"legacy-top-k-default=true;calibrated-mode-opt-in=true;"
|
|
44
|
+
b"calibration-cutoff-exclusive=true;beta-prior=true;"
|
|
45
|
+
b"slate-size=8;portfolio-size=4;"
|
|
46
|
+
b"roles=calibrated-exploit,memory-hypothesis,falsification,coverage;"
|
|
47
|
+
b"benchmark-owned-meaningful-direction=true;"
|
|
48
|
+
b"joint-exact-assignment=true"
|
|
49
|
+
).hexdigest()
|
|
50
|
+
|
|
51
|
+
_STRUCTURAL_DOMAIN = b"agent-evolve:slate-structural-evidence:v1\x00"
|
|
52
|
+
_SLATE_DOMAIN = b"agent-evolve:calibrated-slate:v1\x00"
|
|
53
|
+
_REQUEST_DOMAIN = b"agent-evolve:slate-allocation-request:v1\x00"
|
|
54
|
+
_DECISION_DOMAIN = b"agent-evolve:slate-allocation-decision:v1\x00"
|
|
55
|
+
_TOKEN = re.compile(r"^[a-z][a-z0-9_.-]{0,127}$")
|
|
56
|
+
_METRIC = re.compile(r"^[a-z][a-z0-9_.:-]{0,191}$")
|
|
57
|
+
_OPTION = re.compile(r"^[a-z][a-z0-9_.:-]{0,255}$")
|
|
58
|
+
_MAX_WAVE = (1 << 63) - 1
|
|
59
|
+
_MAX_SLATE_SIZE = 8
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _canonical_json(value: object) -> bytes:
|
|
63
|
+
return json.dumps(
|
|
64
|
+
value,
|
|
65
|
+
allow_nan=False,
|
|
66
|
+
ensure_ascii=True,
|
|
67
|
+
separators=(",", ":"),
|
|
68
|
+
sort_keys=True,
|
|
69
|
+
).encode("ascii", errors="strict")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _hash(domain: bytes, value: object) -> str:
|
|
73
|
+
return hashlib.sha256(domain + _canonical_json(value)).hexdigest()
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _require_token(value: str, *, name: str) -> None:
|
|
77
|
+
if type(value) is not str or _TOKEN.fullmatch(value) is None:
|
|
78
|
+
raise ValueError(f"{name} must use the closed lowercase token grammar")
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _require_metric(value: str, *, name: str = "metric_id") -> None:
|
|
82
|
+
if type(value) is not str or _METRIC.fullmatch(value) is None:
|
|
83
|
+
raise ValueError(f"{name} must use the closed metric identifier grammar")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _require_option(value: str, *, name: str = "option_id") -> None:
|
|
87
|
+
if type(value) is not str or _OPTION.fullmatch(value) is None:
|
|
88
|
+
raise ValueError(f"{name} must use the closed option identifier grammar")
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _require_wave(value: int, *, name: str) -> None:
|
|
92
|
+
if type(value) is not int or not 1 <= value <= _MAX_WAVE:
|
|
93
|
+
raise ValueError(f"{name} must be an exact positive int63")
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _require_finite_float(value: float, *, name: str) -> None:
|
|
97
|
+
if type(value) is not float or not math.isfinite(value):
|
|
98
|
+
raise TypeError(f"{name} must be a finite canonical float")
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _require_unit_interval(value: float, *, name: str) -> None:
|
|
102
|
+
_require_finite_float(value, name=name)
|
|
103
|
+
if not 0.0 <= value <= 1.0:
|
|
104
|
+
raise ValueError(f"{name} must lie in [0, 1]")
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _canonical_tokens(values: tuple[str, ...], *, name: str) -> None:
|
|
108
|
+
if type(values) is not tuple or any(
|
|
109
|
+
type(value) is not str or _TOKEN.fullmatch(value) is None for value in values
|
|
110
|
+
):
|
|
111
|
+
raise TypeError(f"{name} must be an exact tuple of closed tokens")
|
|
112
|
+
if values != tuple(sorted(set(values))):
|
|
113
|
+
raise ValueError(f"{name} must be unique and canonical")
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class SlateRoleProposal(str, Enum):
|
|
117
|
+
"""Untrusted semantic role proposed by the model."""
|
|
118
|
+
|
|
119
|
+
EXPLOIT = "exploit"
|
|
120
|
+
FALSIFY = "falsify"
|
|
121
|
+
COVERAGE = "coverage"
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class MetricOptimizationGoal(str, Enum):
|
|
125
|
+
"""Direction in which an objective improves."""
|
|
126
|
+
|
|
127
|
+
MINIMIZE = "minimize"
|
|
128
|
+
MAXIMIZE = "maximize"
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class SlateAllocationMode(str, Enum):
|
|
132
|
+
"""The legacy path is deliberately the default policy mode."""
|
|
133
|
+
|
|
134
|
+
DIRECT_MODEL_TOP_K = "direct_model_top_k"
|
|
135
|
+
CALIBRATED_FOUR_ROLE = "calibrated_four_role"
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class SlateAllocationRole(str, Enum):
|
|
139
|
+
"""Engine-owned role attached to one evaluated slate member."""
|
|
140
|
+
|
|
141
|
+
DIRECT_MODEL_TOP_K = "direct_model_top_k"
|
|
142
|
+
CALIBRATED_EXPLOIT = "calibrated_exploit"
|
|
143
|
+
MEMORY_HYPOTHESIS = "memory_hypothesis"
|
|
144
|
+
FALSIFICATION_DISAGREEMENT = "falsification_disagreement"
|
|
145
|
+
STRUCTURAL_COVERAGE = "structural_coverage"
|
|
146
|
+
ACQUISITION_CERTIFIED = "acquisition_certified"
|
|
147
|
+
REGRET_BOUNDED_INFORMATION = "regret_bounded_information"
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
_CALIBRATED_ROLES = (
|
|
151
|
+
SlateAllocationRole.CALIBRATED_EXPLOIT,
|
|
152
|
+
SlateAllocationRole.MEMORY_HYPOTHESIS,
|
|
153
|
+
SlateAllocationRole.FALSIFICATION_DISAGREEMENT,
|
|
154
|
+
SlateAllocationRole.STRUCTURAL_COVERAGE,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@dataclass(frozen=True, slots=True)
|
|
159
|
+
class SlateStructuralEvidence:
|
|
160
|
+
"""Benchmark-injected normalized novelty and structural-coverage evidence."""
|
|
161
|
+
|
|
162
|
+
frozen_archive_snapshot_sha256: str
|
|
163
|
+
evidence_receipt_sha256: str
|
|
164
|
+
archive_novelty_score: float
|
|
165
|
+
structural_coverage_score: float
|
|
166
|
+
|
|
167
|
+
def __post_init__(self) -> None:
|
|
168
|
+
require_sha256(
|
|
169
|
+
self.frozen_archive_snapshot_sha256,
|
|
170
|
+
"frozen_archive_snapshot_sha256",
|
|
171
|
+
)
|
|
172
|
+
require_sha256(self.evidence_receipt_sha256, "evidence_receipt_sha256")
|
|
173
|
+
_require_unit_interval(
|
|
174
|
+
self.archive_novelty_score,
|
|
175
|
+
name="archive_novelty_score",
|
|
176
|
+
)
|
|
177
|
+
_require_unit_interval(
|
|
178
|
+
self.structural_coverage_score,
|
|
179
|
+
name="structural_coverage_score",
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
def _unsigned_record(self) -> dict[str, object]:
|
|
183
|
+
self.__post_init__()
|
|
184
|
+
return {
|
|
185
|
+
"schema_version": 1,
|
|
186
|
+
"frozen_archive_snapshot_sha256": (self.frozen_archive_snapshot_sha256),
|
|
187
|
+
"evidence_receipt_sha256": self.evidence_receipt_sha256,
|
|
188
|
+
"archive_novelty_score_hex": self.archive_novelty_score.hex(),
|
|
189
|
+
"structural_coverage_score_hex": (self.structural_coverage_score.hex()),
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
@property
|
|
193
|
+
def evidence_sha256(self) -> str:
|
|
194
|
+
return _hash(_STRUCTURAL_DOMAIN, self._unsigned_record())
|
|
195
|
+
|
|
196
|
+
def to_record(self) -> dict[str, object]:
|
|
197
|
+
return {**self._unsigned_record(), "evidence_sha256": self.evidence_sha256}
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
@dataclass(frozen=True, slots=True)
|
|
201
|
+
class CalibratedSlateMember:
|
|
202
|
+
"""One sealed finite option in the model-proposed K-slate."""
|
|
203
|
+
|
|
204
|
+
model_rank: int
|
|
205
|
+
option_id: str
|
|
206
|
+
option_identity_sha256: str
|
|
207
|
+
family: str
|
|
208
|
+
locus_key: str
|
|
209
|
+
phenotype_identity_sha256: str
|
|
210
|
+
supporting_card_keys: tuple[str, ...]
|
|
211
|
+
role_proposal: SlateRoleProposal
|
|
212
|
+
rationale_sha256: str
|
|
213
|
+
predictions: tuple[ForecastPredictionReceipt, ...]
|
|
214
|
+
structural_evidence: SlateStructuralEvidence
|
|
215
|
+
|
|
216
|
+
def __post_init__(self) -> None:
|
|
217
|
+
if type(self.model_rank) is not int or self.model_rank <= 0:
|
|
218
|
+
raise ValueError("model_rank must be a positive exact integer")
|
|
219
|
+
_require_option(self.option_id)
|
|
220
|
+
for name in (
|
|
221
|
+
"option_identity_sha256",
|
|
222
|
+
"phenotype_identity_sha256",
|
|
223
|
+
"rationale_sha256",
|
|
224
|
+
):
|
|
225
|
+
require_sha256(getattr(self, name), name)
|
|
226
|
+
_require_token(self.family, name="family")
|
|
227
|
+
_require_token(self.locus_key, name="locus_key")
|
|
228
|
+
_canonical_tokens(self.supporting_card_keys, name="supporting_card_keys")
|
|
229
|
+
if type(self.role_proposal) is not SlateRoleProposal:
|
|
230
|
+
raise TypeError("role_proposal must be exact SlateRoleProposal")
|
|
231
|
+
if (
|
|
232
|
+
type(self.predictions) is not tuple
|
|
233
|
+
or not self.predictions
|
|
234
|
+
or any(
|
|
235
|
+
type(value) is not ForecastPredictionReceipt
|
|
236
|
+
for value in self.predictions
|
|
237
|
+
)
|
|
238
|
+
):
|
|
239
|
+
raise ValueError("predictions must contain exact prediction receipts")
|
|
240
|
+
for value in self.predictions:
|
|
241
|
+
value.revalidate()
|
|
242
|
+
if tuple(value.metric_id for value in self.predictions) != tuple(
|
|
243
|
+
sorted({value.metric_id for value in self.predictions})
|
|
244
|
+
):
|
|
245
|
+
raise ValueError("predictions must have unique canonical metric order")
|
|
246
|
+
for value in self.predictions:
|
|
247
|
+
if (
|
|
248
|
+
value.option_id != self.option_id
|
|
249
|
+
or value.option_identity_sha256 != self.option_identity_sha256
|
|
250
|
+
or value.family != self.family
|
|
251
|
+
):
|
|
252
|
+
raise ValueError("prediction receipt belongs to a foreign slate member")
|
|
253
|
+
if type(self.structural_evidence) is not SlateStructuralEvidence:
|
|
254
|
+
raise TypeError("structural_evidence must be exact SlateStructuralEvidence")
|
|
255
|
+
self.structural_evidence.__post_init__()
|
|
256
|
+
|
|
257
|
+
def revalidate(self) -> None:
|
|
258
|
+
if type(self) is not CalibratedSlateMember:
|
|
259
|
+
raise TypeError("member must be exact CalibratedSlateMember")
|
|
260
|
+
CalibratedSlateMember.__post_init__(self)
|
|
261
|
+
|
|
262
|
+
def to_record(self) -> dict[str, object]:
|
|
263
|
+
self.revalidate()
|
|
264
|
+
return {
|
|
265
|
+
"model_rank": self.model_rank,
|
|
266
|
+
"option_id": self.option_id,
|
|
267
|
+
"option_identity_sha256": self.option_identity_sha256,
|
|
268
|
+
"family": self.family,
|
|
269
|
+
"locus_key": self.locus_key,
|
|
270
|
+
"phenotype_identity_sha256": self.phenotype_identity_sha256,
|
|
271
|
+
"supporting_card_keys": list(self.supporting_card_keys),
|
|
272
|
+
"role_proposal": self.role_proposal.value,
|
|
273
|
+
"rationale_sha256": self.rationale_sha256,
|
|
274
|
+
"predictions": [value.to_record() for value in self.predictions],
|
|
275
|
+
"structural_evidence": self.structural_evidence.to_record(),
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
@dataclass(frozen=True, slots=True, eq=False)
|
|
280
|
+
class CalibratedSlate:
|
|
281
|
+
"""Exact K-member proposal bound to one parent and selector decision."""
|
|
282
|
+
|
|
283
|
+
scope: ForecastCalibrationScope
|
|
284
|
+
wave_index: int
|
|
285
|
+
selector_decision_sha256: str
|
|
286
|
+
parent_candidate_identity_sha256: str
|
|
287
|
+
finite_contract_sha256: str
|
|
288
|
+
members: tuple[CalibratedSlateMember, ...]
|
|
289
|
+
|
|
290
|
+
def __post_init__(self) -> None:
|
|
291
|
+
if type(self.scope) is not ForecastCalibrationScope:
|
|
292
|
+
raise TypeError("scope must be exact ForecastCalibrationScope")
|
|
293
|
+
self.scope.revalidate()
|
|
294
|
+
_require_wave(self.wave_index, name="wave_index")
|
|
295
|
+
for name in (
|
|
296
|
+
"selector_decision_sha256",
|
|
297
|
+
"parent_candidate_identity_sha256",
|
|
298
|
+
"finite_contract_sha256",
|
|
299
|
+
):
|
|
300
|
+
require_sha256(getattr(self, name), name)
|
|
301
|
+
if (
|
|
302
|
+
type(self.members) is not tuple
|
|
303
|
+
or not 1 <= len(self.members) <= _MAX_SLATE_SIZE
|
|
304
|
+
or any(type(value) is not CalibratedSlateMember for value in self.members)
|
|
305
|
+
):
|
|
306
|
+
raise ValueError("members must be a bounded exact slate tuple")
|
|
307
|
+
for value in self.members:
|
|
308
|
+
value.revalidate()
|
|
309
|
+
if tuple(value.model_rank for value in self.members) != tuple(
|
|
310
|
+
range(1, len(self.members) + 1)
|
|
311
|
+
):
|
|
312
|
+
raise ValueError("slate members must preserve contiguous model rank")
|
|
313
|
+
if len({value.option_id for value in self.members}) != len(self.members):
|
|
314
|
+
raise ValueError("slate option IDs must be distinct")
|
|
315
|
+
if len({value.option_identity_sha256 for value in self.members}) != len(
|
|
316
|
+
self.members
|
|
317
|
+
):
|
|
318
|
+
raise ValueError("slate option identities must be distinct")
|
|
319
|
+
archive_snapshots = {
|
|
320
|
+
value.structural_evidence.frozen_archive_snapshot_sha256
|
|
321
|
+
for value in self.members
|
|
322
|
+
}
|
|
323
|
+
if len(archive_snapshots) != 1:
|
|
324
|
+
raise ValueError("structural evidence must share one frozen archive")
|
|
325
|
+
for member in self.members:
|
|
326
|
+
for prediction in member.predictions:
|
|
327
|
+
if (
|
|
328
|
+
prediction.scope != self.scope
|
|
329
|
+
or prediction.wave_index != self.wave_index
|
|
330
|
+
or prediction.selector_decision_sha256
|
|
331
|
+
!= self.selector_decision_sha256
|
|
332
|
+
or prediction.parent_candidate_identity_sha256
|
|
333
|
+
!= self.parent_candidate_identity_sha256
|
|
334
|
+
):
|
|
335
|
+
raise ValueError("slate contains a foreign prediction receipt")
|
|
336
|
+
|
|
337
|
+
def revalidate(self) -> None:
|
|
338
|
+
if type(self) is not CalibratedSlate:
|
|
339
|
+
raise TypeError("slate must be exact CalibratedSlate")
|
|
340
|
+
CalibratedSlate.__post_init__(self)
|
|
341
|
+
|
|
342
|
+
def _unsigned_record(self) -> dict[str, object]:
|
|
343
|
+
self.revalidate()
|
|
344
|
+
return {
|
|
345
|
+
"schema_version": 1,
|
|
346
|
+
"scope_sha256": self.scope.scope_sha256,
|
|
347
|
+
"wave_index": self.wave_index,
|
|
348
|
+
"selector_decision_sha256": self.selector_decision_sha256,
|
|
349
|
+
"parent_candidate_identity_sha256": (self.parent_candidate_identity_sha256),
|
|
350
|
+
"finite_contract_sha256": self.finite_contract_sha256,
|
|
351
|
+
"members": [value.to_record() for value in self.members],
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
@property
|
|
355
|
+
def slate_sha256(self) -> str:
|
|
356
|
+
return _hash(_SLATE_DOMAIN, self._unsigned_record())
|
|
357
|
+
|
|
358
|
+
def to_record(self) -> dict[str, object]:
|
|
359
|
+
return {**self._unsigned_record(), "slate_sha256": self.slate_sha256}
|
|
360
|
+
|
|
361
|
+
def __eq__(self, other: object) -> bool:
|
|
362
|
+
return (
|
|
363
|
+
type(other) is CalibratedSlate and self.slate_sha256 == other.slate_sha256
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
__hash__ = None
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def _memory_dose_members(
|
|
370
|
+
members: tuple[CalibratedSlateMember, ...],
|
|
371
|
+
) -> tuple[PortfolioMemoryDoseMember, ...]:
|
|
372
|
+
return tuple(
|
|
373
|
+
PortfolioMemoryDoseMember(
|
|
374
|
+
rank=rank,
|
|
375
|
+
option_id=value.option_id,
|
|
376
|
+
option_identity_sha256=value.option_identity_sha256,
|
|
377
|
+
supporting_card_keys=value.supporting_card_keys,
|
|
378
|
+
)
|
|
379
|
+
for rank, value in enumerate(members, start=1)
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
@dataclass(frozen=True, slots=True)
|
|
384
|
+
class SlateMetricObjective:
|
|
385
|
+
"""Benchmark-injected interpretation of one required prediction metric."""
|
|
386
|
+
|
|
387
|
+
metric_id: str
|
|
388
|
+
goal: MetricOptimizationGoal
|
|
389
|
+
weight: float
|
|
390
|
+
definition_sha256: str
|
|
391
|
+
|
|
392
|
+
def __post_init__(self) -> None:
|
|
393
|
+
_require_metric(self.metric_id)
|
|
394
|
+
if type(self.goal) is not MetricOptimizationGoal:
|
|
395
|
+
raise TypeError("goal must be exact MetricOptimizationGoal")
|
|
396
|
+
_require_finite_float(self.weight, name="weight")
|
|
397
|
+
if self.weight <= 0.0:
|
|
398
|
+
raise ValueError("weight must be strictly positive")
|
|
399
|
+
require_sha256(self.definition_sha256, "definition_sha256")
|
|
400
|
+
|
|
401
|
+
def to_record(self) -> dict[str, object]:
|
|
402
|
+
self.__post_init__()
|
|
403
|
+
return {
|
|
404
|
+
"metric_id": self.metric_id,
|
|
405
|
+
"goal": self.goal.value,
|
|
406
|
+
"weight_hex": self.weight.hex(),
|
|
407
|
+
"definition_sha256": self.definition_sha256,
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
@dataclass(frozen=True, slots=True, eq=False)
|
|
412
|
+
class SlateAllocationRequest:
|
|
413
|
+
"""Complete provider-free inputs for legacy or calibrated allocation."""
|
|
414
|
+
|
|
415
|
+
slate: CalibratedSlate
|
|
416
|
+
portfolio_size: int
|
|
417
|
+
objectives: tuple[SlateMetricObjective, ...]
|
|
418
|
+
assigned_card_keys: tuple[str, ...]
|
|
419
|
+
calibration_snapshot: ForecastCalibrationSnapshot | None = None
|
|
420
|
+
pairwise_disjoint_option_id_pairs: tuple[tuple[str, str], ...] | None = None
|
|
421
|
+
min_distinct_families: int | None = None
|
|
422
|
+
memory_dose_contract: BoundedPortfolioMemoryDoseContract | None = None
|
|
423
|
+
proposal_memory_dose_assessment: PortfolioMemoryDoseAssessment | None = None
|
|
424
|
+
required_option_ids: tuple[str, ...] = ()
|
|
425
|
+
|
|
426
|
+
def __post_init__(self) -> None:
|
|
427
|
+
if type(self.slate) is not CalibratedSlate:
|
|
428
|
+
raise TypeError("slate must be exact CalibratedSlate")
|
|
429
|
+
self.slate.revalidate()
|
|
430
|
+
if type(self.portfolio_size) is not int or not 1 <= self.portfolio_size <= len(
|
|
431
|
+
self.slate.members
|
|
432
|
+
):
|
|
433
|
+
raise ValueError("portfolio_size must lie within the finite slate")
|
|
434
|
+
if (
|
|
435
|
+
type(self.objectives) is not tuple
|
|
436
|
+
or not self.objectives
|
|
437
|
+
or any(type(value) is not SlateMetricObjective for value in self.objectives)
|
|
438
|
+
):
|
|
439
|
+
raise ValueError("objectives must contain exact metric objectives")
|
|
440
|
+
for value in self.objectives:
|
|
441
|
+
value.__post_init__()
|
|
442
|
+
objective_ids = tuple(value.metric_id for value in self.objectives)
|
|
443
|
+
if objective_ids != tuple(sorted(set(objective_ids))):
|
|
444
|
+
raise ValueError("objectives must have unique canonical metric order")
|
|
445
|
+
if any(
|
|
446
|
+
tuple(value.metric_id for value in member.predictions) != objective_ids
|
|
447
|
+
for member in self.slate.members
|
|
448
|
+
):
|
|
449
|
+
raise ValueError("every slate member must predict every objective once")
|
|
450
|
+
_canonical_tokens(self.assigned_card_keys, name="assigned_card_keys")
|
|
451
|
+
if self.calibration_snapshot is not None:
|
|
452
|
+
if type(self.calibration_snapshot) is not ForecastCalibrationSnapshot:
|
|
453
|
+
raise TypeError(
|
|
454
|
+
"calibration_snapshot must be exact ForecastCalibrationSnapshot"
|
|
455
|
+
)
|
|
456
|
+
self.calibration_snapshot.revalidate()
|
|
457
|
+
if self.calibration_snapshot.scope != self.slate.scope:
|
|
458
|
+
raise ValueError("calibration snapshot has a foreign scope")
|
|
459
|
+
pairs = self.pairwise_disjoint_option_id_pairs
|
|
460
|
+
if pairs is not None:
|
|
461
|
+
if type(pairs) is not tuple or any(
|
|
462
|
+
type(pair) is not tuple
|
|
463
|
+
or len(pair) != 2
|
|
464
|
+
or any(type(value) is not str for value in pair)
|
|
465
|
+
for pair in pairs
|
|
466
|
+
):
|
|
467
|
+
raise TypeError(
|
|
468
|
+
"pairwise_disjoint_option_id_pairs must be exact pairs or None"
|
|
469
|
+
)
|
|
470
|
+
option_ids = {value.option_id for value in self.slate.members}
|
|
471
|
+
for left, right in pairs:
|
|
472
|
+
_require_option(left, name="pairwise_disjoint_option_id_pairs.left")
|
|
473
|
+
_require_option(right, name="pairwise_disjoint_option_id_pairs.right")
|
|
474
|
+
if left >= right:
|
|
475
|
+
raise ValueError("disjoint option pairs must be canonical")
|
|
476
|
+
if left not in option_ids or right not in option_ids:
|
|
477
|
+
raise ValueError("disjoint option pair escapes the slate")
|
|
478
|
+
if pairs != tuple(sorted(set(pairs))):
|
|
479
|
+
raise ValueError("disjoint option pairs must be unique and canonical")
|
|
480
|
+
if self.min_distinct_families is not None:
|
|
481
|
+
if (
|
|
482
|
+
type(self.min_distinct_families) is not int
|
|
483
|
+
or not 1 <= self.min_distinct_families <= self.portfolio_size
|
|
484
|
+
):
|
|
485
|
+
raise ValueError(
|
|
486
|
+
"min_distinct_families must lie within the portfolio size"
|
|
487
|
+
)
|
|
488
|
+
if self.min_distinct_families > len(
|
|
489
|
+
{value.family for value in self.slate.members}
|
|
490
|
+
):
|
|
491
|
+
raise ValueError("slate cannot satisfy min_distinct_families")
|
|
492
|
+
if type(self.required_option_ids) is not tuple or any(
|
|
493
|
+
type(value) is not str for value in self.required_option_ids
|
|
494
|
+
):
|
|
495
|
+
raise TypeError("required_option_ids must be an exact string tuple")
|
|
496
|
+
if self.required_option_ids != tuple(
|
|
497
|
+
sorted(set(self.required_option_ids))
|
|
498
|
+
):
|
|
499
|
+
raise ValueError("required_option_ids must be unique and canonical")
|
|
500
|
+
slate_option_ids = {value.option_id for value in self.slate.members}
|
|
501
|
+
if not set(self.required_option_ids).issubset(slate_option_ids):
|
|
502
|
+
raise ValueError("required_option_ids escape the sealed slate")
|
|
503
|
+
if len(self.required_option_ids) > self.portfolio_size:
|
|
504
|
+
raise ValueError("required_option_ids exceed the portfolio size")
|
|
505
|
+
if (self.memory_dose_contract is None) != (
|
|
506
|
+
self.proposal_memory_dose_assessment is None
|
|
507
|
+
):
|
|
508
|
+
raise ValueError(
|
|
509
|
+
"memory-dose contract and proposal assessment must be supplied together"
|
|
510
|
+
)
|
|
511
|
+
if self.memory_dose_contract is not None:
|
|
512
|
+
if type(self.memory_dose_contract) is not (
|
|
513
|
+
BoundedPortfolioMemoryDoseContract
|
|
514
|
+
):
|
|
515
|
+
raise TypeError("memory_dose_contract must be exact or None")
|
|
516
|
+
if type(self.proposal_memory_dose_assessment) is not (
|
|
517
|
+
PortfolioMemoryDoseAssessment
|
|
518
|
+
):
|
|
519
|
+
raise TypeError(
|
|
520
|
+
"proposal_memory_dose_assessment must be exact or None"
|
|
521
|
+
)
|
|
522
|
+
self.memory_dose_contract.__post_init__()
|
|
523
|
+
self.proposal_memory_dose_assessment.__post_init__()
|
|
524
|
+
if (
|
|
525
|
+
self.memory_dose_contract.finite_contract_identity_sha256
|
|
526
|
+
!= self.slate.finite_contract_sha256
|
|
527
|
+
):
|
|
528
|
+
raise ValueError("memory dose names a foreign finite contract")
|
|
529
|
+
if (
|
|
530
|
+
self.memory_dose_contract.assigned_card_keys
|
|
531
|
+
!= self.assigned_card_keys
|
|
532
|
+
):
|
|
533
|
+
raise ValueError("memory-dose cards differ from assigned cards")
|
|
534
|
+
expected_proposal = assess_proposed_portfolio_memory_dose(
|
|
535
|
+
self.memory_dose_contract,
|
|
536
|
+
_memory_dose_members(self.slate.members),
|
|
537
|
+
)
|
|
538
|
+
if (
|
|
539
|
+
not expected_proposal.passed
|
|
540
|
+
or self.proposal_memory_dose_assessment != expected_proposal
|
|
541
|
+
):
|
|
542
|
+
raise ValueError(
|
|
543
|
+
"proposal memory-dose assessment differs from the sealed slate"
|
|
544
|
+
)
|
|
545
|
+
|
|
546
|
+
def revalidate(self) -> None:
|
|
547
|
+
if type(self) is not SlateAllocationRequest:
|
|
548
|
+
raise TypeError("request must be exact SlateAllocationRequest")
|
|
549
|
+
SlateAllocationRequest.__post_init__(self)
|
|
550
|
+
|
|
551
|
+
def _unsigned_record(self) -> dict[str, object]:
|
|
552
|
+
self.revalidate()
|
|
553
|
+
return {
|
|
554
|
+
"schema_version": 1,
|
|
555
|
+
"slate": self.slate.to_record(),
|
|
556
|
+
"portfolio_size": self.portfolio_size,
|
|
557
|
+
"objectives": [value.to_record() for value in self.objectives],
|
|
558
|
+
"assigned_card_keys": list(self.assigned_card_keys),
|
|
559
|
+
"calibration_snapshot": (
|
|
560
|
+
None
|
|
561
|
+
if self.calibration_snapshot is None
|
|
562
|
+
else self.calibration_snapshot.to_record()
|
|
563
|
+
),
|
|
564
|
+
**(
|
|
565
|
+
{}
|
|
566
|
+
if self.pairwise_disjoint_option_id_pairs is None
|
|
567
|
+
else {
|
|
568
|
+
"pairwise_disjoint_option_id_pairs": [
|
|
569
|
+
list(value)
|
|
570
|
+
for value in self.pairwise_disjoint_option_id_pairs
|
|
571
|
+
]
|
|
572
|
+
}
|
|
573
|
+
),
|
|
574
|
+
**(
|
|
575
|
+
{}
|
|
576
|
+
if self.min_distinct_families is None
|
|
577
|
+
else {"min_distinct_families": self.min_distinct_families}
|
|
578
|
+
),
|
|
579
|
+
**(
|
|
580
|
+
{}
|
|
581
|
+
if self.memory_dose_contract is None
|
|
582
|
+
else {
|
|
583
|
+
"memory_dose_contract": self.memory_dose_contract.to_record(),
|
|
584
|
+
"proposal_memory_dose_assessment": (
|
|
585
|
+
self.proposal_memory_dose_assessment.to_record()
|
|
586
|
+
),
|
|
587
|
+
}
|
|
588
|
+
),
|
|
589
|
+
**(
|
|
590
|
+
{}
|
|
591
|
+
if not self.required_option_ids
|
|
592
|
+
else {"required_option_ids": list(self.required_option_ids)}
|
|
593
|
+
),
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
@property
|
|
597
|
+
def request_sha256(self) -> str:
|
|
598
|
+
return _hash(_REQUEST_DOMAIN, self._unsigned_record())
|
|
599
|
+
|
|
600
|
+
def to_record(self) -> dict[str, object]:
|
|
601
|
+
return {**self._unsigned_record(), "request_sha256": self.request_sha256}
|
|
602
|
+
|
|
603
|
+
def __eq__(self, other: object) -> bool:
|
|
604
|
+
return (
|
|
605
|
+
type(other) is SlateAllocationRequest
|
|
606
|
+
and self.request_sha256 == other.request_sha256
|
|
607
|
+
)
|
|
608
|
+
|
|
609
|
+
__hash__ = None
|
|
610
|
+
|
|
611
|
+
|
|
612
|
+
def assess_allocated_slate_memory_dose(
|
|
613
|
+
request: SlateAllocationRequest,
|
|
614
|
+
members: tuple[CalibratedSlateMember, ...],
|
|
615
|
+
) -> PortfolioMemoryDoseAssessment | None:
|
|
616
|
+
"""Assess one prospective K4 subset against the sealed K8 dose receipt."""
|
|
617
|
+
|
|
618
|
+
if type(request) is not SlateAllocationRequest:
|
|
619
|
+
raise TypeError("request must be exact SlateAllocationRequest")
|
|
620
|
+
contract = request.memory_dose_contract
|
|
621
|
+
proposal_assessment = request.proposal_memory_dose_assessment
|
|
622
|
+
if (contract is None) != (proposal_assessment is None):
|
|
623
|
+
raise ValueError(
|
|
624
|
+
"memory-dose contract and proposal assessment must be supplied together"
|
|
625
|
+
)
|
|
626
|
+
if contract is None:
|
|
627
|
+
return None
|
|
628
|
+
if (
|
|
629
|
+
type(members) is not tuple
|
|
630
|
+
or len(members) != request.portfolio_size
|
|
631
|
+
or any(type(value) is not CalibratedSlateMember for value in members)
|
|
632
|
+
):
|
|
633
|
+
raise ValueError("members must be the exact allocated slate subset")
|
|
634
|
+
slate_by_option = {value.option_id: value for value in request.slate.members}
|
|
635
|
+
if any(
|
|
636
|
+
value.option_id not in slate_by_option
|
|
637
|
+
or slate_by_option[value.option_id] != value
|
|
638
|
+
for value in members
|
|
639
|
+
):
|
|
640
|
+
raise ValueError("allocated memory-dose member escapes the sealed slate")
|
|
641
|
+
if type(contract) is not BoundedPortfolioMemoryDoseContract:
|
|
642
|
+
raise TypeError("memory_dose_contract must be exact or None")
|
|
643
|
+
if type(proposal_assessment) is not PortfolioMemoryDoseAssessment:
|
|
644
|
+
raise TypeError("proposal_memory_dose_assessment must be exact or None")
|
|
645
|
+
contract.__post_init__()
|
|
646
|
+
proposal_assessment.__post_init__()
|
|
647
|
+
if (
|
|
648
|
+
contract.finite_contract_identity_sha256
|
|
649
|
+
!= request.slate.finite_contract_sha256
|
|
650
|
+
):
|
|
651
|
+
raise ValueError("memory dose names a foreign finite contract")
|
|
652
|
+
if contract.assigned_card_keys != request.assigned_card_keys:
|
|
653
|
+
raise ValueError("memory-dose cards differ from assigned cards")
|
|
654
|
+
if (
|
|
655
|
+
proposal_assessment.stage is not PortfolioMemoryDoseStage.PROPOSED_SLATE
|
|
656
|
+
or proposal_assessment.contract_sha256 != contract.contract_sha256
|
|
657
|
+
or not proposal_assessment.passed
|
|
658
|
+
or proposal_assessment.member_content_binding_sha256s
|
|
659
|
+
!= tuple(
|
|
660
|
+
value.content_binding_sha256
|
|
661
|
+
for value in _memory_dose_members(request.slate.members)
|
|
662
|
+
)
|
|
663
|
+
):
|
|
664
|
+
raise ValueError(
|
|
665
|
+
"proposal memory-dose assessment differs from the sealed slate"
|
|
666
|
+
)
|
|
667
|
+
return assess_evaluated_portfolio_memory_dose(
|
|
668
|
+
contract,
|
|
669
|
+
_memory_dose_members(members),
|
|
670
|
+
proposal_assessment=proposal_assessment,
|
|
671
|
+
)
|
|
672
|
+
|
|
673
|
+
|
|
674
|
+
@dataclass(frozen=True, slots=True)
|
|
675
|
+
class MetricCalibrationAllocationScore:
|
|
676
|
+
"""Trace row explaining one metric's contribution to engine scores."""
|
|
677
|
+
|
|
678
|
+
metric_id: str
|
|
679
|
+
goal: MetricOptimizationGoal
|
|
680
|
+
asserted_direction: MetricEffectDirection
|
|
681
|
+
confidence: ForecastConfidenceBin
|
|
682
|
+
weight: float
|
|
683
|
+
calibration_cell: ForecastCalibrationCell
|
|
684
|
+
calibration_source: str
|
|
685
|
+
favorable_assertion: bool
|
|
686
|
+
adverse_assertion: bool
|
|
687
|
+
signed_exploitation_score: float
|
|
688
|
+
falsification_score: float
|
|
689
|
+
|
|
690
|
+
def __post_init__(self) -> None:
|
|
691
|
+
_require_metric(self.metric_id)
|
|
692
|
+
if type(self.goal) is not MetricOptimizationGoal:
|
|
693
|
+
raise TypeError("goal must be exact MetricOptimizationGoal")
|
|
694
|
+
if type(self.asserted_direction) is not MetricEffectDirection:
|
|
695
|
+
raise TypeError("asserted_direction must be exact MetricEffectDirection")
|
|
696
|
+
if type(self.confidence) is not ForecastConfidenceBin:
|
|
697
|
+
raise TypeError("confidence must be exact ForecastConfidenceBin")
|
|
698
|
+
_require_finite_float(self.weight, name="weight")
|
|
699
|
+
if self.weight <= 0.0:
|
|
700
|
+
raise ValueError("weight must be strictly positive")
|
|
701
|
+
if type(self.calibration_cell) is not ForecastCalibrationCell:
|
|
702
|
+
raise TypeError("calibration_cell must be exact ForecastCalibrationCell")
|
|
703
|
+
self.calibration_cell.__post_init__()
|
|
704
|
+
if self.calibration_source not in {
|
|
705
|
+
"supported_family",
|
|
706
|
+
"metric_direction_confidence",
|
|
707
|
+
"declared_prior",
|
|
708
|
+
}:
|
|
709
|
+
raise ValueError("unsupported calibration_source")
|
|
710
|
+
if type(self.favorable_assertion) is not bool:
|
|
711
|
+
raise TypeError("favorable_assertion must be exact bool")
|
|
712
|
+
if type(self.adverse_assertion) is not bool:
|
|
713
|
+
raise TypeError("adverse_assertion must be exact bool")
|
|
714
|
+
if self.favorable_assertion and self.adverse_assertion:
|
|
715
|
+
raise ValueError("one assertion cannot be both favorable and adverse")
|
|
716
|
+
_require_finite_float(
|
|
717
|
+
self.signed_exploitation_score,
|
|
718
|
+
name="signed_exploitation_score",
|
|
719
|
+
)
|
|
720
|
+
_require_finite_float(self.falsification_score, name="falsification_score")
|
|
721
|
+
|
|
722
|
+
def to_record(self) -> dict[str, object]:
|
|
723
|
+
self.__post_init__()
|
|
724
|
+
return {
|
|
725
|
+
"metric_id": self.metric_id,
|
|
726
|
+
"goal": self.goal.value,
|
|
727
|
+
"asserted_direction": self.asserted_direction.value,
|
|
728
|
+
"confidence": self.confidence.value,
|
|
729
|
+
"weight_hex": self.weight.hex(),
|
|
730
|
+
"calibration_cell": self.calibration_cell.to_record(),
|
|
731
|
+
"calibration_source": self.calibration_source,
|
|
732
|
+
"favorable_assertion": self.favorable_assertion,
|
|
733
|
+
"adverse_assertion": self.adverse_assertion,
|
|
734
|
+
"signed_exploitation_score_hex": (self.signed_exploitation_score.hex()),
|
|
735
|
+
"falsification_score_hex": self.falsification_score.hex(),
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
|
|
739
|
+
@dataclass(frozen=True, slots=True)
|
|
740
|
+
class SlateMemberScoreRow:
|
|
741
|
+
"""All prior-only role scores for one member of the current slate."""
|
|
742
|
+
|
|
743
|
+
option_id: str
|
|
744
|
+
option_identity_sha256: str
|
|
745
|
+
model_rank: int
|
|
746
|
+
metric_scores: tuple[MetricCalibrationAllocationScore, ...]
|
|
747
|
+
calibrated_exploitation_score: float
|
|
748
|
+
memory_hypothesis_score: float
|
|
749
|
+
falsification_disagreement_score: float
|
|
750
|
+
structural_coverage_score: float
|
|
751
|
+
supported_assigned_card_keys: tuple[str, ...]
|
|
752
|
+
|
|
753
|
+
def __post_init__(self) -> None:
|
|
754
|
+
_require_option(self.option_id)
|
|
755
|
+
require_sha256(self.option_identity_sha256, "option_identity_sha256")
|
|
756
|
+
if type(self.model_rank) is not int or self.model_rank <= 0:
|
|
757
|
+
raise ValueError("model_rank must be positive")
|
|
758
|
+
if (
|
|
759
|
+
type(self.metric_scores) is not tuple
|
|
760
|
+
or not self.metric_scores
|
|
761
|
+
or any(
|
|
762
|
+
type(value) is not MetricCalibrationAllocationScore
|
|
763
|
+
for value in self.metric_scores
|
|
764
|
+
)
|
|
765
|
+
):
|
|
766
|
+
raise ValueError("metric_scores must contain exact metric score rows")
|
|
767
|
+
for value in self.metric_scores:
|
|
768
|
+
value.__post_init__()
|
|
769
|
+
if tuple(value.metric_id for value in self.metric_scores) != tuple(
|
|
770
|
+
sorted({value.metric_id for value in self.metric_scores})
|
|
771
|
+
):
|
|
772
|
+
raise ValueError("metric_scores must use unique canonical metric order")
|
|
773
|
+
for name in (
|
|
774
|
+
"calibrated_exploitation_score",
|
|
775
|
+
"memory_hypothesis_score",
|
|
776
|
+
"falsification_disagreement_score",
|
|
777
|
+
"structural_coverage_score",
|
|
778
|
+
):
|
|
779
|
+
_require_finite_float(getattr(self, name), name=name)
|
|
780
|
+
_canonical_tokens(
|
|
781
|
+
self.supported_assigned_card_keys,
|
|
782
|
+
name="supported_assigned_card_keys",
|
|
783
|
+
)
|
|
784
|
+
|
|
785
|
+
def score_for(self, role: SlateAllocationRole) -> float:
|
|
786
|
+
self.__post_init__()
|
|
787
|
+
if role is SlateAllocationRole.CALIBRATED_EXPLOIT:
|
|
788
|
+
return self.calibrated_exploitation_score
|
|
789
|
+
if role is SlateAllocationRole.MEMORY_HYPOTHESIS:
|
|
790
|
+
return self.memory_hypothesis_score
|
|
791
|
+
if role is SlateAllocationRole.FALSIFICATION_DISAGREEMENT:
|
|
792
|
+
return self.falsification_disagreement_score
|
|
793
|
+
if role is SlateAllocationRole.STRUCTURAL_COVERAGE:
|
|
794
|
+
return self.structural_coverage_score
|
|
795
|
+
raise ValueError("direct top-k has no calibrated role score")
|
|
796
|
+
|
|
797
|
+
def to_record(self) -> dict[str, object]:
|
|
798
|
+
self.__post_init__()
|
|
799
|
+
return {
|
|
800
|
+
"option_id": self.option_id,
|
|
801
|
+
"option_identity_sha256": self.option_identity_sha256,
|
|
802
|
+
"model_rank": self.model_rank,
|
|
803
|
+
"metric_scores": [value.to_record() for value in self.metric_scores],
|
|
804
|
+
"calibrated_exploitation_score_hex": (
|
|
805
|
+
self.calibrated_exploitation_score.hex()
|
|
806
|
+
),
|
|
807
|
+
"memory_hypothesis_score_hex": self.memory_hypothesis_score.hex(),
|
|
808
|
+
"falsification_disagreement_score_hex": (
|
|
809
|
+
self.falsification_disagreement_score.hex()
|
|
810
|
+
),
|
|
811
|
+
"structural_coverage_score_hex": self.structural_coverage_score.hex(),
|
|
812
|
+
"supported_assigned_card_keys": list(self.supported_assigned_card_keys),
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
|
|
816
|
+
def _favorable(
|
|
817
|
+
direction: MetricEffectDirection,
|
|
818
|
+
goal: MetricOptimizationGoal,
|
|
819
|
+
) -> tuple[bool, bool]:
|
|
820
|
+
if direction in {MetricEffectDirection.UNKNOWN, MetricEffectDirection.UNCHANGED}:
|
|
821
|
+
return False, False
|
|
822
|
+
favorable = (
|
|
823
|
+
goal is MetricOptimizationGoal.MINIMIZE
|
|
824
|
+
and direction is MetricEffectDirection.DECREASE
|
|
825
|
+
) or (
|
|
826
|
+
goal is MetricOptimizationGoal.MAXIMIZE
|
|
827
|
+
and direction is MetricEffectDirection.INCREASE
|
|
828
|
+
)
|
|
829
|
+
return favorable, not favorable
|
|
830
|
+
|
|
831
|
+
|
|
832
|
+
def _score_member(
|
|
833
|
+
request: SlateAllocationRequest,
|
|
834
|
+
member: CalibratedSlateMember,
|
|
835
|
+
) -> SlateMemberScoreRow:
|
|
836
|
+
snapshot = request.calibration_snapshot
|
|
837
|
+
if snapshot is None:
|
|
838
|
+
raise ValueError("calibrated allocation requires a calibration snapshot")
|
|
839
|
+
objective_index = {value.metric_id: value for value in request.objectives}
|
|
840
|
+
scores: list[MetricCalibrationAllocationScore] = []
|
|
841
|
+
weighted_exploit = 0.0
|
|
842
|
+
weighted_falsification = 0.0
|
|
843
|
+
total_weight = sum(value.weight for value in request.objectives)
|
|
844
|
+
for prediction in member.predictions:
|
|
845
|
+
objective = objective_index[prediction.metric_id]
|
|
846
|
+
cell, source = snapshot.lookup(
|
|
847
|
+
metric_id=prediction.metric_id,
|
|
848
|
+
asserted_direction=prediction.asserted_direction,
|
|
849
|
+
confidence=prediction.confidence,
|
|
850
|
+
family=member.family,
|
|
851
|
+
)
|
|
852
|
+
probability = cell.posterior_correctness
|
|
853
|
+
favorable, adverse = _favorable(
|
|
854
|
+
prediction.asserted_direction,
|
|
855
|
+
objective.goal,
|
|
856
|
+
)
|
|
857
|
+
signed_exploit = probability if favorable else -probability if adverse else 0.0
|
|
858
|
+
if prediction.asserted_direction is MetricEffectDirection.UNKNOWN:
|
|
859
|
+
falsification = 0.0
|
|
860
|
+
else:
|
|
861
|
+
uncertainty = 1.0 - abs((2.0 * probability) - 1.0)
|
|
862
|
+
calibrated_disagreement = max(0.0, 0.5 - probability) * 2.0
|
|
863
|
+
falsification = (uncertainty + calibrated_disagreement) / 2.0
|
|
864
|
+
weighted_exploit += objective.weight * signed_exploit
|
|
865
|
+
weighted_falsification += objective.weight * falsification
|
|
866
|
+
scores.append(
|
|
867
|
+
MetricCalibrationAllocationScore(
|
|
868
|
+
metric_id=prediction.metric_id,
|
|
869
|
+
goal=objective.goal,
|
|
870
|
+
asserted_direction=prediction.asserted_direction,
|
|
871
|
+
confidence=prediction.confidence,
|
|
872
|
+
weight=objective.weight,
|
|
873
|
+
calibration_cell=cell,
|
|
874
|
+
calibration_source=source,
|
|
875
|
+
favorable_assertion=favorable,
|
|
876
|
+
adverse_assertion=adverse,
|
|
877
|
+
signed_exploitation_score=signed_exploit,
|
|
878
|
+
falsification_score=falsification,
|
|
879
|
+
)
|
|
880
|
+
)
|
|
881
|
+
exploit_score = weighted_exploit / total_weight
|
|
882
|
+
falsification_score = weighted_falsification / total_weight
|
|
883
|
+
supported = tuple(
|
|
884
|
+
value
|
|
885
|
+
for value in request.assigned_card_keys
|
|
886
|
+
if value in member.supporting_card_keys
|
|
887
|
+
)
|
|
888
|
+
administration_fraction = len(supported) / len(request.assigned_card_keys)
|
|
889
|
+
memory_score = exploit_score + administration_fraction
|
|
890
|
+
structural = (
|
|
891
|
+
member.structural_evidence.archive_novelty_score
|
|
892
|
+
+ member.structural_evidence.structural_coverage_score
|
|
893
|
+
) / 2.0
|
|
894
|
+
return SlateMemberScoreRow(
|
|
895
|
+
option_id=member.option_id,
|
|
896
|
+
option_identity_sha256=member.option_identity_sha256,
|
|
897
|
+
model_rank=member.model_rank,
|
|
898
|
+
metric_scores=tuple(scores),
|
|
899
|
+
calibrated_exploitation_score=exploit_score,
|
|
900
|
+
memory_hypothesis_score=memory_score,
|
|
901
|
+
falsification_disagreement_score=falsification_score,
|
|
902
|
+
structural_coverage_score=structural,
|
|
903
|
+
supported_assigned_card_keys=supported,
|
|
904
|
+
)
|
|
905
|
+
|
|
906
|
+
|
|
907
|
+
@dataclass(frozen=True, slots=True)
|
|
908
|
+
class AllocatedSlateMember:
|
|
909
|
+
"""One selected member and its engine-owned portfolio role."""
|
|
910
|
+
|
|
911
|
+
role: SlateAllocationRole
|
|
912
|
+
option_id: str
|
|
913
|
+
option_identity_sha256: str
|
|
914
|
+
model_rank: int
|
|
915
|
+
role_score: float | None
|
|
916
|
+
|
|
917
|
+
def __post_init__(self) -> None:
|
|
918
|
+
if type(self.role) is not SlateAllocationRole:
|
|
919
|
+
raise TypeError("role must be exact SlateAllocationRole")
|
|
920
|
+
_require_option(self.option_id)
|
|
921
|
+
require_sha256(self.option_identity_sha256, "option_identity_sha256")
|
|
922
|
+
if type(self.model_rank) is not int or self.model_rank <= 0:
|
|
923
|
+
raise ValueError("model_rank must be positive")
|
|
924
|
+
if self.role is SlateAllocationRole.DIRECT_MODEL_TOP_K:
|
|
925
|
+
if self.role_score is not None:
|
|
926
|
+
raise ValueError("legacy top-k members have no calibrated score")
|
|
927
|
+
else:
|
|
928
|
+
if self.role_score is None:
|
|
929
|
+
raise ValueError("calibrated members require a role score")
|
|
930
|
+
_require_finite_float(self.role_score, name="role_score")
|
|
931
|
+
|
|
932
|
+
def to_record(self) -> dict[str, object]:
|
|
933
|
+
self.__post_init__()
|
|
934
|
+
return {
|
|
935
|
+
"role": self.role.value,
|
|
936
|
+
"option_id": self.option_id,
|
|
937
|
+
"option_identity_sha256": self.option_identity_sha256,
|
|
938
|
+
"model_rank": self.model_rank,
|
|
939
|
+
"role_score_hex": (
|
|
940
|
+
None if self.role_score is None else self.role_score.hex()
|
|
941
|
+
),
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
|
|
945
|
+
@dataclass(frozen=True, slots=True)
|
|
946
|
+
class _CalibratedAssignment:
|
|
947
|
+
selected: tuple[AllocatedSlateMember, ...]
|
|
948
|
+
joint_score: float
|
|
949
|
+
diversity_score: float
|
|
950
|
+
distinct_family_count: int
|
|
951
|
+
distinct_locus_count: int
|
|
952
|
+
distinct_phenotype_count: int
|
|
953
|
+
administered_card_keys: tuple[str, ...]
|
|
954
|
+
memory_dose_assessment: PortfolioMemoryDoseAssessment | None
|
|
955
|
+
|
|
956
|
+
|
|
957
|
+
def _best_calibrated_assignment(
|
|
958
|
+
request: SlateAllocationRequest,
|
|
959
|
+
score_rows: tuple[SlateMemberScoreRow, ...],
|
|
960
|
+
) -> _CalibratedAssignment:
|
|
961
|
+
member_by_id = {value.option_id: value for value in request.slate.members}
|
|
962
|
+
best_key: tuple[object, ...] | None = None
|
|
963
|
+
best_rows: tuple[SlateMemberScoreRow, ...] | None = None
|
|
964
|
+
best_role_scores: tuple[float, ...] | None = None
|
|
965
|
+
best_joint_score: float | None = None
|
|
966
|
+
best_diversity: float | None = None
|
|
967
|
+
best_family_count: int | None = None
|
|
968
|
+
best_locus_count: int | None = None
|
|
969
|
+
best_phenotype_count: int | None = None
|
|
970
|
+
best_administered: tuple[str, ...] | None = None
|
|
971
|
+
memory_dose_pass_by_subset: dict[tuple[str, ...], bool] = {}
|
|
972
|
+
compatible_pairs = (
|
|
973
|
+
None
|
|
974
|
+
if request.pairwise_disjoint_option_id_pairs is None
|
|
975
|
+
else {frozenset(value) for value in request.pairwise_disjoint_option_id_pairs}
|
|
976
|
+
)
|
|
977
|
+
for rows in permutations(score_rows, 4):
|
|
978
|
+
if compatible_pairs is not None and any(
|
|
979
|
+
frozenset((left.option_id, right.option_id)) not in compatible_pairs
|
|
980
|
+
for left_index, left in enumerate(rows)
|
|
981
|
+
for right in rows[left_index + 1 :]
|
|
982
|
+
):
|
|
983
|
+
continue
|
|
984
|
+
if request.min_distinct_families is not None and len(
|
|
985
|
+
{member_by_id[row.option_id].family for row in rows}
|
|
986
|
+
) < request.min_distinct_families:
|
|
987
|
+
continue
|
|
988
|
+
memory_row = rows[1]
|
|
989
|
+
if not memory_row.supported_assigned_card_keys:
|
|
990
|
+
continue
|
|
991
|
+
administered = tuple(
|
|
992
|
+
sorted({card for row in rows for card in row.supported_assigned_card_keys})
|
|
993
|
+
)
|
|
994
|
+
if administered != request.assigned_card_keys:
|
|
995
|
+
continue
|
|
996
|
+
members = tuple(member_by_id[row.option_id] for row in rows)
|
|
997
|
+
if request.memory_dose_contract is not None:
|
|
998
|
+
dose_subset_key = tuple(sorted(row.option_id for row in rows))
|
|
999
|
+
dose_passed = memory_dose_pass_by_subset.get(dose_subset_key)
|
|
1000
|
+
if dose_passed is None:
|
|
1001
|
+
canonical_members = tuple(
|
|
1002
|
+
member_by_id[option_id] for option_id in dose_subset_key
|
|
1003
|
+
)
|
|
1004
|
+
dose_passed = assess_allocated_slate_memory_dose(
|
|
1005
|
+
request,
|
|
1006
|
+
canonical_members,
|
|
1007
|
+
).passed
|
|
1008
|
+
memory_dose_pass_by_subset[dose_subset_key] = dose_passed
|
|
1009
|
+
if not dose_passed:
|
|
1010
|
+
continue
|
|
1011
|
+
family_count = len({value.family for value in members})
|
|
1012
|
+
locus_count = len({value.locus_key for value in members})
|
|
1013
|
+
phenotype_count = len({value.phenotype_identity_sha256 for value in members})
|
|
1014
|
+
diversity = (family_count + locus_count + phenotype_count) / 12.0
|
|
1015
|
+
role_scores = tuple(
|
|
1016
|
+
row.score_for(role) for role, row in zip(_CALIBRATED_ROLES, rows)
|
|
1017
|
+
)
|
|
1018
|
+
joint_score = sum(role_scores) + diversity
|
|
1019
|
+
tie_key: tuple[object, ...] = (
|
|
1020
|
+
-joint_score,
|
|
1021
|
+
*(-value for value in role_scores),
|
|
1022
|
+
tuple(value.option_id for value in rows),
|
|
1023
|
+
)
|
|
1024
|
+
if best_key is None or tie_key < best_key:
|
|
1025
|
+
best_key = tie_key
|
|
1026
|
+
best_rows = rows
|
|
1027
|
+
best_role_scores = role_scores
|
|
1028
|
+
best_joint_score = joint_score
|
|
1029
|
+
best_diversity = diversity
|
|
1030
|
+
best_family_count = family_count
|
|
1031
|
+
best_locus_count = locus_count
|
|
1032
|
+
best_phenotype_count = phenotype_count
|
|
1033
|
+
best_administered = administered
|
|
1034
|
+
if best_rows is None:
|
|
1035
|
+
raise ValueError(
|
|
1036
|
+
"slate has no four-role assignment administering every assigned card"
|
|
1037
|
+
)
|
|
1038
|
+
assert best_role_scores is not None
|
|
1039
|
+
assert best_joint_score is not None
|
|
1040
|
+
assert best_diversity is not None
|
|
1041
|
+
assert best_family_count is not None
|
|
1042
|
+
assert best_locus_count is not None
|
|
1043
|
+
assert best_phenotype_count is not None
|
|
1044
|
+
assert best_administered is not None
|
|
1045
|
+
selected = tuple(
|
|
1046
|
+
AllocatedSlateMember(
|
|
1047
|
+
role=role,
|
|
1048
|
+
option_id=row.option_id,
|
|
1049
|
+
option_identity_sha256=row.option_identity_sha256,
|
|
1050
|
+
model_rank=row.model_rank,
|
|
1051
|
+
role_score=score,
|
|
1052
|
+
)
|
|
1053
|
+
for role, row, score in zip(
|
|
1054
|
+
_CALIBRATED_ROLES,
|
|
1055
|
+
best_rows,
|
|
1056
|
+
best_role_scores,
|
|
1057
|
+
)
|
|
1058
|
+
)
|
|
1059
|
+
winner = _CalibratedAssignment(
|
|
1060
|
+
selected=selected,
|
|
1061
|
+
joint_score=best_joint_score,
|
|
1062
|
+
diversity_score=best_diversity,
|
|
1063
|
+
distinct_family_count=best_family_count,
|
|
1064
|
+
distinct_locus_count=best_locus_count,
|
|
1065
|
+
distinct_phenotype_count=best_phenotype_count,
|
|
1066
|
+
administered_card_keys=best_administered,
|
|
1067
|
+
memory_dose_assessment=None,
|
|
1068
|
+
)
|
|
1069
|
+
if request.memory_dose_contract is None:
|
|
1070
|
+
return winner
|
|
1071
|
+
winner_members = tuple(
|
|
1072
|
+
member_by_id[value.option_id] for value in winner.selected
|
|
1073
|
+
)
|
|
1074
|
+
exact_assessment = assess_allocated_slate_memory_dose(
|
|
1075
|
+
request,
|
|
1076
|
+
winner_members,
|
|
1077
|
+
)
|
|
1078
|
+
if not exact_assessment.passed: # Defensive against cache/key drift.
|
|
1079
|
+
raise AssertionError("winning assignment violated bounded memory dose")
|
|
1080
|
+
return replace(winner, memory_dose_assessment=exact_assessment)
|
|
1081
|
+
|
|
1082
|
+
|
|
1083
|
+
@dataclass(frozen=True, slots=True, eq=False)
|
|
1084
|
+
class SlateAllocationDecision:
|
|
1085
|
+
"""Replayable legacy top-k or prior-only calibrated allocation receipt."""
|
|
1086
|
+
|
|
1087
|
+
request: SlateAllocationRequest
|
|
1088
|
+
mode: SlateAllocationMode
|
|
1089
|
+
score_rows: tuple[SlateMemberScoreRow, ...]
|
|
1090
|
+
selected: tuple[AllocatedSlateMember, ...]
|
|
1091
|
+
joint_score: float | None
|
|
1092
|
+
diversity_score: float | None
|
|
1093
|
+
distinct_family_count: int | None
|
|
1094
|
+
distinct_locus_count: int | None
|
|
1095
|
+
distinct_phenotype_count: int | None
|
|
1096
|
+
administered_card_keys: tuple[str, ...]
|
|
1097
|
+
memory_dose_assessment: PortfolioMemoryDoseAssessment | None = None
|
|
1098
|
+
|
|
1099
|
+
policy_id: ClassVar[str] = POLICY_ID
|
|
1100
|
+
policy_version: ClassVar[int] = POLICY_VERSION
|
|
1101
|
+
policy_definition_sha256: ClassVar[str] = POLICY_DEFINITION_SHA256
|
|
1102
|
+
|
|
1103
|
+
def __post_init__(self) -> None:
|
|
1104
|
+
if type(self.request) is not SlateAllocationRequest:
|
|
1105
|
+
raise TypeError("request must be exact SlateAllocationRequest")
|
|
1106
|
+
self.request.revalidate()
|
|
1107
|
+
if type(self.mode) is not SlateAllocationMode:
|
|
1108
|
+
raise TypeError("mode must be exact SlateAllocationMode")
|
|
1109
|
+
if type(self.score_rows) is not tuple or any(
|
|
1110
|
+
type(value) is not SlateMemberScoreRow for value in self.score_rows
|
|
1111
|
+
):
|
|
1112
|
+
raise TypeError("score_rows must contain exact member scores")
|
|
1113
|
+
for value in self.score_rows:
|
|
1114
|
+
value.__post_init__()
|
|
1115
|
+
if type(self.selected) is not tuple or any(
|
|
1116
|
+
type(value) is not AllocatedSlateMember for value in self.selected
|
|
1117
|
+
):
|
|
1118
|
+
raise TypeError("selected must contain exact allocated members")
|
|
1119
|
+
for value in self.selected:
|
|
1120
|
+
value.__post_init__()
|
|
1121
|
+
_canonical_tokens(self.administered_card_keys, name="administered_card_keys")
|
|
1122
|
+
if self.memory_dose_assessment is not None:
|
|
1123
|
+
if type(self.memory_dose_assessment) is not (
|
|
1124
|
+
PortfolioMemoryDoseAssessment
|
|
1125
|
+
):
|
|
1126
|
+
raise TypeError("memory_dose_assessment must be exact or None")
|
|
1127
|
+
self.memory_dose_assessment.__post_init__()
|
|
1128
|
+
|
|
1129
|
+
if self.mode is SlateAllocationMode.DIRECT_MODEL_TOP_K:
|
|
1130
|
+
expected_members = self.request.slate.members[: self.request.portfolio_size]
|
|
1131
|
+
expected = tuple(
|
|
1132
|
+
AllocatedSlateMember(
|
|
1133
|
+
role=SlateAllocationRole.DIRECT_MODEL_TOP_K,
|
|
1134
|
+
option_id=value.option_id,
|
|
1135
|
+
option_identity_sha256=value.option_identity_sha256,
|
|
1136
|
+
model_rank=value.model_rank,
|
|
1137
|
+
role_score=None,
|
|
1138
|
+
)
|
|
1139
|
+
for value in expected_members
|
|
1140
|
+
)
|
|
1141
|
+
if self.score_rows or self.selected != expected:
|
|
1142
|
+
raise ValueError("legacy decision is not the exact model top-k prefix")
|
|
1143
|
+
expected_dose = (
|
|
1144
|
+
None
|
|
1145
|
+
if self.request.memory_dose_contract is None
|
|
1146
|
+
else assess_allocated_slate_memory_dose(
|
|
1147
|
+
self.request,
|
|
1148
|
+
expected_members,
|
|
1149
|
+
)
|
|
1150
|
+
)
|
|
1151
|
+
if expected_dose is not None and not expected_dose.passed:
|
|
1152
|
+
raise ValueError("direct model top-k violates bounded memory dose")
|
|
1153
|
+
expected_administered = (
|
|
1154
|
+
()
|
|
1155
|
+
if expected_dose is None
|
|
1156
|
+
else self.request.assigned_card_keys
|
|
1157
|
+
)
|
|
1158
|
+
if (
|
|
1159
|
+
any(
|
|
1160
|
+
value is not None
|
|
1161
|
+
for value in (
|
|
1162
|
+
self.joint_score,
|
|
1163
|
+
self.diversity_score,
|
|
1164
|
+
self.distinct_family_count,
|
|
1165
|
+
self.distinct_locus_count,
|
|
1166
|
+
self.distinct_phenotype_count,
|
|
1167
|
+
)
|
|
1168
|
+
)
|
|
1169
|
+
or self.administered_card_keys != expected_administered
|
|
1170
|
+
or self.memory_dose_assessment != expected_dose
|
|
1171
|
+
):
|
|
1172
|
+
raise ValueError("legacy top-k cannot claim calibrated evidence")
|
|
1173
|
+
return
|
|
1174
|
+
|
|
1175
|
+
snapshot = self.request.calibration_snapshot
|
|
1176
|
+
if self.request.portfolio_size != 4:
|
|
1177
|
+
raise ValueError("calibrated mode requires exactly four evaluations")
|
|
1178
|
+
if len(self.request.slate.members) != _MAX_SLATE_SIZE:
|
|
1179
|
+
raise ValueError("calibrated mode requires the declared eight-member slate")
|
|
1180
|
+
if snapshot is None:
|
|
1181
|
+
raise ValueError("calibrated mode requires a prior snapshot")
|
|
1182
|
+
if snapshot.cutoff_wave_index_exclusive > self.request.slate.wave_index:
|
|
1183
|
+
raise ValueError("calibration snapshot cutoff reaches beyond current wave")
|
|
1184
|
+
if not self.request.assigned_card_keys:
|
|
1185
|
+
raise ValueError("memory role requires assigned card keys")
|
|
1186
|
+
expected_rows = tuple(
|
|
1187
|
+
_score_member(self.request, value) for value in self.request.slate.members
|
|
1188
|
+
)
|
|
1189
|
+
if self.score_rows != expected_rows:
|
|
1190
|
+
raise ValueError("score rows differ from prior calibration evidence")
|
|
1191
|
+
expected_assignment = _best_calibrated_assignment(
|
|
1192
|
+
self.request,
|
|
1193
|
+
expected_rows,
|
|
1194
|
+
)
|
|
1195
|
+
if self.selected != expected_assignment.selected:
|
|
1196
|
+
raise ValueError("selected roles do not replay the exact allocator")
|
|
1197
|
+
observed_summary = (
|
|
1198
|
+
self.joint_score,
|
|
1199
|
+
self.diversity_score,
|
|
1200
|
+
self.distinct_family_count,
|
|
1201
|
+
self.distinct_locus_count,
|
|
1202
|
+
self.distinct_phenotype_count,
|
|
1203
|
+
self.administered_card_keys,
|
|
1204
|
+
self.memory_dose_assessment,
|
|
1205
|
+
)
|
|
1206
|
+
expected_summary = (
|
|
1207
|
+
expected_assignment.joint_score,
|
|
1208
|
+
expected_assignment.diversity_score,
|
|
1209
|
+
expected_assignment.distinct_family_count,
|
|
1210
|
+
expected_assignment.distinct_locus_count,
|
|
1211
|
+
expected_assignment.distinct_phenotype_count,
|
|
1212
|
+
expected_assignment.administered_card_keys,
|
|
1213
|
+
expected_assignment.memory_dose_assessment,
|
|
1214
|
+
)
|
|
1215
|
+
if observed_summary != expected_summary:
|
|
1216
|
+
raise ValueError("allocation summary differs from exact assignment")
|
|
1217
|
+
|
|
1218
|
+
def revalidate(self) -> None:
|
|
1219
|
+
if type(self) is not SlateAllocationDecision:
|
|
1220
|
+
raise TypeError("decision must be exact SlateAllocationDecision")
|
|
1221
|
+
SlateAllocationDecision.__post_init__(self)
|
|
1222
|
+
|
|
1223
|
+
@property
|
|
1224
|
+
def prior_only(self) -> bool:
|
|
1225
|
+
self.revalidate()
|
|
1226
|
+
if self.mode is SlateAllocationMode.DIRECT_MODEL_TOP_K:
|
|
1227
|
+
return False
|
|
1228
|
+
snapshot = self.request.calibration_snapshot
|
|
1229
|
+
if snapshot is None: # Defensive after validation.
|
|
1230
|
+
return False
|
|
1231
|
+
return all(
|
|
1232
|
+
value.prediction.wave_index < self.request.slate.wave_index
|
|
1233
|
+
for value in snapshot.observations
|
|
1234
|
+
)
|
|
1235
|
+
|
|
1236
|
+
def _unsigned_record(self) -> dict[str, object]:
|
|
1237
|
+
self.revalidate()
|
|
1238
|
+
return {
|
|
1239
|
+
"schema_version": 1,
|
|
1240
|
+
"event_type": "trace_calibrated_slate_allocated",
|
|
1241
|
+
"policy_id": self.policy_id,
|
|
1242
|
+
"policy_version": self.policy_version,
|
|
1243
|
+
"policy_definition_sha256": self.policy_definition_sha256,
|
|
1244
|
+
"mode": self.mode.value,
|
|
1245
|
+
"request": self.request.to_record(),
|
|
1246
|
+
"request_sha256": self.request.request_sha256,
|
|
1247
|
+
"calibration_snapshot_sha256": (
|
|
1248
|
+
None
|
|
1249
|
+
if self.request.calibration_snapshot is None
|
|
1250
|
+
else self.request.calibration_snapshot.snapshot_sha256
|
|
1251
|
+
),
|
|
1252
|
+
"prior_only": self.prior_only,
|
|
1253
|
+
"score_rows": [value.to_record() for value in self.score_rows],
|
|
1254
|
+
"selected": [value.to_record() for value in self.selected],
|
|
1255
|
+
"joint_score_hex": (
|
|
1256
|
+
None if self.joint_score is None else self.joint_score.hex()
|
|
1257
|
+
),
|
|
1258
|
+
"diversity_score_hex": (
|
|
1259
|
+
None if self.diversity_score is None else self.diversity_score.hex()
|
|
1260
|
+
),
|
|
1261
|
+
"distinct_family_count": self.distinct_family_count,
|
|
1262
|
+
"distinct_locus_count": self.distinct_locus_count,
|
|
1263
|
+
"distinct_phenotype_count": self.distinct_phenotype_count,
|
|
1264
|
+
"administered_card_keys": list(self.administered_card_keys),
|
|
1265
|
+
**(
|
|
1266
|
+
{}
|
|
1267
|
+
if self.memory_dose_assessment is None
|
|
1268
|
+
else {
|
|
1269
|
+
"memory_dose_assessment": (
|
|
1270
|
+
self.memory_dose_assessment.to_record()
|
|
1271
|
+
)
|
|
1272
|
+
}
|
|
1273
|
+
),
|
|
1274
|
+
"claim_scope": (
|
|
1275
|
+
"replayable_prior_only_allocation_not_efficacy_or_outcome_claim"
|
|
1276
|
+
),
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
@property
|
|
1280
|
+
def decision_sha256(self) -> str:
|
|
1281
|
+
return _hash(_DECISION_DOMAIN, self._unsigned_record())
|
|
1282
|
+
|
|
1283
|
+
def to_record(self) -> dict[str, object]:
|
|
1284
|
+
return {**self._unsigned_record(), "decision_sha256": self.decision_sha256}
|
|
1285
|
+
|
|
1286
|
+
def __eq__(self, other: object) -> bool:
|
|
1287
|
+
return (
|
|
1288
|
+
type(other) is SlateAllocationDecision
|
|
1289
|
+
and self.decision_sha256 == other.decision_sha256
|
|
1290
|
+
)
|
|
1291
|
+
|
|
1292
|
+
__hash__ = None
|
|
1293
|
+
|
|
1294
|
+
|
|
1295
|
+
@dataclass(frozen=True, slots=True)
|
|
1296
|
+
class TraceCalibratedSlatePolicy:
|
|
1297
|
+
"""Select the legacy prefix by default or opt into four-role allocation."""
|
|
1298
|
+
|
|
1299
|
+
mode: SlateAllocationMode = SlateAllocationMode.DIRECT_MODEL_TOP_K
|
|
1300
|
+
|
|
1301
|
+
policy_id: ClassVar[str] = POLICY_ID
|
|
1302
|
+
policy_version: ClassVar[int] = POLICY_VERSION
|
|
1303
|
+
definition_sha256: ClassVar[str] = POLICY_DEFINITION_SHA256
|
|
1304
|
+
|
|
1305
|
+
def __post_init__(self) -> None:
|
|
1306
|
+
if type(self.mode) is not SlateAllocationMode:
|
|
1307
|
+
raise TypeError("mode must be exact SlateAllocationMode")
|
|
1308
|
+
|
|
1309
|
+
def select(self, request: SlateAllocationRequest) -> SlateAllocationDecision:
|
|
1310
|
+
if type(request) is not SlateAllocationRequest:
|
|
1311
|
+
raise TypeError("request must be exact SlateAllocationRequest")
|
|
1312
|
+
request.revalidate()
|
|
1313
|
+
if self.mode is SlateAllocationMode.DIRECT_MODEL_TOP_K:
|
|
1314
|
+
selected_members = request.slate.members[: request.portfolio_size]
|
|
1315
|
+
memory_dose_assessment = (
|
|
1316
|
+
None
|
|
1317
|
+
if request.memory_dose_contract is None
|
|
1318
|
+
else assess_allocated_slate_memory_dose(
|
|
1319
|
+
request,
|
|
1320
|
+
selected_members,
|
|
1321
|
+
)
|
|
1322
|
+
)
|
|
1323
|
+
if (
|
|
1324
|
+
memory_dose_assessment is not None
|
|
1325
|
+
and not memory_dose_assessment.passed
|
|
1326
|
+
):
|
|
1327
|
+
raise ValueError("direct model top-k violates bounded memory dose")
|
|
1328
|
+
selected = tuple(
|
|
1329
|
+
AllocatedSlateMember(
|
|
1330
|
+
role=SlateAllocationRole.DIRECT_MODEL_TOP_K,
|
|
1331
|
+
option_id=value.option_id,
|
|
1332
|
+
option_identity_sha256=value.option_identity_sha256,
|
|
1333
|
+
model_rank=value.model_rank,
|
|
1334
|
+
role_score=None,
|
|
1335
|
+
)
|
|
1336
|
+
for value in selected_members
|
|
1337
|
+
)
|
|
1338
|
+
return SlateAllocationDecision(
|
|
1339
|
+
request=request,
|
|
1340
|
+
mode=self.mode,
|
|
1341
|
+
score_rows=(),
|
|
1342
|
+
selected=selected,
|
|
1343
|
+
joint_score=None,
|
|
1344
|
+
diversity_score=None,
|
|
1345
|
+
distinct_family_count=None,
|
|
1346
|
+
distinct_locus_count=None,
|
|
1347
|
+
distinct_phenotype_count=None,
|
|
1348
|
+
administered_card_keys=(
|
|
1349
|
+
()
|
|
1350
|
+
if memory_dose_assessment is None
|
|
1351
|
+
else request.assigned_card_keys
|
|
1352
|
+
),
|
|
1353
|
+
memory_dose_assessment=memory_dose_assessment,
|
|
1354
|
+
)
|
|
1355
|
+
rows = tuple(_score_member(request, value) for value in request.slate.members)
|
|
1356
|
+
assignment = _best_calibrated_assignment(request, rows)
|
|
1357
|
+
return SlateAllocationDecision(
|
|
1358
|
+
request=request,
|
|
1359
|
+
mode=self.mode,
|
|
1360
|
+
score_rows=rows,
|
|
1361
|
+
selected=assignment.selected,
|
|
1362
|
+
joint_score=assignment.joint_score,
|
|
1363
|
+
diversity_score=assignment.diversity_score,
|
|
1364
|
+
distinct_family_count=assignment.distinct_family_count,
|
|
1365
|
+
distinct_locus_count=assignment.distinct_locus_count,
|
|
1366
|
+
distinct_phenotype_count=assignment.distinct_phenotype_count,
|
|
1367
|
+
administered_card_keys=assignment.administered_card_keys,
|
|
1368
|
+
memory_dose_assessment=assignment.memory_dose_assessment,
|
|
1369
|
+
)
|
|
1370
|
+
|
|
1371
|
+
def to_record(self) -> dict[str, object]:
|
|
1372
|
+
self.__post_init__()
|
|
1373
|
+
return {
|
|
1374
|
+
"policy_id": self.policy_id,
|
|
1375
|
+
"policy_version": self.policy_version,
|
|
1376
|
+
"definition_sha256": self.definition_sha256,
|
|
1377
|
+
"mode": self.mode.value,
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
|
|
1381
|
+
__all__ = [
|
|
1382
|
+
"CalibratedSlate",
|
|
1383
|
+
"CalibratedSlateMember",
|
|
1384
|
+
"MetricOptimizationGoal",
|
|
1385
|
+
"SlateAllocationDecision",
|
|
1386
|
+
"SlateAllocationMode",
|
|
1387
|
+
"SlateAllocationRequest",
|
|
1388
|
+
"SlateAllocationRole",
|
|
1389
|
+
"SlateMetricObjective",
|
|
1390
|
+
"SlateRoleProposal",
|
|
1391
|
+
"SlateStructuralEvidence",
|
|
1392
|
+
"TraceCalibratedSlatePolicy",
|
|
1393
|
+
"assess_allocated_slate_memory_dose",
|
|
1394
|
+
]
|