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,325 @@
|
|
|
1
|
+
"""Workload-neutral accounting for candidate occurrences and physical evaluations.
|
|
2
|
+
|
|
3
|
+
An optimizer can administer two distinct candidate occurrences that resolve to
|
|
4
|
+
the same benchmark phenotype. The evaluation cache should coalesce that work,
|
|
5
|
+
but experiment reports must never silently relabel the cache hit as a fresh
|
|
6
|
+
physical evaluation. This module validates the complete seed/stage partition
|
|
7
|
+
and publishes both quantities explicitly.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(frozen=True, slots=True)
|
|
16
|
+
class CampaignEvaluationAccounting:
|
|
17
|
+
"""Exact occurrence/physical-call partition for one completed campaign.
|
|
18
|
+
|
|
19
|
+
``planned_candidate_occurrences`` is an exact commitment by default. A
|
|
20
|
+
campaign whose evolutionary operators may *typed-abstain* (for example,
|
|
21
|
+
when no legal crossover pair exists) can additionally publish a
|
|
22
|
+
``minimum_candidate_occurrences``. In that mode the frozen plan is a hard
|
|
23
|
+
capacity ceiling and the minimum is the mandatory work floor. This keeps
|
|
24
|
+
legal operator abstention distinct from both silent budget loss and cache
|
|
25
|
+
reuse: every realized occurrence is still partitioned exactly, while the
|
|
26
|
+
unfilled capacity is reported explicitly.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
planned_candidate_occurrences: int
|
|
30
|
+
seed_occurrences: int
|
|
31
|
+
seed_unique_evaluations: int
|
|
32
|
+
stage_occurrences: tuple[int, ...]
|
|
33
|
+
stage_unique_evaluations: tuple[int, ...]
|
|
34
|
+
candidate_occurrences: int
|
|
35
|
+
unique_evaluations: int
|
|
36
|
+
minimum_candidate_occurrences: int | None = None
|
|
37
|
+
|
|
38
|
+
def __post_init__(self) -> None:
|
|
39
|
+
for name in (
|
|
40
|
+
"planned_candidate_occurrences",
|
|
41
|
+
"seed_occurrences",
|
|
42
|
+
"seed_unique_evaluations",
|
|
43
|
+
"candidate_occurrences",
|
|
44
|
+
"unique_evaluations",
|
|
45
|
+
):
|
|
46
|
+
value = getattr(self, name)
|
|
47
|
+
if type(value) is not int or value < 0:
|
|
48
|
+
raise ValueError(f"{name} must be a non-negative exact integer")
|
|
49
|
+
if self.minimum_candidate_occurrences is not None and (
|
|
50
|
+
type(self.minimum_candidate_occurrences) is not int
|
|
51
|
+
or self.minimum_candidate_occurrences < 0
|
|
52
|
+
):
|
|
53
|
+
raise ValueError(
|
|
54
|
+
"minimum_candidate_occurrences must be a non-negative exact "
|
|
55
|
+
"integer or None"
|
|
56
|
+
)
|
|
57
|
+
for name in ("stage_occurrences", "stage_unique_evaluations"):
|
|
58
|
+
values = getattr(self, name)
|
|
59
|
+
if type(values) is not tuple or any(
|
|
60
|
+
type(value) is not int or value < 0 for value in values
|
|
61
|
+
):
|
|
62
|
+
raise ValueError(f"{name} must be an exact non-negative tuple")
|
|
63
|
+
if len(self.stage_occurrences) != len(self.stage_unique_evaluations):
|
|
64
|
+
raise ValueError("stage occurrence and evaluation tuples must align")
|
|
65
|
+
if self.seed_unique_evaluations > self.seed_occurrences:
|
|
66
|
+
raise ValueError("seed physical evaluations exceed seed occurrences")
|
|
67
|
+
if any(
|
|
68
|
+
unique > occurrences
|
|
69
|
+
for occurrences, unique in zip(
|
|
70
|
+
self.stage_occurrences,
|
|
71
|
+
self.stage_unique_evaluations,
|
|
72
|
+
strict=True,
|
|
73
|
+
)
|
|
74
|
+
):
|
|
75
|
+
raise ValueError("stage physical evaluations exceed occurrences")
|
|
76
|
+
if self.candidate_occurrences != (
|
|
77
|
+
self.seed_occurrences + sum(self.stage_occurrences)
|
|
78
|
+
):
|
|
79
|
+
raise ValueError("candidate occurrences do not equal the stage partition")
|
|
80
|
+
if self.unique_evaluations != (
|
|
81
|
+
self.seed_unique_evaluations + sum(self.stage_unique_evaluations)
|
|
82
|
+
):
|
|
83
|
+
raise ValueError("unique evaluations do not equal the stage partition")
|
|
84
|
+
if self.minimum_candidate_occurrences is None:
|
|
85
|
+
if self.candidate_occurrences != self.planned_candidate_occurrences:
|
|
86
|
+
raise ValueError("completed occurrences differ from the frozen plan")
|
|
87
|
+
elif not (
|
|
88
|
+
self.minimum_candidate_occurrences
|
|
89
|
+
<= self.candidate_occurrences
|
|
90
|
+
<= self.planned_candidate_occurrences
|
|
91
|
+
):
|
|
92
|
+
raise ValueError(
|
|
93
|
+
"completed occurrences escape the frozen capacity envelope"
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def planned_underfill_occurrences(self) -> int:
|
|
98
|
+
self.__post_init__()
|
|
99
|
+
return self.planned_candidate_occurrences - self.candidate_occurrences
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def candidate_capacity_utilization(self) -> float:
|
|
103
|
+
self.__post_init__()
|
|
104
|
+
if self.planned_candidate_occurrences == 0:
|
|
105
|
+
return 1.0
|
|
106
|
+
return self.candidate_occurrences / self.planned_candidate_occurrences
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def cache_reuse_occurrences(self) -> int:
|
|
110
|
+
self.__post_init__()
|
|
111
|
+
return self.candidate_occurrences - self.unique_evaluations
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def physical_evaluation_utilization(self) -> float:
|
|
115
|
+
self.__post_init__()
|
|
116
|
+
if self.candidate_occurrences == 0:
|
|
117
|
+
return 1.0
|
|
118
|
+
return self.unique_evaluations / self.candidate_occurrences
|
|
119
|
+
|
|
120
|
+
def within_cache_reuse_limit(self, maximum: int) -> bool:
|
|
121
|
+
self.__post_init__()
|
|
122
|
+
if type(maximum) is not int or maximum < 0:
|
|
123
|
+
raise ValueError("maximum must be a non-negative exact integer")
|
|
124
|
+
return self.cache_reuse_occurrences <= maximum
|
|
125
|
+
|
|
126
|
+
def to_record(self) -> dict[str, object]:
|
|
127
|
+
self.__post_init__()
|
|
128
|
+
return {
|
|
129
|
+
"schema_version": 1,
|
|
130
|
+
"planned_candidate_occurrences": self.planned_candidate_occurrences,
|
|
131
|
+
"minimum_candidate_occurrences": self.minimum_candidate_occurrences,
|
|
132
|
+
"candidate_plan_mode": (
|
|
133
|
+
"exact"
|
|
134
|
+
if self.minimum_candidate_occurrences is None
|
|
135
|
+
else "typed_operator_abstention_capacity_envelope"
|
|
136
|
+
),
|
|
137
|
+
"seed_occurrences": self.seed_occurrences,
|
|
138
|
+
"seed_unique_evaluations": self.seed_unique_evaluations,
|
|
139
|
+
"stage_occurrences": list(self.stage_occurrences),
|
|
140
|
+
"stage_unique_evaluations": list(self.stage_unique_evaluations),
|
|
141
|
+
"candidate_occurrences": self.candidate_occurrences,
|
|
142
|
+
"unique_evaluations": self.unique_evaluations,
|
|
143
|
+
"planned_underfill_occurrences": self.planned_underfill_occurrences,
|
|
144
|
+
"candidate_capacity_utilization_hex": (
|
|
145
|
+
self.candidate_capacity_utilization.hex()
|
|
146
|
+
),
|
|
147
|
+
"cache_reuse_occurrences": self.cache_reuse_occurrences,
|
|
148
|
+
"physical_evaluation_utilization_hex": (
|
|
149
|
+
self.physical_evaluation_utilization.hex()
|
|
150
|
+
),
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
@dataclass(frozen=True, slots=True)
|
|
155
|
+
class CampaignPortfolioEvidenceAccounting:
|
|
156
|
+
"""Exact evidence partition for finite-portfolio campaign outcomes.
|
|
157
|
+
|
|
158
|
+
Evaluator-declared candidate infeasibility is a terminal scientific
|
|
159
|
+
outcome, but it cannot publish objective deltas or forecast-calibration
|
|
160
|
+
observations. This accounting keeps those candidates in the proposal and
|
|
161
|
+
feedback action budget without fabricating numeric evidence for them.
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
planned_portfolio_occurrences: int
|
|
165
|
+
portfolio_scored_occurrences: int
|
|
166
|
+
portfolio_candidate_infeasible_occurrences: int
|
|
167
|
+
authenticated_mutation_observations: int
|
|
168
|
+
reflection_source_scored_occurrences: int
|
|
169
|
+
reflection_identifiable_contrasts: int
|
|
170
|
+
forecast_enabled: bool
|
|
171
|
+
planned_selector_receipts: int
|
|
172
|
+
forecast_receipts: int
|
|
173
|
+
forecast_actions: int
|
|
174
|
+
forecast_scored_actions: int
|
|
175
|
+
forecast_candidate_infeasible_actions: int
|
|
176
|
+
objective_metric_count: int
|
|
177
|
+
forecast_observations: int
|
|
178
|
+
reflection_typed_exclusions: int = 0
|
|
179
|
+
|
|
180
|
+
def __post_init__(self) -> None:
|
|
181
|
+
if type(self.forecast_enabled) is not bool:
|
|
182
|
+
raise TypeError("forecast_enabled must be an exact bool")
|
|
183
|
+
for name in (
|
|
184
|
+
"planned_portfolio_occurrences",
|
|
185
|
+
"portfolio_scored_occurrences",
|
|
186
|
+
"portfolio_candidate_infeasible_occurrences",
|
|
187
|
+
"authenticated_mutation_observations",
|
|
188
|
+
"reflection_source_scored_occurrences",
|
|
189
|
+
"reflection_identifiable_contrasts",
|
|
190
|
+
"reflection_typed_exclusions",
|
|
191
|
+
"planned_selector_receipts",
|
|
192
|
+
"forecast_receipts",
|
|
193
|
+
"forecast_actions",
|
|
194
|
+
"forecast_scored_actions",
|
|
195
|
+
"forecast_candidate_infeasible_actions",
|
|
196
|
+
"objective_metric_count",
|
|
197
|
+
"forecast_observations",
|
|
198
|
+
):
|
|
199
|
+
value = getattr(self, name)
|
|
200
|
+
if type(value) is not int or value < 0:
|
|
201
|
+
raise ValueError(f"{name} must be a non-negative exact integer")
|
|
202
|
+
if self.objective_metric_count == 0:
|
|
203
|
+
raise ValueError("objective_metric_count must be positive")
|
|
204
|
+
if self.reflection_source_scored_occurrences > (
|
|
205
|
+
self.portfolio_scored_occurrences
|
|
206
|
+
):
|
|
207
|
+
raise ValueError(
|
|
208
|
+
"reflection source scored occurrences exceed portfolio scores"
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
@property
|
|
212
|
+
def exact_portfolio_outcome_partition(self) -> bool:
|
|
213
|
+
self.__post_init__()
|
|
214
|
+
return self.planned_portfolio_occurrences == (
|
|
215
|
+
self.portfolio_scored_occurrences
|
|
216
|
+
+ self.portfolio_candidate_infeasible_occurrences
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
@property
|
|
220
|
+
def exact_authenticated_mutation_evidence(self) -> bool:
|
|
221
|
+
self.__post_init__()
|
|
222
|
+
return (
|
|
223
|
+
self.authenticated_mutation_observations
|
|
224
|
+
== self.portfolio_scored_occurrences
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
@property
|
|
228
|
+
def exact_reflection_contrast_accounting(self) -> bool:
|
|
229
|
+
"""Compatibility name for the exact typed reflection partition."""
|
|
230
|
+
|
|
231
|
+
return self.exact_reflection_evidence_partition
|
|
232
|
+
|
|
233
|
+
@property
|
|
234
|
+
def exact_reflection_evidence_partition(self) -> bool:
|
|
235
|
+
self.__post_init__()
|
|
236
|
+
return (
|
|
237
|
+
self.reflection_identifiable_contrasts
|
|
238
|
+
+ self.reflection_typed_exclusions
|
|
239
|
+
== self.reflection_source_scored_occurrences
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
@property
|
|
243
|
+
def exact_forecast_feedback(self) -> bool:
|
|
244
|
+
self.__post_init__()
|
|
245
|
+
if not self.forecast_enabled:
|
|
246
|
+
return (
|
|
247
|
+
self.planned_selector_receipts == 0
|
|
248
|
+
and self.forecast_receipts == 0
|
|
249
|
+
and self.forecast_actions == 0
|
|
250
|
+
and self.forecast_scored_actions == 0
|
|
251
|
+
and self.forecast_candidate_infeasible_actions == 0
|
|
252
|
+
and self.forecast_observations == 0
|
|
253
|
+
)
|
|
254
|
+
return (
|
|
255
|
+
self.forecast_receipts == self.planned_selector_receipts
|
|
256
|
+
and self.forecast_actions == self.planned_portfolio_occurrences
|
|
257
|
+
and self.forecast_scored_actions == self.portfolio_scored_occurrences
|
|
258
|
+
and self.forecast_candidate_infeasible_actions
|
|
259
|
+
== self.portfolio_candidate_infeasible_occurrences
|
|
260
|
+
and self.forecast_observations
|
|
261
|
+
== self.portfolio_scored_occurrences * self.objective_metric_count
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
@property
|
|
265
|
+
def all_exact(self) -> bool:
|
|
266
|
+
self.__post_init__()
|
|
267
|
+
return (
|
|
268
|
+
self.exact_portfolio_outcome_partition
|
|
269
|
+
and self.exact_authenticated_mutation_evidence
|
|
270
|
+
and self.exact_reflection_evidence_partition
|
|
271
|
+
and self.exact_forecast_feedback
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
def to_record(self) -> dict[str, object]:
|
|
275
|
+
self.__post_init__()
|
|
276
|
+
return {
|
|
277
|
+
"schema_version": 2,
|
|
278
|
+
"planned_portfolio_occurrences": self.planned_portfolio_occurrences,
|
|
279
|
+
"portfolio_scored_occurrences": self.portfolio_scored_occurrences,
|
|
280
|
+
"portfolio_candidate_infeasible_occurrences": (
|
|
281
|
+
self.portfolio_candidate_infeasible_occurrences
|
|
282
|
+
),
|
|
283
|
+
"authenticated_mutation_observations": (
|
|
284
|
+
self.authenticated_mutation_observations
|
|
285
|
+
),
|
|
286
|
+
"reflection_source_scored_occurrences": (
|
|
287
|
+
self.reflection_source_scored_occurrences
|
|
288
|
+
),
|
|
289
|
+
"reflection_identifiable_contrasts": (
|
|
290
|
+
self.reflection_identifiable_contrasts
|
|
291
|
+
),
|
|
292
|
+
"reflection_typed_exclusions": self.reflection_typed_exclusions,
|
|
293
|
+
"forecast_enabled": self.forecast_enabled,
|
|
294
|
+
"planned_selector_receipts": self.planned_selector_receipts,
|
|
295
|
+
"forecast_receipts": self.forecast_receipts,
|
|
296
|
+
"forecast_actions": self.forecast_actions,
|
|
297
|
+
"forecast_scored_actions": self.forecast_scored_actions,
|
|
298
|
+
"forecast_candidate_infeasible_actions": (
|
|
299
|
+
self.forecast_candidate_infeasible_actions
|
|
300
|
+
),
|
|
301
|
+
"objective_metric_count": self.objective_metric_count,
|
|
302
|
+
"forecast_observations": self.forecast_observations,
|
|
303
|
+
"gates": {
|
|
304
|
+
"exact_portfolio_outcome_partition": (
|
|
305
|
+
self.exact_portfolio_outcome_partition
|
|
306
|
+
),
|
|
307
|
+
"exact_authenticated_mutation_evidence": (
|
|
308
|
+
self.exact_authenticated_mutation_evidence
|
|
309
|
+
),
|
|
310
|
+
"exact_reflection_contrast_accounting": (
|
|
311
|
+
self.exact_reflection_contrast_accounting
|
|
312
|
+
),
|
|
313
|
+
"exact_reflection_evidence_partition": (
|
|
314
|
+
self.exact_reflection_evidence_partition
|
|
315
|
+
),
|
|
316
|
+
"exact_forecast_feedback": self.exact_forecast_feedback,
|
|
317
|
+
},
|
|
318
|
+
"all_exact": self.all_exact,
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
__all__ = [
|
|
323
|
+
"CampaignEvaluationAccounting",
|
|
324
|
+
"CampaignPortfolioEvidenceAccounting",
|
|
325
|
+
]
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"""Cancellation-safe single-flight caching for expensive async evaluations.
|
|
2
|
+
|
|
3
|
+
The caller owns canonical configuration hashing. This module never serializes or
|
|
4
|
+
normalizes configurations, so cache identity cannot silently lose type or value
|
|
5
|
+
information. Every normally returned value is cacheable, including a domain's
|
|
6
|
+
ordinary invalid-result value; raised exceptions and cancellations are not.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import asyncio
|
|
12
|
+
from collections import OrderedDict
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
from enum import Enum
|
|
15
|
+
from typing import Awaitable, Callable, Generic, Optional, TypeVar
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
ValueT = TypeVar("ValueT")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class EvaluationCacheEventType(str, Enum):
|
|
22
|
+
"""The deliberately small observable lifecycle of the cache."""
|
|
23
|
+
|
|
24
|
+
HIT = "hit"
|
|
25
|
+
MISS = "miss"
|
|
26
|
+
COALESCED = "coalesced"
|
|
27
|
+
EVICTED = "evicted"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass(frozen=True, slots=True)
|
|
31
|
+
class EvaluationCacheSnapshot:
|
|
32
|
+
"""Immutable cache occupancy and cumulative request counters."""
|
|
33
|
+
|
|
34
|
+
capacity: Optional[int]
|
|
35
|
+
cached_entries: int
|
|
36
|
+
in_flight: int
|
|
37
|
+
hits: int
|
|
38
|
+
misses: int
|
|
39
|
+
coalesced: int
|
|
40
|
+
evictions: int
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass(frozen=True, slots=True)
|
|
44
|
+
class EvaluationCacheTraceEvent:
|
|
45
|
+
"""One ordered cache lifecycle event with its post-event snapshot."""
|
|
46
|
+
|
|
47
|
+
sequence: int
|
|
48
|
+
event_type: EvaluationCacheEventType
|
|
49
|
+
config_hash: str
|
|
50
|
+
snapshot: EvaluationCacheSnapshot
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
EvaluationFactory = Callable[[], Awaitable[ValueT]]
|
|
54
|
+
EvaluationCacheTraceCallback = Callable[[EvaluationCacheTraceEvent], None]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class AsyncEvaluationCache(Generic[ValueT]):
|
|
58
|
+
"""Coalesce identical evaluations and optionally retain results in an LRU.
|
|
59
|
+
|
|
60
|
+
``get_or_evaluate`` shields shared work from each individual awaiter. Thus,
|
|
61
|
+
cancelling one awaiter does not cancel an evaluation still needed by another
|
|
62
|
+
awaiter (or prevent its result from being cached). If the evaluation itself
|
|
63
|
+
raises or is cancelled, its in-flight entry is removed and a later request is
|
|
64
|
+
a fresh miss.
|
|
65
|
+
|
|
66
|
+
Trace callbacks are synchronous observation hooks. They run in serialized
|
|
67
|
+
event order, and callback failures are isolated from cache semantics.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
def __init__(
|
|
71
|
+
self,
|
|
72
|
+
*,
|
|
73
|
+
capacity: Optional[int] = None,
|
|
74
|
+
trace_callback: Optional[EvaluationCacheTraceCallback] = None,
|
|
75
|
+
) -> None:
|
|
76
|
+
if capacity is not None and (type(capacity) is not int or capacity < 1):
|
|
77
|
+
raise ValueError("capacity must be None or a positive integer")
|
|
78
|
+
if trace_callback is not None and not callable(trace_callback):
|
|
79
|
+
raise TypeError("trace_callback must be callable or None")
|
|
80
|
+
|
|
81
|
+
self._capacity = capacity
|
|
82
|
+
self._trace_callback = trace_callback
|
|
83
|
+
self._values: "OrderedDict[str, ValueT]" = OrderedDict()
|
|
84
|
+
self._in_flight: dict[str, "asyncio.Task[ValueT]"] = {}
|
|
85
|
+
self._lock = asyncio.Lock()
|
|
86
|
+
|
|
87
|
+
self._hits = 0
|
|
88
|
+
self._misses = 0
|
|
89
|
+
self._coalesced = 0
|
|
90
|
+
self._evictions = 0
|
|
91
|
+
self._event_sequence = 0
|
|
92
|
+
|
|
93
|
+
async def get_or_evaluate(
|
|
94
|
+
self,
|
|
95
|
+
config_hash: str,
|
|
96
|
+
evaluate: EvaluationFactory[ValueT],
|
|
97
|
+
) -> ValueT:
|
|
98
|
+
"""Return a cached value or await one shared evaluation for ``config_hash``."""
|
|
99
|
+
|
|
100
|
+
self._validate_config_hash(config_hash)
|
|
101
|
+
if not callable(evaluate):
|
|
102
|
+
raise TypeError("evaluate must be callable")
|
|
103
|
+
|
|
104
|
+
async with self._lock:
|
|
105
|
+
if config_hash in self._values:
|
|
106
|
+
value = self._values.pop(config_hash)
|
|
107
|
+
self._values[config_hash] = value
|
|
108
|
+
self._hits += 1
|
|
109
|
+
self._emit_locked(EvaluationCacheEventType.HIT, config_hash)
|
|
110
|
+
return value
|
|
111
|
+
|
|
112
|
+
task = self._in_flight.get(config_hash)
|
|
113
|
+
if task is None:
|
|
114
|
+
task = asyncio.create_task(
|
|
115
|
+
self._evaluate_and_store(config_hash, evaluate)
|
|
116
|
+
)
|
|
117
|
+
self._in_flight[config_hash] = task
|
|
118
|
+
self._misses += 1
|
|
119
|
+
self._emit_locked(EvaluationCacheEventType.MISS, config_hash)
|
|
120
|
+
else:
|
|
121
|
+
self._coalesced += 1
|
|
122
|
+
self._emit_locked(EvaluationCacheEventType.COALESCED, config_hash)
|
|
123
|
+
|
|
124
|
+
# An awaiter's cancellation must not propagate into shared evaluation work.
|
|
125
|
+
return await asyncio.shield(task)
|
|
126
|
+
|
|
127
|
+
async def snapshot(self) -> EvaluationCacheSnapshot:
|
|
128
|
+
"""Return an atomic occupancy and counter snapshot."""
|
|
129
|
+
|
|
130
|
+
async with self._lock:
|
|
131
|
+
return self._snapshot_locked()
|
|
132
|
+
|
|
133
|
+
async def _evaluate_and_store(
|
|
134
|
+
self,
|
|
135
|
+
config_hash: str,
|
|
136
|
+
evaluate: EvaluationFactory[ValueT],
|
|
137
|
+
) -> ValueT:
|
|
138
|
+
current_task = asyncio.current_task()
|
|
139
|
+
if current_task is None: # pragma: no cover - guaranteed by create_task
|
|
140
|
+
raise RuntimeError("evaluation did not run inside an asyncio task")
|
|
141
|
+
|
|
142
|
+
try:
|
|
143
|
+
value = await evaluate()
|
|
144
|
+
except BaseException:
|
|
145
|
+
async with self._lock:
|
|
146
|
+
if self._in_flight.get(config_hash) is current_task:
|
|
147
|
+
del self._in_flight[config_hash]
|
|
148
|
+
raise
|
|
149
|
+
|
|
150
|
+
async with self._lock:
|
|
151
|
+
if self._in_flight.get(config_hash) is not current_task:
|
|
152
|
+
raise RuntimeError("single-flight evaluation lost its ownership entry")
|
|
153
|
+
del self._in_flight[config_hash]
|
|
154
|
+
self._values[config_hash] = value
|
|
155
|
+
self._values.move_to_end(config_hash)
|
|
156
|
+
|
|
157
|
+
if self._capacity is not None and len(self._values) > self._capacity:
|
|
158
|
+
evicted_hash, _ = self._values.popitem(last=False)
|
|
159
|
+
self._evictions += 1
|
|
160
|
+
self._emit_locked(EvaluationCacheEventType.EVICTED, evicted_hash)
|
|
161
|
+
|
|
162
|
+
return value
|
|
163
|
+
|
|
164
|
+
@staticmethod
|
|
165
|
+
def _validate_config_hash(config_hash: str) -> None:
|
|
166
|
+
if type(config_hash) is not str or not config_hash:
|
|
167
|
+
raise ValueError("config_hash must be a nonempty string")
|
|
168
|
+
|
|
169
|
+
def _snapshot_locked(self) -> EvaluationCacheSnapshot:
|
|
170
|
+
return EvaluationCacheSnapshot(
|
|
171
|
+
capacity=self._capacity,
|
|
172
|
+
cached_entries=len(self._values),
|
|
173
|
+
in_flight=len(self._in_flight),
|
|
174
|
+
hits=self._hits,
|
|
175
|
+
misses=self._misses,
|
|
176
|
+
coalesced=self._coalesced,
|
|
177
|
+
evictions=self._evictions,
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
def _emit_locked(
|
|
181
|
+
self,
|
|
182
|
+
event_type: EvaluationCacheEventType,
|
|
183
|
+
config_hash: str,
|
|
184
|
+
) -> None:
|
|
185
|
+
callback = self._trace_callback
|
|
186
|
+
if callback is None:
|
|
187
|
+
return
|
|
188
|
+
self._event_sequence += 1
|
|
189
|
+
event = EvaluationCacheTraceEvent(
|
|
190
|
+
sequence=self._event_sequence,
|
|
191
|
+
event_type=event_type,
|
|
192
|
+
config_hash=config_hash,
|
|
193
|
+
snapshot=self._snapshot_locked(),
|
|
194
|
+
)
|
|
195
|
+
try:
|
|
196
|
+
callback(event)
|
|
197
|
+
except Exception:
|
|
198
|
+
# Observation must not turn a valid evaluation into a failed one.
|
|
199
|
+
return
|