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,1828 @@
|
|
|
1
|
+
"""Budgeted orchestration for explicit agentic evolution generations.
|
|
2
|
+
|
|
3
|
+
The :class:`AgenticEvolutionEngine` deliberately does not choose generations.
|
|
4
|
+
This module supplies the missing application boundary: a planner receives an
|
|
5
|
+
immutable history/archive cutoff, returns one ordered wave, and the optimizer
|
|
6
|
+
reserves hard budgets before either a model call or an evaluation can start.
|
|
7
|
+
|
|
8
|
+
The coordinator is domain- and provider-agnostic. Model-authored and
|
|
9
|
+
engine-materialized slots execute concurrently, but candidates are appended to
|
|
10
|
+
history and published to the Pareto archive in the planner's frozen slot order.
|
|
11
|
+
Every wave uses one reward binding tied to the exact pre-wave archive cutoff.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import asyncio
|
|
17
|
+
import hashlib
|
|
18
|
+
import json
|
|
19
|
+
import math
|
|
20
|
+
import time
|
|
21
|
+
from collections.abc import Callable, Mapping, Sequence
|
|
22
|
+
from dataclasses import dataclass
|
|
23
|
+
from enum import Enum
|
|
24
|
+
from fractions import Fraction
|
|
25
|
+
from typing import Protocol
|
|
26
|
+
|
|
27
|
+
from agent_evolve.application.agentic_evolution import (
|
|
28
|
+
AgenticEvolutionEngine,
|
|
29
|
+
EvolutionCandidate,
|
|
30
|
+
InvocationOutcome,
|
|
31
|
+
InvocationPlan,
|
|
32
|
+
MaterializedInvocation,
|
|
33
|
+
OperatorKind,
|
|
34
|
+
ProposalAuthority,
|
|
35
|
+
RewardPolicyBinding,
|
|
36
|
+
)
|
|
37
|
+
from agent_evolve.application.generation_feedback import (
|
|
38
|
+
GenerationFeedbackContext,
|
|
39
|
+
GenerationFeedbackInterceptor,
|
|
40
|
+
GenerationFeedbackReceipt,
|
|
41
|
+
GenerationFeedbackReservation,
|
|
42
|
+
GenerationFeedbackResult,
|
|
43
|
+
seal_generation_feedback,
|
|
44
|
+
validate_generation_feedback_receipt,
|
|
45
|
+
)
|
|
46
|
+
from agent_evolve.application.pareto_archive import (
|
|
47
|
+
ParetoArchive,
|
|
48
|
+
ParetoArchiveSnapshot,
|
|
49
|
+
ParetoDecision,
|
|
50
|
+
pareto_candidate_hash,
|
|
51
|
+
)
|
|
52
|
+
from agent_evolve.domain.patch import canonical_path_bytes, require_sha256
|
|
53
|
+
from agent_evolve.domain.typed_json import freeze_json, typed_json_sha256
|
|
54
|
+
from agent_evolve.policies.variation.exact_parent_crossover import (
|
|
55
|
+
exact_parent_import_exclusions_sha256,
|
|
56
|
+
)
|
|
57
|
+
from agent_evolve.ports.agentic_generator import AtomicMutationDraft, CandidateDraft
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
OptimizerTraceSink = Callable[[Mapping[str, object]], None]
|
|
61
|
+
_HASH_DOMAIN = b"agent-evolve:budgeted-agentic-optimizer:v1\x00"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class OptimizerContractError(ValueError):
|
|
65
|
+
"""A planner, budget, or optimizer input violated the frozen contract."""
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class OptimizerBudgetExceeded(OptimizerContractError):
|
|
69
|
+
"""A complete wave could exceed a hard resource cap."""
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class OptimizerPlanningError(RuntimeError):
|
|
73
|
+
"""The injected generation planner failed before a wave was admitted."""
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class OptimizerExecutionError(RuntimeError):
|
|
77
|
+
"""An admitted wave failed outside the engine's typed outcome boundary."""
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class OptimizerStopReason(str, Enum):
|
|
81
|
+
GENERATION_LIMIT_REACHED = "generation_limit_reached"
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _canonical_json(value: object) -> bytes:
|
|
85
|
+
return json.dumps(
|
|
86
|
+
value,
|
|
87
|
+
ensure_ascii=True,
|
|
88
|
+
allow_nan=False,
|
|
89
|
+
separators=(",", ":"),
|
|
90
|
+
sort_keys=True,
|
|
91
|
+
).encode("ascii")
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _record_hash(kind: str, value: object) -> str:
|
|
95
|
+
if type(kind) is not str or not kind:
|
|
96
|
+
raise ValueError("hash record kind must be non-empty")
|
|
97
|
+
return hashlib.sha256(
|
|
98
|
+
_HASH_DOMAIN + kind.encode("ascii") + b"\x00" + _canonical_json(value)
|
|
99
|
+
).hexdigest()
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def pareto_archive_snapshot_hash(snapshot: ParetoArchiveSnapshot) -> str:
|
|
103
|
+
"""Hash the complete immutable archive cutoff, including its decision ledger."""
|
|
104
|
+
|
|
105
|
+
if type(snapshot) is not ParetoArchiveSnapshot:
|
|
106
|
+
raise TypeError("snapshot must be an exact ParetoArchiveSnapshot")
|
|
107
|
+
record = {
|
|
108
|
+
"objectives": [
|
|
109
|
+
{"name": objective.name, "goal": objective.goal}
|
|
110
|
+
for objective in snapshot.objectives
|
|
111
|
+
],
|
|
112
|
+
"front": [
|
|
113
|
+
reference.to_trace_record() for reference in snapshot.front_references
|
|
114
|
+
],
|
|
115
|
+
"decisions": [decision.to_trace_record() for decision in snapshot.decisions],
|
|
116
|
+
"consideration_count": snapshot.consideration_count,
|
|
117
|
+
"eligible_configuration_count": snapshot.eligible_configuration_count,
|
|
118
|
+
"evidence_admission_policy": snapshot.evidence_admission_policy.value,
|
|
119
|
+
}
|
|
120
|
+
if not snapshot.objective_pareto_relation:
|
|
121
|
+
record["outcome_relation_policy"] = {
|
|
122
|
+
"policy_id": snapshot.outcome_relation_policy[0],
|
|
123
|
+
"policy_version": snapshot.outcome_relation_policy[1],
|
|
124
|
+
"definition_sha256": snapshot.outcome_relation_policy[2],
|
|
125
|
+
}
|
|
126
|
+
return _record_hash("pareto-archive-snapshot", record)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@dataclass(frozen=True, slots=True)
|
|
130
|
+
class OptimizerBudget:
|
|
131
|
+
"""Hard run-level caps; retries remain owned by the provider queue."""
|
|
132
|
+
|
|
133
|
+
max_unique_evaluations: int
|
|
134
|
+
max_logical_llm_calls: int
|
|
135
|
+
max_generations: int
|
|
136
|
+
|
|
137
|
+
def __post_init__(self) -> None:
|
|
138
|
+
if (
|
|
139
|
+
type(self.max_unique_evaluations) is not int
|
|
140
|
+
or self.max_unique_evaluations <= 0
|
|
141
|
+
):
|
|
142
|
+
raise ValueError("max_unique_evaluations must be a positive integer")
|
|
143
|
+
if (
|
|
144
|
+
type(self.max_logical_llm_calls) is not int
|
|
145
|
+
or self.max_logical_llm_calls < 0
|
|
146
|
+
):
|
|
147
|
+
raise ValueError("max_logical_llm_calls must be a non-negative integer")
|
|
148
|
+
if type(self.max_generations) is not int or self.max_generations < 0:
|
|
149
|
+
raise ValueError("max_generations must be a non-negative integer")
|
|
150
|
+
|
|
151
|
+
def to_trace_record(self) -> dict[str, int]:
|
|
152
|
+
return {
|
|
153
|
+
"max_unique_evaluations": self.max_unique_evaluations,
|
|
154
|
+
"max_logical_llm_calls": self.max_logical_llm_calls,
|
|
155
|
+
"max_generations": self.max_generations,
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
@property
|
|
159
|
+
def budget_hash(self) -> str:
|
|
160
|
+
return _record_hash("budget", self.to_trace_record())
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@dataclass(frozen=True, slots=True)
|
|
164
|
+
class FrozenWaveReward:
|
|
165
|
+
"""One reward policy frozen against one explicit pre-wave evidence cutoff."""
|
|
166
|
+
|
|
167
|
+
binding: RewardPolicyBinding
|
|
168
|
+
archive_snapshot_hash: str
|
|
169
|
+
reward_snapshot_hash: str
|
|
170
|
+
|
|
171
|
+
def __post_init__(self) -> None:
|
|
172
|
+
if type(self.binding) is not RewardPolicyBinding:
|
|
173
|
+
raise TypeError("binding must be an exact RewardPolicyBinding")
|
|
174
|
+
RewardPolicyBinding.__post_init__(self.binding)
|
|
175
|
+
require_sha256(self.archive_snapshot_hash, "archive_snapshot_hash")
|
|
176
|
+
require_sha256(self.reward_snapshot_hash, "reward_snapshot_hash")
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
@dataclass(frozen=True, slots=True)
|
|
180
|
+
class SeedGateContext:
|
|
181
|
+
"""Run facts available to a domain-specific seed admission policy."""
|
|
182
|
+
|
|
183
|
+
seed_index: int
|
|
184
|
+
label: str
|
|
185
|
+
requested_configuration_hash: str
|
|
186
|
+
unique_evaluations_before: int
|
|
187
|
+
unique_evaluations_after: int
|
|
188
|
+
|
|
189
|
+
def __post_init__(self) -> None:
|
|
190
|
+
if type(self.seed_index) is not int or self.seed_index < 0:
|
|
191
|
+
raise ValueError("seed_index must be non-negative")
|
|
192
|
+
if type(self.label) is not str or not self.label:
|
|
193
|
+
raise ValueError("label must be non-empty")
|
|
194
|
+
require_sha256(
|
|
195
|
+
self.requested_configuration_hash,
|
|
196
|
+
"requested_configuration_hash",
|
|
197
|
+
)
|
|
198
|
+
for name in ("unique_evaluations_before", "unique_evaluations_after"):
|
|
199
|
+
value = getattr(self, name)
|
|
200
|
+
if type(value) is not int or value < 0:
|
|
201
|
+
raise ValueError(f"{name} must be non-negative")
|
|
202
|
+
if self.unique_evaluations_after < self.unique_evaluations_before:
|
|
203
|
+
raise ValueError("seed evaluation counters cannot decrease")
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
@dataclass(frozen=True, slots=True)
|
|
207
|
+
class SeedGateDecision:
|
|
208
|
+
"""Versioned seed identity/objective/provenance admission evidence."""
|
|
209
|
+
|
|
210
|
+
admitted: bool
|
|
211
|
+
policy_id: str
|
|
212
|
+
policy_version: int
|
|
213
|
+
reason: str
|
|
214
|
+
evidence: tuple[tuple[str, str], ...] = ()
|
|
215
|
+
|
|
216
|
+
def __post_init__(self) -> None:
|
|
217
|
+
if type(self.admitted) is not bool:
|
|
218
|
+
raise TypeError("admitted must be bool")
|
|
219
|
+
if (
|
|
220
|
+
type(self.policy_id) is not str
|
|
221
|
+
or not self.policy_id
|
|
222
|
+
or self.policy_id != self.policy_id.strip()
|
|
223
|
+
):
|
|
224
|
+
raise ValueError("policy_id must be canonical non-empty text")
|
|
225
|
+
if type(self.policy_version) is not int or self.policy_version <= 0:
|
|
226
|
+
raise ValueError("policy_version must be positive")
|
|
227
|
+
if type(self.reason) is not str or not self.reason.strip():
|
|
228
|
+
raise ValueError("reason must be non-empty")
|
|
229
|
+
if type(self.evidence) is not tuple:
|
|
230
|
+
raise TypeError("evidence must be an exact tuple")
|
|
231
|
+
for item in self.evidence:
|
|
232
|
+
if (
|
|
233
|
+
type(item) is not tuple
|
|
234
|
+
or len(item) != 2
|
|
235
|
+
or any(type(value) is not str for value in item)
|
|
236
|
+
):
|
|
237
|
+
raise TypeError("evidence must contain exact string pairs")
|
|
238
|
+
if self.evidence != tuple(sorted(set(self.evidence))):
|
|
239
|
+
raise ValueError("evidence must be unique and canonically sorted")
|
|
240
|
+
|
|
241
|
+
def to_trace_record(self) -> dict[str, object]:
|
|
242
|
+
record = {
|
|
243
|
+
"admitted": self.admitted,
|
|
244
|
+
"policy_id": self.policy_id,
|
|
245
|
+
"policy_version": self.policy_version,
|
|
246
|
+
"reason": self.reason,
|
|
247
|
+
"evidence": [list(item) for item in self.evidence],
|
|
248
|
+
}
|
|
249
|
+
return {
|
|
250
|
+
**record,
|
|
251
|
+
"decision_hash": _record_hash("seed-gate-decision", record),
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
class SeedAdmissionPolicy(Protocol):
|
|
256
|
+
"""Domain gate for exact seed identity, objectives, and external provenance."""
|
|
257
|
+
|
|
258
|
+
def assess(
|
|
259
|
+
self,
|
|
260
|
+
candidate: EvolutionCandidate,
|
|
261
|
+
context: SeedGateContext,
|
|
262
|
+
) -> SeedGateDecision: ...
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
class ValidSeedAdmissionPolicy:
|
|
266
|
+
"""Default gate: require a valid evaluated seed with a complete objective vector."""
|
|
267
|
+
|
|
268
|
+
policy_id = "valid_evaluated_seed"
|
|
269
|
+
policy_version = 1
|
|
270
|
+
|
|
271
|
+
def assess(
|
|
272
|
+
self,
|
|
273
|
+
candidate: EvolutionCandidate,
|
|
274
|
+
context: SeedGateContext,
|
|
275
|
+
) -> SeedGateDecision:
|
|
276
|
+
del context
|
|
277
|
+
complete = candidate.valid and len(candidate.objectives) > 0
|
|
278
|
+
return SeedGateDecision(
|
|
279
|
+
admitted=complete,
|
|
280
|
+
policy_id=self.policy_id,
|
|
281
|
+
policy_version=self.policy_version,
|
|
282
|
+
reason=(
|
|
283
|
+
"seed is valid with a non-empty objective vector"
|
|
284
|
+
if complete
|
|
285
|
+
else "seed is invalid or lacks an objective vector"
|
|
286
|
+
),
|
|
287
|
+
evidence=(
|
|
288
|
+
("candidate_hash", pareto_candidate_hash(candidate)),
|
|
289
|
+
("valid", str(candidate.valid).lower()),
|
|
290
|
+
),
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
@dataclass(frozen=True, slots=True)
|
|
295
|
+
class OptimizerSlot:
|
|
296
|
+
"""One ordered generation slot with explicit proposal authority."""
|
|
297
|
+
|
|
298
|
+
slot_id: str
|
|
299
|
+
role: str
|
|
300
|
+
proposal_authority: ProposalAuthority
|
|
301
|
+
plan: InvocationPlan
|
|
302
|
+
materialized: MaterializedInvocation | None = None
|
|
303
|
+
|
|
304
|
+
def __post_init__(self) -> None:
|
|
305
|
+
for name in ("slot_id", "role"):
|
|
306
|
+
value = getattr(self, name)
|
|
307
|
+
if type(value) is not str or not value or value != value.strip():
|
|
308
|
+
raise ValueError(f"{name} must be canonical non-empty text")
|
|
309
|
+
if type(self.proposal_authority) is not ProposalAuthority:
|
|
310
|
+
raise TypeError("proposal_authority must be a ProposalAuthority")
|
|
311
|
+
if type(self.plan) is not InvocationPlan:
|
|
312
|
+
raise TypeError("plan must be an exact InvocationPlan")
|
|
313
|
+
InvocationPlan.__post_init__(self.plan)
|
|
314
|
+
if self.proposal_authority is ProposalAuthority.ENGINE:
|
|
315
|
+
if type(self.materialized) is not MaterializedInvocation:
|
|
316
|
+
raise ValueError("engine authority requires a materialized invocation")
|
|
317
|
+
MaterializedInvocation.__post_init__(self.materialized)
|
|
318
|
+
if self.materialized.plan != self.plan:
|
|
319
|
+
raise ValueError("slot plan and materialized invocation plan differ")
|
|
320
|
+
elif self.materialized is not None:
|
|
321
|
+
raise ValueError("only engine authority accepts a materialized invocation")
|
|
322
|
+
if self.proposal_authority is ProposalAuthority.MODEL:
|
|
323
|
+
if self.plan.operator_kind is OperatorKind.REPRODUCTION:
|
|
324
|
+
raise ValueError("reproduction is not a model-authored proposal")
|
|
325
|
+
elif self.proposal_authority is ProposalAuthority.REPRODUCTION:
|
|
326
|
+
if self.plan.operator_kind is not OperatorKind.REPRODUCTION:
|
|
327
|
+
raise ValueError("reproduction authority requires a reproduction plan")
|
|
328
|
+
elif self.plan.operator_kind is OperatorKind.REPRODUCTION:
|
|
329
|
+
raise ValueError("reproduction plans require reproduction authority")
|
|
330
|
+
|
|
331
|
+
@classmethod
|
|
332
|
+
def model(
|
|
333
|
+
cls,
|
|
334
|
+
*,
|
|
335
|
+
slot_id: str,
|
|
336
|
+
role: str,
|
|
337
|
+
plan: InvocationPlan,
|
|
338
|
+
) -> "OptimizerSlot":
|
|
339
|
+
return cls(slot_id, role, ProposalAuthority.MODEL, plan)
|
|
340
|
+
|
|
341
|
+
@classmethod
|
|
342
|
+
def engine(
|
|
343
|
+
cls,
|
|
344
|
+
*,
|
|
345
|
+
slot_id: str,
|
|
346
|
+
role: str,
|
|
347
|
+
invocation: MaterializedInvocation,
|
|
348
|
+
) -> "OptimizerSlot":
|
|
349
|
+
if type(invocation) is not MaterializedInvocation:
|
|
350
|
+
raise TypeError("invocation must be an exact MaterializedInvocation")
|
|
351
|
+
return cls(
|
|
352
|
+
slot_id,
|
|
353
|
+
role,
|
|
354
|
+
ProposalAuthority.ENGINE,
|
|
355
|
+
invocation.plan,
|
|
356
|
+
invocation,
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
@classmethod
|
|
360
|
+
def reproduction(
|
|
361
|
+
cls,
|
|
362
|
+
*,
|
|
363
|
+
slot_id: str,
|
|
364
|
+
role: str,
|
|
365
|
+
plan: InvocationPlan,
|
|
366
|
+
) -> "OptimizerSlot":
|
|
367
|
+
return cls(slot_id, role, ProposalAuthority.REPRODUCTION, plan)
|
|
368
|
+
|
|
369
|
+
@property
|
|
370
|
+
def logical_llm_call_reservation(self) -> int:
|
|
371
|
+
return int(self.proposal_authority is ProposalAuthority.MODEL)
|
|
372
|
+
|
|
373
|
+
@property
|
|
374
|
+
def unique_evaluation_reservation(self) -> int:
|
|
375
|
+
# Reproduction is guaranteed to reuse the exact parent configuration.
|
|
376
|
+
# Every other slot may require one new physical evaluation; reserving the
|
|
377
|
+
# upper bound is what makes concurrent admission safe.
|
|
378
|
+
return int(self.proposal_authority is not ProposalAuthority.REPRODUCTION)
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
@dataclass(frozen=True, slots=True)
|
|
382
|
+
class GenerationPlan:
|
|
383
|
+
"""An immutable, ordered wave returned by a generation policy."""
|
|
384
|
+
|
|
385
|
+
generation: int
|
|
386
|
+
slots: tuple[OptimizerSlot, ...]
|
|
387
|
+
reward: FrozenWaveReward
|
|
388
|
+
planner_policy_id: str
|
|
389
|
+
planner_policy_version: int
|
|
390
|
+
metadata: tuple[tuple[str, str], ...] = ()
|
|
391
|
+
|
|
392
|
+
def __post_init__(self) -> None:
|
|
393
|
+
if type(self.generation) is not int or self.generation <= 0:
|
|
394
|
+
raise ValueError("generation must be a positive integer")
|
|
395
|
+
if type(self.slots) is not tuple or any(
|
|
396
|
+
type(slot) is not OptimizerSlot for slot in self.slots
|
|
397
|
+
):
|
|
398
|
+
raise TypeError("slots must contain exact OptimizerSlot values")
|
|
399
|
+
if len({slot.slot_id for slot in self.slots}) != len(self.slots):
|
|
400
|
+
raise ValueError("generation slot IDs must be unique")
|
|
401
|
+
if any(slot.plan.generation != self.generation for slot in self.slots):
|
|
402
|
+
raise ValueError("every invocation must target the plan generation")
|
|
403
|
+
if type(self.reward) is not FrozenWaveReward:
|
|
404
|
+
raise TypeError("reward must be an exact FrozenWaveReward")
|
|
405
|
+
FrozenWaveReward.__post_init__(self.reward)
|
|
406
|
+
if (
|
|
407
|
+
type(self.planner_policy_id) is not str
|
|
408
|
+
or not self.planner_policy_id
|
|
409
|
+
or self.planner_policy_id != self.planner_policy_id.strip()
|
|
410
|
+
):
|
|
411
|
+
raise ValueError("planner_policy_id must be canonical non-empty text")
|
|
412
|
+
if (
|
|
413
|
+
type(self.planner_policy_version) is not int
|
|
414
|
+
or self.planner_policy_version <= 0
|
|
415
|
+
):
|
|
416
|
+
raise ValueError("planner_policy_version must be positive")
|
|
417
|
+
if type(self.metadata) is not tuple:
|
|
418
|
+
raise TypeError("metadata must be an exact tuple")
|
|
419
|
+
for item in self.metadata:
|
|
420
|
+
if (
|
|
421
|
+
type(item) is not tuple
|
|
422
|
+
or len(item) != 2
|
|
423
|
+
or any(type(value) is not str for value in item)
|
|
424
|
+
):
|
|
425
|
+
raise TypeError("metadata must contain exact string pairs")
|
|
426
|
+
if self.metadata != tuple(sorted(set(self.metadata))):
|
|
427
|
+
raise ValueError("metadata must be unique and canonically sorted")
|
|
428
|
+
|
|
429
|
+
@property
|
|
430
|
+
def logical_llm_call_reservation(self) -> int:
|
|
431
|
+
return sum(slot.logical_llm_call_reservation for slot in self.slots)
|
|
432
|
+
|
|
433
|
+
@property
|
|
434
|
+
def unique_evaluation_reservation(self) -> int:
|
|
435
|
+
return sum(slot.unique_evaluation_reservation for slot in self.slots)
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
@dataclass(frozen=True, slots=True)
|
|
439
|
+
class OptimizerState:
|
|
440
|
+
"""Immutable planner view after an entire generation has been published."""
|
|
441
|
+
|
|
442
|
+
generation: int
|
|
443
|
+
candidates: tuple[EvolutionCandidate, ...]
|
|
444
|
+
archive: ParetoArchiveSnapshot
|
|
445
|
+
archive_snapshot_hash: str
|
|
446
|
+
unique_evaluations: int
|
|
447
|
+
logical_llm_calls: int
|
|
448
|
+
generation_receipts: tuple["GenerationReceipt", ...] = ()
|
|
449
|
+
feedback_receipts: tuple[GenerationFeedbackReceipt, ...] = ()
|
|
450
|
+
|
|
451
|
+
def __post_init__(self) -> None:
|
|
452
|
+
if type(self.generation) is not int or self.generation < 0:
|
|
453
|
+
raise ValueError("generation must be non-negative")
|
|
454
|
+
if type(self.candidates) is not tuple or any(
|
|
455
|
+
type(candidate) is not EvolutionCandidate for candidate in self.candidates
|
|
456
|
+
):
|
|
457
|
+
raise TypeError("candidates must contain exact EvolutionCandidate values")
|
|
458
|
+
if len({candidate.candidate_id for candidate in self.candidates}) != len(
|
|
459
|
+
self.candidates
|
|
460
|
+
):
|
|
461
|
+
raise ValueError("candidate history contains duplicate occurrence IDs")
|
|
462
|
+
if type(self.archive) is not ParetoArchiveSnapshot:
|
|
463
|
+
raise TypeError("archive must be an exact ParetoArchiveSnapshot")
|
|
464
|
+
require_sha256(self.archive_snapshot_hash, "archive_snapshot_hash")
|
|
465
|
+
if self.archive_snapshot_hash != pareto_archive_snapshot_hash(self.archive):
|
|
466
|
+
raise ValueError("archive_snapshot_hash does not identify archive")
|
|
467
|
+
for name in ("unique_evaluations", "logical_llm_calls"):
|
|
468
|
+
value = getattr(self, name)
|
|
469
|
+
if type(value) is not int or value < 0:
|
|
470
|
+
raise ValueError(f"{name} must be a non-negative integer")
|
|
471
|
+
if type(self.generation_receipts) is not tuple or any(
|
|
472
|
+
type(receipt) is not GenerationReceipt
|
|
473
|
+
for receipt in self.generation_receipts
|
|
474
|
+
):
|
|
475
|
+
raise TypeError(
|
|
476
|
+
"generation_receipts must contain exact GenerationReceipt values"
|
|
477
|
+
)
|
|
478
|
+
for receipt in self.generation_receipts:
|
|
479
|
+
validate_generation_receipt_integrity(receipt)
|
|
480
|
+
receipt_generations = tuple(
|
|
481
|
+
receipt.generation for receipt in self.generation_receipts
|
|
482
|
+
)
|
|
483
|
+
if receipt_generations != tuple(range(1, len(receipt_generations) + 1)):
|
|
484
|
+
raise ValueError("generation_receipts must be contiguous and ordered")
|
|
485
|
+
if self.generation_receipts and receipt_generations[-1] > self.generation:
|
|
486
|
+
raise ValueError("generation_receipts cannot be newer than planner state")
|
|
487
|
+
if type(self.feedback_receipts) is not tuple or any(
|
|
488
|
+
type(receipt) is not GenerationFeedbackReceipt
|
|
489
|
+
for receipt in self.feedback_receipts
|
|
490
|
+
):
|
|
491
|
+
raise TypeError(
|
|
492
|
+
"feedback_receipts must contain exact GenerationFeedbackReceipt values"
|
|
493
|
+
)
|
|
494
|
+
for receipt in self.feedback_receipts:
|
|
495
|
+
validate_generation_feedback_receipt(receipt)
|
|
496
|
+
feedback_generations = tuple(
|
|
497
|
+
receipt.generation for receipt in self.feedback_receipts
|
|
498
|
+
)
|
|
499
|
+
if feedback_generations != tuple(range(1, len(feedback_generations) + 1)):
|
|
500
|
+
raise ValueError("feedback_receipts must be contiguous and ordered")
|
|
501
|
+
if self.feedback_receipts and feedback_generations[-1] > self.generation:
|
|
502
|
+
raise ValueError("feedback_receipts cannot be newer than planner state")
|
|
503
|
+
if len(self.feedback_receipts) > len(self.generation_receipts):
|
|
504
|
+
raise ValueError("feedback receipts cannot outnumber generation receipts")
|
|
505
|
+
for feedback_receipt in self.feedback_receipts:
|
|
506
|
+
generation_receipt = self.generation_receipts[
|
|
507
|
+
feedback_receipt.generation - 1
|
|
508
|
+
]
|
|
509
|
+
if (
|
|
510
|
+
feedback_receipt.generation_receipt_hash
|
|
511
|
+
!= generation_receipt.receipt_hash
|
|
512
|
+
):
|
|
513
|
+
raise ValueError(
|
|
514
|
+
"feedback receipt does not identify its generation receipt"
|
|
515
|
+
)
|
|
516
|
+
if self.feedback_receipts and (
|
|
517
|
+
self.feedback_receipts[-1].logical_llm_calls_after > self.logical_llm_calls
|
|
518
|
+
):
|
|
519
|
+
raise ValueError("feedback receipt exceeds planner logical-call state")
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
class GenerationPlanner(Protocol):
|
|
523
|
+
"""Injected, deterministic generation policy."""
|
|
524
|
+
|
|
525
|
+
def plan(
|
|
526
|
+
self,
|
|
527
|
+
state: OptimizerState,
|
|
528
|
+
budget: OptimizerBudget,
|
|
529
|
+
) -> GenerationPlan: ...
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
@dataclass(frozen=True, slots=True)
|
|
533
|
+
class SeedReceipt:
|
|
534
|
+
label: str
|
|
535
|
+
candidate: EvolutionCandidate
|
|
536
|
+
gate_decision: SeedGateDecision
|
|
537
|
+
archive_decisions: tuple[ParetoDecision, ...]
|
|
538
|
+
unique_evaluations_before: int
|
|
539
|
+
unique_evaluations_after: int
|
|
540
|
+
archive_snapshot_hash: str
|
|
541
|
+
receipt_hash: str
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
@dataclass(frozen=True, slots=True)
|
|
545
|
+
class SlotResult:
|
|
546
|
+
slot: OptimizerSlot
|
|
547
|
+
outcome: InvocationOutcome
|
|
548
|
+
archive_decisions: tuple[ParetoDecision, ...]
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
@dataclass(frozen=True, slots=True)
|
|
552
|
+
class GenerationReceipt:
|
|
553
|
+
generation: int
|
|
554
|
+
plan_hash: str
|
|
555
|
+
pre_archive_snapshot_hash: str
|
|
556
|
+
post_archive_snapshot_hash: str
|
|
557
|
+
reward_definition_hash: str
|
|
558
|
+
reward_snapshot_hash: str
|
|
559
|
+
logical_llm_calls_before: int
|
|
560
|
+
logical_llm_calls_after: int
|
|
561
|
+
unique_evaluations_before: int
|
|
562
|
+
unique_evaluations_after: int
|
|
563
|
+
reserved_logical_llm_calls: int
|
|
564
|
+
reserved_unique_evaluations: int
|
|
565
|
+
slot_results: tuple[SlotResult, ...]
|
|
566
|
+
receipt_hash: str
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
@dataclass(frozen=True, slots=True)
|
|
570
|
+
class OptimizerResult:
|
|
571
|
+
budget: OptimizerBudget
|
|
572
|
+
final_state: OptimizerState
|
|
573
|
+
seed_receipts: tuple[SeedReceipt, ...]
|
|
574
|
+
generation_receipts: tuple[GenerationReceipt, ...]
|
|
575
|
+
stop_reason: OptimizerStopReason
|
|
576
|
+
result_hash: str
|
|
577
|
+
feedback_receipts: tuple[GenerationFeedbackReceipt, ...] = ()
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
def _fraction_record(value: Fraction | None) -> object:
|
|
581
|
+
if value is None:
|
|
582
|
+
return None
|
|
583
|
+
return {"numerator": value.numerator, "denominator": value.denominator}
|
|
584
|
+
|
|
585
|
+
|
|
586
|
+
def _candidate_identity(candidate: EvolutionCandidate | None) -> object:
|
|
587
|
+
if candidate is None:
|
|
588
|
+
return None
|
|
589
|
+
return {
|
|
590
|
+
"candidate_id": candidate.candidate_id.value,
|
|
591
|
+
"candidate_hash": pareto_candidate_hash(candidate),
|
|
592
|
+
"configuration_hash": candidate.occurrence.configuration_hash,
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
|
|
596
|
+
def _invocation_plan_record(plan: InvocationPlan) -> dict[str, object]:
|
|
597
|
+
contract = plan.mutation_contract
|
|
598
|
+
record: dict[str, object] = {
|
|
599
|
+
"operator_kind": plan.operator_kind.value,
|
|
600
|
+
"generation": plan.generation,
|
|
601
|
+
"label": plan.label,
|
|
602
|
+
"parents": [_candidate_identity(parent) for parent in plan.parents],
|
|
603
|
+
"common_ancestor": _candidate_identity(plan.common_ancestor),
|
|
604
|
+
"allowed_top_level": list(plan.allowed_top_level),
|
|
605
|
+
"phase": plan.phase,
|
|
606
|
+
"use_memory": plan.use_memory,
|
|
607
|
+
"memory_subset_size": plan.memory_subset_size,
|
|
608
|
+
"memory_exploration_probability": _fraction_record(
|
|
609
|
+
plan.memory_exploration_probability
|
|
610
|
+
),
|
|
611
|
+
"memory_score_phase": plan.memory_score_phase,
|
|
612
|
+
"mutation_response_mode": plan.mutation_response_mode.value,
|
|
613
|
+
"mutation_contract": (
|
|
614
|
+
None
|
|
615
|
+
if contract is None
|
|
616
|
+
else {
|
|
617
|
+
"editable_path_hashes": [
|
|
618
|
+
hashlib.sha256(canonical_path_bytes(path)).hexdigest()
|
|
619
|
+
for path in contract.editable_paths
|
|
620
|
+
],
|
|
621
|
+
"max_changed_paths": contract.max_changed_paths,
|
|
622
|
+
"max_operations": contract.max_operations,
|
|
623
|
+
"allow_abstention": contract.allow_abstention,
|
|
624
|
+
}
|
|
625
|
+
),
|
|
626
|
+
"atomic_replacement_option_hashes": [
|
|
627
|
+
typed_json_sha256(option) for option in plan.atomic_replacement_options
|
|
628
|
+
],
|
|
629
|
+
"quarantine_test_insights": [
|
|
630
|
+
{"insight_id": ref.insight_id.value, "version": ref.version}
|
|
631
|
+
for ref in plan.quarantine_test_insights
|
|
632
|
+
],
|
|
633
|
+
"resolved_insight_assignment": (
|
|
634
|
+
None
|
|
635
|
+
if plan.resolved_insight_assignment is None
|
|
636
|
+
else {
|
|
637
|
+
**plan.resolved_insight_assignment.to_record(),
|
|
638
|
+
"assignment_sha256": (
|
|
639
|
+
plan.resolved_insight_assignment.assignment_sha256
|
|
640
|
+
),
|
|
641
|
+
}
|
|
642
|
+
),
|
|
643
|
+
"insight_treatment_requirement": (
|
|
644
|
+
None
|
|
645
|
+
if plan.insight_treatment_requirement is None
|
|
646
|
+
else {
|
|
647
|
+
**plan.insight_treatment_requirement.to_record(),
|
|
648
|
+
"requirement_sha256": (
|
|
649
|
+
plan.insight_treatment_requirement.requirement_sha256
|
|
650
|
+
),
|
|
651
|
+
}
|
|
652
|
+
),
|
|
653
|
+
"compiled_hypothesis_treatment": (
|
|
654
|
+
None
|
|
655
|
+
if plan.compiled_hypothesis_treatment is None
|
|
656
|
+
else {
|
|
657
|
+
**plan.compiled_hypothesis_treatment.to_record(),
|
|
658
|
+
"binding_sha256": (plan.compiled_hypothesis_treatment.binding_sha256),
|
|
659
|
+
}
|
|
660
|
+
),
|
|
661
|
+
"compiled_hypothesis_eligibility": [
|
|
662
|
+
{
|
|
663
|
+
**value.to_record(),
|
|
664
|
+
"binding_sha256": value.binding_sha256,
|
|
665
|
+
}
|
|
666
|
+
for value in plan.compiled_hypothesis_eligibility
|
|
667
|
+
],
|
|
668
|
+
}
|
|
669
|
+
# The contract identity binds the exact parent, ordered palette, prompt
|
|
670
|
+
# semantics, and every sealed child. Omitting the key for legacy modes keeps
|
|
671
|
+
# their historical plan and generation receipt hashes byte-compatible.
|
|
672
|
+
if plan.finite_variation_contract is not None:
|
|
673
|
+
record["finite_variation_contract"] = (
|
|
674
|
+
plan.finite_variation_contract.evidence_record()
|
|
675
|
+
)
|
|
676
|
+
if plan.finite_action_set_authority is not None:
|
|
677
|
+
record["finite_action_set_authority"] = {
|
|
678
|
+
**plan.finite_action_set_authority.to_record(),
|
|
679
|
+
"authority_sha256": plan.finite_action_set_authority.authority_sha256,
|
|
680
|
+
}
|
|
681
|
+
# Keep the historical record shape byte-stable for the default/full
|
|
682
|
+
# crossover representation. Exact parent import is a distinct model
|
|
683
|
+
# action space, so its mode, complete machine contract, and independently
|
|
684
|
+
# recomputable contract identity must all be authenticated by the
|
|
685
|
+
# generation-plan hash.
|
|
686
|
+
if plan.exact_parent_crossover_contract is not None:
|
|
687
|
+
exact_contract = plan.exact_parent_crossover_contract
|
|
688
|
+
record["crossover_response_mode"] = plan.crossover_response_mode.value
|
|
689
|
+
record["exact_parent_crossover_contract"] = exact_contract.to_record()
|
|
690
|
+
record["exact_parent_crossover_contract_sha256"] = (
|
|
691
|
+
exact_contract.contract_sha256
|
|
692
|
+
)
|
|
693
|
+
record["forbidden_exact_parent_import_sets"] = [
|
|
694
|
+
list(value) for value in plan.forbidden_exact_parent_import_sets
|
|
695
|
+
]
|
|
696
|
+
record["exact_parent_import_exclusions_sha256"] = (
|
|
697
|
+
exact_parent_import_exclusions_sha256(
|
|
698
|
+
exact_contract,
|
|
699
|
+
plan.forbidden_exact_parent_import_sets,
|
|
700
|
+
)
|
|
701
|
+
)
|
|
702
|
+
return record
|
|
703
|
+
|
|
704
|
+
|
|
705
|
+
def _materialized_draft_record(
|
|
706
|
+
draft: CandidateDraft | AtomicMutationDraft,
|
|
707
|
+
) -> dict[str, object]:
|
|
708
|
+
if type(draft) is CandidateDraft:
|
|
709
|
+
return {
|
|
710
|
+
"kind": "candidate_draft",
|
|
711
|
+
"configuration_hash": typed_json_sha256(freeze_json(draft.configuration)),
|
|
712
|
+
"design_rationale_sha256": hashlib.sha256(
|
|
713
|
+
draft.design_rationale.encode("utf-8")
|
|
714
|
+
).hexdigest(),
|
|
715
|
+
"intended_changes": list(draft.intended_changes),
|
|
716
|
+
"source_attribution": [
|
|
717
|
+
{"path": item.path, "source": item.source}
|
|
718
|
+
for item in draft.source_attribution
|
|
719
|
+
],
|
|
720
|
+
"claimed_insight_ids": list(draft.claimed_insight_ids),
|
|
721
|
+
"claimed_preservation_obligation_ids": list(
|
|
722
|
+
draft.claimed_preservation_obligation_ids
|
|
723
|
+
),
|
|
724
|
+
"conflict_resolutions": [
|
|
725
|
+
{
|
|
726
|
+
"relation_id": item.relation_id,
|
|
727
|
+
"choice": item.choice,
|
|
728
|
+
"explanation_sha256": hashlib.sha256(
|
|
729
|
+
item.explanation.encode("utf-8")
|
|
730
|
+
).hexdigest(),
|
|
731
|
+
}
|
|
732
|
+
for item in draft.conflict_resolutions
|
|
733
|
+
],
|
|
734
|
+
}
|
|
735
|
+
if type(draft) is AtomicMutationDraft:
|
|
736
|
+
return {
|
|
737
|
+
"kind": "atomic_mutation_draft",
|
|
738
|
+
"path_hash": hashlib.sha256(canonical_path_bytes(draft.path)).hexdigest(),
|
|
739
|
+
"replacement_hash": typed_json_sha256(draft.replacement),
|
|
740
|
+
"design_rationale_sha256": hashlib.sha256(
|
|
741
|
+
draft.design_rationale.encode("utf-8")
|
|
742
|
+
).hexdigest(),
|
|
743
|
+
"claimed_insight_ids": list(draft.claimed_insight_ids),
|
|
744
|
+
}
|
|
745
|
+
raise TypeError("unsupported materialized draft")
|
|
746
|
+
|
|
747
|
+
|
|
748
|
+
def _slot_record(slot: OptimizerSlot) -> dict[str, object]:
|
|
749
|
+
materialized = slot.materialized
|
|
750
|
+
return {
|
|
751
|
+
"slot_id": slot.slot_id,
|
|
752
|
+
"role": slot.role,
|
|
753
|
+
"proposal_authority": slot.proposal_authority.value,
|
|
754
|
+
"logical_llm_call_reservation": slot.logical_llm_call_reservation,
|
|
755
|
+
"unique_evaluation_reservation": slot.unique_evaluation_reservation,
|
|
756
|
+
"invocation": _invocation_plan_record(slot.plan),
|
|
757
|
+
"materialized": (
|
|
758
|
+
None
|
|
759
|
+
if materialized is None
|
|
760
|
+
else {
|
|
761
|
+
"candidate_id": materialized.candidate_id.value,
|
|
762
|
+
"policy_id": materialized.materialization_policy_id,
|
|
763
|
+
"policy_version": materialized.materialization_policy_version,
|
|
764
|
+
"receipt_hash": materialized.materialization_receipt_hash,
|
|
765
|
+
"draft": _materialized_draft_record(materialized.draft),
|
|
766
|
+
}
|
|
767
|
+
),
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
|
|
771
|
+
def _generation_plan_record(
|
|
772
|
+
plan: GenerationPlan,
|
|
773
|
+
*,
|
|
774
|
+
budget_hash: str,
|
|
775
|
+
) -> dict[str, object]:
|
|
776
|
+
return {
|
|
777
|
+
"generation": plan.generation,
|
|
778
|
+
"planner_policy_id": plan.planner_policy_id,
|
|
779
|
+
"planner_policy_version": plan.planner_policy_version,
|
|
780
|
+
"metadata": [list(item) for item in plan.metadata],
|
|
781
|
+
"budget_hash": budget_hash,
|
|
782
|
+
"pre_archive_snapshot_hash": plan.reward.archive_snapshot_hash,
|
|
783
|
+
"reward_definition_hash": plan.reward.binding.definition_hash,
|
|
784
|
+
"reward_failure_score_hex": plan.reward.binding.failure_score.hex(),
|
|
785
|
+
"reward_binding_sha256": plan.reward.binding.binding_sha256,
|
|
786
|
+
"reward_snapshot_hash": plan.reward.reward_snapshot_hash,
|
|
787
|
+
"logical_llm_call_reservation": plan.logical_llm_call_reservation,
|
|
788
|
+
"unique_evaluation_reservation": plan.unique_evaluation_reservation,
|
|
789
|
+
"slots": [_slot_record(slot) for slot in plan.slots],
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
|
|
793
|
+
def _generation_receipt_record(
|
|
794
|
+
*,
|
|
795
|
+
generation: int,
|
|
796
|
+
plan_hash: str,
|
|
797
|
+
pre_archive_snapshot_hash: str,
|
|
798
|
+
post_archive_snapshot_hash: str,
|
|
799
|
+
reward_definition_hash: str,
|
|
800
|
+
reward_snapshot_hash: str,
|
|
801
|
+
logical_llm_calls_before: int,
|
|
802
|
+
logical_llm_calls_after: int,
|
|
803
|
+
unique_evaluations_before: int,
|
|
804
|
+
unique_evaluations_after: int,
|
|
805
|
+
reserved_logical_llm_calls: int,
|
|
806
|
+
reserved_unique_evaluations: int,
|
|
807
|
+
slot_results: tuple[SlotResult, ...],
|
|
808
|
+
) -> dict[str, object]:
|
|
809
|
+
"""Project the exact immutable fields authenticated by a receipt hash."""
|
|
810
|
+
|
|
811
|
+
if type(generation) is not int or generation <= 0:
|
|
812
|
+
raise ValueError("receipt generation must be a positive exact integer")
|
|
813
|
+
for name in (
|
|
814
|
+
"plan_hash",
|
|
815
|
+
"pre_archive_snapshot_hash",
|
|
816
|
+
"post_archive_snapshot_hash",
|
|
817
|
+
"reward_definition_hash",
|
|
818
|
+
"reward_snapshot_hash",
|
|
819
|
+
):
|
|
820
|
+
require_sha256(locals()[name], name)
|
|
821
|
+
for name in (
|
|
822
|
+
"logical_llm_calls_before",
|
|
823
|
+
"logical_llm_calls_after",
|
|
824
|
+
"unique_evaluations_before",
|
|
825
|
+
"unique_evaluations_after",
|
|
826
|
+
"reserved_logical_llm_calls",
|
|
827
|
+
"reserved_unique_evaluations",
|
|
828
|
+
):
|
|
829
|
+
value = locals()[name]
|
|
830
|
+
if type(value) is not int or value < 0:
|
|
831
|
+
raise ValueError(f"{name} must be a non-negative exact integer")
|
|
832
|
+
if logical_llm_calls_after < logical_llm_calls_before:
|
|
833
|
+
raise ValueError("logical LLM-call counters cannot decrease")
|
|
834
|
+
if unique_evaluations_after < unique_evaluations_before:
|
|
835
|
+
raise ValueError("unique-evaluation counters cannot decrease")
|
|
836
|
+
if type(slot_results) is not tuple:
|
|
837
|
+
raise TypeError("slot_results must be an exact tuple")
|
|
838
|
+
if any(type(result) is not SlotResult for result in slot_results):
|
|
839
|
+
raise TypeError("slot_results must contain exact SlotResult values")
|
|
840
|
+
if any(type(result.slot) is not OptimizerSlot for result in slot_results):
|
|
841
|
+
raise TypeError("receipt results must contain exact OptimizerSlot values")
|
|
842
|
+
if any(type(result.outcome) is not InvocationOutcome for result in slot_results):
|
|
843
|
+
raise TypeError("receipt results must contain exact InvocationOutcome values")
|
|
844
|
+
if any(
|
|
845
|
+
type(result.archive_decisions) is not tuple
|
|
846
|
+
or any(
|
|
847
|
+
type(decision) is not ParetoDecision
|
|
848
|
+
for decision in result.archive_decisions
|
|
849
|
+
)
|
|
850
|
+
for result in slot_results
|
|
851
|
+
):
|
|
852
|
+
raise TypeError(
|
|
853
|
+
"receipt archive_decisions must contain exact ParetoDecision values"
|
|
854
|
+
)
|
|
855
|
+
if len({result.slot.slot_id for result in slot_results}) != len(slot_results):
|
|
856
|
+
raise OptimizerContractError("generation receipt repeats a slot ID")
|
|
857
|
+
for result in slot_results:
|
|
858
|
+
prepared = result.outcome.prepared
|
|
859
|
+
InvocationOutcome.__post_init__(result.outcome)
|
|
860
|
+
if result.slot.plan != prepared.plan:
|
|
861
|
+
raise OptimizerContractError(
|
|
862
|
+
"generation receipt slot plan differs from its prepared outcome"
|
|
863
|
+
)
|
|
864
|
+
if result.slot.proposal_authority is not prepared.proposal_authority:
|
|
865
|
+
raise OptimizerContractError(
|
|
866
|
+
"generation receipt slot authority differs from its outcome"
|
|
867
|
+
)
|
|
868
|
+
if result.slot.plan.generation != generation:
|
|
869
|
+
raise OptimizerContractError(
|
|
870
|
+
"generation receipt slot targets a different generation"
|
|
871
|
+
)
|
|
872
|
+
if prepared.variation_case.reward_definition_hash != reward_definition_hash:
|
|
873
|
+
raise OptimizerContractError(
|
|
874
|
+
"generation receipt outcome has a different reward definition"
|
|
875
|
+
)
|
|
876
|
+
|
|
877
|
+
expected_logical_reservation = sum(
|
|
878
|
+
result.slot.logical_llm_call_reservation for result in slot_results
|
|
879
|
+
)
|
|
880
|
+
expected_unique_reservation = sum(
|
|
881
|
+
result.slot.unique_evaluation_reservation for result in slot_results
|
|
882
|
+
)
|
|
883
|
+
if reserved_logical_llm_calls != expected_logical_reservation:
|
|
884
|
+
raise OptimizerContractError(
|
|
885
|
+
"generation receipt logical reservation differs from its slots"
|
|
886
|
+
)
|
|
887
|
+
if reserved_unique_evaluations != expected_unique_reservation:
|
|
888
|
+
raise OptimizerContractError(
|
|
889
|
+
"generation receipt evaluation reservation differs from its slots"
|
|
890
|
+
)
|
|
891
|
+
if logical_llm_calls_after - logical_llm_calls_before != (
|
|
892
|
+
reserved_logical_llm_calls
|
|
893
|
+
):
|
|
894
|
+
raise OptimizerContractError(
|
|
895
|
+
"generation receipt logical-call counters differ from its reservation"
|
|
896
|
+
)
|
|
897
|
+
if unique_evaluations_after - unique_evaluations_before > (
|
|
898
|
+
reserved_unique_evaluations
|
|
899
|
+
):
|
|
900
|
+
raise OptimizerContractError(
|
|
901
|
+
"generation receipt physical evaluations exceed its reservation"
|
|
902
|
+
)
|
|
903
|
+
|
|
904
|
+
return {
|
|
905
|
+
"generation": generation,
|
|
906
|
+
"plan_hash": plan_hash,
|
|
907
|
+
"pre_archive_snapshot_hash": pre_archive_snapshot_hash,
|
|
908
|
+
"post_archive_snapshot_hash": post_archive_snapshot_hash,
|
|
909
|
+
"reward_definition_hash": reward_definition_hash,
|
|
910
|
+
"reward_snapshot_hash": reward_snapshot_hash,
|
|
911
|
+
"logical_llm_calls_before": logical_llm_calls_before,
|
|
912
|
+
"logical_llm_calls_after": logical_llm_calls_after,
|
|
913
|
+
"unique_evaluations_before": unique_evaluations_before,
|
|
914
|
+
"unique_evaluations_after": unique_evaluations_after,
|
|
915
|
+
"reserved_logical_llm_calls": reserved_logical_llm_calls,
|
|
916
|
+
"reserved_unique_evaluations": reserved_unique_evaluations,
|
|
917
|
+
"slots": [
|
|
918
|
+
{
|
|
919
|
+
"slot": _slot_record(result.slot),
|
|
920
|
+
"operator_invocation_id": (
|
|
921
|
+
result.outcome.prepared.operator_invocation_id.value
|
|
922
|
+
),
|
|
923
|
+
"call_id": (
|
|
924
|
+
None
|
|
925
|
+
if result.outcome.prepared.call_id is None
|
|
926
|
+
else result.outcome.prepared.call_id.value
|
|
927
|
+
),
|
|
928
|
+
"reserved_candidate_id": result.outcome.prepared.candidate_id.value,
|
|
929
|
+
"proposal_sequence": result.outcome.prepared.proposal_sequence,
|
|
930
|
+
"prepared_reward_definition_hash": (
|
|
931
|
+
result.outcome.prepared.variation_case.reward_definition_hash
|
|
932
|
+
),
|
|
933
|
+
"prepared_selected_insights": [
|
|
934
|
+
{
|
|
935
|
+
"insight_id": reference.insight_id.value,
|
|
936
|
+
"version": reference.version,
|
|
937
|
+
}
|
|
938
|
+
for reference in (
|
|
939
|
+
result.outcome.prepared.variation_case.selected_insights
|
|
940
|
+
)
|
|
941
|
+
],
|
|
942
|
+
"prepared_assignment_sha256": (
|
|
943
|
+
None
|
|
944
|
+
if result.outcome.prepared.plan.resolved_insight_assignment is None
|
|
945
|
+
else result.outcome.prepared.plan.resolved_insight_assignment.assignment_sha256
|
|
946
|
+
),
|
|
947
|
+
"candidate": _candidate_identity(result.outcome.candidate),
|
|
948
|
+
"reward": result.outcome.reward.hex(),
|
|
949
|
+
"call_failure_type": result.outcome.call_failure_type,
|
|
950
|
+
"failure_stage": result.outcome.failure_stage,
|
|
951
|
+
"finite_action_decision": (
|
|
952
|
+
None
|
|
953
|
+
if result.outcome.finite_action_decision is None
|
|
954
|
+
else {
|
|
955
|
+
**result.outcome.finite_action_decision.to_record(),
|
|
956
|
+
"decision_sha256": (
|
|
957
|
+
result.outcome.finite_action_decision.decision_sha256
|
|
958
|
+
),
|
|
959
|
+
}
|
|
960
|
+
),
|
|
961
|
+
"treatment_admission": (
|
|
962
|
+
None
|
|
963
|
+
if result.outcome.treatment_admission_receipt is None
|
|
964
|
+
else {
|
|
965
|
+
**result.outcome.treatment_admission_receipt.to_record(),
|
|
966
|
+
"receipt_sha256": (
|
|
967
|
+
result.outcome.treatment_admission_receipt.receipt_sha256
|
|
968
|
+
),
|
|
969
|
+
}
|
|
970
|
+
),
|
|
971
|
+
"dominates_any_parent": result.outcome.dominates_any_parent,
|
|
972
|
+
"better_than_any_parent": result.outcome.better_than_any_parent,
|
|
973
|
+
"archive_decisions": [
|
|
974
|
+
decision.to_trace_record() for decision in result.archive_decisions
|
|
975
|
+
],
|
|
976
|
+
}
|
|
977
|
+
for result in slot_results
|
|
978
|
+
],
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
|
|
982
|
+
def generation_receipt_hash(receipt: GenerationReceipt) -> str:
|
|
983
|
+
"""Recompute the canonical identity of a published generation receipt.
|
|
984
|
+
|
|
985
|
+
This is intentionally public: replay and downstream evidence adapters must
|
|
986
|
+
verify a receipt before trusting its nested outcomes. Frozen dataclasses
|
|
987
|
+
prevent in-place mutation but do not by themselves authenticate values
|
|
988
|
+
reconstructed from durable storage or made with ``dataclasses.replace``.
|
|
989
|
+
"""
|
|
990
|
+
|
|
991
|
+
if type(receipt) is not GenerationReceipt:
|
|
992
|
+
raise TypeError("receipt must be an exact GenerationReceipt")
|
|
993
|
+
return _record_hash(
|
|
994
|
+
"generation-receipt",
|
|
995
|
+
_generation_receipt_record(
|
|
996
|
+
generation=receipt.generation,
|
|
997
|
+
plan_hash=receipt.plan_hash,
|
|
998
|
+
pre_archive_snapshot_hash=receipt.pre_archive_snapshot_hash,
|
|
999
|
+
post_archive_snapshot_hash=receipt.post_archive_snapshot_hash,
|
|
1000
|
+
reward_definition_hash=receipt.reward_definition_hash,
|
|
1001
|
+
reward_snapshot_hash=receipt.reward_snapshot_hash,
|
|
1002
|
+
logical_llm_calls_before=receipt.logical_llm_calls_before,
|
|
1003
|
+
logical_llm_calls_after=receipt.logical_llm_calls_after,
|
|
1004
|
+
unique_evaluations_before=receipt.unique_evaluations_before,
|
|
1005
|
+
unique_evaluations_after=receipt.unique_evaluations_after,
|
|
1006
|
+
reserved_logical_llm_calls=receipt.reserved_logical_llm_calls,
|
|
1007
|
+
reserved_unique_evaluations=receipt.reserved_unique_evaluations,
|
|
1008
|
+
slot_results=receipt.slot_results,
|
|
1009
|
+
),
|
|
1010
|
+
)
|
|
1011
|
+
|
|
1012
|
+
|
|
1013
|
+
def validate_generation_receipt_integrity(receipt: GenerationReceipt) -> None:
|
|
1014
|
+
"""Fail closed unless ``receipt_hash`` authenticates the exact projection."""
|
|
1015
|
+
|
|
1016
|
+
if type(receipt) is not GenerationReceipt:
|
|
1017
|
+
raise TypeError("receipt must be an exact GenerationReceipt")
|
|
1018
|
+
require_sha256(receipt.receipt_hash, "receipt_hash")
|
|
1019
|
+
if generation_receipt_hash(receipt) != receipt.receipt_hash:
|
|
1020
|
+
raise OptimizerContractError(
|
|
1021
|
+
"generation receipt hash does not authenticate its contents"
|
|
1022
|
+
)
|
|
1023
|
+
|
|
1024
|
+
|
|
1025
|
+
def seed_receipt_hash(receipt: SeedReceipt) -> str:
|
|
1026
|
+
"""Recompute the canonical identity of one seed-admission receipt."""
|
|
1027
|
+
|
|
1028
|
+
if type(receipt) is not SeedReceipt:
|
|
1029
|
+
raise TypeError("receipt must be an exact SeedReceipt")
|
|
1030
|
+
record = {
|
|
1031
|
+
"label": receipt.label,
|
|
1032
|
+
"candidate": _candidate_identity(receipt.candidate),
|
|
1033
|
+
"candidate_objectives": [
|
|
1034
|
+
[name, float(value).hex()] for name, value in receipt.candidate.objectives
|
|
1035
|
+
],
|
|
1036
|
+
"candidate_configuration_artifact_hash": (
|
|
1037
|
+
receipt.candidate.occurrence.configuration_artifact_hash
|
|
1038
|
+
),
|
|
1039
|
+
"requested_configuration_hash": (
|
|
1040
|
+
receipt.candidate.occurrence.configuration_hash
|
|
1041
|
+
),
|
|
1042
|
+
"gate": receipt.gate_decision.to_trace_record(),
|
|
1043
|
+
"archive_decision_sequences": [
|
|
1044
|
+
decision.decision_sequence for decision in receipt.archive_decisions
|
|
1045
|
+
],
|
|
1046
|
+
"unique_evaluations_before": receipt.unique_evaluations_before,
|
|
1047
|
+
"unique_evaluations_after": receipt.unique_evaluations_after,
|
|
1048
|
+
"archive_snapshot_hash": receipt.archive_snapshot_hash,
|
|
1049
|
+
}
|
|
1050
|
+
if receipt.candidate.objective_resolution_receipt is not None:
|
|
1051
|
+
record["objective_resolution_receipt_sha256"] = (
|
|
1052
|
+
receipt.candidate.objective_resolution_receipt.receipt_sha256
|
|
1053
|
+
)
|
|
1054
|
+
return _record_hash("seed-receipt", record)
|
|
1055
|
+
|
|
1056
|
+
|
|
1057
|
+
def validate_seed_receipt_integrity(receipt: SeedReceipt) -> None:
|
|
1058
|
+
"""Fail closed unless a seed receipt authenticates its full projection."""
|
|
1059
|
+
|
|
1060
|
+
if type(receipt) is not SeedReceipt:
|
|
1061
|
+
raise TypeError("receipt must be an exact SeedReceipt")
|
|
1062
|
+
require_sha256(receipt.receipt_hash, "receipt_hash")
|
|
1063
|
+
if seed_receipt_hash(receipt) != receipt.receipt_hash:
|
|
1064
|
+
raise OptimizerContractError(
|
|
1065
|
+
"seed receipt hash does not authenticate its contents"
|
|
1066
|
+
)
|
|
1067
|
+
|
|
1068
|
+
|
|
1069
|
+
def optimizer_result_hash(result: OptimizerResult) -> str:
|
|
1070
|
+
"""Recompute the public terminal optimizer-result commitment."""
|
|
1071
|
+
|
|
1072
|
+
if type(result) is not OptimizerResult:
|
|
1073
|
+
raise TypeError("result must be an exact OptimizerResult")
|
|
1074
|
+
return _record_hash(
|
|
1075
|
+
"optimizer-result",
|
|
1076
|
+
{
|
|
1077
|
+
"budget_hash": result.budget.budget_hash,
|
|
1078
|
+
"stop_reason": result.stop_reason.value,
|
|
1079
|
+
"generation": result.final_state.generation,
|
|
1080
|
+
"unique_evaluations": result.final_state.unique_evaluations,
|
|
1081
|
+
"logical_llm_calls": result.final_state.logical_llm_calls,
|
|
1082
|
+
"archive_snapshot_hash": result.final_state.archive_snapshot_hash,
|
|
1083
|
+
"seed_receipt_hashes": [item.receipt_hash for item in result.seed_receipts],
|
|
1084
|
+
"generation_receipt_hashes": [
|
|
1085
|
+
item.receipt_hash for item in result.generation_receipts
|
|
1086
|
+
],
|
|
1087
|
+
"feedback_receipt_hashes": [
|
|
1088
|
+
item.receipt_hash for item in result.feedback_receipts
|
|
1089
|
+
],
|
|
1090
|
+
},
|
|
1091
|
+
)
|
|
1092
|
+
|
|
1093
|
+
|
|
1094
|
+
def validate_optimizer_result_integrity(result: OptimizerResult) -> None:
|
|
1095
|
+
"""Authenticate a complete result and every receipt it directly owns."""
|
|
1096
|
+
|
|
1097
|
+
if type(result) is not OptimizerResult:
|
|
1098
|
+
raise TypeError("result must be an exact OptimizerResult")
|
|
1099
|
+
OptimizerState.__post_init__(result.final_state)
|
|
1100
|
+
if result.generation_receipts != result.final_state.generation_receipts:
|
|
1101
|
+
raise OptimizerContractError(
|
|
1102
|
+
"result generation receipts differ from its final state"
|
|
1103
|
+
)
|
|
1104
|
+
if result.feedback_receipts != result.final_state.feedback_receipts:
|
|
1105
|
+
raise OptimizerContractError(
|
|
1106
|
+
"result feedback receipts differ from its final state"
|
|
1107
|
+
)
|
|
1108
|
+
for receipt in result.seed_receipts:
|
|
1109
|
+
validate_seed_receipt_integrity(receipt)
|
|
1110
|
+
for receipt in result.generation_receipts:
|
|
1111
|
+
validate_generation_receipt_integrity(receipt)
|
|
1112
|
+
for receipt in result.feedback_receipts:
|
|
1113
|
+
validate_generation_feedback_receipt(receipt)
|
|
1114
|
+
require_sha256(result.result_hash, "result_hash")
|
|
1115
|
+
if optimizer_result_hash(result) != result.result_hash:
|
|
1116
|
+
raise OptimizerContractError(
|
|
1117
|
+
"optimizer result hash does not authenticate its contents"
|
|
1118
|
+
)
|
|
1119
|
+
|
|
1120
|
+
|
|
1121
|
+
class BudgetedAgenticOptimizer:
|
|
1122
|
+
"""Compose a planner, evolution engine, and archive under hard run budgets."""
|
|
1123
|
+
|
|
1124
|
+
def __init__(
|
|
1125
|
+
self,
|
|
1126
|
+
*,
|
|
1127
|
+
engine: AgenticEvolutionEngine,
|
|
1128
|
+
archive: ParetoArchive,
|
|
1129
|
+
planner: GenerationPlanner,
|
|
1130
|
+
budget: OptimizerBudget,
|
|
1131
|
+
seed_admission_policy: SeedAdmissionPolicy | None = None,
|
|
1132
|
+
feedback_interceptor: GenerationFeedbackInterceptor | None = None,
|
|
1133
|
+
trace_sink: OptimizerTraceSink | None = None,
|
|
1134
|
+
) -> None:
|
|
1135
|
+
if not isinstance(engine, AgenticEvolutionEngine):
|
|
1136
|
+
raise TypeError("engine must be an AgenticEvolutionEngine")
|
|
1137
|
+
if type(archive) is not ParetoArchive:
|
|
1138
|
+
raise TypeError("archive must be an exact ParetoArchive")
|
|
1139
|
+
if tuple(archive.objectives) != tuple(engine.objectives):
|
|
1140
|
+
raise ValueError("engine and archive objective contracts differ")
|
|
1141
|
+
if (
|
|
1142
|
+
archive.outcome_relation_binding.identity
|
|
1143
|
+
!= engine.outcome_relation_binding.identity
|
|
1144
|
+
):
|
|
1145
|
+
raise ValueError("engine and archive outcome relation bindings differ")
|
|
1146
|
+
if not callable(getattr(planner, "plan", None)):
|
|
1147
|
+
raise TypeError("planner must implement plan(state, budget)")
|
|
1148
|
+
if type(budget) is not OptimizerBudget:
|
|
1149
|
+
raise TypeError("budget must be an exact OptimizerBudget")
|
|
1150
|
+
if feedback_interceptor is not None and not isinstance(
|
|
1151
|
+
feedback_interceptor,
|
|
1152
|
+
GenerationFeedbackInterceptor,
|
|
1153
|
+
):
|
|
1154
|
+
raise TypeError(
|
|
1155
|
+
"feedback_interceptor must implement reserve and after_generation"
|
|
1156
|
+
)
|
|
1157
|
+
if trace_sink is not None and not callable(trace_sink):
|
|
1158
|
+
raise TypeError("trace_sink must be callable")
|
|
1159
|
+
gate = (
|
|
1160
|
+
ValidSeedAdmissionPolicy()
|
|
1161
|
+
if seed_admission_policy is None
|
|
1162
|
+
else seed_admission_policy
|
|
1163
|
+
)
|
|
1164
|
+
if not callable(getattr(gate, "assess", None)):
|
|
1165
|
+
raise TypeError("seed_admission_policy must implement assess")
|
|
1166
|
+
self.engine = engine
|
|
1167
|
+
self.archive = archive
|
|
1168
|
+
self.planner = planner
|
|
1169
|
+
self.budget = budget
|
|
1170
|
+
self.seed_admission_policy = gate
|
|
1171
|
+
self.feedback_interceptor = feedback_interceptor
|
|
1172
|
+
self._trace_sink = trace_sink
|
|
1173
|
+
self._started = False
|
|
1174
|
+
self._trace_sequence = 0
|
|
1175
|
+
self._trace_origin_ns = time.monotonic_ns()
|
|
1176
|
+
|
|
1177
|
+
def _emit(self, event_type: str, **payload: object) -> None:
|
|
1178
|
+
if self._trace_sink is None:
|
|
1179
|
+
return
|
|
1180
|
+
self._trace_sequence += 1
|
|
1181
|
+
self._trace_sink(
|
|
1182
|
+
{
|
|
1183
|
+
"optimizer_trace_sequence": self._trace_sequence,
|
|
1184
|
+
"event_type": event_type,
|
|
1185
|
+
"optimizer_monotonic_offset_ns": (
|
|
1186
|
+
time.monotonic_ns() - self._trace_origin_ns
|
|
1187
|
+
),
|
|
1188
|
+
**payload,
|
|
1189
|
+
}
|
|
1190
|
+
)
|
|
1191
|
+
|
|
1192
|
+
async def _evaluation_misses(self) -> int:
|
|
1193
|
+
snapshot = await self.engine.evaluation_cache_snapshot()
|
|
1194
|
+
misses = snapshot["misses"]
|
|
1195
|
+
in_flight = snapshot["in_flight"]
|
|
1196
|
+
if type(misses) is not int or type(in_flight) is not int:
|
|
1197
|
+
raise RuntimeError("engine evaluation cache returned invalid counters")
|
|
1198
|
+
if in_flight != 0:
|
|
1199
|
+
raise OptimizerContractError(
|
|
1200
|
+
"engine has evaluations in flight at an optimizer checkpoint"
|
|
1201
|
+
)
|
|
1202
|
+
return misses
|
|
1203
|
+
|
|
1204
|
+
def _state(
|
|
1205
|
+
self,
|
|
1206
|
+
*,
|
|
1207
|
+
generation: int,
|
|
1208
|
+
candidates: tuple[EvolutionCandidate, ...],
|
|
1209
|
+
unique_evaluations: int,
|
|
1210
|
+
logical_llm_calls: int,
|
|
1211
|
+
generation_receipts: tuple[GenerationReceipt, ...],
|
|
1212
|
+
feedback_receipts: tuple[GenerationFeedbackReceipt, ...],
|
|
1213
|
+
) -> OptimizerState:
|
|
1214
|
+
snapshot = self.archive.snapshot()
|
|
1215
|
+
return OptimizerState(
|
|
1216
|
+
generation=generation,
|
|
1217
|
+
candidates=candidates,
|
|
1218
|
+
archive=snapshot,
|
|
1219
|
+
archive_snapshot_hash=pareto_archive_snapshot_hash(snapshot),
|
|
1220
|
+
unique_evaluations=unique_evaluations,
|
|
1221
|
+
logical_llm_calls=logical_llm_calls,
|
|
1222
|
+
generation_receipts=generation_receipts,
|
|
1223
|
+
feedback_receipts=feedback_receipts,
|
|
1224
|
+
)
|
|
1225
|
+
|
|
1226
|
+
def _validate_plan(self, plan: GenerationPlan, state: OptimizerState) -> None:
|
|
1227
|
+
if plan.generation != state.generation + 1:
|
|
1228
|
+
raise OptimizerContractError(
|
|
1229
|
+
"planner generation is not the next unpublished generation"
|
|
1230
|
+
)
|
|
1231
|
+
if plan.reward.archive_snapshot_hash != state.archive_snapshot_hash:
|
|
1232
|
+
raise OptimizerContractError(
|
|
1233
|
+
"wave reward is not bound to the exact pre-wave archive cutoff"
|
|
1234
|
+
)
|
|
1235
|
+
known = {candidate.candidate_id: candidate for candidate in state.candidates}
|
|
1236
|
+
materialized_ids = set()
|
|
1237
|
+
for slot in plan.slots:
|
|
1238
|
+
for parent in slot.plan.parents:
|
|
1239
|
+
if known.get(parent.candidate_id) != parent:
|
|
1240
|
+
raise OptimizerContractError(
|
|
1241
|
+
f"slot {slot.slot_id!r} refers to an unknown or altered parent"
|
|
1242
|
+
)
|
|
1243
|
+
ancestor = slot.plan.common_ancestor
|
|
1244
|
+
if ancestor is not None and known.get(ancestor.candidate_id) != ancestor:
|
|
1245
|
+
raise OptimizerContractError(
|
|
1246
|
+
f"slot {slot.slot_id!r} refers to an unknown or altered ancestor"
|
|
1247
|
+
)
|
|
1248
|
+
if slot.materialized is not None:
|
|
1249
|
+
candidate_id = slot.materialized.candidate_id
|
|
1250
|
+
if candidate_id in known or candidate_id in materialized_ids:
|
|
1251
|
+
raise OptimizerContractError(
|
|
1252
|
+
"materialized candidate occurrence IDs must be new and unique"
|
|
1253
|
+
)
|
|
1254
|
+
materialized_ids.add(candidate_id)
|
|
1255
|
+
|
|
1256
|
+
def _reserve_plan(
|
|
1257
|
+
self,
|
|
1258
|
+
plan: GenerationPlan,
|
|
1259
|
+
state: OptimizerState,
|
|
1260
|
+
feedback_reservation: GenerationFeedbackReservation | None,
|
|
1261
|
+
) -> None:
|
|
1262
|
+
auxiliary_calls = (
|
|
1263
|
+
0
|
|
1264
|
+
if feedback_reservation is None
|
|
1265
|
+
else feedback_reservation.logical_llm_calls
|
|
1266
|
+
)
|
|
1267
|
+
if (
|
|
1268
|
+
state.logical_llm_calls
|
|
1269
|
+
+ plan.logical_llm_call_reservation
|
|
1270
|
+
+ auxiliary_calls
|
|
1271
|
+
> self.budget.max_logical_llm_calls
|
|
1272
|
+
):
|
|
1273
|
+
raise OptimizerBudgetExceeded(
|
|
1274
|
+
"generation exceeds the logical LLM-call budget"
|
|
1275
|
+
)
|
|
1276
|
+
if (
|
|
1277
|
+
state.unique_evaluations + plan.unique_evaluation_reservation
|
|
1278
|
+
> self.budget.max_unique_evaluations
|
|
1279
|
+
):
|
|
1280
|
+
raise OptimizerBudgetExceeded(
|
|
1281
|
+
"generation could exceed the unique-evaluation budget"
|
|
1282
|
+
)
|
|
1283
|
+
|
|
1284
|
+
async def _execute_plan(
|
|
1285
|
+
self,
|
|
1286
|
+
plan: GenerationPlan,
|
|
1287
|
+
) -> tuple[InvocationOutcome, ...]:
|
|
1288
|
+
direct_slots = tuple(
|
|
1289
|
+
slot
|
|
1290
|
+
for slot in plan.slots
|
|
1291
|
+
if slot.proposal_authority is not ProposalAuthority.ENGINE
|
|
1292
|
+
)
|
|
1293
|
+
engine_slots = tuple(
|
|
1294
|
+
slot
|
|
1295
|
+
for slot in plan.slots
|
|
1296
|
+
if slot.proposal_authority is ProposalAuthority.ENGINE
|
|
1297
|
+
)
|
|
1298
|
+
|
|
1299
|
+
async def direct() -> tuple[InvocationOutcome, ...]:
|
|
1300
|
+
if not direct_slots:
|
|
1301
|
+
return ()
|
|
1302
|
+
return await self.engine.run_invocations(
|
|
1303
|
+
tuple(slot.plan for slot in direct_slots),
|
|
1304
|
+
reward_binding=plan.reward.binding,
|
|
1305
|
+
)
|
|
1306
|
+
|
|
1307
|
+
async def materialized() -> tuple[InvocationOutcome, ...]:
|
|
1308
|
+
if not engine_slots:
|
|
1309
|
+
return ()
|
|
1310
|
+
return await self.engine.run_materialized_invocations(
|
|
1311
|
+
tuple(slot.materialized for slot in engine_slots), # type: ignore[arg-type]
|
|
1312
|
+
reward_binding=plan.reward.binding,
|
|
1313
|
+
)
|
|
1314
|
+
|
|
1315
|
+
direct_outcomes, engine_outcomes = await asyncio.gather(
|
|
1316
|
+
direct(), materialized()
|
|
1317
|
+
)
|
|
1318
|
+
by_slot: dict[str, InvocationOutcome] = {}
|
|
1319
|
+
for slot, outcome in zip(direct_slots, direct_outcomes, strict=True):
|
|
1320
|
+
by_slot[slot.slot_id] = outcome
|
|
1321
|
+
for slot, outcome in zip(engine_slots, engine_outcomes, strict=True):
|
|
1322
|
+
by_slot[slot.slot_id] = outcome
|
|
1323
|
+
if set(by_slot) != {slot.slot_id for slot in plan.slots}:
|
|
1324
|
+
raise RuntimeError("engine returned an incomplete generation")
|
|
1325
|
+
ordered = tuple(by_slot[slot.slot_id] for slot in plan.slots)
|
|
1326
|
+
for slot, outcome in zip(plan.slots, ordered, strict=True):
|
|
1327
|
+
prepared = outcome.prepared
|
|
1328
|
+
if prepared.plan != slot.plan:
|
|
1329
|
+
raise RuntimeError("engine outcome differs from its admitted slot plan")
|
|
1330
|
+
if prepared.proposal_authority is not slot.proposal_authority:
|
|
1331
|
+
raise RuntimeError("engine outcome has the wrong proposal authority")
|
|
1332
|
+
if (
|
|
1333
|
+
prepared.variation_case.reward_definition_hash
|
|
1334
|
+
!= plan.reward.binding.definition_hash
|
|
1335
|
+
):
|
|
1336
|
+
raise RuntimeError("engine outcome has the wrong reward identity")
|
|
1337
|
+
if not math.isfinite(outcome.reward):
|
|
1338
|
+
raise RuntimeError("engine outcome reward must be finite")
|
|
1339
|
+
return ordered
|
|
1340
|
+
|
|
1341
|
+
async def run(
|
|
1342
|
+
self,
|
|
1343
|
+
seed_configs: Sequence[dict[str, object]],
|
|
1344
|
+
) -> OptimizerResult:
|
|
1345
|
+
"""Evaluate seeds and execute exactly ``max_generations`` planner waves."""
|
|
1346
|
+
|
|
1347
|
+
if self._started:
|
|
1348
|
+
raise OptimizerContractError("an optimizer instance is single-use")
|
|
1349
|
+
self._started = True
|
|
1350
|
+
if self.archive.snapshot().consideration_count != 0:
|
|
1351
|
+
raise OptimizerContractError("optimizer requires a fresh empty archive")
|
|
1352
|
+
seeds = tuple(seed_configs)
|
|
1353
|
+
if not seeds:
|
|
1354
|
+
raise OptimizerContractError("at least one seed configuration is required")
|
|
1355
|
+
if any(type(config) is not dict for config in seeds):
|
|
1356
|
+
raise TypeError("seed configurations must be exact dictionaries")
|
|
1357
|
+
seed_hashes = tuple(typed_json_sha256(freeze_json(config)) for config in seeds)
|
|
1358
|
+
if len(set(seed_hashes)) != len(seed_hashes):
|
|
1359
|
+
raise OptimizerContractError("seed configurations must be unique")
|
|
1360
|
+
if len(seeds) > self.budget.max_unique_evaluations:
|
|
1361
|
+
raise OptimizerBudgetExceeded(
|
|
1362
|
+
"seed gate could exceed the unique-evaluation budget"
|
|
1363
|
+
)
|
|
1364
|
+
|
|
1365
|
+
initial_misses = await self._evaluation_misses()
|
|
1366
|
+
self._emit(
|
|
1367
|
+
"optimizer_started",
|
|
1368
|
+
budget=self.budget.to_trace_record(),
|
|
1369
|
+
budget_hash=self.budget.budget_hash,
|
|
1370
|
+
initial_engine_evaluation_misses=initial_misses,
|
|
1371
|
+
seed_configuration_hashes=list(seed_hashes),
|
|
1372
|
+
)
|
|
1373
|
+
candidates: tuple[EvolutionCandidate, ...] = ()
|
|
1374
|
+
seed_receipts: list[SeedReceipt] = []
|
|
1375
|
+
prior_unique = 0
|
|
1376
|
+
for index, config in enumerate(seeds):
|
|
1377
|
+
label = f"seed_{index}"
|
|
1378
|
+
candidate = await self.engine.register_seed(config, label=label)
|
|
1379
|
+
current_unique = (await self._evaluation_misses()) - initial_misses
|
|
1380
|
+
if not 0 <= current_unique <= self.budget.max_unique_evaluations:
|
|
1381
|
+
raise OptimizerExecutionError(
|
|
1382
|
+
"seed evaluation counters exceeded budget"
|
|
1383
|
+
)
|
|
1384
|
+
gate_context = SeedGateContext(
|
|
1385
|
+
seed_index=index,
|
|
1386
|
+
label=label,
|
|
1387
|
+
requested_configuration_hash=seed_hashes[index],
|
|
1388
|
+
unique_evaluations_before=prior_unique,
|
|
1389
|
+
unique_evaluations_after=current_unique,
|
|
1390
|
+
)
|
|
1391
|
+
try:
|
|
1392
|
+
gate_decision = self.seed_admission_policy.assess(
|
|
1393
|
+
candidate,
|
|
1394
|
+
gate_context,
|
|
1395
|
+
)
|
|
1396
|
+
except Exception as exc:
|
|
1397
|
+
self._emit(
|
|
1398
|
+
"optimizer_seed_gate_failed",
|
|
1399
|
+
label=label,
|
|
1400
|
+
candidate=_candidate_identity(candidate),
|
|
1401
|
+
requested_configuration_hash=seed_hashes[index],
|
|
1402
|
+
unique_evaluations_before=prior_unique,
|
|
1403
|
+
unique_evaluations_after=current_unique,
|
|
1404
|
+
failure_type=type(exc).__name__,
|
|
1405
|
+
)
|
|
1406
|
+
raise OptimizerExecutionError(
|
|
1407
|
+
f"seed admission policy failed for {label}"
|
|
1408
|
+
) from exc
|
|
1409
|
+
if type(gate_decision) is not SeedGateDecision:
|
|
1410
|
+
raise OptimizerContractError(
|
|
1411
|
+
"seed admission policy must return an exact SeedGateDecision"
|
|
1412
|
+
)
|
|
1413
|
+
SeedGateDecision.__post_init__(gate_decision)
|
|
1414
|
+
if gate_decision.admitted and not candidate.valid:
|
|
1415
|
+
raise OptimizerContractError(
|
|
1416
|
+
"a seed admission policy cannot override engine invalidity"
|
|
1417
|
+
)
|
|
1418
|
+
decisions = (
|
|
1419
|
+
self.archive.consider(candidate) if gate_decision.admitted else ()
|
|
1420
|
+
)
|
|
1421
|
+
snapshot_hash = pareto_archive_snapshot_hash(self.archive.snapshot())
|
|
1422
|
+
seed_record = {
|
|
1423
|
+
"label": label,
|
|
1424
|
+
"candidate": _candidate_identity(candidate),
|
|
1425
|
+
"candidate_objectives": [
|
|
1426
|
+
[name, float(value).hex()] for name, value in candidate.objectives
|
|
1427
|
+
],
|
|
1428
|
+
"candidate_configuration_artifact_hash": (
|
|
1429
|
+
candidate.occurrence.configuration_artifact_hash
|
|
1430
|
+
),
|
|
1431
|
+
"requested_configuration_hash": seed_hashes[index],
|
|
1432
|
+
"gate": gate_decision.to_trace_record(),
|
|
1433
|
+
"archive_decision_sequences": [
|
|
1434
|
+
decision.decision_sequence for decision in decisions
|
|
1435
|
+
],
|
|
1436
|
+
"unique_evaluations_before": prior_unique,
|
|
1437
|
+
"unique_evaluations_after": current_unique,
|
|
1438
|
+
"archive_snapshot_hash": snapshot_hash,
|
|
1439
|
+
}
|
|
1440
|
+
if candidate.objective_resolution_receipt is not None:
|
|
1441
|
+
seed_record["objective_resolution_receipt_sha256"] = (
|
|
1442
|
+
candidate.objective_resolution_receipt.receipt_sha256
|
|
1443
|
+
)
|
|
1444
|
+
receipt = SeedReceipt(
|
|
1445
|
+
label=label,
|
|
1446
|
+
candidate=candidate,
|
|
1447
|
+
gate_decision=gate_decision,
|
|
1448
|
+
archive_decisions=decisions,
|
|
1449
|
+
unique_evaluations_before=prior_unique,
|
|
1450
|
+
unique_evaluations_after=current_unique,
|
|
1451
|
+
archive_snapshot_hash=snapshot_hash,
|
|
1452
|
+
receipt_hash=_record_hash("seed-receipt", seed_record),
|
|
1453
|
+
)
|
|
1454
|
+
seed_receipts.append(receipt)
|
|
1455
|
+
candidates = (*candidates, candidate)
|
|
1456
|
+
prior_unique = current_unique
|
|
1457
|
+
self._emit(
|
|
1458
|
+
"optimizer_seed_completed",
|
|
1459
|
+
**seed_record,
|
|
1460
|
+
receipt_hash=receipt.receipt_hash,
|
|
1461
|
+
valid=candidate.valid,
|
|
1462
|
+
)
|
|
1463
|
+
if not gate_decision.admitted:
|
|
1464
|
+
raise OptimizerExecutionError(
|
|
1465
|
+
"seed gate rejected candidate "
|
|
1466
|
+
f"{candidate.candidate_id.value}: {gate_decision.reason}"
|
|
1467
|
+
)
|
|
1468
|
+
|
|
1469
|
+
state = self._state(
|
|
1470
|
+
generation=0,
|
|
1471
|
+
candidates=candidates,
|
|
1472
|
+
unique_evaluations=prior_unique,
|
|
1473
|
+
logical_llm_calls=0,
|
|
1474
|
+
generation_receipts=(),
|
|
1475
|
+
feedback_receipts=(),
|
|
1476
|
+
)
|
|
1477
|
+
generation_receipts: list[GenerationReceipt] = []
|
|
1478
|
+
feedback_receipts: list[GenerationFeedbackReceipt] = []
|
|
1479
|
+
|
|
1480
|
+
while state.generation < self.budget.max_generations:
|
|
1481
|
+
try:
|
|
1482
|
+
plan = self.planner.plan(state, self.budget)
|
|
1483
|
+
except Exception as exc:
|
|
1484
|
+
self._emit(
|
|
1485
|
+
"optimizer_planning_failed",
|
|
1486
|
+
generation=state.generation + 1,
|
|
1487
|
+
failure_type=type(exc).__name__,
|
|
1488
|
+
)
|
|
1489
|
+
raise OptimizerPlanningError(
|
|
1490
|
+
f"planner failed for generation {state.generation + 1}"
|
|
1491
|
+
) from exc
|
|
1492
|
+
if type(plan) is not GenerationPlan:
|
|
1493
|
+
raise OptimizerContractError(
|
|
1494
|
+
"planner must return an exact GenerationPlan"
|
|
1495
|
+
)
|
|
1496
|
+
GenerationPlan.__post_init__(plan)
|
|
1497
|
+
self._validate_plan(plan, state)
|
|
1498
|
+
feedback_reservation: GenerationFeedbackReservation | None = None
|
|
1499
|
+
if self.feedback_interceptor is not None:
|
|
1500
|
+
try:
|
|
1501
|
+
feedback_reservation = self.feedback_interceptor.reserve(
|
|
1502
|
+
state=state,
|
|
1503
|
+
plan=plan,
|
|
1504
|
+
)
|
|
1505
|
+
except Exception as exc:
|
|
1506
|
+
self._emit(
|
|
1507
|
+
"optimizer_generation_feedback_reservation_failed",
|
|
1508
|
+
generation=plan.generation,
|
|
1509
|
+
failure_type=type(exc).__name__,
|
|
1510
|
+
)
|
|
1511
|
+
raise OptimizerPlanningError(
|
|
1512
|
+
"generation feedback reservation failed for "
|
|
1513
|
+
f"generation {plan.generation}"
|
|
1514
|
+
) from exc
|
|
1515
|
+
if type(feedback_reservation) is not GenerationFeedbackReservation:
|
|
1516
|
+
raise OptimizerContractError(
|
|
1517
|
+
"feedback interceptor must return an exact reservation"
|
|
1518
|
+
)
|
|
1519
|
+
GenerationFeedbackReservation.__post_init__(feedback_reservation)
|
|
1520
|
+
plan_record = _generation_plan_record(
|
|
1521
|
+
plan,
|
|
1522
|
+
budget_hash=self.budget.budget_hash,
|
|
1523
|
+
)
|
|
1524
|
+
plan_hash = _record_hash("generation-plan", plan_record)
|
|
1525
|
+
try:
|
|
1526
|
+
self._reserve_plan(plan, state, feedback_reservation)
|
|
1527
|
+
except OptimizerBudgetExceeded:
|
|
1528
|
+
self._emit(
|
|
1529
|
+
"optimizer_generation_rejected",
|
|
1530
|
+
plan_hash=plan_hash,
|
|
1531
|
+
feedback_reservation=(
|
|
1532
|
+
None
|
|
1533
|
+
if feedback_reservation is None
|
|
1534
|
+
else {
|
|
1535
|
+
**feedback_reservation.to_record(),
|
|
1536
|
+
"reservation_hash": (feedback_reservation.reservation_hash),
|
|
1537
|
+
}
|
|
1538
|
+
),
|
|
1539
|
+
**plan_record,
|
|
1540
|
+
)
|
|
1541
|
+
raise
|
|
1542
|
+
if feedback_reservation is not None:
|
|
1543
|
+
self._emit(
|
|
1544
|
+
"optimizer_generation_feedback_reserved",
|
|
1545
|
+
generation=plan.generation,
|
|
1546
|
+
plan_hash=plan_hash,
|
|
1547
|
+
**feedback_reservation.to_record(),
|
|
1548
|
+
reservation_hash=feedback_reservation.reservation_hash,
|
|
1549
|
+
)
|
|
1550
|
+
self._emit(
|
|
1551
|
+
"optimizer_generation_planned",
|
|
1552
|
+
plan_hash=plan_hash,
|
|
1553
|
+
feedback_reservation_hash=(
|
|
1554
|
+
None
|
|
1555
|
+
if feedback_reservation is None
|
|
1556
|
+
else feedback_reservation.reservation_hash
|
|
1557
|
+
),
|
|
1558
|
+
reserved_feedback_logical_llm_calls=(
|
|
1559
|
+
0
|
|
1560
|
+
if feedback_reservation is None
|
|
1561
|
+
else feedback_reservation.logical_llm_calls
|
|
1562
|
+
),
|
|
1563
|
+
**plan_record,
|
|
1564
|
+
)
|
|
1565
|
+
if (
|
|
1566
|
+
_record_hash(
|
|
1567
|
+
"generation-plan",
|
|
1568
|
+
_generation_plan_record(
|
|
1569
|
+
plan,
|
|
1570
|
+
budget_hash=self.budget.budget_hash,
|
|
1571
|
+
),
|
|
1572
|
+
)
|
|
1573
|
+
!= plan_hash
|
|
1574
|
+
):
|
|
1575
|
+
raise OptimizerContractError(
|
|
1576
|
+
"generation plan changed after its admission receipt"
|
|
1577
|
+
)
|
|
1578
|
+
|
|
1579
|
+
before_unique = state.unique_evaluations
|
|
1580
|
+
before_calls = state.logical_llm_calls
|
|
1581
|
+
try:
|
|
1582
|
+
outcomes = await self._execute_plan(plan)
|
|
1583
|
+
except asyncio.CancelledError:
|
|
1584
|
+
raise
|
|
1585
|
+
except Exception as exc:
|
|
1586
|
+
self._emit(
|
|
1587
|
+
"optimizer_generation_execution_failed",
|
|
1588
|
+
generation=plan.generation,
|
|
1589
|
+
plan_hash=plan_hash,
|
|
1590
|
+
failure_type=type(exc).__name__,
|
|
1591
|
+
)
|
|
1592
|
+
raise OptimizerExecutionError(
|
|
1593
|
+
f"generation {plan.generation} execution failed"
|
|
1594
|
+
) from exc
|
|
1595
|
+
if (
|
|
1596
|
+
_record_hash(
|
|
1597
|
+
"generation-plan",
|
|
1598
|
+
_generation_plan_record(
|
|
1599
|
+
plan,
|
|
1600
|
+
budget_hash=self.budget.budget_hash,
|
|
1601
|
+
),
|
|
1602
|
+
)
|
|
1603
|
+
!= plan_hash
|
|
1604
|
+
):
|
|
1605
|
+
raise OptimizerExecutionError(
|
|
1606
|
+
"generation plan changed while its slots were executing"
|
|
1607
|
+
)
|
|
1608
|
+
|
|
1609
|
+
observed_calls = sum(
|
|
1610
|
+
outcome.prepared.call_id is not None for outcome in outcomes
|
|
1611
|
+
)
|
|
1612
|
+
if observed_calls != plan.logical_llm_call_reservation:
|
|
1613
|
+
raise OptimizerExecutionError(
|
|
1614
|
+
"engine logical-call identities differ from reservations"
|
|
1615
|
+
)
|
|
1616
|
+
after_calls = before_calls + observed_calls
|
|
1617
|
+
after_unique = (await self._evaluation_misses()) - initial_misses
|
|
1618
|
+
if (
|
|
1619
|
+
after_calls > self.budget.max_logical_llm_calls
|
|
1620
|
+
or after_unique > self.budget.max_unique_evaluations
|
|
1621
|
+
or after_unique < before_unique
|
|
1622
|
+
):
|
|
1623
|
+
raise OptimizerExecutionError("engine counters violated hard budgets")
|
|
1624
|
+
|
|
1625
|
+
slot_results: list[SlotResult] = []
|
|
1626
|
+
next_candidates = list(state.candidates)
|
|
1627
|
+
for slot, outcome in zip(plan.slots, outcomes, strict=True):
|
|
1628
|
+
candidate = outcome.candidate
|
|
1629
|
+
decisions: tuple[ParetoDecision, ...] = ()
|
|
1630
|
+
if candidate is not None:
|
|
1631
|
+
if any(
|
|
1632
|
+
existing.candidate_id == candidate.candidate_id
|
|
1633
|
+
for existing in next_candidates
|
|
1634
|
+
):
|
|
1635
|
+
raise OptimizerExecutionError(
|
|
1636
|
+
"engine reused a candidate occurrence ID"
|
|
1637
|
+
)
|
|
1638
|
+
next_candidates.append(candidate)
|
|
1639
|
+
decisions = self.archive.consider(candidate)
|
|
1640
|
+
slot_results.append(SlotResult(slot, outcome, decisions))
|
|
1641
|
+
|
|
1642
|
+
post_snapshot = self.archive.snapshot()
|
|
1643
|
+
post_archive_hash = pareto_archive_snapshot_hash(post_snapshot)
|
|
1644
|
+
receipt_record = _generation_receipt_record(
|
|
1645
|
+
generation=plan.generation,
|
|
1646
|
+
plan_hash=plan_hash,
|
|
1647
|
+
pre_archive_snapshot_hash=state.archive_snapshot_hash,
|
|
1648
|
+
post_archive_snapshot_hash=post_archive_hash,
|
|
1649
|
+
reward_definition_hash=plan.reward.binding.definition_hash,
|
|
1650
|
+
reward_snapshot_hash=plan.reward.reward_snapshot_hash,
|
|
1651
|
+
logical_llm_calls_before=before_calls,
|
|
1652
|
+
logical_llm_calls_after=after_calls,
|
|
1653
|
+
unique_evaluations_before=before_unique,
|
|
1654
|
+
unique_evaluations_after=after_unique,
|
|
1655
|
+
reserved_logical_llm_calls=plan.logical_llm_call_reservation,
|
|
1656
|
+
reserved_unique_evaluations=plan.unique_evaluation_reservation,
|
|
1657
|
+
slot_results=tuple(slot_results),
|
|
1658
|
+
)
|
|
1659
|
+
receipt_hash = _record_hash("generation-receipt", receipt_record)
|
|
1660
|
+
receipt = GenerationReceipt(
|
|
1661
|
+
generation=plan.generation,
|
|
1662
|
+
plan_hash=plan_hash,
|
|
1663
|
+
pre_archive_snapshot_hash=state.archive_snapshot_hash,
|
|
1664
|
+
post_archive_snapshot_hash=post_archive_hash,
|
|
1665
|
+
reward_definition_hash=plan.reward.binding.definition_hash,
|
|
1666
|
+
reward_snapshot_hash=plan.reward.reward_snapshot_hash,
|
|
1667
|
+
logical_llm_calls_before=before_calls,
|
|
1668
|
+
logical_llm_calls_after=after_calls,
|
|
1669
|
+
unique_evaluations_before=before_unique,
|
|
1670
|
+
unique_evaluations_after=after_unique,
|
|
1671
|
+
reserved_logical_llm_calls=plan.logical_llm_call_reservation,
|
|
1672
|
+
reserved_unique_evaluations=plan.unique_evaluation_reservation,
|
|
1673
|
+
slot_results=tuple(slot_results),
|
|
1674
|
+
receipt_hash=receipt_hash,
|
|
1675
|
+
)
|
|
1676
|
+
generation_receipts.append(receipt)
|
|
1677
|
+
self._emit(
|
|
1678
|
+
"optimizer_generation_completed",
|
|
1679
|
+
**receipt_record,
|
|
1680
|
+
receipt_hash=receipt_hash,
|
|
1681
|
+
)
|
|
1682
|
+
state = self._state(
|
|
1683
|
+
generation=plan.generation,
|
|
1684
|
+
candidates=tuple(next_candidates),
|
|
1685
|
+
unique_evaluations=after_unique,
|
|
1686
|
+
logical_llm_calls=after_calls,
|
|
1687
|
+
generation_receipts=tuple(generation_receipts),
|
|
1688
|
+
feedback_receipts=tuple(feedback_receipts),
|
|
1689
|
+
)
|
|
1690
|
+
if self.feedback_interceptor is not None:
|
|
1691
|
+
assert feedback_reservation is not None
|
|
1692
|
+
context = GenerationFeedbackContext(
|
|
1693
|
+
state=state,
|
|
1694
|
+
plan=plan,
|
|
1695
|
+
generation_receipt=receipt,
|
|
1696
|
+
reservation=feedback_reservation,
|
|
1697
|
+
)
|
|
1698
|
+
self._emit(
|
|
1699
|
+
"optimizer_generation_feedback_started",
|
|
1700
|
+
generation=plan.generation,
|
|
1701
|
+
generation_receipt_hash=receipt.receipt_hash,
|
|
1702
|
+
reservation_hash=feedback_reservation.reservation_hash,
|
|
1703
|
+
)
|
|
1704
|
+
try:
|
|
1705
|
+
feedback_result = await self.feedback_interceptor.after_generation(
|
|
1706
|
+
context
|
|
1707
|
+
)
|
|
1708
|
+
except asyncio.CancelledError:
|
|
1709
|
+
raise
|
|
1710
|
+
except Exception as exc:
|
|
1711
|
+
self._emit(
|
|
1712
|
+
"optimizer_generation_feedback_failed",
|
|
1713
|
+
generation=plan.generation,
|
|
1714
|
+
generation_receipt_hash=receipt.receipt_hash,
|
|
1715
|
+
reservation_hash=feedback_reservation.reservation_hash,
|
|
1716
|
+
failure_type=type(exc).__name__,
|
|
1717
|
+
)
|
|
1718
|
+
raise OptimizerExecutionError(
|
|
1719
|
+
f"generation feedback failed for generation {plan.generation}"
|
|
1720
|
+
) from exc
|
|
1721
|
+
if type(feedback_result) is not GenerationFeedbackResult:
|
|
1722
|
+
raise OptimizerContractError(
|
|
1723
|
+
"feedback interceptor must return an exact result"
|
|
1724
|
+
)
|
|
1725
|
+
try:
|
|
1726
|
+
feedback_receipt = seal_generation_feedback(
|
|
1727
|
+
context=context,
|
|
1728
|
+
result=feedback_result,
|
|
1729
|
+
)
|
|
1730
|
+
except (TypeError, ValueError) as exc:
|
|
1731
|
+
raise OptimizerContractError(
|
|
1732
|
+
"feedback result differs from its admitted reservation"
|
|
1733
|
+
) from exc
|
|
1734
|
+
if (
|
|
1735
|
+
feedback_receipt.logical_llm_calls_after
|
|
1736
|
+
> self.budget.max_logical_llm_calls
|
|
1737
|
+
):
|
|
1738
|
+
raise OptimizerExecutionError(
|
|
1739
|
+
"feedback logical-call counters violated the hard budget"
|
|
1740
|
+
)
|
|
1741
|
+
feedback_receipts.append(feedback_receipt)
|
|
1742
|
+
self._emit(
|
|
1743
|
+
"optimizer_generation_feedback_completed",
|
|
1744
|
+
generation=plan.generation,
|
|
1745
|
+
policy_id=feedback_receipt.policy_id,
|
|
1746
|
+
policy_version=feedback_receipt.policy_version,
|
|
1747
|
+
generation_receipt_hash=receipt.receipt_hash,
|
|
1748
|
+
reservation_hash=feedback_receipt.reservation_hash,
|
|
1749
|
+
reserved_logical_llm_calls=(
|
|
1750
|
+
feedback_receipt.reserved_logical_llm_calls
|
|
1751
|
+
),
|
|
1752
|
+
used_logical_llm_calls=feedback_receipt.used_logical_llm_calls,
|
|
1753
|
+
logical_llm_calls_before=(
|
|
1754
|
+
feedback_receipt.logical_llm_calls_before
|
|
1755
|
+
),
|
|
1756
|
+
logical_llm_calls_after=(feedback_receipt.logical_llm_calls_after),
|
|
1757
|
+
result_metadata=[
|
|
1758
|
+
list(item) for item in feedback_receipt.result_metadata
|
|
1759
|
+
],
|
|
1760
|
+
feedback_receipt_hash=feedback_receipt.receipt_hash,
|
|
1761
|
+
)
|
|
1762
|
+
state = self._state(
|
|
1763
|
+
generation=plan.generation,
|
|
1764
|
+
candidates=tuple(next_candidates),
|
|
1765
|
+
unique_evaluations=after_unique,
|
|
1766
|
+
logical_llm_calls=(feedback_receipt.logical_llm_calls_after),
|
|
1767
|
+
generation_receipts=tuple(generation_receipts),
|
|
1768
|
+
feedback_receipts=tuple(feedback_receipts),
|
|
1769
|
+
)
|
|
1770
|
+
|
|
1771
|
+
stop_reason = OptimizerStopReason.GENERATION_LIMIT_REACHED
|
|
1772
|
+
result_record = {
|
|
1773
|
+
"budget_hash": self.budget.budget_hash,
|
|
1774
|
+
"stop_reason": stop_reason.value,
|
|
1775
|
+
"generation": state.generation,
|
|
1776
|
+
"unique_evaluations": state.unique_evaluations,
|
|
1777
|
+
"logical_llm_calls": state.logical_llm_calls,
|
|
1778
|
+
"archive_snapshot_hash": state.archive_snapshot_hash,
|
|
1779
|
+
"seed_receipt_hashes": [item.receipt_hash for item in seed_receipts],
|
|
1780
|
+
"generation_receipt_hashes": [
|
|
1781
|
+
item.receipt_hash for item in generation_receipts
|
|
1782
|
+
],
|
|
1783
|
+
"feedback_receipt_hashes": [
|
|
1784
|
+
item.receipt_hash for item in feedback_receipts
|
|
1785
|
+
],
|
|
1786
|
+
}
|
|
1787
|
+
result_hash = _record_hash("optimizer-result", result_record)
|
|
1788
|
+
self._emit("optimizer_completed", **result_record, result_hash=result_hash)
|
|
1789
|
+
return OptimizerResult(
|
|
1790
|
+
budget=self.budget,
|
|
1791
|
+
final_state=state,
|
|
1792
|
+
seed_receipts=tuple(seed_receipts),
|
|
1793
|
+
generation_receipts=tuple(generation_receipts),
|
|
1794
|
+
feedback_receipts=tuple(feedback_receipts),
|
|
1795
|
+
stop_reason=stop_reason,
|
|
1796
|
+
result_hash=result_hash,
|
|
1797
|
+
)
|
|
1798
|
+
|
|
1799
|
+
|
|
1800
|
+
__all__ = [
|
|
1801
|
+
"BudgetedAgenticOptimizer",
|
|
1802
|
+
"FrozenWaveReward",
|
|
1803
|
+
"GenerationPlan",
|
|
1804
|
+
"GenerationPlanner",
|
|
1805
|
+
"GenerationReceipt",
|
|
1806
|
+
"OptimizerBudget",
|
|
1807
|
+
"OptimizerBudgetExceeded",
|
|
1808
|
+
"OptimizerContractError",
|
|
1809
|
+
"OptimizerExecutionError",
|
|
1810
|
+
"OptimizerPlanningError",
|
|
1811
|
+
"OptimizerResult",
|
|
1812
|
+
"OptimizerSlot",
|
|
1813
|
+
"OptimizerState",
|
|
1814
|
+
"OptimizerStopReason",
|
|
1815
|
+
"SeedReceipt",
|
|
1816
|
+
"SeedAdmissionPolicy",
|
|
1817
|
+
"SeedGateContext",
|
|
1818
|
+
"SeedGateDecision",
|
|
1819
|
+
"SlotResult",
|
|
1820
|
+
"ValidSeedAdmissionPolicy",
|
|
1821
|
+
"generation_receipt_hash",
|
|
1822
|
+
"optimizer_result_hash",
|
|
1823
|
+
"pareto_archive_snapshot_hash",
|
|
1824
|
+
"seed_receipt_hash",
|
|
1825
|
+
"validate_generation_receipt_integrity",
|
|
1826
|
+
"validate_optimizer_result_integrity",
|
|
1827
|
+
"validate_seed_receipt_integrity",
|
|
1828
|
+
]
|