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,1325 @@
|
|
|
1
|
+
"""Injectable six-call outcome-reflection and held-out A/S/N policy.
|
|
2
|
+
|
|
3
|
+
This experiment policy restores one concrete reflection-to-action step without
|
|
4
|
+
making a two-card A/S/N design a universal AgentEvolve assumption. One
|
|
5
|
+
interceptor converts two sealed diagnostic outcomes into exactly two
|
|
6
|
+
quarantined cards. A small mailbox adapter then binds the higher-score card, its
|
|
7
|
+
score-swapped counterpart, and a neutral sham card to three otherwise matched
|
|
8
|
+
held-out invocation plans.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import asyncio
|
|
14
|
+
import hashlib
|
|
15
|
+
import json
|
|
16
|
+
from dataclasses import dataclass, replace
|
|
17
|
+
from enum import Enum
|
|
18
|
+
from typing import Protocol
|
|
19
|
+
|
|
20
|
+
from agent_evolve.application.agentic_evolution import (
|
|
21
|
+
InvocationOutcome,
|
|
22
|
+
InvocationPlan,
|
|
23
|
+
)
|
|
24
|
+
from agent_evolve.application.budgeted_optimizer import OptimizerState
|
|
25
|
+
from agent_evolve.application.generation_feedback import (
|
|
26
|
+
GenerationFeedbackContext,
|
|
27
|
+
GenerationFeedbackReservation,
|
|
28
|
+
GenerationFeedbackResult,
|
|
29
|
+
validate_generation_feedback_receipt,
|
|
30
|
+
)
|
|
31
|
+
from agent_evolve.application.insight_memory import (
|
|
32
|
+
InsightLifecycleState,
|
|
33
|
+
InsightMemoryBank,
|
|
34
|
+
InsightMemoryEntry,
|
|
35
|
+
InsightOrigin,
|
|
36
|
+
QuarantineAssignmentStructuralError,
|
|
37
|
+
)
|
|
38
|
+
from agent_evolve.domain.ids import CandidateId
|
|
39
|
+
from agent_evolve.domain.insight import InsightRef
|
|
40
|
+
from agent_evolve.domain.patch import ArrayIndex, JsonPath, ObjectKey
|
|
41
|
+
from agent_evolve.ports.agentic_generator import (
|
|
42
|
+
InsightDraft,
|
|
43
|
+
MetricEffectDirection,
|
|
44
|
+
MetricEffectPrediction,
|
|
45
|
+
ReflectionInsightContract,
|
|
46
|
+
validate_reflection_insight_draft,
|
|
47
|
+
)
|
|
48
|
+
from agent_evolve.ports.generation_failure import (
|
|
49
|
+
GenerationFailureDisposition,
|
|
50
|
+
classify_generation_failure,
|
|
51
|
+
)
|
|
52
|
+
from agent_evolve.policies.memory.treatment_compliance import (
|
|
53
|
+
InsightTreatmentRequirement,
|
|
54
|
+
TreatmentActionBinding,
|
|
55
|
+
TreatmentAssignmentRole,
|
|
56
|
+
TreatmentClaimMode,
|
|
57
|
+
TreatmentInsightEvidence,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
REFLECTIVE_FEEDBACK_POLICY_ID = "v7_reflective_feedback"
|
|
62
|
+
REFLECTIVE_FEEDBACK_POLICY_VERSION = 2
|
|
63
|
+
_METADATA_SCHEMA = "v7-reflected-card-batch-v2"
|
|
64
|
+
_REFLECTION_CONTRAST_DOMAIN = b"agent-evolve:reflection-contrast:v1\x00"
|
|
65
|
+
HELD_OUT_SELECTOR_POLICY_ID = "held_out_asn_origin_score_swap"
|
|
66
|
+
HELD_OUT_SELECTOR_POLICY_VERSION = 1
|
|
67
|
+
_ASSIGNMENT_COMMITMENT_DOMAIN = (
|
|
68
|
+
b"agent-evolve:held-out-asn-assignment-commitment:v1\x00"
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class ReflectiveFeedbackContractError(ValueError):
|
|
73
|
+
"""Diagnostic evidence or reflected cards violated the frozen contract."""
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class HeldOutAssignmentUnavailableReason(str, Enum):
|
|
77
|
+
"""Closed, experiment-safe reasons for omitting the held-out A/S/N wave."""
|
|
78
|
+
|
|
79
|
+
REFLECTED_CARD_BATCH_UNAVAILABLE = "reflected_card_batch_unavailable"
|
|
80
|
+
EQUAL_ORIGIN_SCORES = "equal_origin_scores"
|
|
81
|
+
STRUCTURALLY_INAPPLICABLE_ASSIGNMENT = "structurally_inapplicable_assignment"
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class HeldOutAssignmentUnavailable(ValueError):
|
|
85
|
+
"""The A/S/N block cannot be constructed for one closed safe reason."""
|
|
86
|
+
|
|
87
|
+
def __init__(
|
|
88
|
+
self,
|
|
89
|
+
reason: HeldOutAssignmentUnavailableReason,
|
|
90
|
+
detail: str,
|
|
91
|
+
) -> None:
|
|
92
|
+
if type(reason) is not HeldOutAssignmentUnavailableReason:
|
|
93
|
+
raise TypeError(
|
|
94
|
+
"reason must be an exact HeldOutAssignmentUnavailableReason"
|
|
95
|
+
)
|
|
96
|
+
if type(detail) is not str or not detail.strip() or detail != detail.strip():
|
|
97
|
+
raise ValueError("detail must be non-empty canonical text")
|
|
98
|
+
self.reason = reason
|
|
99
|
+
self.detail = detail
|
|
100
|
+
super().__init__(f"{reason.value}: {detail}")
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class OutcomeReflector(Protocol):
|
|
104
|
+
"""Narrow engine capability consumed by the feedback interceptor."""
|
|
105
|
+
|
|
106
|
+
async def reflect(
|
|
107
|
+
self,
|
|
108
|
+
outcomes: tuple[InvocationOutcome, ...],
|
|
109
|
+
*,
|
|
110
|
+
label: str,
|
|
111
|
+
max_insights: int,
|
|
112
|
+
insight_contract: ReflectionInsightContract | None = None,
|
|
113
|
+
) -> tuple[InsightMemoryEntry, ...]: ...
|
|
114
|
+
|
|
115
|
+
def identify_phenotype(self, configuration): ...
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def reflection_contrast_id(outcome: InvocationOutcome) -> str:
|
|
119
|
+
"""Reproduce the engine-owned contrast identity for one-parent variation."""
|
|
120
|
+
|
|
121
|
+
if type(outcome) is not InvocationOutcome:
|
|
122
|
+
raise TypeError("outcome must be an exact InvocationOutcome")
|
|
123
|
+
if len(outcome.prepared.plan.parents) != 1:
|
|
124
|
+
raise ReflectiveFeedbackContractError(
|
|
125
|
+
"v7 diagnostic reflection requires exactly one parent per outcome"
|
|
126
|
+
)
|
|
127
|
+
parent = outcome.prepared.plan.parents[0]
|
|
128
|
+
return hashlib.sha256(
|
|
129
|
+
_REFLECTION_CONTRAST_DOMAIN
|
|
130
|
+
+ outcome.prepared.operator_invocation_id.value.encode("ascii")
|
|
131
|
+
+ b"\x00"
|
|
132
|
+
+ parent.candidate_id.value.encode("ascii")
|
|
133
|
+
).hexdigest()
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _ternary_reward(outcome: InvocationOutcome) -> int:
|
|
137
|
+
reward = outcome.reward
|
|
138
|
+
if reward not in {-1.0, 0.0, 1.0}:
|
|
139
|
+
raise ReflectiveFeedbackContractError(
|
|
140
|
+
"diagnostic reward must be exactly one of -1, 0, +1"
|
|
141
|
+
)
|
|
142
|
+
return int(reward)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _successful_diagnostic_outcomes(
|
|
146
|
+
outcomes: tuple[InvocationOutcome, ...],
|
|
147
|
+
) -> tuple[InvocationOutcome, InvocationOutcome]:
|
|
148
|
+
if type(outcomes) is not tuple or any(
|
|
149
|
+
type(outcome) is not InvocationOutcome for outcome in outcomes
|
|
150
|
+
):
|
|
151
|
+
raise TypeError("outcomes must contain exact InvocationOutcome values")
|
|
152
|
+
if len(outcomes) != 2:
|
|
153
|
+
raise ReflectiveFeedbackContractError(
|
|
154
|
+
"v7 diagnostic reflection requires exactly two outcomes"
|
|
155
|
+
)
|
|
156
|
+
generations = {outcome.prepared.plan.generation for outcome in outcomes}
|
|
157
|
+
if generations != {1}:
|
|
158
|
+
raise ReflectiveFeedbackContractError(
|
|
159
|
+
"v7 diagnostic reflection is restricted to generation one"
|
|
160
|
+
)
|
|
161
|
+
configuration_hashes: set[str] = set()
|
|
162
|
+
parent_ids: set[CandidateId] = set()
|
|
163
|
+
for outcome in outcomes:
|
|
164
|
+
candidate = outcome.candidate
|
|
165
|
+
if (
|
|
166
|
+
outcome.failure_stage is not None
|
|
167
|
+
or candidate is None
|
|
168
|
+
or not candidate.valid
|
|
169
|
+
or not candidate.operator_compliant
|
|
170
|
+
or not candidate.evidence_compliant
|
|
171
|
+
):
|
|
172
|
+
raise ReflectiveFeedbackContractError(
|
|
173
|
+
"every diagnostic outcome must be a compliant successful candidate"
|
|
174
|
+
)
|
|
175
|
+
if len(outcome.prepared.plan.parents) != 1:
|
|
176
|
+
raise ReflectiveFeedbackContractError(
|
|
177
|
+
"every diagnostic outcome must have exactly one parent"
|
|
178
|
+
)
|
|
179
|
+
parent_ids.add(outcome.prepared.plan.parents[0].candidate_id)
|
|
180
|
+
configuration_hashes.add(candidate.occurrence.configuration_hash)
|
|
181
|
+
_ternary_reward(outcome)
|
|
182
|
+
if len(parent_ids) != 1:
|
|
183
|
+
raise ReflectiveFeedbackContractError(
|
|
184
|
+
"both diagnostic outcomes must share one frozen parent"
|
|
185
|
+
)
|
|
186
|
+
if len(configuration_hashes) != 2:
|
|
187
|
+
raise ReflectiveFeedbackContractError(
|
|
188
|
+
"diagnostic candidates must have distinct exact configurations"
|
|
189
|
+
)
|
|
190
|
+
return outcomes
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
@dataclass(frozen=True, slots=True, order=True)
|
|
194
|
+
class ReflectedCard:
|
|
195
|
+
reference: InsightRef
|
|
196
|
+
origin_contrast_id: str
|
|
197
|
+
origin_transfer_score: int
|
|
198
|
+
|
|
199
|
+
def __post_init__(self) -> None:
|
|
200
|
+
if type(self.reference) is not InsightRef:
|
|
201
|
+
raise TypeError("reference must be an exact InsightRef")
|
|
202
|
+
InsightRef.__post_init__(self.reference)
|
|
203
|
+
if (
|
|
204
|
+
type(self.origin_contrast_id) is not str
|
|
205
|
+
or len(self.origin_contrast_id) != 64
|
|
206
|
+
or any(
|
|
207
|
+
character not in "0123456789abcdef"
|
|
208
|
+
for character in self.origin_contrast_id
|
|
209
|
+
)
|
|
210
|
+
):
|
|
211
|
+
raise ValueError("origin_contrast_id must be a lowercase SHA-256 digest")
|
|
212
|
+
if type(self.origin_transfer_score) is not int or (
|
|
213
|
+
self.origin_transfer_score not in {-1, 0, 1}
|
|
214
|
+
):
|
|
215
|
+
raise ValueError("origin_transfer_score must be exactly -1, 0, or +1")
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
@dataclass(frozen=True, slots=True)
|
|
219
|
+
class ReflectedCardBatch:
|
|
220
|
+
source_generation: int
|
|
221
|
+
diagnostic_parent_id: CandidateId
|
|
222
|
+
cards: tuple[ReflectedCard, ReflectedCard]
|
|
223
|
+
reflection_logical_calls: int = 1
|
|
224
|
+
|
|
225
|
+
def __post_init__(self) -> None:
|
|
226
|
+
if type(self.source_generation) is not int or self.source_generation != 1:
|
|
227
|
+
raise ValueError("source_generation must be exactly one")
|
|
228
|
+
if type(self.diagnostic_parent_id) is not CandidateId:
|
|
229
|
+
raise TypeError("diagnostic_parent_id must be an exact CandidateId")
|
|
230
|
+
CandidateId.__post_init__(self.diagnostic_parent_id)
|
|
231
|
+
if (
|
|
232
|
+
type(self.cards) is not tuple
|
|
233
|
+
or len(self.cards) != 2
|
|
234
|
+
or any(type(card) is not ReflectedCard for card in self.cards)
|
|
235
|
+
):
|
|
236
|
+
raise TypeError("cards must contain exactly two ReflectedCard values")
|
|
237
|
+
for card in self.cards:
|
|
238
|
+
ReflectedCard.__post_init__(card)
|
|
239
|
+
canonical = tuple(sorted(self.cards, key=lambda card: card.origin_contrast_id))
|
|
240
|
+
if self.cards != canonical:
|
|
241
|
+
raise ValueError("cards must be ordered by origin contrast identity")
|
|
242
|
+
if len({card.reference for card in self.cards}) != 2:
|
|
243
|
+
raise ValueError("reflected cards must use distinct insight references")
|
|
244
|
+
if len({card.origin_contrast_id for card in self.cards}) != 2:
|
|
245
|
+
raise ValueError("reflected cards must cite distinct origin contrasts")
|
|
246
|
+
if (
|
|
247
|
+
type(self.reflection_logical_calls) is not int
|
|
248
|
+
or self.reflection_logical_calls <= 0
|
|
249
|
+
):
|
|
250
|
+
raise ValueError("reflection_logical_calls must be positive")
|
|
251
|
+
|
|
252
|
+
@property
|
|
253
|
+
def feedback_metadata(self) -> tuple[tuple[str, str], ...]:
|
|
254
|
+
rows: list[tuple[str, str]] = [
|
|
255
|
+
("card_count", "2"),
|
|
256
|
+
("diagnostic_parent_id", self.diagnostic_parent_id.value),
|
|
257
|
+
("schema", _METADATA_SCHEMA),
|
|
258
|
+
("source_generation", str(self.source_generation)),
|
|
259
|
+
("status", "ready"),
|
|
260
|
+
]
|
|
261
|
+
for index, card in enumerate(self.cards):
|
|
262
|
+
prefix = f"card.{index}"
|
|
263
|
+
rows.extend(
|
|
264
|
+
(
|
|
265
|
+
(f"{prefix}.insight_id", card.reference.insight_id.value),
|
|
266
|
+
(f"{prefix}.insight_version", str(card.reference.version)),
|
|
267
|
+
(f"{prefix}.origin_contrast_id", card.origin_contrast_id),
|
|
268
|
+
(
|
|
269
|
+
f"{prefix}.origin_transfer_score",
|
|
270
|
+
str(card.origin_transfer_score),
|
|
271
|
+
),
|
|
272
|
+
)
|
|
273
|
+
)
|
|
274
|
+
return tuple(sorted(rows))
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def _entry_for_reference(
|
|
278
|
+
memory: InsightMemoryBank,
|
|
279
|
+
reference: InsightRef,
|
|
280
|
+
) -> InsightMemoryEntry:
|
|
281
|
+
matches = tuple(entry for entry in memory.entries if entry.reference == reference)
|
|
282
|
+
if len(matches) != 1:
|
|
283
|
+
raise ValueError(
|
|
284
|
+
"assignment references an insight absent from the bound memory bank"
|
|
285
|
+
)
|
|
286
|
+
return matches[0]
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def build_reflected_card_batch(
|
|
290
|
+
*,
|
|
291
|
+
outcomes: tuple[InvocationOutcome, ...],
|
|
292
|
+
entries: tuple[InsightMemoryEntry, ...],
|
|
293
|
+
insight_contract: ReflectionInsightContract | None = None,
|
|
294
|
+
reflection_logical_calls: int = 1,
|
|
295
|
+
) -> ReflectedCardBatch:
|
|
296
|
+
"""Validate one-to-one exact citations and bind engine-derived scores."""
|
|
297
|
+
|
|
298
|
+
first, second = _successful_diagnostic_outcomes(outcomes)
|
|
299
|
+
ordered_outcomes = (first, second)
|
|
300
|
+
contrast_to_outcome = {
|
|
301
|
+
reflection_contrast_id(outcome): outcome for outcome in ordered_outcomes
|
|
302
|
+
}
|
|
303
|
+
expected_contrasts = set(contrast_to_outcome)
|
|
304
|
+
if type(entries) is not tuple or any(
|
|
305
|
+
type(entry) is not InsightMemoryEntry for entry in entries
|
|
306
|
+
):
|
|
307
|
+
raise TypeError("entries must contain exact InsightMemoryEntry values")
|
|
308
|
+
if len(entries) != 2:
|
|
309
|
+
raise ReflectiveFeedbackContractError(
|
|
310
|
+
"reflection must yield exactly two accepted insight entries"
|
|
311
|
+
)
|
|
312
|
+
if type(reflection_logical_calls) is not int or reflection_logical_calls <= 0:
|
|
313
|
+
raise ValueError("reflection_logical_calls must be positive")
|
|
314
|
+
|
|
315
|
+
available_sets = tuple(
|
|
316
|
+
set(entry.evidence_lineage.available_contrast_ids)
|
|
317
|
+
for entry in entries
|
|
318
|
+
if entry.evidence_lineage is not None
|
|
319
|
+
)
|
|
320
|
+
full_batch_lineage = len(available_sets) == len(entries) and all(
|
|
321
|
+
available == expected_contrasts for available in available_sets
|
|
322
|
+
)
|
|
323
|
+
singleton_lineage = len(available_sets) == len(entries) and all(
|
|
324
|
+
len(available) == 1 for available in available_sets
|
|
325
|
+
)
|
|
326
|
+
if full_batch_lineage == singleton_lineage:
|
|
327
|
+
raise ReflectiveFeedbackContractError(
|
|
328
|
+
"reflected cards must use one consistent full-batch or singleton lineage mode"
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
cards: list[ReflectedCard] = []
|
|
332
|
+
cited: set[str] = set()
|
|
333
|
+
for entry in entries:
|
|
334
|
+
if insight_contract is not None:
|
|
335
|
+
try:
|
|
336
|
+
validate_reflection_insight_draft(
|
|
337
|
+
entry.draft,
|
|
338
|
+
insight_contract,
|
|
339
|
+
)
|
|
340
|
+
except (TypeError, ValueError) as exc:
|
|
341
|
+
raise ReflectiveFeedbackContractError(
|
|
342
|
+
"reflected card violates the actionable insight contract"
|
|
343
|
+
) from exc
|
|
344
|
+
if (
|
|
345
|
+
entry.origin is not InsightOrigin.REFLECTION
|
|
346
|
+
or entry.lifecycle_state is not InsightLifecycleState.QUARANTINED
|
|
347
|
+
or entry.retrievable
|
|
348
|
+
or entry.evidence_lineage is None
|
|
349
|
+
):
|
|
350
|
+
raise ReflectiveFeedbackContractError(
|
|
351
|
+
"every reflected card must be a non-retrievable quarantine entry"
|
|
352
|
+
)
|
|
353
|
+
lineage = entry.evidence_lineage
|
|
354
|
+
if len(lineage.cited_contrast_ids) != 1:
|
|
355
|
+
raise ReflectiveFeedbackContractError(
|
|
356
|
+
"each reflected card must cite exactly one full origin contrast"
|
|
357
|
+
)
|
|
358
|
+
contrast_id = lineage.cited_contrast_ids[0]
|
|
359
|
+
if singleton_lineage and lineage.available_contrast_ids != (contrast_id,):
|
|
360
|
+
raise ReflectiveFeedbackContractError(
|
|
361
|
+
"singleton reflected-card lineage differs from its exact citation"
|
|
362
|
+
)
|
|
363
|
+
if contrast_id not in expected_contrasts or contrast_id in cited:
|
|
364
|
+
raise ReflectiveFeedbackContractError(
|
|
365
|
+
"reflected cards must form a one-to-one exact contrast assignment"
|
|
366
|
+
)
|
|
367
|
+
if entry.draft.evidence_contrast_ids != (contrast_id,):
|
|
368
|
+
raise ReflectiveFeedbackContractError(
|
|
369
|
+
"reflected draft citation differs from its evidence lineage"
|
|
370
|
+
)
|
|
371
|
+
outcome = contrast_to_outcome[contrast_id]
|
|
372
|
+
candidate = outcome.candidate
|
|
373
|
+
assert candidate is not None
|
|
374
|
+
expected_operator_ids = (outcome.prepared.operator_invocation_id,)
|
|
375
|
+
expected_candidate_ids = tuple(
|
|
376
|
+
sorted(
|
|
377
|
+
(
|
|
378
|
+
outcome.prepared.plan.parents[0].candidate_id,
|
|
379
|
+
candidate.candidate_id,
|
|
380
|
+
)
|
|
381
|
+
)
|
|
382
|
+
)
|
|
383
|
+
if lineage.source_operator_invocation_ids != expected_operator_ids or (
|
|
384
|
+
lineage.source_candidate_ids != expected_candidate_ids
|
|
385
|
+
):
|
|
386
|
+
raise ReflectiveFeedbackContractError(
|
|
387
|
+
"reflected evidence lineage differs from its cited contrast"
|
|
388
|
+
)
|
|
389
|
+
cited.add(contrast_id)
|
|
390
|
+
cards.append(
|
|
391
|
+
ReflectedCard(
|
|
392
|
+
reference=entry.reference,
|
|
393
|
+
origin_contrast_id=contrast_id,
|
|
394
|
+
origin_transfer_score=_ternary_reward(outcome),
|
|
395
|
+
)
|
|
396
|
+
)
|
|
397
|
+
if cited != expected_contrasts:
|
|
398
|
+
raise ReflectiveFeedbackContractError(
|
|
399
|
+
"reflection omitted a diagnostic origin contrast"
|
|
400
|
+
)
|
|
401
|
+
return ReflectedCardBatch(
|
|
402
|
+
source_generation=1,
|
|
403
|
+
diagnostic_parent_id=ordered_outcomes[0].prepared.plan.parents[0].candidate_id,
|
|
404
|
+
cards=tuple(sorted(cards, key=lambda card: card.origin_contrast_id)), # type: ignore[arg-type]
|
|
405
|
+
reflection_logical_calls=reflection_logical_calls,
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
class ReflectedCardMailbox:
|
|
410
|
+
"""Write-once typed handoff from feedback to the next planner call."""
|
|
411
|
+
|
|
412
|
+
def __init__(self) -> None:
|
|
413
|
+
self._batches: dict[int, ReflectedCardBatch] = {}
|
|
414
|
+
|
|
415
|
+
def publish(self, batch: ReflectedCardBatch) -> None:
|
|
416
|
+
if type(batch) is not ReflectedCardBatch:
|
|
417
|
+
raise TypeError("batch must be an exact ReflectedCardBatch")
|
|
418
|
+
ReflectedCardBatch.__post_init__(batch)
|
|
419
|
+
if batch.source_generation in self._batches:
|
|
420
|
+
raise ReflectiveFeedbackContractError(
|
|
421
|
+
"a reflected card batch was already published for this generation"
|
|
422
|
+
)
|
|
423
|
+
self._batches[batch.source_generation] = batch
|
|
424
|
+
|
|
425
|
+
def read_verified(
|
|
426
|
+
self,
|
|
427
|
+
*,
|
|
428
|
+
state: OptimizerState,
|
|
429
|
+
source_generation: int = 1,
|
|
430
|
+
) -> ReflectedCardBatch:
|
|
431
|
+
"""Read only if the next planner state authenticates the same metadata."""
|
|
432
|
+
|
|
433
|
+
if type(state) is not OptimizerState:
|
|
434
|
+
raise TypeError("state must be an exact OptimizerState")
|
|
435
|
+
if type(source_generation) is not int or source_generation != 1:
|
|
436
|
+
raise ValueError("source_generation must be exactly one")
|
|
437
|
+
receipts = tuple(
|
|
438
|
+
receipt
|
|
439
|
+
for receipt in state.feedback_receipts
|
|
440
|
+
if receipt.generation == source_generation
|
|
441
|
+
)
|
|
442
|
+
if len(receipts) != 1:
|
|
443
|
+
raise ReflectiveFeedbackContractError(
|
|
444
|
+
"planner state lacks one exact source feedback receipt"
|
|
445
|
+
)
|
|
446
|
+
receipt = receipts[0]
|
|
447
|
+
validate_generation_feedback_receipt(receipt)
|
|
448
|
+
if (
|
|
449
|
+
receipt.policy_id != REFLECTIVE_FEEDBACK_POLICY_ID
|
|
450
|
+
or receipt.policy_version != REFLECTIVE_FEEDBACK_POLICY_VERSION
|
|
451
|
+
):
|
|
452
|
+
raise ReflectiveFeedbackContractError(
|
|
453
|
+
"planner feedback receipt names a different feedback policy"
|
|
454
|
+
)
|
|
455
|
+
try:
|
|
456
|
+
batch = self._batches[source_generation]
|
|
457
|
+
except KeyError as exc:
|
|
458
|
+
metadata = dict(receipt.result_metadata)
|
|
459
|
+
status = metadata.get("status")
|
|
460
|
+
reason = metadata.get("reason")
|
|
461
|
+
expected_calls = {
|
|
462
|
+
"diagnostic_rejected": 0,
|
|
463
|
+
"reflection_failed": receipt.reserved_logical_llm_calls,
|
|
464
|
+
"reflection_rejected": receipt.reserved_logical_llm_calls,
|
|
465
|
+
}.get(status)
|
|
466
|
+
if (
|
|
467
|
+
expected_calls is None
|
|
468
|
+
or type(reason) is not str
|
|
469
|
+
or not reason
|
|
470
|
+
or receipt.used_logical_llm_calls != expected_calls
|
|
471
|
+
or receipt.result_metadata != _status_metadata(status, reason)
|
|
472
|
+
):
|
|
473
|
+
raise ReflectiveFeedbackContractError(
|
|
474
|
+
"a missing card batch lacks one authenticated unavailable status"
|
|
475
|
+
) from exc
|
|
476
|
+
raise HeldOutAssignmentUnavailable(
|
|
477
|
+
HeldOutAssignmentUnavailableReason.REFLECTED_CARD_BATCH_UNAVAILABLE,
|
|
478
|
+
f"no accepted reflected card batch is available after {status}",
|
|
479
|
+
) from exc
|
|
480
|
+
if (
|
|
481
|
+
receipt.used_logical_llm_calls != batch.reflection_logical_calls
|
|
482
|
+
or receipt.result_metadata != batch.feedback_metadata
|
|
483
|
+
):
|
|
484
|
+
raise ReflectiveFeedbackContractError(
|
|
485
|
+
"planner feedback receipt differs from the typed card mailbox"
|
|
486
|
+
)
|
|
487
|
+
return batch
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
def _status_metadata(status: str, reason: str) -> tuple[tuple[str, str], ...]:
|
|
491
|
+
return tuple(
|
|
492
|
+
sorted(
|
|
493
|
+
(
|
|
494
|
+
("card_count", "0"),
|
|
495
|
+
("reason", reason),
|
|
496
|
+
("schema", _METADATA_SCHEMA),
|
|
497
|
+
("source_generation", "1"),
|
|
498
|
+
("status", status),
|
|
499
|
+
)
|
|
500
|
+
)
|
|
501
|
+
)
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
@dataclass(slots=True)
|
|
505
|
+
class G1ReflectionFeedbackInterceptor:
|
|
506
|
+
"""Spend the reserved reflection calls after a valid sealed G1 block."""
|
|
507
|
+
|
|
508
|
+
engine: OutcomeReflector
|
|
509
|
+
mailbox: ReflectedCardMailbox
|
|
510
|
+
diagnostic_slot_ids: tuple[str, str] = ("D-S", "D-T")
|
|
511
|
+
reflection_label: str = "v7_g1_outcome_reflection"
|
|
512
|
+
required_metric_ids: tuple[str, ...] = ()
|
|
513
|
+
allowed_option_families: tuple[str, ...] = ()
|
|
514
|
+
allowed_option_ids: tuple[str, ...] = ()
|
|
515
|
+
reflection_logical_calls: int = 1
|
|
516
|
+
|
|
517
|
+
def __post_init__(self) -> None:
|
|
518
|
+
if not callable(getattr(self.engine, "reflect", None)):
|
|
519
|
+
raise TypeError("engine must provide async reflect")
|
|
520
|
+
if not callable(getattr(self.engine, "identify_phenotype", None)):
|
|
521
|
+
raise TypeError("engine must provide phenotype identity")
|
|
522
|
+
if type(self.mailbox) is not ReflectedCardMailbox:
|
|
523
|
+
raise TypeError("mailbox must be an exact ReflectedCardMailbox")
|
|
524
|
+
if (
|
|
525
|
+
type(self.diagnostic_slot_ids) is not tuple
|
|
526
|
+
or len(self.diagnostic_slot_ids) != 2
|
|
527
|
+
or any(
|
|
528
|
+
type(value) is not str or not value
|
|
529
|
+
for value in self.diagnostic_slot_ids
|
|
530
|
+
)
|
|
531
|
+
or len(set(self.diagnostic_slot_ids)) != 2
|
|
532
|
+
):
|
|
533
|
+
raise ValueError("diagnostic_slot_ids must contain two distinct IDs")
|
|
534
|
+
if type(self.reflection_label) is not str or not self.reflection_label:
|
|
535
|
+
raise ValueError("reflection_label must be non-empty")
|
|
536
|
+
if bool(self.required_metric_ids) != bool(self.allowed_option_families):
|
|
537
|
+
raise ValueError(
|
|
538
|
+
"advanced reflection requires metrics and option families together"
|
|
539
|
+
)
|
|
540
|
+
if self.required_metric_ids:
|
|
541
|
+
ReflectionInsightContract(
|
|
542
|
+
required_metric_ids=self.required_metric_ids,
|
|
543
|
+
allowed_option_families=self.allowed_option_families,
|
|
544
|
+
allowed_option_ids=self.allowed_option_ids,
|
|
545
|
+
)
|
|
546
|
+
elif self.allowed_option_ids:
|
|
547
|
+
raise ValueError("exact option IDs require an advanced reflection contract")
|
|
548
|
+
if (
|
|
549
|
+
type(self.reflection_logical_calls) is not int
|
|
550
|
+
or self.reflection_logical_calls <= 0
|
|
551
|
+
):
|
|
552
|
+
raise ValueError("reflection_logical_calls must be positive")
|
|
553
|
+
|
|
554
|
+
@property
|
|
555
|
+
def insight_contract(self) -> ReflectionInsightContract | None:
|
|
556
|
+
if not self.required_metric_ids:
|
|
557
|
+
return None
|
|
558
|
+
return ReflectionInsightContract(
|
|
559
|
+
required_metric_ids=self.required_metric_ids,
|
|
560
|
+
allowed_option_families=self.allowed_option_families,
|
|
561
|
+
allowed_option_ids=self.allowed_option_ids,
|
|
562
|
+
)
|
|
563
|
+
|
|
564
|
+
def reserve(
|
|
565
|
+
self,
|
|
566
|
+
*,
|
|
567
|
+
state: OptimizerState,
|
|
568
|
+
plan,
|
|
569
|
+
) -> GenerationFeedbackReservation:
|
|
570
|
+
del state
|
|
571
|
+
generation = plan.generation
|
|
572
|
+
return GenerationFeedbackReservation(
|
|
573
|
+
policy_id=REFLECTIVE_FEEDBACK_POLICY_ID,
|
|
574
|
+
policy_version=REFLECTIVE_FEEDBACK_POLICY_VERSION,
|
|
575
|
+
logical_llm_calls=(
|
|
576
|
+
self.reflection_logical_calls if generation == 1 else 0
|
|
577
|
+
),
|
|
578
|
+
metadata=(("scheduled_generation", "1"),),
|
|
579
|
+
)
|
|
580
|
+
|
|
581
|
+
async def after_generation(
|
|
582
|
+
self,
|
|
583
|
+
context: GenerationFeedbackContext,
|
|
584
|
+
) -> GenerationFeedbackResult:
|
|
585
|
+
if context.plan.generation != 1:
|
|
586
|
+
return GenerationFeedbackResult(
|
|
587
|
+
logical_llm_calls_used=0,
|
|
588
|
+
metadata=_status_metadata("not_scheduled", "generation_is_not_one"),
|
|
589
|
+
)
|
|
590
|
+
results_by_slot = {
|
|
591
|
+
result.slot.slot_id: result.outcome
|
|
592
|
+
for result in context.generation_receipt.slot_results
|
|
593
|
+
}
|
|
594
|
+
if set(results_by_slot) != set(self.diagnostic_slot_ids):
|
|
595
|
+
return GenerationFeedbackResult(
|
|
596
|
+
logical_llm_calls_used=0,
|
|
597
|
+
metadata=_status_metadata(
|
|
598
|
+
"diagnostic_rejected",
|
|
599
|
+
"diagnostic_slot_ids_differ",
|
|
600
|
+
),
|
|
601
|
+
)
|
|
602
|
+
outcomes = tuple(
|
|
603
|
+
results_by_slot[slot_id] for slot_id in self.diagnostic_slot_ids
|
|
604
|
+
)
|
|
605
|
+
try:
|
|
606
|
+
_successful_diagnostic_outcomes(outcomes) # type: ignore[arg-type]
|
|
607
|
+
except (TypeError, ReflectiveFeedbackContractError) as exc:
|
|
608
|
+
return GenerationFeedbackResult(
|
|
609
|
+
logical_llm_calls_used=0,
|
|
610
|
+
metadata=_status_metadata(
|
|
611
|
+
"diagnostic_rejected",
|
|
612
|
+
type(exc).__name__,
|
|
613
|
+
),
|
|
614
|
+
)
|
|
615
|
+
phenotype_ids = {
|
|
616
|
+
self.engine.identify_phenotype(
|
|
617
|
+
outcome.candidate.configuration
|
|
618
|
+
).identity_sha256
|
|
619
|
+
for outcome in outcomes
|
|
620
|
+
if outcome.candidate is not None
|
|
621
|
+
}
|
|
622
|
+
if len(phenotype_ids) != 2:
|
|
623
|
+
return GenerationFeedbackResult(
|
|
624
|
+
logical_llm_calls_used=0,
|
|
625
|
+
metadata=_status_metadata(
|
|
626
|
+
"diagnostic_rejected",
|
|
627
|
+
"diagnostic_phenotype_collision",
|
|
628
|
+
),
|
|
629
|
+
)
|
|
630
|
+
try:
|
|
631
|
+
contract = self.insight_contract
|
|
632
|
+
if contract is None:
|
|
633
|
+
entries = await self.engine.reflect(
|
|
634
|
+
outcomes, # type: ignore[arg-type]
|
|
635
|
+
label=self.reflection_label,
|
|
636
|
+
max_insights=2,
|
|
637
|
+
min_insights=2,
|
|
638
|
+
)
|
|
639
|
+
else:
|
|
640
|
+
entries = await self.engine.reflect(
|
|
641
|
+
outcomes, # type: ignore[arg-type]
|
|
642
|
+
label=self.reflection_label,
|
|
643
|
+
max_insights=2,
|
|
644
|
+
min_insights=2,
|
|
645
|
+
insight_contract=contract,
|
|
646
|
+
)
|
|
647
|
+
except asyncio.CancelledError:
|
|
648
|
+
raise
|
|
649
|
+
except Exception as exc:
|
|
650
|
+
if (
|
|
651
|
+
classify_generation_failure(exc)
|
|
652
|
+
is not GenerationFailureDisposition.MODEL_OR_SCHEMA_FAILURE
|
|
653
|
+
):
|
|
654
|
+
# Credential, source-integrity, queue publication, provider,
|
|
655
|
+
# and programming failures are fatal experiment failures. An
|
|
656
|
+
# untyped exception must never masquerade as a clean no-card
|
|
657
|
+
# condition.
|
|
658
|
+
raise
|
|
659
|
+
return GenerationFeedbackResult(
|
|
660
|
+
logical_llm_calls_used=self.reflection_logical_calls,
|
|
661
|
+
metadata=_status_metadata(
|
|
662
|
+
"reflection_failed",
|
|
663
|
+
GenerationFailureDisposition.MODEL_OR_SCHEMA_FAILURE.value,
|
|
664
|
+
),
|
|
665
|
+
)
|
|
666
|
+
try:
|
|
667
|
+
batch = build_reflected_card_batch(
|
|
668
|
+
outcomes=outcomes, # type: ignore[arg-type]
|
|
669
|
+
entries=entries,
|
|
670
|
+
insight_contract=self.insight_contract,
|
|
671
|
+
reflection_logical_calls=self.reflection_logical_calls,
|
|
672
|
+
)
|
|
673
|
+
except (TypeError, ReflectiveFeedbackContractError) as exc:
|
|
674
|
+
return GenerationFeedbackResult(
|
|
675
|
+
logical_llm_calls_used=self.reflection_logical_calls,
|
|
676
|
+
metadata=_status_metadata(
|
|
677
|
+
"reflection_rejected",
|
|
678
|
+
type(exc).__name__,
|
|
679
|
+
),
|
|
680
|
+
)
|
|
681
|
+
self.mailbox.publish(batch)
|
|
682
|
+
return GenerationFeedbackResult(
|
|
683
|
+
logical_llm_calls_used=self.reflection_logical_calls,
|
|
684
|
+
metadata=batch.feedback_metadata,
|
|
685
|
+
)
|
|
686
|
+
|
|
687
|
+
|
|
688
|
+
class HeldOutArm(str, Enum):
|
|
689
|
+
ADAPTIVE = "adaptive"
|
|
690
|
+
SCORE_SWAPPED = "score_swapped"
|
|
691
|
+
SHAM = "sham"
|
|
692
|
+
|
|
693
|
+
|
|
694
|
+
@dataclass(frozen=True, slots=True)
|
|
695
|
+
class HeldOutArmAssignment:
|
|
696
|
+
arm: HeldOutArm
|
|
697
|
+
reference: InsightRef
|
|
698
|
+
origin_transfer_score: int | None
|
|
699
|
+
assigned_selection_score: int | None
|
|
700
|
+
|
|
701
|
+
|
|
702
|
+
@dataclass(frozen=True, slots=True)
|
|
703
|
+
class HeldOutASNAssignments:
|
|
704
|
+
adaptive: HeldOutArmAssignment
|
|
705
|
+
score_swapped: HeldOutArmAssignment
|
|
706
|
+
sham: HeldOutArmAssignment
|
|
707
|
+
|
|
708
|
+
def __post_init__(self) -> None:
|
|
709
|
+
if self.adaptive.arm is not HeldOutArm.ADAPTIVE:
|
|
710
|
+
raise ValueError("adaptive assignment has the wrong arm")
|
|
711
|
+
if self.score_swapped.arm is not HeldOutArm.SCORE_SWAPPED:
|
|
712
|
+
raise ValueError("score-swapped assignment has the wrong arm")
|
|
713
|
+
if self.sham.arm is not HeldOutArm.SHAM:
|
|
714
|
+
raise ValueError("sham assignment has the wrong arm")
|
|
715
|
+
if self.adaptive.reference == self.score_swapped.reference:
|
|
716
|
+
raise ValueError(
|
|
717
|
+
"adaptive and score-swapped arms must select different cards"
|
|
718
|
+
)
|
|
719
|
+
|
|
720
|
+
|
|
721
|
+
@dataclass(frozen=True, slots=True)
|
|
722
|
+
class HeldOutScoreMapEntry:
|
|
723
|
+
"""One card's immutable origin and assigned score in a selector map."""
|
|
724
|
+
|
|
725
|
+
reference: InsightRef
|
|
726
|
+
origin_contrast_id: str
|
|
727
|
+
origin_transfer_score: int
|
|
728
|
+
assigned_selection_score: int
|
|
729
|
+
|
|
730
|
+
def __post_init__(self) -> None:
|
|
731
|
+
ReflectedCard(
|
|
732
|
+
reference=self.reference,
|
|
733
|
+
origin_contrast_id=self.origin_contrast_id,
|
|
734
|
+
origin_transfer_score=self.origin_transfer_score,
|
|
735
|
+
)
|
|
736
|
+
if type(self.assigned_selection_score) is not int or (
|
|
737
|
+
self.assigned_selection_score not in {-1, 0, 1}
|
|
738
|
+
):
|
|
739
|
+
raise ValueError(
|
|
740
|
+
"assigned_selection_score must be exactly -1, 0, or +1"
|
|
741
|
+
)
|
|
742
|
+
|
|
743
|
+
def to_record(self) -> dict[str, object]:
|
|
744
|
+
self.__post_init__()
|
|
745
|
+
return {
|
|
746
|
+
"insight_id": self.reference.insight_id.value,
|
|
747
|
+
"insight_version": self.reference.version,
|
|
748
|
+
"origin_contrast_id": self.origin_contrast_id,
|
|
749
|
+
"origin_transfer_score": self.origin_transfer_score,
|
|
750
|
+
"assigned_selection_score": self.assigned_selection_score,
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
|
|
754
|
+
def _reference_record(reference: InsightRef) -> dict[str, object]:
|
|
755
|
+
if type(reference) is not InsightRef:
|
|
756
|
+
raise TypeError("reference must be an exact InsightRef")
|
|
757
|
+
InsightRef.__post_init__(reference)
|
|
758
|
+
return {
|
|
759
|
+
"insight_id": reference.insight_id.value,
|
|
760
|
+
"insight_version": reference.version,
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
|
|
764
|
+
@dataclass(frozen=True, slots=True)
|
|
765
|
+
class HeldOutASNAssignmentCommitment:
|
|
766
|
+
"""Authenticated full true/swapped selector assignment for one A/S/N wave.
|
|
767
|
+
|
|
768
|
+
Both two-card maps are retained so a downstream prequeue gate can prove
|
|
769
|
+
that the prompts it is about to release correspond to the prospectively
|
|
770
|
+
selected correct, score-swapped, and sham references. The commitment is
|
|
771
|
+
benchmark-neutral: domains choose cards and actions, while this record
|
|
772
|
+
binds only selector semantics and immutable insight references.
|
|
773
|
+
"""
|
|
774
|
+
|
|
775
|
+
true_score_map: tuple[HeldOutScoreMapEntry, HeldOutScoreMapEntry]
|
|
776
|
+
score_swapped_map: tuple[HeldOutScoreMapEntry, HeldOutScoreMapEntry]
|
|
777
|
+
common_score_multiset: tuple[int, int]
|
|
778
|
+
adaptive_reference: InsightRef
|
|
779
|
+
score_swapped_reference: InsightRef
|
|
780
|
+
sham_reference: InsightRef
|
|
781
|
+
selector_policy_id: str = HELD_OUT_SELECTOR_POLICY_ID
|
|
782
|
+
selector_policy_version: int = HELD_OUT_SELECTOR_POLICY_VERSION
|
|
783
|
+
|
|
784
|
+
def __post_init__(self) -> None:
|
|
785
|
+
if self.selector_policy_id != HELD_OUT_SELECTOR_POLICY_ID:
|
|
786
|
+
raise ValueError("assignment names a different held-out selector policy")
|
|
787
|
+
if self.selector_policy_version != HELD_OUT_SELECTOR_POLICY_VERSION:
|
|
788
|
+
raise ValueError("assignment names a different selector policy version")
|
|
789
|
+
maps = (self.true_score_map, self.score_swapped_map)
|
|
790
|
+
if any(
|
|
791
|
+
type(score_map) is not tuple
|
|
792
|
+
or len(score_map) != 2
|
|
793
|
+
or any(type(item) is not HeldOutScoreMapEntry for item in score_map)
|
|
794
|
+
for score_map in maps
|
|
795
|
+
):
|
|
796
|
+
raise TypeError("each score map must contain two exact map entries")
|
|
797
|
+
for score_map in maps:
|
|
798
|
+
for item in score_map:
|
|
799
|
+
item.__post_init__()
|
|
800
|
+
if score_map != tuple(
|
|
801
|
+
sorted(
|
|
802
|
+
score_map,
|
|
803
|
+
key=lambda item: (
|
|
804
|
+
item.reference.insight_id.value,
|
|
805
|
+
item.reference.version,
|
|
806
|
+
),
|
|
807
|
+
)
|
|
808
|
+
):
|
|
809
|
+
raise ValueError("score maps must be canonically reference-ordered")
|
|
810
|
+
true_by_reference = {item.reference: item for item in self.true_score_map}
|
|
811
|
+
swapped_by_reference = {
|
|
812
|
+
item.reference: item for item in self.score_swapped_map
|
|
813
|
+
}
|
|
814
|
+
if len(true_by_reference) != 2 or set(true_by_reference) != set(
|
|
815
|
+
swapped_by_reference
|
|
816
|
+
):
|
|
817
|
+
raise ValueError("true and swapped maps must bind the same two cards")
|
|
818
|
+
for reference, true_entry in true_by_reference.items():
|
|
819
|
+
swapped_entry = swapped_by_reference[reference]
|
|
820
|
+
if (
|
|
821
|
+
true_entry.origin_contrast_id != swapped_entry.origin_contrast_id
|
|
822
|
+
or true_entry.origin_transfer_score
|
|
823
|
+
!= swapped_entry.origin_transfer_score
|
|
824
|
+
or true_entry.assigned_selection_score
|
|
825
|
+
!= true_entry.origin_transfer_score
|
|
826
|
+
):
|
|
827
|
+
raise ValueError("score maps changed immutable card provenance")
|
|
828
|
+
if type(self.common_score_multiset) is not tuple or (
|
|
829
|
+
len(self.common_score_multiset) != 2
|
|
830
|
+
):
|
|
831
|
+
raise TypeError("common_score_multiset must contain exactly two scores")
|
|
832
|
+
origin_scores = tuple(
|
|
833
|
+
sorted(item.origin_transfer_score for item in self.true_score_map)
|
|
834
|
+
)
|
|
835
|
+
if self.common_score_multiset != origin_scores:
|
|
836
|
+
raise ValueError("common score multiset differs from origin scores")
|
|
837
|
+
if tuple(
|
|
838
|
+
sorted(item.assigned_selection_score for item in self.true_score_map)
|
|
839
|
+
) != self.common_score_multiset or tuple(
|
|
840
|
+
sorted(
|
|
841
|
+
item.assigned_selection_score for item in self.score_swapped_map
|
|
842
|
+
)
|
|
843
|
+
) != self.common_score_multiset:
|
|
844
|
+
raise ValueError("true and swapped maps must share one score multiset")
|
|
845
|
+
if all(
|
|
846
|
+
true_by_reference[reference].assigned_selection_score
|
|
847
|
+
== swapped_by_reference[reference].assigned_selection_score
|
|
848
|
+
for reference in true_by_reference
|
|
849
|
+
):
|
|
850
|
+
raise ValueError("score-swapped map must actually exchange the scores")
|
|
851
|
+
for reference in (
|
|
852
|
+
self.adaptive_reference,
|
|
853
|
+
self.score_swapped_reference,
|
|
854
|
+
self.sham_reference,
|
|
855
|
+
):
|
|
856
|
+
_reference_record(reference)
|
|
857
|
+
if self.adaptive_reference not in true_by_reference:
|
|
858
|
+
raise ValueError("adaptive reference is absent from the true score map")
|
|
859
|
+
if self.score_swapped_reference not in true_by_reference:
|
|
860
|
+
raise ValueError(
|
|
861
|
+
"score-swapped reference is absent from the reflected score maps"
|
|
862
|
+
)
|
|
863
|
+
if self.adaptive_reference == self.score_swapped_reference:
|
|
864
|
+
raise ValueError("adaptive and score-swapped references must differ")
|
|
865
|
+
if self.sham_reference in true_by_reference:
|
|
866
|
+
raise ValueError("sham reference must be outside both reflected maps")
|
|
867
|
+
high_score = max(self.common_score_multiset)
|
|
868
|
+
if (
|
|
869
|
+
true_by_reference[self.adaptive_reference].assigned_selection_score
|
|
870
|
+
!= high_score
|
|
871
|
+
or swapped_by_reference[
|
|
872
|
+
self.score_swapped_reference
|
|
873
|
+
].assigned_selection_score
|
|
874
|
+
!= high_score
|
|
875
|
+
):
|
|
876
|
+
raise ValueError("chosen A/S references do not receive the high score")
|
|
877
|
+
|
|
878
|
+
def _unsigned_record(self) -> dict[str, object]:
|
|
879
|
+
self.__post_init__()
|
|
880
|
+
return {
|
|
881
|
+
"schema_version": 1,
|
|
882
|
+
"selector_policy_id": self.selector_policy_id,
|
|
883
|
+
"selector_policy_version": self.selector_policy_version,
|
|
884
|
+
"true_score_map": [item.to_record() for item in self.true_score_map],
|
|
885
|
+
"score_swapped_map": [
|
|
886
|
+
item.to_record() for item in self.score_swapped_map
|
|
887
|
+
],
|
|
888
|
+
"common_score_multiset": list(self.common_score_multiset),
|
|
889
|
+
"chosen_references": {
|
|
890
|
+
"adaptive": _reference_record(self.adaptive_reference),
|
|
891
|
+
"score_swapped": _reference_record(
|
|
892
|
+
self.score_swapped_reference
|
|
893
|
+
),
|
|
894
|
+
"sham": _reference_record(self.sham_reference),
|
|
895
|
+
},
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
@property
|
|
899
|
+
def assignment_sha256(self) -> str:
|
|
900
|
+
payload = json.dumps(
|
|
901
|
+
self._unsigned_record(),
|
|
902
|
+
allow_nan=False,
|
|
903
|
+
ensure_ascii=True,
|
|
904
|
+
separators=(",", ":"),
|
|
905
|
+
sort_keys=True,
|
|
906
|
+
).encode("ascii")
|
|
907
|
+
return hashlib.sha256(_ASSIGNMENT_COMMITMENT_DOMAIN + payload).hexdigest()
|
|
908
|
+
|
|
909
|
+
def to_record(self) -> dict[str, object]:
|
|
910
|
+
record = self._unsigned_record()
|
|
911
|
+
return {**record, "assignment_sha256": self.assignment_sha256}
|
|
912
|
+
|
|
913
|
+
|
|
914
|
+
@dataclass(frozen=True, slots=True)
|
|
915
|
+
class HeldOutASNPlanSet:
|
|
916
|
+
assignments: HeldOutASNAssignments
|
|
917
|
+
assignment_commitment: HeldOutASNAssignmentCommitment
|
|
918
|
+
adaptive: InvocationPlan
|
|
919
|
+
score_swapped: InvocationPlan
|
|
920
|
+
sham: InvocationPlan
|
|
921
|
+
|
|
922
|
+
|
|
923
|
+
def register_neutral_sham_card(
|
|
924
|
+
*,
|
|
925
|
+
memory: InsightMemoryBank,
|
|
926
|
+
affected_paths: tuple[str, ...],
|
|
927
|
+
applicable_operator_kinds: tuple[str, ...],
|
|
928
|
+
insight_contract: ReflectionInsightContract | None = None,
|
|
929
|
+
) -> InsightRef:
|
|
930
|
+
"""Register a schema-matched exploratory hypothesis with neutral effects."""
|
|
931
|
+
|
|
932
|
+
if insight_contract is not None:
|
|
933
|
+
if type(insight_contract) is not ReflectionInsightContract:
|
|
934
|
+
raise TypeError(
|
|
935
|
+
"insight_contract must be an exact ReflectionInsightContract"
|
|
936
|
+
)
|
|
937
|
+
ReflectionInsightContract.__post_init__(insight_contract)
|
|
938
|
+
entry, _ = memory.add(
|
|
939
|
+
InsightDraft(
|
|
940
|
+
claim=(
|
|
941
|
+
"The supplied palette contains legal coordinated interventions "
|
|
942
|
+
"in each listed option family."
|
|
943
|
+
),
|
|
944
|
+
trigger=("The frozen parent admits the listed finite action families."),
|
|
945
|
+
mechanism=(
|
|
946
|
+
"Each finite option specifies an internally consistent coordinated "
|
|
947
|
+
"change within its named family."
|
|
948
|
+
),
|
|
949
|
+
affected_paths=affected_paths,
|
|
950
|
+
evidence_summary="Schema-matched factual palette description.",
|
|
951
|
+
confidence=0.5,
|
|
952
|
+
evidence_contrast_ids=(),
|
|
953
|
+
effect_predictions=(
|
|
954
|
+
()
|
|
955
|
+
if insight_contract is None
|
|
956
|
+
else tuple(
|
|
957
|
+
MetricEffectPrediction(
|
|
958
|
+
metric_id=metric_id,
|
|
959
|
+
direction=MetricEffectDirection.UNKNOWN,
|
|
960
|
+
)
|
|
961
|
+
for metric_id in insight_contract.required_metric_ids
|
|
962
|
+
)
|
|
963
|
+
),
|
|
964
|
+
recommended_option_families=(
|
|
965
|
+
()
|
|
966
|
+
if insight_contract is None
|
|
967
|
+
else insight_contract.allowed_option_families
|
|
968
|
+
),
|
|
969
|
+
recommended_option_ids=(
|
|
970
|
+
()
|
|
971
|
+
if insight_contract is None
|
|
972
|
+
else insight_contract.allowed_option_ids
|
|
973
|
+
),
|
|
974
|
+
action_template=(
|
|
975
|
+
None
|
|
976
|
+
if insight_contract is None
|
|
977
|
+
else (
|
|
978
|
+
"A legal option is represented by its named family and sealed "
|
|
979
|
+
"option identifier."
|
|
980
|
+
)
|
|
981
|
+
),
|
|
982
|
+
falsification_condition=(
|
|
983
|
+
None
|
|
984
|
+
if insight_contract is None
|
|
985
|
+
else (
|
|
986
|
+
"The two named held-out metric values are the complete empirical "
|
|
987
|
+
"check for the intervention."
|
|
988
|
+
)
|
|
989
|
+
),
|
|
990
|
+
),
|
|
991
|
+
applicable_operator_kinds=applicable_operator_kinds,
|
|
992
|
+
origin=InsightOrigin.MANUAL,
|
|
993
|
+
)
|
|
994
|
+
if insight_contract is not None:
|
|
995
|
+
validate_reflection_insight_draft(
|
|
996
|
+
entry.draft,
|
|
997
|
+
insight_contract,
|
|
998
|
+
allow_all_unknown=True,
|
|
999
|
+
allow_missing_evidence=True,
|
|
1000
|
+
)
|
|
1001
|
+
if any(
|
|
1002
|
+
prediction.direction is not MetricEffectDirection.UNKNOWN
|
|
1003
|
+
for prediction in entry.draft.effect_predictions
|
|
1004
|
+
):
|
|
1005
|
+
raise ValueError("the neutral sham must use only unknown predictions")
|
|
1006
|
+
if (
|
|
1007
|
+
entry.draft.recommended_option_families
|
|
1008
|
+
!= insight_contract.allowed_option_families
|
|
1009
|
+
):
|
|
1010
|
+
raise ValueError(
|
|
1011
|
+
"the neutral sham must expose the complete option-family vocabulary"
|
|
1012
|
+
)
|
|
1013
|
+
if entry.draft.recommended_option_ids != insight_contract.allowed_option_ids:
|
|
1014
|
+
raise ValueError(
|
|
1015
|
+
"the neutral sham must expose the precommitted exact option IDs"
|
|
1016
|
+
)
|
|
1017
|
+
return entry.reference
|
|
1018
|
+
|
|
1019
|
+
|
|
1020
|
+
def _path_text(path: JsonPath) -> str:
|
|
1021
|
+
parts = ["$"]
|
|
1022
|
+
for segment in path.segments:
|
|
1023
|
+
if type(segment) is ObjectKey:
|
|
1024
|
+
parts.append(f".{segment.value}")
|
|
1025
|
+
elif type(segment) is ArrayIndex:
|
|
1026
|
+
parts.append(f"[{segment.value}]")
|
|
1027
|
+
else: # pragma: no cover - JsonPath closes the segment union.
|
|
1028
|
+
raise AssertionError("unsupported JSON path segment")
|
|
1029
|
+
return "".join(parts)
|
|
1030
|
+
|
|
1031
|
+
|
|
1032
|
+
def _editable_paths(plan: InvocationPlan) -> tuple[str, ...] | None:
|
|
1033
|
+
contract = plan.mutation_contract
|
|
1034
|
+
if contract is not None:
|
|
1035
|
+
return tuple(_path_text(path) for path in contract.editable_paths)
|
|
1036
|
+
if plan.allowed_top_level:
|
|
1037
|
+
return tuple(f"$.{key}" for key in plan.allowed_top_level)
|
|
1038
|
+
return None
|
|
1039
|
+
|
|
1040
|
+
|
|
1041
|
+
def _exact_treatment_requirement(
|
|
1042
|
+
*,
|
|
1043
|
+
memory: InsightMemoryBank,
|
|
1044
|
+
reference: InsightRef,
|
|
1045
|
+
plan: InvocationPlan,
|
|
1046
|
+
assignment_role: TreatmentAssignmentRole,
|
|
1047
|
+
) -> InsightTreatmentRequirement:
|
|
1048
|
+
"""Bind one assigned card to its exact options in this parent palette."""
|
|
1049
|
+
|
|
1050
|
+
entry = _entry_for_reference(memory, reference)
|
|
1051
|
+
evidence = TreatmentInsightEvidence(
|
|
1052
|
+
reference=entry.reference,
|
|
1053
|
+
insight_content_sha256=entry.draft.content_sha256,
|
|
1054
|
+
applicable_operator_kinds=entry.applicable_operator_kinds,
|
|
1055
|
+
affected_paths=tuple(sorted(entry.draft.affected_paths)),
|
|
1056
|
+
recommended_option_families=tuple(
|
|
1057
|
+
sorted(entry.draft.recommended_option_families)
|
|
1058
|
+
),
|
|
1059
|
+
recommended_option_ids=tuple(sorted(entry.draft.recommended_option_ids)),
|
|
1060
|
+
)
|
|
1061
|
+
if not evidence.recommended_option_ids:
|
|
1062
|
+
raise ValueError(
|
|
1063
|
+
"exact treatment assignment requires card-recommended option IDs"
|
|
1064
|
+
)
|
|
1065
|
+
contract = plan.finite_variation_contract
|
|
1066
|
+
if contract is None:
|
|
1067
|
+
raise ValueError("exact treatment assignment requires a finite contract")
|
|
1068
|
+
allowed_actions = tuple(
|
|
1069
|
+
sorted(
|
|
1070
|
+
(
|
|
1071
|
+
TreatmentActionBinding(
|
|
1072
|
+
option_id=option_id,
|
|
1073
|
+
option_identity_sha256=contract.resolve(
|
|
1074
|
+
option_id
|
|
1075
|
+
).identity_sha256,
|
|
1076
|
+
)
|
|
1077
|
+
for option_id in evidence.recommended_option_ids
|
|
1078
|
+
),
|
|
1079
|
+
key=lambda value: (value.option_id, value.option_identity_sha256),
|
|
1080
|
+
)
|
|
1081
|
+
)
|
|
1082
|
+
return InsightTreatmentRequirement(
|
|
1083
|
+
insight_bindings=(evidence.binding(),),
|
|
1084
|
+
finite_contract_sha256=contract.identity_sha256,
|
|
1085
|
+
allowed_actions=allowed_actions,
|
|
1086
|
+
claim_mode=TreatmentClaimMode.EXACT_REQUIRED,
|
|
1087
|
+
assignment_role=assignment_role,
|
|
1088
|
+
)
|
|
1089
|
+
|
|
1090
|
+
|
|
1091
|
+
def _plan_shape(plan: InvocationPlan) -> tuple[object, ...]:
|
|
1092
|
+
return (
|
|
1093
|
+
plan.operator_kind,
|
|
1094
|
+
plan.parents,
|
|
1095
|
+
plan.generation,
|
|
1096
|
+
plan.common_ancestor,
|
|
1097
|
+
plan.allowed_top_level,
|
|
1098
|
+
plan.phase,
|
|
1099
|
+
plan.mutation_contract,
|
|
1100
|
+
plan.mutation_response_mode,
|
|
1101
|
+
plan.atomic_replacement_options,
|
|
1102
|
+
plan.finite_variation_contract,
|
|
1103
|
+
plan.memory_subset_size,
|
|
1104
|
+
)
|
|
1105
|
+
|
|
1106
|
+
|
|
1107
|
+
@dataclass(frozen=True, slots=True)
|
|
1108
|
+
class HeldOutASNPlannerAdapter:
|
|
1109
|
+
"""Resolve correct/swapped/sham cards and bind three matched base plans."""
|
|
1110
|
+
|
|
1111
|
+
mailbox: ReflectedCardMailbox
|
|
1112
|
+
memory: InsightMemoryBank
|
|
1113
|
+
sham_reference: InsightRef
|
|
1114
|
+
|
|
1115
|
+
def __post_init__(self) -> None:
|
|
1116
|
+
if type(self.mailbox) is not ReflectedCardMailbox:
|
|
1117
|
+
raise TypeError("mailbox must be an exact ReflectedCardMailbox")
|
|
1118
|
+
if type(self.memory) is not InsightMemoryBank:
|
|
1119
|
+
raise TypeError("memory must be an exact InsightMemoryBank")
|
|
1120
|
+
if type(self.sham_reference) is not InsightRef:
|
|
1121
|
+
raise TypeError("sham_reference must be an exact InsightRef")
|
|
1122
|
+
sham = _entry_for_reference(self.memory, self.sham_reference)
|
|
1123
|
+
if (
|
|
1124
|
+
sham.origin is not InsightOrigin.MANUAL
|
|
1125
|
+
or sham.lifecycle_state is not InsightLifecycleState.QUARANTINED
|
|
1126
|
+
or sham.evidence_lineage is not None
|
|
1127
|
+
or sham.draft.evidence_contrast_ids
|
|
1128
|
+
):
|
|
1129
|
+
raise ValueError(
|
|
1130
|
+
"sham_reference must identify a manual, evidence-free quarantine card"
|
|
1131
|
+
)
|
|
1132
|
+
|
|
1133
|
+
def resolve(self, state: OptimizerState) -> HeldOutASNAssignments:
|
|
1134
|
+
batch = self.mailbox.read_verified(state=state)
|
|
1135
|
+
if batch.cards[0].origin_transfer_score == batch.cards[1].origin_transfer_score:
|
|
1136
|
+
raise HeldOutAssignmentUnavailable(
|
|
1137
|
+
HeldOutAssignmentUnavailableReason.EQUAL_ORIGIN_SCORES,
|
|
1138
|
+
"tied origin scores provide no correct-vs-swapped contrast",
|
|
1139
|
+
)
|
|
1140
|
+
low, high = sorted(
|
|
1141
|
+
batch.cards,
|
|
1142
|
+
key=lambda card: (card.origin_transfer_score, card.origin_contrast_id),
|
|
1143
|
+
)
|
|
1144
|
+
if self.sham_reference in {low.reference, high.reference}:
|
|
1145
|
+
raise ReflectiveFeedbackContractError(
|
|
1146
|
+
"sham card must differ from both outcome-grounded cards"
|
|
1147
|
+
)
|
|
1148
|
+
return HeldOutASNAssignments(
|
|
1149
|
+
adaptive=HeldOutArmAssignment(
|
|
1150
|
+
HeldOutArm.ADAPTIVE,
|
|
1151
|
+
high.reference,
|
|
1152
|
+
high.origin_transfer_score,
|
|
1153
|
+
high.origin_transfer_score,
|
|
1154
|
+
),
|
|
1155
|
+
score_swapped=HeldOutArmAssignment(
|
|
1156
|
+
HeldOutArm.SCORE_SWAPPED,
|
|
1157
|
+
low.reference,
|
|
1158
|
+
low.origin_transfer_score,
|
|
1159
|
+
high.origin_transfer_score,
|
|
1160
|
+
),
|
|
1161
|
+
sham=HeldOutArmAssignment(
|
|
1162
|
+
HeldOutArm.SHAM,
|
|
1163
|
+
self.sham_reference,
|
|
1164
|
+
None,
|
|
1165
|
+
None,
|
|
1166
|
+
),
|
|
1167
|
+
)
|
|
1168
|
+
|
|
1169
|
+
def bind_plans(
|
|
1170
|
+
self,
|
|
1171
|
+
state: OptimizerState,
|
|
1172
|
+
*,
|
|
1173
|
+
adaptive_base: InvocationPlan,
|
|
1174
|
+
score_swapped_base: InvocationPlan,
|
|
1175
|
+
sham_base: InvocationPlan,
|
|
1176
|
+
) -> HeldOutASNPlanSet:
|
|
1177
|
+
bases = (adaptive_base, score_swapped_base, sham_base)
|
|
1178
|
+
if any(type(plan) is not InvocationPlan for plan in bases):
|
|
1179
|
+
raise TypeError("held-out bases must be exact InvocationPlan values")
|
|
1180
|
+
for plan in bases:
|
|
1181
|
+
InvocationPlan.__post_init__(plan)
|
|
1182
|
+
if (
|
|
1183
|
+
plan.use_memory
|
|
1184
|
+
or plan.quarantine_test_insights
|
|
1185
|
+
or plan.resolved_insight_assignment is not None
|
|
1186
|
+
):
|
|
1187
|
+
raise ValueError(
|
|
1188
|
+
"held-out base plans must not carry memory assignments"
|
|
1189
|
+
)
|
|
1190
|
+
if len({plan.label for plan in bases}) != 3:
|
|
1191
|
+
raise ValueError("held-out base plan labels must be distinct")
|
|
1192
|
+
first_shape = _plan_shape(bases[0])
|
|
1193
|
+
if any(_plan_shape(plan) != first_shape for plan in bases[1:]):
|
|
1194
|
+
raise ValueError("held-out base plans differ outside label and assignment")
|
|
1195
|
+
if adaptive_base.generation != 2 or state.generation != 1:
|
|
1196
|
+
raise ValueError("the held-out A/S/N block must be generation two")
|
|
1197
|
+
|
|
1198
|
+
batch = self.mailbox.read_verified(state=state)
|
|
1199
|
+
if len(adaptive_base.parents) != 1:
|
|
1200
|
+
raise ValueError("held-out A/S/N plans require exactly one shared parent")
|
|
1201
|
+
if adaptive_base.parents[0].candidate_id == batch.diagnostic_parent_id:
|
|
1202
|
+
raise ValueError("held-out parent must differ from the diagnostic parent")
|
|
1203
|
+
assignments = self.resolve(state)
|
|
1204
|
+
references = (
|
|
1205
|
+
assignments.adaptive.reference,
|
|
1206
|
+
assignments.score_swapped.reference,
|
|
1207
|
+
assignments.sham.reference,
|
|
1208
|
+
)
|
|
1209
|
+
for plan, reference in zip(bases, references, strict=True):
|
|
1210
|
+
try:
|
|
1211
|
+
self.memory.validate_quarantine_test_assignment(
|
|
1212
|
+
(reference,),
|
|
1213
|
+
operator_kind=plan.operator_kind.value,
|
|
1214
|
+
editable_paths=_editable_paths(plan),
|
|
1215
|
+
)
|
|
1216
|
+
except QuarantineAssignmentStructuralError as exc:
|
|
1217
|
+
raise HeldOutAssignmentUnavailable(
|
|
1218
|
+
HeldOutAssignmentUnavailableReason.STRUCTURALLY_INAPPLICABLE_ASSIGNMENT,
|
|
1219
|
+
(
|
|
1220
|
+
"one otherwise valid quarantine card is structurally "
|
|
1221
|
+
"inapplicable to its matched held-out plan"
|
|
1222
|
+
),
|
|
1223
|
+
) from exc
|
|
1224
|
+
try:
|
|
1225
|
+
treatment_requirements = tuple(
|
|
1226
|
+
_exact_treatment_requirement(
|
|
1227
|
+
memory=self.memory,
|
|
1228
|
+
reference=reference,
|
|
1229
|
+
plan=plan,
|
|
1230
|
+
assignment_role=(
|
|
1231
|
+
TreatmentAssignmentRole.SHAM_CONTROL
|
|
1232
|
+
if index == 2
|
|
1233
|
+
else TreatmentAssignmentRole.ACTIVE
|
|
1234
|
+
),
|
|
1235
|
+
)
|
|
1236
|
+
for index, (plan, reference) in enumerate(
|
|
1237
|
+
zip(bases, references, strict=True)
|
|
1238
|
+
)
|
|
1239
|
+
)
|
|
1240
|
+
except (TypeError, ValueError) as exc:
|
|
1241
|
+
raise HeldOutAssignmentUnavailable(
|
|
1242
|
+
HeldOutAssignmentUnavailableReason.STRUCTURALLY_INAPPLICABLE_ASSIGNMENT,
|
|
1243
|
+
(
|
|
1244
|
+
"one assigned card lacks an exact action binding in its "
|
|
1245
|
+
"matched held-out finite palette"
|
|
1246
|
+
),
|
|
1247
|
+
) from exc
|
|
1248
|
+
true_entries = tuple(
|
|
1249
|
+
sorted(
|
|
1250
|
+
(
|
|
1251
|
+
HeldOutScoreMapEntry(
|
|
1252
|
+
reference=card.reference,
|
|
1253
|
+
origin_contrast_id=card.origin_contrast_id,
|
|
1254
|
+
origin_transfer_score=card.origin_transfer_score,
|
|
1255
|
+
assigned_selection_score=card.origin_transfer_score,
|
|
1256
|
+
)
|
|
1257
|
+
for card in batch.cards
|
|
1258
|
+
),
|
|
1259
|
+
key=lambda item: (
|
|
1260
|
+
item.reference.insight_id.value,
|
|
1261
|
+
item.reference.version,
|
|
1262
|
+
),
|
|
1263
|
+
)
|
|
1264
|
+
)
|
|
1265
|
+
swapped_scores = tuple(
|
|
1266
|
+
reversed(tuple(item.origin_transfer_score for item in true_entries))
|
|
1267
|
+
)
|
|
1268
|
+
score_swapped_entries = tuple(
|
|
1269
|
+
replace(item, assigned_selection_score=score)
|
|
1270
|
+
for item, score in zip(true_entries, swapped_scores, strict=True)
|
|
1271
|
+
)
|
|
1272
|
+
assignment_commitment = HeldOutASNAssignmentCommitment(
|
|
1273
|
+
true_score_map=true_entries, # type: ignore[arg-type]
|
|
1274
|
+
score_swapped_map=score_swapped_entries, # type: ignore[arg-type]
|
|
1275
|
+
common_score_multiset=tuple( # type: ignore[arg-type]
|
|
1276
|
+
sorted(item.origin_transfer_score for item in true_entries)
|
|
1277
|
+
),
|
|
1278
|
+
adaptive_reference=assignments.adaptive.reference,
|
|
1279
|
+
score_swapped_reference=assignments.score_swapped.reference,
|
|
1280
|
+
sham_reference=assignments.sham.reference,
|
|
1281
|
+
)
|
|
1282
|
+
return HeldOutASNPlanSet(
|
|
1283
|
+
assignments=assignments,
|
|
1284
|
+
assignment_commitment=assignment_commitment,
|
|
1285
|
+
adaptive=replace(
|
|
1286
|
+
adaptive_base,
|
|
1287
|
+
quarantine_test_insights=(assignments.adaptive.reference,),
|
|
1288
|
+
insight_treatment_requirement=treatment_requirements[0],
|
|
1289
|
+
),
|
|
1290
|
+
score_swapped=replace(
|
|
1291
|
+
score_swapped_base,
|
|
1292
|
+
quarantine_test_insights=(assignments.score_swapped.reference,),
|
|
1293
|
+
insight_treatment_requirement=treatment_requirements[1],
|
|
1294
|
+
),
|
|
1295
|
+
sham=replace(
|
|
1296
|
+
sham_base,
|
|
1297
|
+
quarantine_test_insights=(assignments.sham.reference,),
|
|
1298
|
+
insight_treatment_requirement=treatment_requirements[2],
|
|
1299
|
+
),
|
|
1300
|
+
)
|
|
1301
|
+
|
|
1302
|
+
|
|
1303
|
+
__all__ = [
|
|
1304
|
+
"G1ReflectionFeedbackInterceptor",
|
|
1305
|
+
"HeldOutASNAssignments",
|
|
1306
|
+
"HeldOutASNAssignmentCommitment",
|
|
1307
|
+
"HeldOutASNPlanSet",
|
|
1308
|
+
"HeldOutASNPlannerAdapter",
|
|
1309
|
+
"HeldOutArm",
|
|
1310
|
+
"HeldOutArmAssignment",
|
|
1311
|
+
"HeldOutScoreMapEntry",
|
|
1312
|
+
"HELD_OUT_SELECTOR_POLICY_ID",
|
|
1313
|
+
"HELD_OUT_SELECTOR_POLICY_VERSION",
|
|
1314
|
+
"HeldOutAssignmentUnavailable",
|
|
1315
|
+
"HeldOutAssignmentUnavailableReason",
|
|
1316
|
+
"REFLECTIVE_FEEDBACK_POLICY_ID",
|
|
1317
|
+
"REFLECTIVE_FEEDBACK_POLICY_VERSION",
|
|
1318
|
+
"ReflectedCard",
|
|
1319
|
+
"ReflectedCardBatch",
|
|
1320
|
+
"ReflectedCardMailbox",
|
|
1321
|
+
"ReflectiveFeedbackContractError",
|
|
1322
|
+
"build_reflected_card_batch",
|
|
1323
|
+
"reflection_contrast_id",
|
|
1324
|
+
"register_neutral_sham_card",
|
|
1325
|
+
]
|