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,1270 @@
|
|
|
1
|
+
"""Wave-sealed causal memory for staged agentic evolution.
|
|
2
|
+
|
|
3
|
+
This module is deliberately framework- and provider-free. It defines the
|
|
4
|
+
immutable records that an application layer can bind into an invocation plan,
|
|
5
|
+
plus deterministic policies for diagnostic credit and later matched controls.
|
|
6
|
+
|
|
7
|
+
The central timing rule is strict: every assignment in a diagnostic wave is
|
|
8
|
+
resolved against one immutable score snapshot, and no reward from that wave is
|
|
9
|
+
visible until the complete wave is sealed. Successful calls use their frozen
|
|
10
|
+
reward. Model/schema failures and candidate failures receive the
|
|
11
|
+
pre-registered no-yield reward under an intention-to-treat (ITT) estimand.
|
|
12
|
+
Infrastructure failures invalidate the whole wave and publish no checkpoint.
|
|
13
|
+
|
|
14
|
+
For insight ``i``, the frozen causal-search score is::
|
|
15
|
+
|
|
16
|
+
support_i = min(treated_ESS_i, control_ESS_i)
|
|
17
|
+
shrink_i = support_i / (support_i + n0)
|
|
18
|
+
mean_i = prior_i + shrink_i * effect_i
|
|
19
|
+
uncertainty_i = c / sqrt(support_i + n0)
|
|
20
|
+
retrieval_i = mean_i + beta * uncertainty_i
|
|
21
|
+
|
|
22
|
+
An unidentified effect is exactly zero for the update (not evidence of harm),
|
|
23
|
+
while its support remains zero. ``n0``, ``c``, and ``beta`` are recorded in
|
|
24
|
+
every snapshot and therefore cannot drift silently between replay and use.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
import hashlib
|
|
30
|
+
import json
|
|
31
|
+
import math
|
|
32
|
+
import re
|
|
33
|
+
from collections import Counter
|
|
34
|
+
from collections.abc import Mapping, Sequence
|
|
35
|
+
from dataclasses import dataclass, field
|
|
36
|
+
from enum import Enum
|
|
37
|
+
from fractions import Fraction
|
|
38
|
+
from numbers import Real
|
|
39
|
+
|
|
40
|
+
from agent_evolve.domain.ids import CandidateId, OperatorInvocationId
|
|
41
|
+
from agent_evolve.domain.insight import InsightRef
|
|
42
|
+
from agent_evolve.policies.memory.randomized_subset import (
|
|
43
|
+
EpsilonGreedySubsetSelector,
|
|
44
|
+
InsightSelectionDecision,
|
|
45
|
+
InsightSelectionMode,
|
|
46
|
+
InsightTrial,
|
|
47
|
+
estimate_marginal_effect,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
_LOWER_SHA256 = frozenset("0123456789abcdef")
|
|
52
|
+
_SAFE_BLOCK_ID = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$")
|
|
53
|
+
_SELECTION_DECISION_DOMAIN = b"agent-evolve:insight-selection-decision:v1\x00"
|
|
54
|
+
_ASSIGNMENT_DOMAIN = b"agent-evolve:resolved-insight-assignment:v1\x00"
|
|
55
|
+
_SCORE_SNAPSHOT_DOMAIN = b"agent-evolve:causal-memory-score-snapshot:v1\x00"
|
|
56
|
+
_WAVE_DOMAIN = b"agent-evolve:frozen-diagnostic-memory-wave:v1\x00"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _require_sha256(value: str, name: str) -> None:
|
|
60
|
+
if (
|
|
61
|
+
type(value) is not str
|
|
62
|
+
or len(value) != 64
|
|
63
|
+
or any(character not in _LOWER_SHA256 for character in value)
|
|
64
|
+
):
|
|
65
|
+
raise ValueError(f"{name} must be a lowercase SHA-256 digest")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _require_block_id(value: str, name: str) -> None:
|
|
69
|
+
if type(value) is not str or _SAFE_BLOCK_ID.fullmatch(value) is None:
|
|
70
|
+
raise ValueError(f"{name} must be a bounded durable identifier")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _canonical_float(value: Real, name: str) -> float:
|
|
74
|
+
if isinstance(value, bool) or not isinstance(value, Real):
|
|
75
|
+
raise TypeError(f"{name} must be a real number")
|
|
76
|
+
result = float(value)
|
|
77
|
+
if not math.isfinite(result):
|
|
78
|
+
raise ValueError(f"{name} must be finite")
|
|
79
|
+
return result
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _require_canonical_float(value: float, name: str) -> None:
|
|
83
|
+
if type(value) is not float or not math.isfinite(value):
|
|
84
|
+
raise TypeError(f"{name} must be a finite canonical float")
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _ref_record(reference: InsightRef) -> dict[str, object]:
|
|
88
|
+
return {
|
|
89
|
+
"insight_id": reference.insight_id.value,
|
|
90
|
+
"version": reference.version,
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _fraction_record(value: Fraction) -> list[int]:
|
|
95
|
+
return [value.numerator, value.denominator]
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _hash_record(domain: bytes, record: Mapping[str, object]) -> str:
|
|
99
|
+
payload = json.dumps(
|
|
100
|
+
record,
|
|
101
|
+
ensure_ascii=True,
|
|
102
|
+
allow_nan=False,
|
|
103
|
+
separators=(",", ":"),
|
|
104
|
+
sort_keys=True,
|
|
105
|
+
).encode("ascii", errors="strict")
|
|
106
|
+
return hashlib.sha256(domain + payload).hexdigest()
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _decision_record(decision: InsightSelectionDecision) -> dict[str, object]:
|
|
110
|
+
return {
|
|
111
|
+
"policy_id": decision.policy_id,
|
|
112
|
+
"policy_version": decision.policy_version,
|
|
113
|
+
"context_hash": decision.context_hash,
|
|
114
|
+
"eligible": [_ref_record(reference) for reference in decision.eligible],
|
|
115
|
+
"selected": [_ref_record(reference) for reference in decision.selected],
|
|
116
|
+
"exploitation_subset": [
|
|
117
|
+
_ref_record(reference) for reference in decision.exploitation_subset
|
|
118
|
+
],
|
|
119
|
+
"score_snapshot": [
|
|
120
|
+
{
|
|
121
|
+
"reference": _ref_record(reference),
|
|
122
|
+
"score_hex": score.hex(),
|
|
123
|
+
}
|
|
124
|
+
for reference, score in decision.score_snapshot
|
|
125
|
+
],
|
|
126
|
+
"subset_size": decision.subset_size,
|
|
127
|
+
"exploration_probability": _fraction_record(decision.exploration_probability),
|
|
128
|
+
"mode": decision.mode.value,
|
|
129
|
+
"selected_subset_probability": _fraction_record(
|
|
130
|
+
decision.selected_subset_probability
|
|
131
|
+
),
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def insight_selection_decision_sha256(
|
|
136
|
+
decision: InsightSelectionDecision,
|
|
137
|
+
) -> str:
|
|
138
|
+
"""Return a stable digest of the complete assignment law and realization."""
|
|
139
|
+
|
|
140
|
+
if not isinstance(decision, InsightSelectionDecision):
|
|
141
|
+
raise TypeError("decision must be an InsightSelectionDecision")
|
|
142
|
+
return _hash_record(_SELECTION_DECISION_DOMAIN, _decision_record(decision))
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class MemoryAssignmentArm(str, Enum):
|
|
146
|
+
"""Predeclared role of one invocation in a staged memory experiment."""
|
|
147
|
+
|
|
148
|
+
DIAGNOSTIC = "diagnostic"
|
|
149
|
+
ADAPTIVE = "adaptive"
|
|
150
|
+
SCORE_SHUFFLED_CONTROL = "score_shuffled_control"
|
|
151
|
+
UNIFORM_CONTROL = "uniform_control"
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class DelayedCreditMode(str, Enum):
|
|
155
|
+
"""When and under which estimand an assignment may update memory."""
|
|
156
|
+
|
|
157
|
+
WAVE_SEALED_ITT = "wave_sealed_intention_to_treat"
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
@dataclass(frozen=True, slots=True)
|
|
161
|
+
class ResolvedInsightAssignment:
|
|
162
|
+
"""One plan-ready retrieval assignment bound to immutable evidence.
|
|
163
|
+
|
|
164
|
+
``score_snapshot_sha256`` identifies the complete
|
|
165
|
+
:class:`MemoryScoreSnapshot`, not merely its score table. The exact score
|
|
166
|
+
table and selected references remain embedded in ``selection_decision``.
|
|
167
|
+
Application code should construct values with :meth:`resolve` whenever the
|
|
168
|
+
source snapshot is available; direct construction remains useful for a
|
|
169
|
+
durable codec and is revalidated at a frozen-wave boundary.
|
|
170
|
+
"""
|
|
171
|
+
|
|
172
|
+
credit_unit_id: OperatorInvocationId
|
|
173
|
+
exact_context_hash: str
|
|
174
|
+
estimand_stratum_hash: str
|
|
175
|
+
block_id: str
|
|
176
|
+
arm: MemoryAssignmentArm
|
|
177
|
+
selection_decision: InsightSelectionDecision
|
|
178
|
+
selection_decision_sha256: str
|
|
179
|
+
score_snapshot_sha256: str
|
|
180
|
+
prompt_shape_sha256: str
|
|
181
|
+
credit_mode: DelayedCreditMode = DelayedCreditMode.WAVE_SEALED_ITT
|
|
182
|
+
assignment_sha256: str = field(init=False)
|
|
183
|
+
|
|
184
|
+
def __post_init__(self) -> None:
|
|
185
|
+
if not isinstance(self.credit_unit_id, OperatorInvocationId):
|
|
186
|
+
raise TypeError("credit_unit_id must be an OperatorInvocationId")
|
|
187
|
+
_require_sha256(self.exact_context_hash, "exact_context_hash")
|
|
188
|
+
_require_sha256(self.estimand_stratum_hash, "estimand_stratum_hash")
|
|
189
|
+
_require_block_id(self.block_id, "block_id")
|
|
190
|
+
if not isinstance(self.arm, MemoryAssignmentArm):
|
|
191
|
+
raise TypeError("arm must be a MemoryAssignmentArm")
|
|
192
|
+
if not isinstance(self.selection_decision, InsightSelectionDecision):
|
|
193
|
+
raise TypeError("selection_decision must be an InsightSelectionDecision")
|
|
194
|
+
if self.selection_decision.context_hash != self.exact_context_hash:
|
|
195
|
+
raise ValueError(
|
|
196
|
+
"selection decision context does not match exact_context_hash"
|
|
197
|
+
)
|
|
198
|
+
_require_sha256(self.selection_decision_sha256, "selection_decision_sha256")
|
|
199
|
+
if self.selection_decision_sha256 != insight_selection_decision_sha256(
|
|
200
|
+
self.selection_decision
|
|
201
|
+
):
|
|
202
|
+
raise ValueError("selection_decision_sha256 does not match the decision")
|
|
203
|
+
_require_sha256(self.score_snapshot_sha256, "score_snapshot_sha256")
|
|
204
|
+
_require_sha256(self.prompt_shape_sha256, "prompt_shape_sha256")
|
|
205
|
+
if not isinstance(self.credit_mode, DelayedCreditMode):
|
|
206
|
+
raise TypeError("credit_mode must be a DelayedCreditMode")
|
|
207
|
+
self._validate_arm_law()
|
|
208
|
+
object.__setattr__(
|
|
209
|
+
self,
|
|
210
|
+
"assignment_sha256",
|
|
211
|
+
_hash_record(_ASSIGNMENT_DOMAIN, self.to_record()),
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
def _validate_arm_law(self) -> None:
|
|
215
|
+
decision = self.selection_decision
|
|
216
|
+
if self.arm is MemoryAssignmentArm.DIAGNOSTIC:
|
|
217
|
+
if not decision.credit_identifiable:
|
|
218
|
+
raise ValueError(
|
|
219
|
+
"diagnostic assignments require inclusion-probability overlap"
|
|
220
|
+
)
|
|
221
|
+
return
|
|
222
|
+
if self.arm in {
|
|
223
|
+
MemoryAssignmentArm.ADAPTIVE,
|
|
224
|
+
MemoryAssignmentArm.SCORE_SHUFFLED_CONTROL,
|
|
225
|
+
}:
|
|
226
|
+
if (
|
|
227
|
+
decision.exploration_probability != 0
|
|
228
|
+
or decision.mode is not InsightSelectionMode.EXPLOIT
|
|
229
|
+
):
|
|
230
|
+
raise ValueError(
|
|
231
|
+
"adaptive and score-shuffled arms require deterministic exploitation"
|
|
232
|
+
)
|
|
233
|
+
return
|
|
234
|
+
if self.arm is MemoryAssignmentArm.UNIFORM_CONTROL and (
|
|
235
|
+
decision.exploration_probability != 1
|
|
236
|
+
or decision.mode is not InsightSelectionMode.EXPLORE_UNIFORM
|
|
237
|
+
):
|
|
238
|
+
raise ValueError(
|
|
239
|
+
"uniform-control assignments require the exact uniform-subset law"
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
@classmethod
|
|
243
|
+
def resolve(
|
|
244
|
+
cls,
|
|
245
|
+
*,
|
|
246
|
+
credit_unit_id: OperatorInvocationId,
|
|
247
|
+
snapshot: MemoryScoreSnapshot,
|
|
248
|
+
expected_snapshot_sha256: str,
|
|
249
|
+
block_id: str,
|
|
250
|
+
arm: MemoryAssignmentArm,
|
|
251
|
+
selection_decision: InsightSelectionDecision,
|
|
252
|
+
prompt_shape_sha256: str,
|
|
253
|
+
credit_mode: DelayedCreditMode = DelayedCreditMode.WAVE_SEALED_ITT,
|
|
254
|
+
) -> ResolvedInsightAssignment:
|
|
255
|
+
"""Bind a decision to the expected current snapshot, failing stale."""
|
|
256
|
+
|
|
257
|
+
if not isinstance(snapshot, MemoryScoreSnapshot):
|
|
258
|
+
raise TypeError("snapshot must be a MemoryScoreSnapshot")
|
|
259
|
+
_require_sha256(expected_snapshot_sha256, "expected_snapshot_sha256")
|
|
260
|
+
if snapshot.snapshot_sha256 != expected_snapshot_sha256:
|
|
261
|
+
raise StaleMemorySnapshotError(
|
|
262
|
+
"current memory snapshot differs from the predeclared snapshot"
|
|
263
|
+
)
|
|
264
|
+
assignment = cls(
|
|
265
|
+
credit_unit_id=credit_unit_id,
|
|
266
|
+
exact_context_hash=snapshot.exact_context_hash,
|
|
267
|
+
estimand_stratum_hash=snapshot.estimand_stratum_hash,
|
|
268
|
+
block_id=block_id,
|
|
269
|
+
arm=arm,
|
|
270
|
+
selection_decision=selection_decision,
|
|
271
|
+
selection_decision_sha256=insight_selection_decision_sha256(
|
|
272
|
+
selection_decision
|
|
273
|
+
),
|
|
274
|
+
score_snapshot_sha256=snapshot.snapshot_sha256,
|
|
275
|
+
prompt_shape_sha256=prompt_shape_sha256,
|
|
276
|
+
credit_mode=credit_mode,
|
|
277
|
+
)
|
|
278
|
+
assignment.validate_against_snapshot(snapshot)
|
|
279
|
+
return assignment
|
|
280
|
+
|
|
281
|
+
def validate_against_snapshot(self, snapshot: MemoryScoreSnapshot) -> None:
|
|
282
|
+
"""Revalidate the snapshot binding and arm-specific score relationship."""
|
|
283
|
+
|
|
284
|
+
if not isinstance(snapshot, MemoryScoreSnapshot):
|
|
285
|
+
raise TypeError("snapshot must be a MemoryScoreSnapshot")
|
|
286
|
+
if self.score_snapshot_sha256 != snapshot.snapshot_sha256:
|
|
287
|
+
raise StaleMemorySnapshotError(
|
|
288
|
+
"assignment is bound to a different memory snapshot"
|
|
289
|
+
)
|
|
290
|
+
if (
|
|
291
|
+
self.exact_context_hash != snapshot.exact_context_hash
|
|
292
|
+
or self.estimand_stratum_hash != snapshot.estimand_stratum_hash
|
|
293
|
+
):
|
|
294
|
+
raise ValueError("assignment and snapshot strata differ")
|
|
295
|
+
decision = self.selection_decision
|
|
296
|
+
expected_refs = tuple(entry.reference for entry in snapshot.entries)
|
|
297
|
+
if decision.eligible != expected_refs:
|
|
298
|
+
raise ValueError(
|
|
299
|
+
"selection decision eligible set differs from the score snapshot"
|
|
300
|
+
)
|
|
301
|
+
observed_scores = tuple(score for _, score in decision.score_snapshot)
|
|
302
|
+
snapshot_scores = tuple(entry.retrieval_score for entry in snapshot.entries)
|
|
303
|
+
if self.arm is MemoryAssignmentArm.SCORE_SHUFFLED_CONTROL:
|
|
304
|
+
observed_multiset = Counter(score.hex() for score in observed_scores)
|
|
305
|
+
snapshot_multiset = Counter(score.hex() for score in snapshot_scores)
|
|
306
|
+
if observed_multiset != snapshot_multiset:
|
|
307
|
+
raise ValueError(
|
|
308
|
+
"score-shuffled control must preserve the snapshot score multiset"
|
|
309
|
+
)
|
|
310
|
+
elif observed_scores != snapshot_scores:
|
|
311
|
+
raise ValueError(
|
|
312
|
+
"selection decision scores differ from the bound memory snapshot"
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
def to_record(self) -> dict[str, object]:
|
|
316
|
+
"""Return a deterministic JSON-ready record used by plan hashing."""
|
|
317
|
+
|
|
318
|
+
return {
|
|
319
|
+
"schema_version": 1,
|
|
320
|
+
"credit_unit_id": self.credit_unit_id.value,
|
|
321
|
+
"exact_context_hash": self.exact_context_hash,
|
|
322
|
+
"estimand_stratum_hash": self.estimand_stratum_hash,
|
|
323
|
+
"block_id": self.block_id,
|
|
324
|
+
"arm": self.arm.value,
|
|
325
|
+
"selection_decision": _decision_record(self.selection_decision),
|
|
326
|
+
"selection_decision_sha256": self.selection_decision_sha256,
|
|
327
|
+
"score_snapshot_sha256": self.score_snapshot_sha256,
|
|
328
|
+
"prompt_shape_sha256": self.prompt_shape_sha256,
|
|
329
|
+
"credit_mode": self.credit_mode.value,
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
class StaleMemorySnapshotError(ValueError):
|
|
334
|
+
"""Raised when an assignment or wave refers to a superseded checkpoint."""
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
class IncompleteMemoryWaveError(ValueError):
|
|
338
|
+
"""Raised when a wave cannot be sealed with exactly one receipt per unit."""
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
@dataclass(frozen=True, slots=True)
|
|
342
|
+
class CausalSearchScore:
|
|
343
|
+
"""Auditable score and support for one exact insight version."""
|
|
344
|
+
|
|
345
|
+
reference: InsightRef
|
|
346
|
+
prior_score: float
|
|
347
|
+
effect_estimate: float | None
|
|
348
|
+
treated_trials: int
|
|
349
|
+
control_trials: int
|
|
350
|
+
treated_effective_sample_size: float
|
|
351
|
+
control_effective_sample_size: float
|
|
352
|
+
effective_support: float
|
|
353
|
+
shrinkage: float
|
|
354
|
+
posterior_mean: float
|
|
355
|
+
uncertainty_bonus: float
|
|
356
|
+
retrieval_score: float
|
|
357
|
+
|
|
358
|
+
def __post_init__(self) -> None:
|
|
359
|
+
if not isinstance(self.reference, InsightRef):
|
|
360
|
+
raise TypeError("reference must be an InsightRef")
|
|
361
|
+
for name in (
|
|
362
|
+
"prior_score",
|
|
363
|
+
"treated_effective_sample_size",
|
|
364
|
+
"control_effective_sample_size",
|
|
365
|
+
"effective_support",
|
|
366
|
+
"shrinkage",
|
|
367
|
+
"posterior_mean",
|
|
368
|
+
"uncertainty_bonus",
|
|
369
|
+
"retrieval_score",
|
|
370
|
+
):
|
|
371
|
+
_require_canonical_float(getattr(self, name), name)
|
|
372
|
+
if self.effect_estimate is not None:
|
|
373
|
+
_require_canonical_float(self.effect_estimate, "effect_estimate")
|
|
374
|
+
for name in ("treated_trials", "control_trials"):
|
|
375
|
+
value = getattr(self, name)
|
|
376
|
+
if type(value) is not int or value < 0:
|
|
377
|
+
raise ValueError(f"{name} must be a non-negative exact integer")
|
|
378
|
+
if (
|
|
379
|
+
min(
|
|
380
|
+
self.treated_effective_sample_size,
|
|
381
|
+
self.control_effective_sample_size,
|
|
382
|
+
self.effective_support,
|
|
383
|
+
self.uncertainty_bonus,
|
|
384
|
+
)
|
|
385
|
+
< 0
|
|
386
|
+
):
|
|
387
|
+
raise ValueError("sample support and uncertainty cannot be negative")
|
|
388
|
+
if not 0.0 <= self.shrinkage < 1.0:
|
|
389
|
+
raise ValueError("shrinkage must lie in [0,1)")
|
|
390
|
+
|
|
391
|
+
@property
|
|
392
|
+
def identified(self) -> bool:
|
|
393
|
+
return self.effect_estimate is not None
|
|
394
|
+
|
|
395
|
+
def to_record(self) -> dict[str, object]:
|
|
396
|
+
return {
|
|
397
|
+
"reference": _ref_record(self.reference),
|
|
398
|
+
"prior_score_hex": self.prior_score.hex(),
|
|
399
|
+
"effect_estimate_hex": (
|
|
400
|
+
None if self.effect_estimate is None else self.effect_estimate.hex()
|
|
401
|
+
),
|
|
402
|
+
"treated_trials": self.treated_trials,
|
|
403
|
+
"control_trials": self.control_trials,
|
|
404
|
+
"treated_ess_hex": self.treated_effective_sample_size.hex(),
|
|
405
|
+
"control_ess_hex": self.control_effective_sample_size.hex(),
|
|
406
|
+
"effective_support_hex": self.effective_support.hex(),
|
|
407
|
+
"shrinkage_hex": self.shrinkage.hex(),
|
|
408
|
+
"posterior_mean_hex": self.posterior_mean.hex(),
|
|
409
|
+
"uncertainty_bonus_hex": self.uncertainty_bonus.hex(),
|
|
410
|
+
"retrieval_score_hex": self.retrieval_score.hex(),
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
class MemoryTrialTerminalStatus(str, Enum):
|
|
415
|
+
"""Terminal classification used by the frozen ITT policy."""
|
|
416
|
+
|
|
417
|
+
SUCCEEDED = "succeeded"
|
|
418
|
+
MODEL_FAILURE = "model_or_schema_failure"
|
|
419
|
+
CANDIDATE_FAILURE = "candidate_failure"
|
|
420
|
+
INFRASTRUCTURE_FAILURE = "infrastructure_failure"
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
@dataclass(frozen=True, slots=True)
|
|
424
|
+
class MemoryAssignmentReceipt:
|
|
425
|
+
"""One terminal result; publication order has no scoring meaning."""
|
|
426
|
+
|
|
427
|
+
assignment_sha256: str
|
|
428
|
+
credit_unit_id: OperatorInvocationId
|
|
429
|
+
status: MemoryTrialTerminalStatus
|
|
430
|
+
candidate_ids: tuple[CandidateId, ...] = ()
|
|
431
|
+
observed_reward: float | None = None
|
|
432
|
+
|
|
433
|
+
def __post_init__(self) -> None:
|
|
434
|
+
_require_sha256(self.assignment_sha256, "assignment_sha256")
|
|
435
|
+
if not isinstance(self.credit_unit_id, OperatorInvocationId):
|
|
436
|
+
raise TypeError("credit_unit_id must be an OperatorInvocationId")
|
|
437
|
+
if not isinstance(self.status, MemoryTrialTerminalStatus):
|
|
438
|
+
raise TypeError("status must be a MemoryTrialTerminalStatus")
|
|
439
|
+
if type(self.candidate_ids) is not tuple or any(
|
|
440
|
+
not isinstance(value, CandidateId) for value in self.candidate_ids
|
|
441
|
+
):
|
|
442
|
+
raise TypeError("candidate_ids must be a tuple of CandidateId values")
|
|
443
|
+
if self.candidate_ids != tuple(sorted(set(self.candidate_ids))):
|
|
444
|
+
raise ValueError("candidate_ids must be unique and canonically sorted")
|
|
445
|
+
if self.status is MemoryTrialTerminalStatus.SUCCEEDED:
|
|
446
|
+
if not self.candidate_ids:
|
|
447
|
+
raise ValueError("a successful receipt must identify a candidate")
|
|
448
|
+
_require_canonical_float(self.observed_reward, "observed_reward")
|
|
449
|
+
elif self.observed_reward is not None:
|
|
450
|
+
raise ValueError("failure receipts cannot carry an observed reward")
|
|
451
|
+
|
|
452
|
+
def to_record(self) -> dict[str, object]:
|
|
453
|
+
return {
|
|
454
|
+
"assignment_sha256": self.assignment_sha256,
|
|
455
|
+
"credit_unit_id": self.credit_unit_id.value,
|
|
456
|
+
"status": self.status.value,
|
|
457
|
+
"candidate_ids": [value.value for value in self.candidate_ids],
|
|
458
|
+
"observed_reward_hex": (
|
|
459
|
+
None if self.observed_reward is None else self.observed_reward.hex()
|
|
460
|
+
),
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
@dataclass(frozen=True, slots=True)
|
|
465
|
+
class CausalMemoryObservation:
|
|
466
|
+
"""A sealed, non-infrastructure assignment with its ITT reward."""
|
|
467
|
+
|
|
468
|
+
assignment: ResolvedInsightAssignment
|
|
469
|
+
status: MemoryTrialTerminalStatus
|
|
470
|
+
candidate_ids: tuple[CandidateId, ...]
|
|
471
|
+
credited_reward: float
|
|
472
|
+
reward_definition_hash: str
|
|
473
|
+
|
|
474
|
+
def __post_init__(self) -> None:
|
|
475
|
+
if not isinstance(self.assignment, ResolvedInsightAssignment):
|
|
476
|
+
raise TypeError("assignment must be a ResolvedInsightAssignment")
|
|
477
|
+
if self.assignment.arm is not MemoryAssignmentArm.DIAGNOSTIC:
|
|
478
|
+
raise ValueError("causal memory observations must be diagnostic")
|
|
479
|
+
if self.status not in {
|
|
480
|
+
MemoryTrialTerminalStatus.SUCCEEDED,
|
|
481
|
+
MemoryTrialTerminalStatus.MODEL_FAILURE,
|
|
482
|
+
MemoryTrialTerminalStatus.CANDIDATE_FAILURE,
|
|
483
|
+
}:
|
|
484
|
+
raise ValueError("infrastructure failures cannot enter causal evidence")
|
|
485
|
+
if type(self.candidate_ids) is not tuple or any(
|
|
486
|
+
not isinstance(value, CandidateId) for value in self.candidate_ids
|
|
487
|
+
):
|
|
488
|
+
raise TypeError("candidate_ids must be a tuple of CandidateId values")
|
|
489
|
+
if self.candidate_ids != tuple(sorted(set(self.candidate_ids))):
|
|
490
|
+
raise ValueError("candidate_ids must be unique and canonically sorted")
|
|
491
|
+
_require_canonical_float(self.credited_reward, "credited_reward")
|
|
492
|
+
_require_sha256(self.reward_definition_hash, "reward_definition_hash")
|
|
493
|
+
|
|
494
|
+
@property
|
|
495
|
+
def reward_was_imputed(self) -> bool:
|
|
496
|
+
return self.status is not MemoryTrialTerminalStatus.SUCCEEDED
|
|
497
|
+
|
|
498
|
+
def to_trial(self) -> InsightTrial:
|
|
499
|
+
return InsightTrial(
|
|
500
|
+
credit_unit_id=self.assignment.credit_unit_id,
|
|
501
|
+
candidate_ids=self.candidate_ids,
|
|
502
|
+
reward_definition_hash=self.reward_definition_hash,
|
|
503
|
+
decision=self.assignment.selection_decision,
|
|
504
|
+
reward=self.credited_reward,
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
def to_record(self) -> dict[str, object]:
|
|
508
|
+
return {
|
|
509
|
+
"assignment": self.assignment.to_record(),
|
|
510
|
+
"assignment_sha256": self.assignment.assignment_sha256,
|
|
511
|
+
"status": self.status.value,
|
|
512
|
+
"candidate_ids": [value.value for value in self.candidate_ids],
|
|
513
|
+
"credited_reward_hex": self.credited_reward.hex(),
|
|
514
|
+
"reward_definition_hash": self.reward_definition_hash,
|
|
515
|
+
"reward_was_imputed": self.reward_was_imputed,
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
@dataclass(frozen=True, slots=True)
|
|
520
|
+
class MemoryScoreSnapshot:
|
|
521
|
+
"""An immutable causal score checkpoint and its complete valid evidence."""
|
|
522
|
+
|
|
523
|
+
exact_context_hash: str
|
|
524
|
+
estimand_stratum_hash: str
|
|
525
|
+
checkpoint_index: int
|
|
526
|
+
entries: tuple[CausalSearchScore, ...]
|
|
527
|
+
observations: tuple[CausalMemoryObservation, ...]
|
|
528
|
+
prior_effective_sample_size: float
|
|
529
|
+
uncertainty_scale: float
|
|
530
|
+
exploration_weight: float
|
|
531
|
+
reward_definition_hash: str | None = None
|
|
532
|
+
parent_snapshot_sha256: str | None = None
|
|
533
|
+
source_wave_sha256: str | None = None
|
|
534
|
+
scoring_policy_id: str = "min_ess_shrunken_causal_ucb"
|
|
535
|
+
scoring_policy_version: int = 1
|
|
536
|
+
snapshot_sha256: str = field(init=False)
|
|
537
|
+
|
|
538
|
+
def __post_init__(self) -> None:
|
|
539
|
+
_require_sha256(self.exact_context_hash, "exact_context_hash")
|
|
540
|
+
_require_sha256(self.estimand_stratum_hash, "estimand_stratum_hash")
|
|
541
|
+
if type(self.checkpoint_index) is not int or self.checkpoint_index < 0:
|
|
542
|
+
raise ValueError("checkpoint_index must be a non-negative exact integer")
|
|
543
|
+
if type(self.entries) is not tuple or not self.entries:
|
|
544
|
+
raise ValueError("entries must be a non-empty exact tuple")
|
|
545
|
+
if any(not isinstance(entry, CausalSearchScore) for entry in self.entries):
|
|
546
|
+
raise TypeError("entries must contain CausalSearchScore values")
|
|
547
|
+
references = tuple(entry.reference for entry in self.entries)
|
|
548
|
+
if references != tuple(sorted(set(references))):
|
|
549
|
+
raise ValueError("score entries must be unique and canonically sorted")
|
|
550
|
+
if type(self.observations) is not tuple or any(
|
|
551
|
+
not isinstance(value, CausalMemoryObservation)
|
|
552
|
+
for value in self.observations
|
|
553
|
+
):
|
|
554
|
+
raise TypeError(
|
|
555
|
+
"observations must be a tuple of CausalMemoryObservation values"
|
|
556
|
+
)
|
|
557
|
+
observation_keys = tuple(
|
|
558
|
+
value.assignment.assignment_sha256 for value in self.observations
|
|
559
|
+
)
|
|
560
|
+
if observation_keys != tuple(sorted(set(observation_keys))):
|
|
561
|
+
raise ValueError("observations must be unique and canonically sorted")
|
|
562
|
+
for observation in self.observations:
|
|
563
|
+
assignment = observation.assignment
|
|
564
|
+
if (
|
|
565
|
+
assignment.exact_context_hash != self.exact_context_hash
|
|
566
|
+
or assignment.estimand_stratum_hash != self.estimand_stratum_hash
|
|
567
|
+
):
|
|
568
|
+
raise ValueError(
|
|
569
|
+
"snapshot observations must share its estimand stratum"
|
|
570
|
+
)
|
|
571
|
+
for name in (
|
|
572
|
+
"prior_effective_sample_size",
|
|
573
|
+
"uncertainty_scale",
|
|
574
|
+
"exploration_weight",
|
|
575
|
+
):
|
|
576
|
+
_require_canonical_float(getattr(self, name), name)
|
|
577
|
+
if self.prior_effective_sample_size <= 0:
|
|
578
|
+
raise ValueError("prior_effective_sample_size must be positive")
|
|
579
|
+
if self.uncertainty_scale < 0 or self.exploration_weight < 0:
|
|
580
|
+
raise ValueError("uncertainty parameters cannot be negative")
|
|
581
|
+
if self.scoring_policy_id != "min_ess_shrunken_causal_ucb":
|
|
582
|
+
raise ValueError("unsupported scoring_policy_id")
|
|
583
|
+
if (
|
|
584
|
+
type(self.scoring_policy_version) is not int
|
|
585
|
+
or self.scoring_policy_version != 1
|
|
586
|
+
):
|
|
587
|
+
raise ValueError("unsupported scoring_policy_version")
|
|
588
|
+
|
|
589
|
+
if self.checkpoint_index == 0:
|
|
590
|
+
if (
|
|
591
|
+
self.observations
|
|
592
|
+
or self.reward_definition_hash is not None
|
|
593
|
+
or self.parent_snapshot_sha256 is not None
|
|
594
|
+
or self.source_wave_sha256 is not None
|
|
595
|
+
):
|
|
596
|
+
raise ValueError("a genesis snapshot cannot contain wave evidence")
|
|
597
|
+
else:
|
|
598
|
+
if not self.observations:
|
|
599
|
+
raise ValueError("a non-genesis snapshot must contain evidence")
|
|
600
|
+
if self.reward_definition_hash is None:
|
|
601
|
+
raise ValueError("a non-genesis snapshot requires a reward definition")
|
|
602
|
+
_require_sha256(self.reward_definition_hash, "reward_definition_hash")
|
|
603
|
+
if self.parent_snapshot_sha256 is None or self.source_wave_sha256 is None:
|
|
604
|
+
raise ValueError(
|
|
605
|
+
"a non-genesis snapshot requires parent and wave hashes"
|
|
606
|
+
)
|
|
607
|
+
_require_sha256(self.parent_snapshot_sha256, "parent_snapshot_sha256")
|
|
608
|
+
_require_sha256(self.source_wave_sha256, "source_wave_sha256")
|
|
609
|
+
if any(
|
|
610
|
+
observation.reward_definition_hash != self.reward_definition_hash
|
|
611
|
+
for observation in self.observations
|
|
612
|
+
):
|
|
613
|
+
raise ValueError("snapshot cannot mix reward definitions")
|
|
614
|
+
|
|
615
|
+
for entry in self.entries:
|
|
616
|
+
support = min(
|
|
617
|
+
entry.treated_effective_sample_size,
|
|
618
|
+
entry.control_effective_sample_size,
|
|
619
|
+
)
|
|
620
|
+
shrinkage = support / (support + self.prior_effective_sample_size)
|
|
621
|
+
effect = 0.0 if entry.effect_estimate is None else entry.effect_estimate
|
|
622
|
+
posterior_mean = entry.prior_score + shrinkage * effect
|
|
623
|
+
uncertainty = self.uncertainty_scale / math.sqrt(
|
|
624
|
+
support + self.prior_effective_sample_size
|
|
625
|
+
)
|
|
626
|
+
retrieval = posterior_mean + self.exploration_weight * uncertainty
|
|
627
|
+
expected = (
|
|
628
|
+
support,
|
|
629
|
+
shrinkage,
|
|
630
|
+
posterior_mean,
|
|
631
|
+
uncertainty,
|
|
632
|
+
retrieval,
|
|
633
|
+
)
|
|
634
|
+
observed = (
|
|
635
|
+
entry.effective_support,
|
|
636
|
+
entry.shrinkage,
|
|
637
|
+
entry.posterior_mean,
|
|
638
|
+
entry.uncertainty_bonus,
|
|
639
|
+
entry.retrieval_score,
|
|
640
|
+
)
|
|
641
|
+
if observed != expected:
|
|
642
|
+
raise ValueError(
|
|
643
|
+
"score entry does not match the frozen scoring formula"
|
|
644
|
+
)
|
|
645
|
+
|
|
646
|
+
# Re-run the existing causal estimator at this trust boundary. Besides
|
|
647
|
+
# defending the formula, this rejects duplicate credit/candidate units
|
|
648
|
+
# and silent mixtures of assignment or reward strata.
|
|
649
|
+
trials = tuple(observation.to_trial() for observation in self.observations)
|
|
650
|
+
for entry in self.entries:
|
|
651
|
+
estimate = estimate_marginal_effect(
|
|
652
|
+
trials,
|
|
653
|
+
entry.reference,
|
|
654
|
+
context_hash=self.exact_context_hash,
|
|
655
|
+
)
|
|
656
|
+
if (
|
|
657
|
+
entry.effect_estimate != estimate.effect
|
|
658
|
+
or entry.treated_trials != estimate.treated_trials
|
|
659
|
+
or entry.control_trials != estimate.control_trials
|
|
660
|
+
or entry.treated_effective_sample_size
|
|
661
|
+
!= estimate.treated_effective_sample_size
|
|
662
|
+
or entry.control_effective_sample_size
|
|
663
|
+
!= estimate.control_effective_sample_size
|
|
664
|
+
):
|
|
665
|
+
raise ValueError("score entry does not match its causal evidence")
|
|
666
|
+
|
|
667
|
+
object.__setattr__(
|
|
668
|
+
self,
|
|
669
|
+
"snapshot_sha256",
|
|
670
|
+
_hash_record(_SCORE_SNAPSHOT_DOMAIN, self.to_record()),
|
|
671
|
+
)
|
|
672
|
+
|
|
673
|
+
@property
|
|
674
|
+
def retrieval_scores(self) -> dict[InsightRef, float]:
|
|
675
|
+
return {entry.reference: entry.retrieval_score for entry in self.entries}
|
|
676
|
+
|
|
677
|
+
def to_record(self) -> dict[str, object]:
|
|
678
|
+
return {
|
|
679
|
+
"schema_version": 1,
|
|
680
|
+
"exact_context_hash": self.exact_context_hash,
|
|
681
|
+
"estimand_stratum_hash": self.estimand_stratum_hash,
|
|
682
|
+
"checkpoint_index": self.checkpoint_index,
|
|
683
|
+
"entries": [entry.to_record() for entry in self.entries],
|
|
684
|
+
"observations": [value.to_record() for value in self.observations],
|
|
685
|
+
"prior_effective_sample_size_hex": (self.prior_effective_sample_size.hex()),
|
|
686
|
+
"uncertainty_scale_hex": self.uncertainty_scale.hex(),
|
|
687
|
+
"exploration_weight_hex": self.exploration_weight.hex(),
|
|
688
|
+
"reward_definition_hash": self.reward_definition_hash,
|
|
689
|
+
"parent_snapshot_sha256": self.parent_snapshot_sha256,
|
|
690
|
+
"source_wave_sha256": self.source_wave_sha256,
|
|
691
|
+
"scoring_policy_id": self.scoring_policy_id,
|
|
692
|
+
"scoring_policy_version": self.scoring_policy_version,
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
|
|
696
|
+
@dataclass(frozen=True, slots=True)
|
|
697
|
+
class CausalSearchScorePolicy:
|
|
698
|
+
"""Frozen support-shrinkage and uncertainty-aware retrieval policy."""
|
|
699
|
+
|
|
700
|
+
prior_effective_sample_size: float = 4.0
|
|
701
|
+
uncertainty_scale: float = 1.0
|
|
702
|
+
exploration_weight: float = 0.25
|
|
703
|
+
policy_id: str = "min_ess_shrunken_causal_ucb"
|
|
704
|
+
policy_version: int = 1
|
|
705
|
+
|
|
706
|
+
def __post_init__(self) -> None:
|
|
707
|
+
for name in (
|
|
708
|
+
"prior_effective_sample_size",
|
|
709
|
+
"uncertainty_scale",
|
|
710
|
+
"exploration_weight",
|
|
711
|
+
):
|
|
712
|
+
_require_canonical_float(getattr(self, name), name)
|
|
713
|
+
if self.prior_effective_sample_size <= 0:
|
|
714
|
+
raise ValueError("prior_effective_sample_size must be positive")
|
|
715
|
+
if self.uncertainty_scale < 0 or self.exploration_weight < 0:
|
|
716
|
+
raise ValueError("uncertainty parameters cannot be negative")
|
|
717
|
+
if self.policy_id != "min_ess_shrunken_causal_ucb":
|
|
718
|
+
raise ValueError("unsupported causal score policy_id")
|
|
719
|
+
if type(self.policy_version) is not int or self.policy_version != 1:
|
|
720
|
+
raise ValueError("unsupported causal score policy_version")
|
|
721
|
+
|
|
722
|
+
def genesis(
|
|
723
|
+
self,
|
|
724
|
+
*,
|
|
725
|
+
exact_context_hash: str,
|
|
726
|
+
estimand_stratum_hash: str,
|
|
727
|
+
priors: Mapping[InsightRef, Real],
|
|
728
|
+
) -> MemoryScoreSnapshot:
|
|
729
|
+
"""Create checkpoint zero without manufacturing causal evidence."""
|
|
730
|
+
|
|
731
|
+
if not isinstance(priors, Mapping) or not priors:
|
|
732
|
+
raise ValueError("priors must be a non-empty mapping")
|
|
733
|
+
if any(not isinstance(reference, InsightRef) for reference in priors):
|
|
734
|
+
raise TypeError("prior keys must be InsightRef values")
|
|
735
|
+
entries = tuple(
|
|
736
|
+
self._entry(
|
|
737
|
+
reference=reference,
|
|
738
|
+
prior_score=_canonical_float(priors[reference], "prior score"),
|
|
739
|
+
effect_estimate=None,
|
|
740
|
+
treated_trials=0,
|
|
741
|
+
control_trials=0,
|
|
742
|
+
treated_ess=0.0,
|
|
743
|
+
control_ess=0.0,
|
|
744
|
+
)
|
|
745
|
+
for reference in sorted(priors)
|
|
746
|
+
)
|
|
747
|
+
return MemoryScoreSnapshot(
|
|
748
|
+
exact_context_hash=exact_context_hash,
|
|
749
|
+
estimand_stratum_hash=estimand_stratum_hash,
|
|
750
|
+
checkpoint_index=0,
|
|
751
|
+
entries=entries,
|
|
752
|
+
observations=(),
|
|
753
|
+
prior_effective_sample_size=self.prior_effective_sample_size,
|
|
754
|
+
uncertainty_scale=self.uncertainty_scale,
|
|
755
|
+
exploration_weight=self.exploration_weight,
|
|
756
|
+
scoring_policy_id=self.policy_id,
|
|
757
|
+
scoring_policy_version=self.policy_version,
|
|
758
|
+
)
|
|
759
|
+
|
|
760
|
+
def score_evidence(
|
|
761
|
+
self,
|
|
762
|
+
*,
|
|
763
|
+
parent: MemoryScoreSnapshot,
|
|
764
|
+
observations: Sequence[CausalMemoryObservation],
|
|
765
|
+
reward_definition_hash: str,
|
|
766
|
+
source_wave_sha256: str,
|
|
767
|
+
) -> MemoryScoreSnapshot:
|
|
768
|
+
"""Recompute one immutable checkpoint from all sealed evidence."""
|
|
769
|
+
|
|
770
|
+
self._require_compatible(parent)
|
|
771
|
+
_require_sha256(reward_definition_hash, "reward_definition_hash")
|
|
772
|
+
_require_sha256(source_wave_sha256, "source_wave_sha256")
|
|
773
|
+
if isinstance(observations, (str, bytes)) or not isinstance(
|
|
774
|
+
observations, Sequence
|
|
775
|
+
):
|
|
776
|
+
raise TypeError("observations must be a sequence")
|
|
777
|
+
current = tuple(observations)
|
|
778
|
+
if not current:
|
|
779
|
+
raise ValueError("a checkpoint requires at least one new observation")
|
|
780
|
+
if any(not isinstance(value, CausalMemoryObservation) for value in current):
|
|
781
|
+
raise TypeError("observations must contain CausalMemoryObservation values")
|
|
782
|
+
combined = tuple(
|
|
783
|
+
sorted(
|
|
784
|
+
(*parent.observations, *current),
|
|
785
|
+
key=lambda value: value.assignment.assignment_sha256,
|
|
786
|
+
)
|
|
787
|
+
)
|
|
788
|
+
trials = tuple(observation.to_trial() for observation in combined)
|
|
789
|
+
entries = []
|
|
790
|
+
for prior_entry in parent.entries:
|
|
791
|
+
estimate = estimate_marginal_effect(
|
|
792
|
+
trials,
|
|
793
|
+
prior_entry.reference,
|
|
794
|
+
context_hash=parent.exact_context_hash,
|
|
795
|
+
)
|
|
796
|
+
entries.append(
|
|
797
|
+
self._entry(
|
|
798
|
+
reference=prior_entry.reference,
|
|
799
|
+
prior_score=prior_entry.prior_score,
|
|
800
|
+
effect_estimate=estimate.effect,
|
|
801
|
+
treated_trials=estimate.treated_trials,
|
|
802
|
+
control_trials=estimate.control_trials,
|
|
803
|
+
treated_ess=estimate.treated_effective_sample_size,
|
|
804
|
+
control_ess=estimate.control_effective_sample_size,
|
|
805
|
+
)
|
|
806
|
+
)
|
|
807
|
+
return MemoryScoreSnapshot(
|
|
808
|
+
exact_context_hash=parent.exact_context_hash,
|
|
809
|
+
estimand_stratum_hash=parent.estimand_stratum_hash,
|
|
810
|
+
checkpoint_index=parent.checkpoint_index + 1,
|
|
811
|
+
entries=tuple(entries),
|
|
812
|
+
observations=combined,
|
|
813
|
+
prior_effective_sample_size=self.prior_effective_sample_size,
|
|
814
|
+
uncertainty_scale=self.uncertainty_scale,
|
|
815
|
+
exploration_weight=self.exploration_weight,
|
|
816
|
+
reward_definition_hash=reward_definition_hash,
|
|
817
|
+
parent_snapshot_sha256=parent.snapshot_sha256,
|
|
818
|
+
source_wave_sha256=source_wave_sha256,
|
|
819
|
+
scoring_policy_id=self.policy_id,
|
|
820
|
+
scoring_policy_version=self.policy_version,
|
|
821
|
+
)
|
|
822
|
+
|
|
823
|
+
def _entry(
|
|
824
|
+
self,
|
|
825
|
+
*,
|
|
826
|
+
reference: InsightRef,
|
|
827
|
+
prior_score: float,
|
|
828
|
+
effect_estimate: float | None,
|
|
829
|
+
treated_trials: int,
|
|
830
|
+
control_trials: int,
|
|
831
|
+
treated_ess: float,
|
|
832
|
+
control_ess: float,
|
|
833
|
+
) -> CausalSearchScore:
|
|
834
|
+
support = min(treated_ess, control_ess)
|
|
835
|
+
shrinkage = support / (support + self.prior_effective_sample_size)
|
|
836
|
+
effect = 0.0 if effect_estimate is None else effect_estimate
|
|
837
|
+
posterior_mean = prior_score + shrinkage * effect
|
|
838
|
+
uncertainty = self.uncertainty_scale / math.sqrt(
|
|
839
|
+
support + self.prior_effective_sample_size
|
|
840
|
+
)
|
|
841
|
+
retrieval = posterior_mean + self.exploration_weight * uncertainty
|
|
842
|
+
return CausalSearchScore(
|
|
843
|
+
reference=reference,
|
|
844
|
+
prior_score=prior_score,
|
|
845
|
+
effect_estimate=effect_estimate,
|
|
846
|
+
treated_trials=treated_trials,
|
|
847
|
+
control_trials=control_trials,
|
|
848
|
+
treated_effective_sample_size=treated_ess,
|
|
849
|
+
control_effective_sample_size=control_ess,
|
|
850
|
+
effective_support=support,
|
|
851
|
+
shrinkage=shrinkage,
|
|
852
|
+
posterior_mean=posterior_mean,
|
|
853
|
+
uncertainty_bonus=uncertainty,
|
|
854
|
+
retrieval_score=retrieval,
|
|
855
|
+
)
|
|
856
|
+
|
|
857
|
+
def _require_compatible(self, snapshot: MemoryScoreSnapshot) -> None:
|
|
858
|
+
if not isinstance(snapshot, MemoryScoreSnapshot):
|
|
859
|
+
raise TypeError("snapshot must be a MemoryScoreSnapshot")
|
|
860
|
+
observed = (
|
|
861
|
+
snapshot.prior_effective_sample_size,
|
|
862
|
+
snapshot.uncertainty_scale,
|
|
863
|
+
snapshot.exploration_weight,
|
|
864
|
+
snapshot.scoring_policy_id,
|
|
865
|
+
snapshot.scoring_policy_version,
|
|
866
|
+
)
|
|
867
|
+
expected = (
|
|
868
|
+
self.prior_effective_sample_size,
|
|
869
|
+
self.uncertainty_scale,
|
|
870
|
+
self.exploration_weight,
|
|
871
|
+
self.policy_id,
|
|
872
|
+
self.policy_version,
|
|
873
|
+
)
|
|
874
|
+
if observed != expected:
|
|
875
|
+
raise ValueError("score policy differs from the parent snapshot")
|
|
876
|
+
|
|
877
|
+
|
|
878
|
+
@dataclass(frozen=True, slots=True)
|
|
879
|
+
class FrozenDiagnosticMemoryWave:
|
|
880
|
+
"""Complete pre-call diagnostic assignments against one score checkpoint."""
|
|
881
|
+
|
|
882
|
+
wave_id: str
|
|
883
|
+
prior_snapshot: MemoryScoreSnapshot
|
|
884
|
+
assignments: tuple[ResolvedInsightAssignment, ...]
|
|
885
|
+
reward_definition_hash: str
|
|
886
|
+
no_yield_reward: float
|
|
887
|
+
wave_sha256: str = field(init=False)
|
|
888
|
+
|
|
889
|
+
def __post_init__(self) -> None:
|
|
890
|
+
_require_block_id(self.wave_id, "wave_id")
|
|
891
|
+
if not isinstance(self.prior_snapshot, MemoryScoreSnapshot):
|
|
892
|
+
raise TypeError("prior_snapshot must be a MemoryScoreSnapshot")
|
|
893
|
+
if type(self.assignments) is not tuple or not self.assignments:
|
|
894
|
+
raise ValueError("assignments must be a non-empty exact tuple")
|
|
895
|
+
if any(
|
|
896
|
+
not isinstance(value, ResolvedInsightAssignment)
|
|
897
|
+
for value in self.assignments
|
|
898
|
+
):
|
|
899
|
+
raise TypeError("assignments must contain ResolvedInsightAssignment values")
|
|
900
|
+
hashes = tuple(value.assignment_sha256 for value in self.assignments)
|
|
901
|
+
if hashes != tuple(sorted(set(hashes))):
|
|
902
|
+
raise ValueError("assignments must be unique and canonically sorted")
|
|
903
|
+
credit_ids = tuple(value.credit_unit_id for value in self.assignments)
|
|
904
|
+
if len(set(credit_ids)) != len(credit_ids):
|
|
905
|
+
raise ValueError("a wave cannot repeat a credit unit")
|
|
906
|
+
for assignment in self.assignments:
|
|
907
|
+
if assignment.arm is not MemoryAssignmentArm.DIAGNOSTIC:
|
|
908
|
+
raise ValueError("a diagnostic wave can contain only diagnostic arms")
|
|
909
|
+
assignment.validate_against_snapshot(self.prior_snapshot)
|
|
910
|
+
first = self.assignments[0].selection_decision
|
|
911
|
+
stratum = (
|
|
912
|
+
first.eligible,
|
|
913
|
+
first.subset_size,
|
|
914
|
+
first.exploration_probability,
|
|
915
|
+
first.policy_id,
|
|
916
|
+
first.policy_version,
|
|
917
|
+
)
|
|
918
|
+
if any(
|
|
919
|
+
(
|
|
920
|
+
value.selection_decision.eligible,
|
|
921
|
+
value.selection_decision.subset_size,
|
|
922
|
+
value.selection_decision.exploration_probability,
|
|
923
|
+
value.selection_decision.policy_id,
|
|
924
|
+
value.selection_decision.policy_version,
|
|
925
|
+
)
|
|
926
|
+
!= stratum
|
|
927
|
+
for value in self.assignments[1:]
|
|
928
|
+
):
|
|
929
|
+
raise ValueError("a diagnostic wave cannot mix assignment-law strata")
|
|
930
|
+
_require_sha256(self.reward_definition_hash, "reward_definition_hash")
|
|
931
|
+
_require_canonical_float(self.no_yield_reward, "no_yield_reward")
|
|
932
|
+
if (
|
|
933
|
+
self.prior_snapshot.reward_definition_hash is not None
|
|
934
|
+
and self.prior_snapshot.reward_definition_hash
|
|
935
|
+
!= self.reward_definition_hash
|
|
936
|
+
):
|
|
937
|
+
raise ValueError("a diagnostic lineage cannot change reward definition")
|
|
938
|
+
object.__setattr__(
|
|
939
|
+
self,
|
|
940
|
+
"wave_sha256",
|
|
941
|
+
_hash_record(_WAVE_DOMAIN, self.to_record()),
|
|
942
|
+
)
|
|
943
|
+
|
|
944
|
+
def to_record(self) -> dict[str, object]:
|
|
945
|
+
return {
|
|
946
|
+
"schema_version": 1,
|
|
947
|
+
"wave_id": self.wave_id,
|
|
948
|
+
"prior_snapshot_sha256": self.prior_snapshot.snapshot_sha256,
|
|
949
|
+
"assignment_sha256s": [
|
|
950
|
+
value.assignment_sha256 for value in self.assignments
|
|
951
|
+
],
|
|
952
|
+
"reward_definition_hash": self.reward_definition_hash,
|
|
953
|
+
"no_yield_reward_hex": self.no_yield_reward.hex(),
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
|
|
957
|
+
class MemoryCheckpointClosureStatus(str, Enum):
|
|
958
|
+
SEALED = "sealed"
|
|
959
|
+
INVALIDATED_INFRASTRUCTURE = "invalidated_infrastructure"
|
|
960
|
+
|
|
961
|
+
|
|
962
|
+
@dataclass(frozen=True, slots=True)
|
|
963
|
+
class MemoryCheckpointClosure:
|
|
964
|
+
"""Explicit result: exactly one checkpoint or an invalidated wave."""
|
|
965
|
+
|
|
966
|
+
wave_sha256: str
|
|
967
|
+
status: MemoryCheckpointClosureStatus
|
|
968
|
+
receipts: tuple[MemoryAssignmentReceipt, ...]
|
|
969
|
+
observations: tuple[CausalMemoryObservation, ...]
|
|
970
|
+
snapshot: MemoryScoreSnapshot | None
|
|
971
|
+
|
|
972
|
+
def __post_init__(self) -> None:
|
|
973
|
+
_require_sha256(self.wave_sha256, "wave_sha256")
|
|
974
|
+
if not isinstance(self.status, MemoryCheckpointClosureStatus):
|
|
975
|
+
raise TypeError("status must be a MemoryCheckpointClosureStatus")
|
|
976
|
+
if type(self.receipts) is not tuple or any(
|
|
977
|
+
not isinstance(value, MemoryAssignmentReceipt) for value in self.receipts
|
|
978
|
+
):
|
|
979
|
+
raise TypeError("receipts must contain MemoryAssignmentReceipt values")
|
|
980
|
+
receipt_hashes = tuple(value.assignment_sha256 for value in self.receipts)
|
|
981
|
+
if receipt_hashes != tuple(sorted(set(receipt_hashes))):
|
|
982
|
+
raise ValueError("closure receipts must be unique and canonically sorted")
|
|
983
|
+
if type(self.observations) is not tuple or any(
|
|
984
|
+
not isinstance(value, CausalMemoryObservation)
|
|
985
|
+
for value in self.observations
|
|
986
|
+
):
|
|
987
|
+
raise TypeError("observations must contain CausalMemoryObservation values")
|
|
988
|
+
if self.status is MemoryCheckpointClosureStatus.SEALED:
|
|
989
|
+
if self.snapshot is None or not self.observations:
|
|
990
|
+
raise ValueError(
|
|
991
|
+
"a sealed closure requires observations and a snapshot"
|
|
992
|
+
)
|
|
993
|
+
if any(
|
|
994
|
+
receipt.status is MemoryTrialTerminalStatus.INFRASTRUCTURE_FAILURE
|
|
995
|
+
for receipt in self.receipts
|
|
996
|
+
):
|
|
997
|
+
raise ValueError(
|
|
998
|
+
"a sealed closure cannot contain infrastructure failure"
|
|
999
|
+
)
|
|
1000
|
+
elif self.snapshot is not None or self.observations:
|
|
1001
|
+
raise ValueError(
|
|
1002
|
+
"an infrastructure-invalidated closure cannot publish evidence"
|
|
1003
|
+
)
|
|
1004
|
+
elif not any(
|
|
1005
|
+
receipt.status is MemoryTrialTerminalStatus.INFRASTRUCTURE_FAILURE
|
|
1006
|
+
for receipt in self.receipts
|
|
1007
|
+
):
|
|
1008
|
+
raise ValueError(
|
|
1009
|
+
"an infrastructure-invalidated closure requires an infrastructure failure"
|
|
1010
|
+
)
|
|
1011
|
+
|
|
1012
|
+
|
|
1013
|
+
@dataclass(frozen=True, slots=True)
|
|
1014
|
+
class WaveSealedCheckpointBuilder:
|
|
1015
|
+
"""Atomically convert one complete terminal wave into causal memory."""
|
|
1016
|
+
|
|
1017
|
+
score_policy: CausalSearchScorePolicy
|
|
1018
|
+
|
|
1019
|
+
def __post_init__(self) -> None:
|
|
1020
|
+
if not isinstance(self.score_policy, CausalSearchScorePolicy):
|
|
1021
|
+
raise TypeError("score_policy must be a CausalSearchScorePolicy")
|
|
1022
|
+
|
|
1023
|
+
def close(
|
|
1024
|
+
self,
|
|
1025
|
+
wave: FrozenDiagnosticMemoryWave,
|
|
1026
|
+
receipts: Sequence[MemoryAssignmentReceipt],
|
|
1027
|
+
) -> MemoryCheckpointClosure:
|
|
1028
|
+
"""Seal only an exact complete receipt set, independent of completion order."""
|
|
1029
|
+
|
|
1030
|
+
if not isinstance(wave, FrozenDiagnosticMemoryWave):
|
|
1031
|
+
raise TypeError("wave must be a FrozenDiagnosticMemoryWave")
|
|
1032
|
+
self.score_policy._require_compatible(wave.prior_snapshot)
|
|
1033
|
+
if isinstance(receipts, (str, bytes)) or not isinstance(receipts, Sequence):
|
|
1034
|
+
raise TypeError("receipts must be a sequence")
|
|
1035
|
+
terminal = tuple(receipts)
|
|
1036
|
+
if any(not isinstance(value, MemoryAssignmentReceipt) for value in terminal):
|
|
1037
|
+
raise TypeError("receipts must contain MemoryAssignmentReceipt values")
|
|
1038
|
+
by_hash: dict[str, MemoryAssignmentReceipt] = {}
|
|
1039
|
+
for receipt in terminal:
|
|
1040
|
+
if receipt.assignment_sha256 in by_hash:
|
|
1041
|
+
raise IncompleteMemoryWaveError(
|
|
1042
|
+
"a diagnostic assignment has more than one terminal receipt"
|
|
1043
|
+
)
|
|
1044
|
+
by_hash[receipt.assignment_sha256] = receipt
|
|
1045
|
+
expected = {value.assignment_sha256 for value in wave.assignments}
|
|
1046
|
+
observed = set(by_hash)
|
|
1047
|
+
if expected != observed:
|
|
1048
|
+
missing = sorted(expected - observed)
|
|
1049
|
+
extra = sorted(observed - expected)
|
|
1050
|
+
raise IncompleteMemoryWaveError(
|
|
1051
|
+
f"receipt set differs from frozen wave: missing={missing}, extra={extra}"
|
|
1052
|
+
)
|
|
1053
|
+
assignments = {value.assignment_sha256: value for value in wave.assignments}
|
|
1054
|
+
canonical_receipts = tuple(by_hash[key] for key in sorted(by_hash))
|
|
1055
|
+
for receipt in canonical_receipts:
|
|
1056
|
+
assignment = assignments[receipt.assignment_sha256]
|
|
1057
|
+
if receipt.credit_unit_id != assignment.credit_unit_id:
|
|
1058
|
+
raise ValueError("receipt credit unit differs from its assignment")
|
|
1059
|
+
|
|
1060
|
+
if any(
|
|
1061
|
+
value.status is MemoryTrialTerminalStatus.INFRASTRUCTURE_FAILURE
|
|
1062
|
+
for value in canonical_receipts
|
|
1063
|
+
):
|
|
1064
|
+
return MemoryCheckpointClosure(
|
|
1065
|
+
wave_sha256=wave.wave_sha256,
|
|
1066
|
+
status=MemoryCheckpointClosureStatus.INVALIDATED_INFRASTRUCTURE,
|
|
1067
|
+
receipts=canonical_receipts,
|
|
1068
|
+
observations=(),
|
|
1069
|
+
snapshot=None,
|
|
1070
|
+
)
|
|
1071
|
+
|
|
1072
|
+
observations = []
|
|
1073
|
+
for receipt in canonical_receipts:
|
|
1074
|
+
reward = (
|
|
1075
|
+
receipt.observed_reward
|
|
1076
|
+
if receipt.status is MemoryTrialTerminalStatus.SUCCEEDED
|
|
1077
|
+
else wave.no_yield_reward
|
|
1078
|
+
)
|
|
1079
|
+
assert reward is not None # Enforced by MemoryAssignmentReceipt.
|
|
1080
|
+
observations.append(
|
|
1081
|
+
CausalMemoryObservation(
|
|
1082
|
+
assignment=assignments[receipt.assignment_sha256],
|
|
1083
|
+
status=receipt.status,
|
|
1084
|
+
candidate_ids=receipt.candidate_ids,
|
|
1085
|
+
credited_reward=reward,
|
|
1086
|
+
reward_definition_hash=wave.reward_definition_hash,
|
|
1087
|
+
)
|
|
1088
|
+
)
|
|
1089
|
+
canonical_observations = tuple(
|
|
1090
|
+
sorted(
|
|
1091
|
+
observations,
|
|
1092
|
+
key=lambda value: value.assignment.assignment_sha256,
|
|
1093
|
+
)
|
|
1094
|
+
)
|
|
1095
|
+
snapshot = self.score_policy.score_evidence(
|
|
1096
|
+
parent=wave.prior_snapshot,
|
|
1097
|
+
observations=canonical_observations,
|
|
1098
|
+
reward_definition_hash=wave.reward_definition_hash,
|
|
1099
|
+
source_wave_sha256=wave.wave_sha256,
|
|
1100
|
+
)
|
|
1101
|
+
return MemoryCheckpointClosure(
|
|
1102
|
+
wave_sha256=wave.wave_sha256,
|
|
1103
|
+
status=MemoryCheckpointClosureStatus.SEALED,
|
|
1104
|
+
receipts=canonical_receipts,
|
|
1105
|
+
observations=canonical_observations,
|
|
1106
|
+
snapshot=snapshot,
|
|
1107
|
+
)
|
|
1108
|
+
|
|
1109
|
+
|
|
1110
|
+
class _NoRandom:
|
|
1111
|
+
def randrange(self, stop: int) -> int: # pragma: no cover - exact 0/1 only.
|
|
1112
|
+
raise AssertionError(f"unexpected branch draw with stop={stop}")
|
|
1113
|
+
|
|
1114
|
+
def sample(self, population, k: int): # pragma: no cover - exploit only.
|
|
1115
|
+
raise AssertionError("unexpected subset sample")
|
|
1116
|
+
|
|
1117
|
+
|
|
1118
|
+
class _ExactSubsetRandom(_NoRandom):
|
|
1119
|
+
def __init__(self, selected: tuple[InsightRef, ...]) -> None:
|
|
1120
|
+
self._selected = selected
|
|
1121
|
+
|
|
1122
|
+
def sample(self, population, k: int) -> list[InsightRef]:
|
|
1123
|
+
if k != len(self._selected) or not set(self._selected).issubset(population):
|
|
1124
|
+
raise RuntimeError("internal exact subset does not match selector inputs")
|
|
1125
|
+
return list(self._selected)
|
|
1126
|
+
|
|
1127
|
+
|
|
1128
|
+
def _unrank_combination(
|
|
1129
|
+
values: tuple[InsightRef, ...], subset_size: int, rank: int
|
|
1130
|
+
) -> tuple[InsightRef, ...]:
|
|
1131
|
+
count = len(values)
|
|
1132
|
+
combination_count = math.comb(count, subset_size)
|
|
1133
|
+
if type(rank) is not int or rank < 0 or rank >= combination_count:
|
|
1134
|
+
raise ValueError(
|
|
1135
|
+
f"subset_rank must lie in [0, {combination_count}) for this snapshot"
|
|
1136
|
+
)
|
|
1137
|
+
selected_indices = []
|
|
1138
|
+
remaining_rank = rank
|
|
1139
|
+
start = 0
|
|
1140
|
+
for position in range(subset_size):
|
|
1141
|
+
remaining_positions = subset_size - position - 1
|
|
1142
|
+
for index in range(start, count):
|
|
1143
|
+
suffix_count = math.comb(count - index - 1, remaining_positions)
|
|
1144
|
+
if remaining_rank < suffix_count:
|
|
1145
|
+
selected_indices.append(index)
|
|
1146
|
+
start = index + 1
|
|
1147
|
+
break
|
|
1148
|
+
remaining_rank -= suffix_count
|
|
1149
|
+
return tuple(values[index] for index in selected_indices)
|
|
1150
|
+
|
|
1151
|
+
|
|
1152
|
+
def _unrank_permutation(values: tuple[float, ...], rank: int) -> tuple[float, ...]:
|
|
1153
|
+
permutation_count = math.factorial(len(values))
|
|
1154
|
+
if type(rank) is not int or rank < 0 or rank >= permutation_count:
|
|
1155
|
+
raise ValueError(
|
|
1156
|
+
f"permutation_rank must lie in [0, {permutation_count}) for this snapshot"
|
|
1157
|
+
)
|
|
1158
|
+
remaining = list(values)
|
|
1159
|
+
result = []
|
|
1160
|
+
remaining_rank = rank
|
|
1161
|
+
for slots in range(len(values), 0, -1):
|
|
1162
|
+
block_size = math.factorial(slots - 1)
|
|
1163
|
+
index, remaining_rank = divmod(remaining_rank, block_size)
|
|
1164
|
+
result.append(remaining.pop(index))
|
|
1165
|
+
return tuple(result)
|
|
1166
|
+
|
|
1167
|
+
|
|
1168
|
+
@dataclass(frozen=True, slots=True)
|
|
1169
|
+
class DeterministicMemoryControlPolicy:
|
|
1170
|
+
"""Exact replayable controls parameterized by recorded integer ranks.
|
|
1171
|
+
|
|
1172
|
+
A uniformly sampled integer in ``[0, n!)`` yields an exact uniform law over
|
|
1173
|
+
labelled score permutations; a uniformly sampled integer in ``[0, C(n,k))``
|
|
1174
|
+
yields an exact uniform law over k-subsets. This policy performs only the
|
|
1175
|
+
deterministic rank-to-realization mapping so randomization stays outside
|
|
1176
|
+
provider code and is trivial to replay.
|
|
1177
|
+
"""
|
|
1178
|
+
|
|
1179
|
+
def adaptive(
|
|
1180
|
+
self,
|
|
1181
|
+
*,
|
|
1182
|
+
snapshot: MemoryScoreSnapshot,
|
|
1183
|
+
subset_size: int,
|
|
1184
|
+
) -> InsightSelectionDecision:
|
|
1185
|
+
return self._select(
|
|
1186
|
+
snapshot=snapshot,
|
|
1187
|
+
subset_size=subset_size,
|
|
1188
|
+
scores=snapshot.retrieval_scores,
|
|
1189
|
+
exploration_probability=Fraction(0),
|
|
1190
|
+
rng=_NoRandom(),
|
|
1191
|
+
)
|
|
1192
|
+
|
|
1193
|
+
def score_shuffled(
|
|
1194
|
+
self,
|
|
1195
|
+
*,
|
|
1196
|
+
snapshot: MemoryScoreSnapshot,
|
|
1197
|
+
subset_size: int,
|
|
1198
|
+
permutation_rank: int,
|
|
1199
|
+
) -> InsightSelectionDecision:
|
|
1200
|
+
references = tuple(entry.reference for entry in snapshot.entries)
|
|
1201
|
+
values = tuple(entry.retrieval_score for entry in snapshot.entries)
|
|
1202
|
+
permuted = _unrank_permutation(values, permutation_rank)
|
|
1203
|
+
return self._select(
|
|
1204
|
+
snapshot=snapshot,
|
|
1205
|
+
subset_size=subset_size,
|
|
1206
|
+
scores=dict(zip(references, permuted, strict=True)),
|
|
1207
|
+
exploration_probability=Fraction(0),
|
|
1208
|
+
rng=_NoRandom(),
|
|
1209
|
+
)
|
|
1210
|
+
|
|
1211
|
+
def uniform(
|
|
1212
|
+
self,
|
|
1213
|
+
*,
|
|
1214
|
+
snapshot: MemoryScoreSnapshot,
|
|
1215
|
+
subset_size: int,
|
|
1216
|
+
subset_rank: int,
|
|
1217
|
+
) -> InsightSelectionDecision:
|
|
1218
|
+
references = tuple(entry.reference for entry in snapshot.entries)
|
|
1219
|
+
if type(subset_size) is not int or subset_size < 0:
|
|
1220
|
+
raise ValueError("subset_size must be a non-negative exact integer")
|
|
1221
|
+
if subset_size > len(references):
|
|
1222
|
+
raise ValueError("subset_size cannot exceed the snapshot size")
|
|
1223
|
+
selected = _unrank_combination(references, subset_size, subset_rank)
|
|
1224
|
+
return self._select(
|
|
1225
|
+
snapshot=snapshot,
|
|
1226
|
+
subset_size=subset_size,
|
|
1227
|
+
scores=snapshot.retrieval_scores,
|
|
1228
|
+
exploration_probability=Fraction(1),
|
|
1229
|
+
rng=_ExactSubsetRandom(selected),
|
|
1230
|
+
)
|
|
1231
|
+
|
|
1232
|
+
@staticmethod
|
|
1233
|
+
def _select(
|
|
1234
|
+
*,
|
|
1235
|
+
snapshot: MemoryScoreSnapshot,
|
|
1236
|
+
subset_size: int,
|
|
1237
|
+
scores: Mapping[InsightRef, Real],
|
|
1238
|
+
exploration_probability: Fraction,
|
|
1239
|
+
rng: _NoRandom,
|
|
1240
|
+
) -> InsightSelectionDecision:
|
|
1241
|
+
if not isinstance(snapshot, MemoryScoreSnapshot):
|
|
1242
|
+
raise TypeError("snapshot must be a MemoryScoreSnapshot")
|
|
1243
|
+
return EpsilonGreedySubsetSelector(exploration_probability).select(
|
|
1244
|
+
context_hash=snapshot.exact_context_hash,
|
|
1245
|
+
eligible=tuple(entry.reference for entry in snapshot.entries),
|
|
1246
|
+
scores=scores,
|
|
1247
|
+
subset_size=subset_size,
|
|
1248
|
+
rng=rng,
|
|
1249
|
+
)
|
|
1250
|
+
|
|
1251
|
+
|
|
1252
|
+
__all__ = [
|
|
1253
|
+
"CausalMemoryObservation",
|
|
1254
|
+
"CausalSearchScore",
|
|
1255
|
+
"CausalSearchScorePolicy",
|
|
1256
|
+
"DelayedCreditMode",
|
|
1257
|
+
"DeterministicMemoryControlPolicy",
|
|
1258
|
+
"FrozenDiagnosticMemoryWave",
|
|
1259
|
+
"IncompleteMemoryWaveError",
|
|
1260
|
+
"MemoryAssignmentArm",
|
|
1261
|
+
"MemoryAssignmentReceipt",
|
|
1262
|
+
"MemoryCheckpointClosure",
|
|
1263
|
+
"MemoryCheckpointClosureStatus",
|
|
1264
|
+
"MemoryScoreSnapshot",
|
|
1265
|
+
"MemoryTrialTerminalStatus",
|
|
1266
|
+
"ResolvedInsightAssignment",
|
|
1267
|
+
"StaleMemorySnapshotError",
|
|
1268
|
+
"WaveSealedCheckpointBuilder",
|
|
1269
|
+
"insight_selection_decision_sha256",
|
|
1270
|
+
]
|