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,714 @@
|
|
|
1
|
+
"""Randomized fixed-size insight retrieval with logged causal propensities.
|
|
2
|
+
|
|
3
|
+
The policy mixes a deterministic top-k subset with a uniformly random k-subset.
|
|
4
|
+
That small, explicit exploration component gives every eligible insight a known
|
|
5
|
+
conditional inclusion probability. Accumulated trials can therefore estimate
|
|
6
|
+
selected-versus-unselected marginal effects without pretending that an unseen
|
|
7
|
+
insight caused the outcome of one batch.
|
|
8
|
+
|
|
9
|
+
The estimators are intentionally modest. They provide stabilized inverse-
|
|
10
|
+
propensity contrasts for one context stratum and an optional two-insight
|
|
11
|
+
interaction contrast. They do not claim to solve nonstationarity, interference,
|
|
12
|
+
or arbitrary high-order subset interactions; the complete decisions remain
|
|
13
|
+
available for richer offline models.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import math
|
|
19
|
+
from dataclasses import dataclass
|
|
20
|
+
from enum import Enum
|
|
21
|
+
from fractions import Fraction
|
|
22
|
+
from numbers import Real
|
|
23
|
+
from typing import Mapping, Optional, Protocol, Sequence, Tuple
|
|
24
|
+
|
|
25
|
+
from agent_evolve.domain.ids import CandidateId, OperatorInvocationId
|
|
26
|
+
from agent_evolve.domain.insight import InsightRef
|
|
27
|
+
|
|
28
|
+
_LOWER_SHA256 = frozenset("0123456789abcdef")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class RandomSubsetSource(Protocol):
|
|
32
|
+
"""The narrow random-source surface needed by the selector."""
|
|
33
|
+
|
|
34
|
+
def randrange(self, stop: int) -> int: ...
|
|
35
|
+
def sample(self, population: Sequence[InsightRef], k: int) -> list[InsightRef]: ...
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class InsightSelectionMode(str, Enum):
|
|
39
|
+
EXPLOIT = "exploit"
|
|
40
|
+
EXPLORE_UNIFORM = "explore_uniform"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _require_hash(value: str, name: str) -> None:
|
|
44
|
+
if (
|
|
45
|
+
type(value) is not str
|
|
46
|
+
or len(value) != 64
|
|
47
|
+
or any(character not in _LOWER_SHA256 for character in value)
|
|
48
|
+
):
|
|
49
|
+
raise ValueError(f"{name} must be a lowercase SHA-256 digest")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _finite_score(value: Real, name: str) -> float:
|
|
53
|
+
if isinstance(value, bool) or not isinstance(value, Real):
|
|
54
|
+
raise TypeError(f"{name} must be a real number")
|
|
55
|
+
result = float(value)
|
|
56
|
+
if not math.isfinite(result):
|
|
57
|
+
raise ValueError(f"{name} must be finite")
|
|
58
|
+
return result
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _sorted_refs(values: Sequence[InsightRef]) -> Tuple[InsightRef, ...]:
|
|
62
|
+
result = tuple(sorted(values))
|
|
63
|
+
if any(not isinstance(value, InsightRef) for value in result):
|
|
64
|
+
raise TypeError("insight collections must contain InsightRef values")
|
|
65
|
+
if len(set(result)) != len(result):
|
|
66
|
+
raise ValueError("insight collections cannot contain duplicates")
|
|
67
|
+
return result
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _top_k(
|
|
71
|
+
score_snapshot: Tuple[Tuple[InsightRef, float], ...],
|
|
72
|
+
subset_size: int,
|
|
73
|
+
) -> Tuple[InsightRef, ...]:
|
|
74
|
+
ranked = sorted(
|
|
75
|
+
score_snapshot,
|
|
76
|
+
key=lambda item: (-item[1], item[0].insight_id.value, item[0].version),
|
|
77
|
+
)
|
|
78
|
+
return tuple(sorted(reference for reference, _ in ranked[:subset_size]))
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@dataclass(frozen=True, slots=True)
|
|
82
|
+
class InsightSelectionDecision:
|
|
83
|
+
"""One replayable retrieval decision and its conditional assignment law."""
|
|
84
|
+
|
|
85
|
+
context_hash: str
|
|
86
|
+
eligible: Tuple[InsightRef, ...]
|
|
87
|
+
selected: Tuple[InsightRef, ...]
|
|
88
|
+
exploitation_subset: Tuple[InsightRef, ...]
|
|
89
|
+
score_snapshot: Tuple[Tuple[InsightRef, float], ...]
|
|
90
|
+
subset_size: int
|
|
91
|
+
exploration_probability: Fraction
|
|
92
|
+
mode: InsightSelectionMode
|
|
93
|
+
selected_subset_probability: Fraction
|
|
94
|
+
policy_id: str = "epsilon_greedy_uniform_k_subset"
|
|
95
|
+
policy_version: int = 1
|
|
96
|
+
|
|
97
|
+
def __post_init__(self) -> None:
|
|
98
|
+
_require_hash(self.context_hash, "context_hash")
|
|
99
|
+
eligible = _sorted_refs(self.eligible)
|
|
100
|
+
selected = _sorted_refs(self.selected)
|
|
101
|
+
exploitation = _sorted_refs(self.exploitation_subset)
|
|
102
|
+
if eligible != self.eligible or selected != self.selected:
|
|
103
|
+
raise ValueError("eligible and selected insights must use canonical sorted order")
|
|
104
|
+
if exploitation != self.exploitation_subset:
|
|
105
|
+
raise ValueError("exploitation_subset must use canonical sorted order")
|
|
106
|
+
if type(self.subset_size) is not int or self.subset_size < 0:
|
|
107
|
+
raise ValueError("subset_size must be a non-negative integer")
|
|
108
|
+
if self.subset_size > len(eligible):
|
|
109
|
+
raise ValueError("subset_size cannot exceed the eligible set")
|
|
110
|
+
if len(selected) != self.subset_size or len(exploitation) != self.subset_size:
|
|
111
|
+
raise ValueError("selected subsets must have exactly subset_size members")
|
|
112
|
+
if not set(selected).issubset(eligible) or not set(exploitation).issubset(eligible):
|
|
113
|
+
raise ValueError("selected subsets must be drawn from eligible insights")
|
|
114
|
+
if type(self.exploration_probability) is not Fraction:
|
|
115
|
+
raise TypeError("exploration_probability must be an exact Fraction")
|
|
116
|
+
if not Fraction(0) <= self.exploration_probability <= Fraction(1):
|
|
117
|
+
raise ValueError("exploration_probability must lie in [0,1]")
|
|
118
|
+
if not isinstance(self.mode, InsightSelectionMode):
|
|
119
|
+
raise TypeError("mode must be an InsightSelectionMode")
|
|
120
|
+
if (
|
|
121
|
+
self.exploration_probability == 0
|
|
122
|
+
and self.mode is not InsightSelectionMode.EXPLOIT
|
|
123
|
+
):
|
|
124
|
+
raise ValueError("zero exploration probability requires exploit mode")
|
|
125
|
+
if (
|
|
126
|
+
self.exploration_probability == 1
|
|
127
|
+
and self.mode is not InsightSelectionMode.EXPLORE_UNIFORM
|
|
128
|
+
):
|
|
129
|
+
raise ValueError("unit exploration probability requires uniform-exploration mode")
|
|
130
|
+
if type(self.selected_subset_probability) is not Fraction:
|
|
131
|
+
raise TypeError("selected_subset_probability must be an exact Fraction")
|
|
132
|
+
if self.selected_subset_probability <= 0 or self.selected_subset_probability > 1:
|
|
133
|
+
raise ValueError("selected_subset_probability must lie in (0,1]")
|
|
134
|
+
if type(self.policy_id) is not str or self.policy_id != "epsilon_greedy_uniform_k_subset":
|
|
135
|
+
raise ValueError("unsupported insight selection policy_id")
|
|
136
|
+
if type(self.policy_version) is not int or self.policy_version != 1:
|
|
137
|
+
raise ValueError("unsupported insight selection policy_version")
|
|
138
|
+
|
|
139
|
+
if type(self.score_snapshot) is not tuple:
|
|
140
|
+
raise TypeError("score_snapshot must be an immutable tuple")
|
|
141
|
+
score_refs = []
|
|
142
|
+
canonical_scores = []
|
|
143
|
+
for item in self.score_snapshot:
|
|
144
|
+
if type(item) is not tuple or len(item) != 2:
|
|
145
|
+
raise TypeError("score_snapshot entries must be (InsightRef, score) tuples")
|
|
146
|
+
reference, score = item
|
|
147
|
+
if not isinstance(reference, InsightRef):
|
|
148
|
+
raise TypeError("score_snapshot keys must be InsightRef values")
|
|
149
|
+
score_refs.append(reference)
|
|
150
|
+
canonical_scores.append((reference, _finite_score(score, "insight score")))
|
|
151
|
+
if tuple(canonical_scores) != self.score_snapshot:
|
|
152
|
+
raise TypeError("score_snapshot scores must already be canonical floats")
|
|
153
|
+
if tuple(score_refs) != eligible:
|
|
154
|
+
raise ValueError("score_snapshot must align exactly with canonical eligible insights")
|
|
155
|
+
if _top_k(self.score_snapshot, self.subset_size) != exploitation:
|
|
156
|
+
raise ValueError("exploitation_subset does not match the recorded scores")
|
|
157
|
+
|
|
158
|
+
expected_subset_probability = self._subset_probability(selected)
|
|
159
|
+
if self.selected_subset_probability != expected_subset_probability:
|
|
160
|
+
raise ValueError("selected_subset_probability does not match the policy law")
|
|
161
|
+
if self.mode is InsightSelectionMode.EXPLOIT and selected != exploitation:
|
|
162
|
+
raise ValueError("exploit mode must select the exploitation subset")
|
|
163
|
+
|
|
164
|
+
@property
|
|
165
|
+
def credit_identifiable(self) -> bool:
|
|
166
|
+
"""Whether individual inclusion has overlap under this decision law."""
|
|
167
|
+
|
|
168
|
+
return any(
|
|
169
|
+
Fraction(0) < self.inclusion_probability(reference) < Fraction(1)
|
|
170
|
+
for reference in self.eligible
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
def _uniform_subset_probability(self) -> Fraction:
|
|
174
|
+
count = math.comb(len(self.eligible), self.subset_size)
|
|
175
|
+
return Fraction(1, count)
|
|
176
|
+
|
|
177
|
+
def _subset_probability(self, subset: Tuple[InsightRef, ...]) -> Fraction:
|
|
178
|
+
probability = self.exploration_probability * self._uniform_subset_probability()
|
|
179
|
+
if subset == self.exploitation_subset:
|
|
180
|
+
probability += 1 - self.exploration_probability
|
|
181
|
+
return probability
|
|
182
|
+
|
|
183
|
+
def inclusion_probability(self, reference: InsightRef) -> Fraction:
|
|
184
|
+
"""Return the exact conditional probability that ``reference`` is selected."""
|
|
185
|
+
|
|
186
|
+
if reference not in self.eligible:
|
|
187
|
+
raise ValueError("insight was not eligible for this decision")
|
|
188
|
+
count = len(self.eligible)
|
|
189
|
+
uniform = Fraction(self.subset_size, count) if count else Fraction(0)
|
|
190
|
+
exploit = Fraction(int(reference in self.exploitation_subset))
|
|
191
|
+
return self.exploration_probability * uniform + (1 - self.exploration_probability) * exploit
|
|
192
|
+
|
|
193
|
+
def joint_cell_probability(
|
|
194
|
+
self,
|
|
195
|
+
first: InsightRef,
|
|
196
|
+
second: InsightRef,
|
|
197
|
+
first_selected: bool,
|
|
198
|
+
second_selected: bool,
|
|
199
|
+
) -> Fraction:
|
|
200
|
+
"""Exact probability for one two-insight inclusion/exclusion cell."""
|
|
201
|
+
|
|
202
|
+
if first == second:
|
|
203
|
+
raise ValueError("pair probabilities require two distinct insights")
|
|
204
|
+
if type(first_selected) is not bool or type(second_selected) is not bool:
|
|
205
|
+
raise TypeError("pair cell flags must be bool")
|
|
206
|
+
if first not in self.eligible or second not in self.eligible:
|
|
207
|
+
raise ValueError("both insights must be eligible for this decision")
|
|
208
|
+
count = len(self.eligible)
|
|
209
|
+
if count < 2:
|
|
210
|
+
raise ValueError("pair probabilities require at least two eligible insights")
|
|
211
|
+
uniform_both = Fraction(
|
|
212
|
+
self.subset_size * (self.subset_size - 1),
|
|
213
|
+
count * (count - 1),
|
|
214
|
+
)
|
|
215
|
+
exploit_both = Fraction(
|
|
216
|
+
int(first in self.exploitation_subset and second in self.exploitation_subset)
|
|
217
|
+
)
|
|
218
|
+
both = (
|
|
219
|
+
self.exploration_probability * uniform_both
|
|
220
|
+
+ (1 - self.exploration_probability) * exploit_both
|
|
221
|
+
)
|
|
222
|
+
first_probability = self.inclusion_probability(first)
|
|
223
|
+
second_probability = self.inclusion_probability(second)
|
|
224
|
+
cells = {
|
|
225
|
+
(True, True): both,
|
|
226
|
+
(True, False): first_probability - both,
|
|
227
|
+
(False, True): second_probability - both,
|
|
228
|
+
(False, False): 1 - first_probability - second_probability + both,
|
|
229
|
+
}
|
|
230
|
+
probability = cells[(first_selected, second_selected)]
|
|
231
|
+
if probability < 0: # pragma: no cover - algebraic implementation guard.
|
|
232
|
+
raise RuntimeError("invalid negative pair-cell probability")
|
|
233
|
+
return probability
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
@dataclass(frozen=True, slots=True)
|
|
237
|
+
class EpsilonGreedySubsetSelector:
|
|
238
|
+
"""Mix deterministic score exploitation with uniform fixed-size exploration."""
|
|
239
|
+
|
|
240
|
+
exploration_probability: Fraction = Fraction(1, 4)
|
|
241
|
+
|
|
242
|
+
def __post_init__(self) -> None:
|
|
243
|
+
if type(self.exploration_probability) is not Fraction:
|
|
244
|
+
raise TypeError("exploration_probability must be an exact Fraction")
|
|
245
|
+
if not Fraction(0) <= self.exploration_probability <= Fraction(1):
|
|
246
|
+
raise ValueError("exploration_probability must lie in [0,1]")
|
|
247
|
+
|
|
248
|
+
def select(
|
|
249
|
+
self,
|
|
250
|
+
*,
|
|
251
|
+
context_hash: str,
|
|
252
|
+
eligible: Sequence[InsightRef],
|
|
253
|
+
scores: Mapping[InsightRef, Real],
|
|
254
|
+
subset_size: int,
|
|
255
|
+
rng: RandomSubsetSource,
|
|
256
|
+
) -> InsightSelectionDecision:
|
|
257
|
+
"""Select a subset and record the complete conditional assignment law."""
|
|
258
|
+
|
|
259
|
+
_require_hash(context_hash, "context_hash")
|
|
260
|
+
canonical_eligible = _sorted_refs(eligible)
|
|
261
|
+
if type(subset_size) is not int or subset_size < 0:
|
|
262
|
+
raise ValueError("subset_size must be a non-negative integer")
|
|
263
|
+
if subset_size > len(canonical_eligible):
|
|
264
|
+
raise ValueError("subset_size cannot exceed the eligible set")
|
|
265
|
+
if set(scores) != set(canonical_eligible):
|
|
266
|
+
raise ValueError("scores must contain exactly the eligible insight references")
|
|
267
|
+
score_snapshot = tuple(
|
|
268
|
+
(reference, _finite_score(scores[reference], "insight score"))
|
|
269
|
+
for reference in canonical_eligible
|
|
270
|
+
)
|
|
271
|
+
exploitation = _top_k(score_snapshot, subset_size)
|
|
272
|
+
|
|
273
|
+
if self.exploration_probability == 0:
|
|
274
|
+
mode = InsightSelectionMode.EXPLOIT
|
|
275
|
+
elif self.exploration_probability == 1:
|
|
276
|
+
mode = InsightSelectionMode.EXPLORE_UNIFORM
|
|
277
|
+
else:
|
|
278
|
+
# Integer sampling preserves the exact logged Fraction law. A
|
|
279
|
+
# float threshold would silently implement a nearby dyadic law for
|
|
280
|
+
# most rational epsilon values and can double very small rates.
|
|
281
|
+
denominator = self.exploration_probability.denominator
|
|
282
|
+
draw = rng.randrange(denominator)
|
|
283
|
+
if type(draw) is not int:
|
|
284
|
+
raise TypeError("random source randrange must return an integer")
|
|
285
|
+
if draw < 0 or draw >= denominator:
|
|
286
|
+
raise ValueError("random source randrange result is out of bounds")
|
|
287
|
+
mode = (
|
|
288
|
+
InsightSelectionMode.EXPLORE_UNIFORM
|
|
289
|
+
if draw < self.exploration_probability.numerator
|
|
290
|
+
else InsightSelectionMode.EXPLOIT
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
if mode is InsightSelectionMode.EXPLORE_UNIFORM:
|
|
294
|
+
sampled = rng.sample(canonical_eligible, subset_size)
|
|
295
|
+
selected = _sorted_refs(sampled)
|
|
296
|
+
if len(selected) != subset_size or not set(selected).issubset(canonical_eligible):
|
|
297
|
+
raise ValueError("random source returned an invalid uniform subset")
|
|
298
|
+
else:
|
|
299
|
+
selected = exploitation
|
|
300
|
+
|
|
301
|
+
uniform_probability = Fraction(1, math.comb(len(canonical_eligible), subset_size))
|
|
302
|
+
selected_probability = self.exploration_probability * uniform_probability
|
|
303
|
+
if selected == exploitation:
|
|
304
|
+
selected_probability += 1 - self.exploration_probability
|
|
305
|
+
return InsightSelectionDecision(
|
|
306
|
+
context_hash=context_hash,
|
|
307
|
+
eligible=canonical_eligible,
|
|
308
|
+
selected=selected,
|
|
309
|
+
exploitation_subset=exploitation,
|
|
310
|
+
score_snapshot=score_snapshot,
|
|
311
|
+
subset_size=subset_size,
|
|
312
|
+
exploration_probability=self.exploration_probability,
|
|
313
|
+
mode=mode,
|
|
314
|
+
selected_subset_probability=selected_probability,
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
@dataclass(frozen=True, slots=True)
|
|
319
|
+
class InsightTrial:
|
|
320
|
+
"""One randomized selection unit and its predeclared aggregate reward.
|
|
321
|
+
|
|
322
|
+
A single operator invocation may generate several candidates from the same
|
|
323
|
+
selected insight subset. Those candidates must be aggregated into this one
|
|
324
|
+
credit unit; copying the reward into one trial per child would be
|
|
325
|
+
pseudoreplication and is rejected through unique invocation/candidate checks.
|
|
326
|
+
"""
|
|
327
|
+
|
|
328
|
+
credit_unit_id: OperatorInvocationId
|
|
329
|
+
candidate_ids: Tuple[CandidateId, ...]
|
|
330
|
+
reward_definition_hash: str
|
|
331
|
+
decision: InsightSelectionDecision
|
|
332
|
+
reward: float
|
|
333
|
+
treatment_binding_sha256: str | None = None
|
|
334
|
+
generation: int | None = None
|
|
335
|
+
|
|
336
|
+
def __post_init__(self) -> None:
|
|
337
|
+
if not isinstance(self.credit_unit_id, OperatorInvocationId):
|
|
338
|
+
raise TypeError("credit_unit_id must be an OperatorInvocationId")
|
|
339
|
+
if type(self.candidate_ids) is not tuple:
|
|
340
|
+
raise TypeError("candidate_ids must be an immutable tuple")
|
|
341
|
+
if any(not isinstance(value, CandidateId) for value in self.candidate_ids):
|
|
342
|
+
raise TypeError("candidate_ids must contain only CandidateId values")
|
|
343
|
+
if len(set(self.candidate_ids)) != len(self.candidate_ids):
|
|
344
|
+
raise ValueError("candidate_ids cannot contain duplicates")
|
|
345
|
+
_require_hash(self.reward_definition_hash, "reward_definition_hash")
|
|
346
|
+
if not isinstance(self.decision, InsightSelectionDecision):
|
|
347
|
+
raise TypeError("decision must be an InsightSelectionDecision")
|
|
348
|
+
if type(self.reward) is not float or not math.isfinite(self.reward):
|
|
349
|
+
raise TypeError("reward must be a finite canonical float")
|
|
350
|
+
if self.treatment_binding_sha256 is not None:
|
|
351
|
+
_require_hash(
|
|
352
|
+
self.treatment_binding_sha256,
|
|
353
|
+
"treatment_binding_sha256",
|
|
354
|
+
)
|
|
355
|
+
if self.generation is not None and (
|
|
356
|
+
type(self.generation) is not int or self.generation <= 0
|
|
357
|
+
):
|
|
358
|
+
raise ValueError("generation must be a positive exact integer or None")
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
@dataclass(frozen=True, slots=True)
|
|
362
|
+
class MarginalEffectEstimate:
|
|
363
|
+
insight: InsightRef
|
|
364
|
+
context_hash: Optional[str]
|
|
365
|
+
subset_size: Optional[int]
|
|
366
|
+
exploration_probability: Optional[Fraction]
|
|
367
|
+
reward_definition_hash: Optional[str]
|
|
368
|
+
policy_id: Optional[str]
|
|
369
|
+
policy_version: Optional[int]
|
|
370
|
+
effect: Optional[float]
|
|
371
|
+
treated_mean: Optional[float]
|
|
372
|
+
control_mean: Optional[float]
|
|
373
|
+
treated_trials: int
|
|
374
|
+
control_trials: int
|
|
375
|
+
treated_effective_sample_size: float
|
|
376
|
+
control_effective_sample_size: float
|
|
377
|
+
eligible_trials: int
|
|
378
|
+
overlap_trials: int
|
|
379
|
+
|
|
380
|
+
@property
|
|
381
|
+
def identified(self) -> bool:
|
|
382
|
+
return self.effect is not None
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
@dataclass(frozen=True, slots=True)
|
|
386
|
+
class PairSynergyEstimate:
|
|
387
|
+
first: InsightRef
|
|
388
|
+
second: InsightRef
|
|
389
|
+
context_hash: Optional[str]
|
|
390
|
+
subset_size: Optional[int]
|
|
391
|
+
exploration_probability: Optional[Fraction]
|
|
392
|
+
reward_definition_hash: Optional[str]
|
|
393
|
+
policy_id: Optional[str]
|
|
394
|
+
policy_version: Optional[int]
|
|
395
|
+
synergy: Optional[float]
|
|
396
|
+
cell_means: Tuple[Tuple[str, Optional[float]], ...]
|
|
397
|
+
cell_trials: Tuple[Tuple[str, int], ...]
|
|
398
|
+
cell_effective_sample_sizes: Tuple[Tuple[str, float], ...]
|
|
399
|
+
eligible_trials: int
|
|
400
|
+
overlap_trials: int
|
|
401
|
+
|
|
402
|
+
@property
|
|
403
|
+
def identified(self) -> bool:
|
|
404
|
+
return self.synergy is not None
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
@dataclass(frozen=True, slots=True)
|
|
408
|
+
class _WeightedSummary:
|
|
409
|
+
mean: Optional[float]
|
|
410
|
+
exact_mean: Optional[Fraction]
|
|
411
|
+
trials: int
|
|
412
|
+
effective_sample_size: float
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def _validate_trials(trials: Sequence[InsightTrial]) -> None:
|
|
416
|
+
credit_unit_ids = []
|
|
417
|
+
candidate_ids = []
|
|
418
|
+
for trial in trials:
|
|
419
|
+
if not isinstance(trial, InsightTrial):
|
|
420
|
+
raise TypeError("trials must contain InsightTrial values")
|
|
421
|
+
credit_unit_ids.append(trial.credit_unit_id)
|
|
422
|
+
candidate_ids.extend(trial.candidate_ids)
|
|
423
|
+
if len(set(credit_unit_ids)) != len(credit_unit_ids):
|
|
424
|
+
raise ValueError("an operator invocation may appear in the credit trial set only once")
|
|
425
|
+
if len(set(candidate_ids)) != len(candidate_ids):
|
|
426
|
+
raise ValueError("a candidate may appear in the insight-credit trial set only once")
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def _validate_reward_definition(trials: Sequence[InsightTrial]) -> Optional[str]:
|
|
430
|
+
definitions = {trial.reward_definition_hash for trial in trials}
|
|
431
|
+
if len(definitions) > 1:
|
|
432
|
+
raise ValueError("an insight-credit estimate cannot mix reward definitions")
|
|
433
|
+
return next(iter(definitions), None)
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def _weighted_summary(observations: Sequence[Tuple[float, Fraction]]) -> _WeightedSummary:
|
|
437
|
+
if not observations:
|
|
438
|
+
return _WeightedSummary(None, None, 0, 0.0)
|
|
439
|
+
# Accumulate the complete Hájek numerator and denominator as rationals.
|
|
440
|
+
# Normalizing weights and rewards in separate float domains can double-
|
|
441
|
+
# underflow a product even when the final weighted mean is representable.
|
|
442
|
+
# Fraction.from_float preserves the exact admitted binary-float reward.
|
|
443
|
+
exact_weights = [1 / probability for _, probability in observations]
|
|
444
|
+
rewards = [reward for reward, _ in observations]
|
|
445
|
+
weight_sum = sum(exact_weights, Fraction(0))
|
|
446
|
+
weighted_reward_sum = sum(
|
|
447
|
+
(
|
|
448
|
+
weight * Fraction.from_float(reward)
|
|
449
|
+
for weight, reward in zip(exact_weights, rewards, strict=True)
|
|
450
|
+
),
|
|
451
|
+
Fraction(0),
|
|
452
|
+
)
|
|
453
|
+
exact_mean = weighted_reward_sum / weight_sum
|
|
454
|
+
mean = _exact_to_finite_float(exact_mean, "weighted mean")
|
|
455
|
+
squared_weight_sum = sum(
|
|
456
|
+
(weight * weight for weight in exact_weights), Fraction(0)
|
|
457
|
+
)
|
|
458
|
+
exact_effective_sample_size = weight_sum * weight_sum / squared_weight_sum
|
|
459
|
+
effective_sample_size = _exact_to_finite_float(
|
|
460
|
+
exact_effective_sample_size, "effective sample size"
|
|
461
|
+
)
|
|
462
|
+
return _WeightedSummary(
|
|
463
|
+
mean, exact_mean, len(observations), effective_sample_size
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
def _validate_estimand_stratum(
|
|
468
|
+
trials: Sequence[InsightTrial],
|
|
469
|
+
requested_context_hash: Optional[str],
|
|
470
|
+
assignment_trials: Sequence[InsightTrial],
|
|
471
|
+
) -> tuple[
|
|
472
|
+
Optional[str], Optional[int], Optional[Fraction], Optional[str], Optional[int]
|
|
473
|
+
]:
|
|
474
|
+
"""Reject silent pooling across distinct policy-relative estimands."""
|
|
475
|
+
|
|
476
|
+
contexts = {trial.decision.context_hash for trial in trials}
|
|
477
|
+
if requested_context_hash is None:
|
|
478
|
+
if len(contexts) > 1:
|
|
479
|
+
raise ValueError(
|
|
480
|
+
"an insight-credit estimate cannot mix context strata"
|
|
481
|
+
)
|
|
482
|
+
resolved_context = next(iter(contexts), None)
|
|
483
|
+
else:
|
|
484
|
+
resolved_context = requested_context_hash
|
|
485
|
+
if any(context != requested_context_hash for context in contexts):
|
|
486
|
+
raise ValueError("included trial does not match the requested context stratum")
|
|
487
|
+
|
|
488
|
+
subset_sizes = {trial.decision.subset_size for trial in trials}
|
|
489
|
+
if len(subset_sizes) > 1:
|
|
490
|
+
raise ValueError(
|
|
491
|
+
"an insight-credit estimate cannot mix subset-size estimands"
|
|
492
|
+
)
|
|
493
|
+
exploration_probabilities = {
|
|
494
|
+
trial.decision.exploration_probability for trial in assignment_trials
|
|
495
|
+
}
|
|
496
|
+
if len(exploration_probabilities) > 1:
|
|
497
|
+
raise ValueError(
|
|
498
|
+
"an insight-credit estimate cannot mix exploration-policy strata"
|
|
499
|
+
)
|
|
500
|
+
policies = {
|
|
501
|
+
(trial.decision.policy_id, trial.decision.policy_version) for trial in trials
|
|
502
|
+
}
|
|
503
|
+
if len(policies) > 1:
|
|
504
|
+
raise ValueError("an insight-credit estimate cannot mix policy versions")
|
|
505
|
+
policy_id, policy_version = next(iter(policies), (None, None))
|
|
506
|
+
return (
|
|
507
|
+
resolved_context,
|
|
508
|
+
next(iter(subset_sizes), None),
|
|
509
|
+
next(iter(exploration_probabilities), None),
|
|
510
|
+
policy_id,
|
|
511
|
+
policy_version,
|
|
512
|
+
)
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
def _exact_to_finite_float(value: Fraction, name: str) -> float:
|
|
516
|
+
try:
|
|
517
|
+
result = float(value)
|
|
518
|
+
except (OverflowError, ValueError):
|
|
519
|
+
raise ValueError(f"{name} cannot be represented as a finite float") from None
|
|
520
|
+
if not math.isfinite(result) or (value != 0 and result == 0.0):
|
|
521
|
+
raise ValueError(f"{name} cannot be represented as a finite float")
|
|
522
|
+
return result
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
def _finite_linear_contrast(
|
|
526
|
+
values: Sequence[Fraction], coefficients: Sequence[int]
|
|
527
|
+
) -> float:
|
|
528
|
+
if len(values) != len(coefficients) or not values:
|
|
529
|
+
raise ValueError("linear contrast inputs are invalid")
|
|
530
|
+
result = sum(
|
|
531
|
+
(
|
|
532
|
+
coefficient * value
|
|
533
|
+
for value, coefficient in zip(values, coefficients, strict=True)
|
|
534
|
+
),
|
|
535
|
+
Fraction(0),
|
|
536
|
+
)
|
|
537
|
+
return _exact_to_finite_float(result, "effect contrast")
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
def estimate_marginal_effect(
|
|
541
|
+
trials: Sequence[InsightTrial],
|
|
542
|
+
insight: InsightRef,
|
|
543
|
+
*,
|
|
544
|
+
context_hash: Optional[str] = None,
|
|
545
|
+
) -> MarginalEffectEstimate:
|
|
546
|
+
"""Estimate selected-minus-unselected reward with stabilized IPW means.
|
|
547
|
+
|
|
548
|
+
Propensities are conditional on each trial's eligible set and score state.
|
|
549
|
+
Decisions with deterministic inclusion or exclusion provide no overlap and
|
|
550
|
+
are reported but excluded from the contrast.
|
|
551
|
+
"""
|
|
552
|
+
|
|
553
|
+
_validate_trials(trials)
|
|
554
|
+
if not isinstance(insight, InsightRef):
|
|
555
|
+
raise TypeError("insight must be an InsightRef")
|
|
556
|
+
if context_hash is not None:
|
|
557
|
+
_require_hash(context_hash, "context_hash")
|
|
558
|
+
eligible_trials = 0
|
|
559
|
+
overlap_trials = 0
|
|
560
|
+
assignment_trials: list[InsightTrial] = []
|
|
561
|
+
treated: list[Tuple[float, Fraction]] = []
|
|
562
|
+
control: list[Tuple[float, Fraction]] = []
|
|
563
|
+
included_trials: list[InsightTrial] = []
|
|
564
|
+
for trial in trials:
|
|
565
|
+
decision = trial.decision
|
|
566
|
+
if context_hash is not None and decision.context_hash != context_hash:
|
|
567
|
+
continue
|
|
568
|
+
if insight not in decision.eligible:
|
|
569
|
+
continue
|
|
570
|
+
included_trials.append(trial)
|
|
571
|
+
eligible_trials += 1
|
|
572
|
+
inclusion = decision.inclusion_probability(insight)
|
|
573
|
+
if inclusion <= 0 or inclusion >= 1:
|
|
574
|
+
continue
|
|
575
|
+
overlap_trials += 1
|
|
576
|
+
assignment_trials.append(trial)
|
|
577
|
+
if insight in decision.selected:
|
|
578
|
+
treated.append((trial.reward, inclusion))
|
|
579
|
+
else:
|
|
580
|
+
control.append((trial.reward, 1 - inclusion))
|
|
581
|
+
reward_definition_hash = _validate_reward_definition(included_trials)
|
|
582
|
+
(
|
|
583
|
+
resolved_context,
|
|
584
|
+
subset_size,
|
|
585
|
+
exploration_probability,
|
|
586
|
+
policy_id,
|
|
587
|
+
policy_version,
|
|
588
|
+
) = _validate_estimand_stratum(
|
|
589
|
+
included_trials, context_hash, assignment_trials
|
|
590
|
+
)
|
|
591
|
+
treated_summary = _weighted_summary(treated)
|
|
592
|
+
control_summary = _weighted_summary(control)
|
|
593
|
+
effect = None
|
|
594
|
+
if (
|
|
595
|
+
treated_summary.exact_mean is not None
|
|
596
|
+
and control_summary.exact_mean is not None
|
|
597
|
+
):
|
|
598
|
+
effect = _finite_linear_contrast(
|
|
599
|
+
(treated_summary.exact_mean, control_summary.exact_mean), (1, -1)
|
|
600
|
+
)
|
|
601
|
+
return MarginalEffectEstimate(
|
|
602
|
+
insight=insight,
|
|
603
|
+
context_hash=resolved_context,
|
|
604
|
+
subset_size=subset_size,
|
|
605
|
+
exploration_probability=exploration_probability,
|
|
606
|
+
reward_definition_hash=reward_definition_hash,
|
|
607
|
+
policy_id=policy_id,
|
|
608
|
+
policy_version=policy_version,
|
|
609
|
+
effect=effect,
|
|
610
|
+
treated_mean=treated_summary.mean,
|
|
611
|
+
control_mean=control_summary.mean,
|
|
612
|
+
treated_trials=treated_summary.trials,
|
|
613
|
+
control_trials=control_summary.trials,
|
|
614
|
+
treated_effective_sample_size=treated_summary.effective_sample_size,
|
|
615
|
+
control_effective_sample_size=control_summary.effective_sample_size,
|
|
616
|
+
eligible_trials=eligible_trials,
|
|
617
|
+
overlap_trials=overlap_trials,
|
|
618
|
+
)
|
|
619
|
+
|
|
620
|
+
|
|
621
|
+
def estimate_pair_synergy(
|
|
622
|
+
trials: Sequence[InsightTrial],
|
|
623
|
+
first: InsightRef,
|
|
624
|
+
second: InsightRef,
|
|
625
|
+
*,
|
|
626
|
+
context_hash: Optional[str] = None,
|
|
627
|
+
) -> PairSynergyEstimate:
|
|
628
|
+
"""Estimate ``E11 - E10 - E01 + E00`` for two insight versions.
|
|
629
|
+
|
|
630
|
+
Only decisions assigning positive probability to all four pair cells are
|
|
631
|
+
eligible for this interaction contrast. This makes fixed-size designs with
|
|
632
|
+
structurally impossible cells fail closed instead of fabricating synergy.
|
|
633
|
+
"""
|
|
634
|
+
|
|
635
|
+
_validate_trials(trials)
|
|
636
|
+
if not isinstance(first, InsightRef) or not isinstance(second, InsightRef):
|
|
637
|
+
raise TypeError("pair members must be InsightRef values")
|
|
638
|
+
if first == second:
|
|
639
|
+
raise ValueError("pair synergy requires two distinct insight versions")
|
|
640
|
+
if second < first:
|
|
641
|
+
first, second = second, first
|
|
642
|
+
if context_hash is not None:
|
|
643
|
+
_require_hash(context_hash, "context_hash")
|
|
644
|
+
cell_order = ((True, True), (True, False), (False, True), (False, False))
|
|
645
|
+
cell_names = {
|
|
646
|
+
(True, True): "11",
|
|
647
|
+
(True, False): "10",
|
|
648
|
+
(False, True): "01",
|
|
649
|
+
(False, False): "00",
|
|
650
|
+
}
|
|
651
|
+
observations: dict[Tuple[bool, bool], list[Tuple[float, Fraction]]] = {
|
|
652
|
+
cell: [] for cell in cell_order
|
|
653
|
+
}
|
|
654
|
+
eligible_trials = 0
|
|
655
|
+
overlap_trials = 0
|
|
656
|
+
assignment_trials: list[InsightTrial] = []
|
|
657
|
+
included_trials: list[InsightTrial] = []
|
|
658
|
+
for trial in trials:
|
|
659
|
+
decision = trial.decision
|
|
660
|
+
if context_hash is not None and decision.context_hash != context_hash:
|
|
661
|
+
continue
|
|
662
|
+
if first not in decision.eligible or second not in decision.eligible:
|
|
663
|
+
continue
|
|
664
|
+
included_trials.append(trial)
|
|
665
|
+
eligible_trials += 1
|
|
666
|
+
probabilities = {
|
|
667
|
+
cell: decision.joint_cell_probability(first, second, *cell)
|
|
668
|
+
for cell in cell_order
|
|
669
|
+
}
|
|
670
|
+
if any(probability <= 0 for probability in probabilities.values()):
|
|
671
|
+
continue
|
|
672
|
+
overlap_trials += 1
|
|
673
|
+
assignment_trials.append(trial)
|
|
674
|
+
observed_cell = (first in decision.selected, second in decision.selected)
|
|
675
|
+
observations[observed_cell].append(
|
|
676
|
+
(trial.reward, probabilities[observed_cell])
|
|
677
|
+
)
|
|
678
|
+
reward_definition_hash = _validate_reward_definition(included_trials)
|
|
679
|
+
(
|
|
680
|
+
resolved_context,
|
|
681
|
+
subset_size,
|
|
682
|
+
exploration_probability,
|
|
683
|
+
policy_id,
|
|
684
|
+
policy_version,
|
|
685
|
+
) = _validate_estimand_stratum(
|
|
686
|
+
included_trials, context_hash, assignment_trials
|
|
687
|
+
)
|
|
688
|
+
summaries = {cell: _weighted_summary(observations[cell]) for cell in cell_order}
|
|
689
|
+
means = {cell: summaries[cell].mean for cell in cell_order}
|
|
690
|
+
exact_means = {cell: summaries[cell].exact_mean for cell in cell_order}
|
|
691
|
+
synergy = None
|
|
692
|
+
if all(exact_means[cell] is not None for cell in cell_order):
|
|
693
|
+
synergy = _finite_linear_contrast(
|
|
694
|
+
tuple(exact_means[cell] for cell in cell_order), # type: ignore[arg-type]
|
|
695
|
+
(1, -1, -1, 1),
|
|
696
|
+
)
|
|
697
|
+
return PairSynergyEstimate(
|
|
698
|
+
first=first,
|
|
699
|
+
second=second,
|
|
700
|
+
context_hash=resolved_context,
|
|
701
|
+
subset_size=subset_size,
|
|
702
|
+
exploration_probability=exploration_probability,
|
|
703
|
+
reward_definition_hash=reward_definition_hash,
|
|
704
|
+
policy_id=policy_id,
|
|
705
|
+
policy_version=policy_version,
|
|
706
|
+
synergy=synergy,
|
|
707
|
+
cell_means=tuple((cell_names[cell], summaries[cell].mean) for cell in cell_order),
|
|
708
|
+
cell_trials=tuple((cell_names[cell], summaries[cell].trials) for cell in cell_order),
|
|
709
|
+
cell_effective_sample_sizes=tuple(
|
|
710
|
+
(cell_names[cell], summaries[cell].effective_sample_size) for cell in cell_order
|
|
711
|
+
),
|
|
712
|
+
eligible_trials=eligible_trials,
|
|
713
|
+
overlap_trials=overlap_trials,
|
|
714
|
+
)
|