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,1840 @@
|
|
|
1
|
+
"""Workload-neutral preparation boundary for multi-generation campaigns.
|
|
2
|
+
|
|
3
|
+
This module deliberately stops before live evolutionary execution. It seals
|
|
4
|
+
the shared chronology, validates budgets and concurrency against a benchmark
|
|
5
|
+
preflight, loads an exact seed batch, asks an injected agent runtime to
|
|
6
|
+
preflight the composition, and journals the resulting immutable preparation
|
|
7
|
+
record. Existing portfolio-evolution and recombination services remain the
|
|
8
|
+
authorities for executing their respective waves.
|
|
9
|
+
|
|
10
|
+
Benchmark adapters enter only through inverted ports. Model and provider
|
|
11
|
+
configuration belongs to ``CampaignAgentRuntimePort`` implementations and is
|
|
12
|
+
therefore absent from campaign protocols and workload ports.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import hashlib
|
|
18
|
+
import json
|
|
19
|
+
import math
|
|
20
|
+
import re
|
|
21
|
+
from dataclasses import dataclass, field
|
|
22
|
+
from enum import Enum
|
|
23
|
+
from typing import Protocol, runtime_checkable
|
|
24
|
+
|
|
25
|
+
from agent_evolve.application.budgeted_optimizer import OptimizerBudget
|
|
26
|
+
from agent_evolve.application.finite_variation_eligibility import (
|
|
27
|
+
FiniteVariationEligibilityReceipt,
|
|
28
|
+
)
|
|
29
|
+
from agent_evolve.domain.finite_variation import (
|
|
30
|
+
FiniteVariationContract,
|
|
31
|
+
validate_finite_variation_contract,
|
|
32
|
+
)
|
|
33
|
+
from agent_evolve.domain.patch import require_sha256
|
|
34
|
+
from agent_evolve.domain.typed_json import (
|
|
35
|
+
FrozenJsonObject,
|
|
36
|
+
freeze_json,
|
|
37
|
+
thaw_json,
|
|
38
|
+
typed_json_sha256,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
MIN_CAMPAIGN_GENERATIONS = 3
|
|
43
|
+
MAX_CAMPAIGN_GENERATIONS = 24
|
|
44
|
+
|
|
45
|
+
_TOKEN = re.compile(r"^[a-z][a-z0-9_.:-]{0,127}$")
|
|
46
|
+
_PROTOCOL_DOMAIN = b"agent-evolve:evolution-campaign-protocol:v1\x00"
|
|
47
|
+
_SCHEDULE_DOMAIN = b"agent-evolve:evolution-campaign-schedule:v1\x00"
|
|
48
|
+
_SESSION_REQUEST_DOMAIN = b"agent-evolve:campaign-session-request:v1\x00"
|
|
49
|
+
_SESSION_DOMAIN = b"agent-evolve:campaign-benchmark-session:v1\x00"
|
|
50
|
+
_SEED_DOMAIN = b"agent-evolve:campaign-seed:v1\x00"
|
|
51
|
+
_SEED_BATCH_DOMAIN = b"agent-evolve:campaign-seed-batch:v1\x00"
|
|
52
|
+
_ARCHIVE_UTILITY_SNAPSHOT_DOMAIN = (
|
|
53
|
+
b"agent-evolve:campaign-archive-utility-snapshot:v1\x00"
|
|
54
|
+
)
|
|
55
|
+
_POLICIES_DOMAIN = b"agent-evolve:campaign-policies:v1\x00"
|
|
56
|
+
_REFLECTION_SUPERVISION_CONFIGURATION_DOMAIN = (
|
|
57
|
+
b"agent-evolve:campaign-reflection-supervision-configuration:v1\x00"
|
|
58
|
+
)
|
|
59
|
+
_WORKLOAD_PORTS_DOMAIN = b"agent-evolve:campaign-workload-ports:v1\x00"
|
|
60
|
+
_CONCURRENCY_DOMAIN = b"agent-evolve:campaign-concurrency:v1\x00"
|
|
61
|
+
_RUNTIME_REQUEST_DOMAIN = b"agent-evolve:campaign-runtime-request:v1\x00"
|
|
62
|
+
_RUNTIME_RECEIPT_DOMAIN = b"agent-evolve:campaign-runtime-receipt:v1\x00"
|
|
63
|
+
_PREPARATION_DOMAIN = b"agent-evolve:campaign-preparation:v1\x00"
|
|
64
|
+
|
|
65
|
+
ALTERNATING_CADENCE_POLICY_ID = "alternating_portfolio_recombination"
|
|
66
|
+
ALTERNATING_CADENCE_POLICY_VERSION = 3
|
|
67
|
+
ALTERNATING_CADENCE_DEFINITION_SHA256 = hashlib.sha256(
|
|
68
|
+
b"agent-evolve:alternating-portfolio-recombination:v3;"
|
|
69
|
+
b"actual-generations-not-cycles;portfolio-first;recombination-second;"
|
|
70
|
+
b"odd-terminal-portfolio-permitted;recombination-source-is-prior-portfolio;"
|
|
71
|
+
b"protocol-bound-terminal-reflection-consumer-requirement;"
|
|
72
|
+
b"future-consumer-requires-complete-scheduled-admission-barrier"
|
|
73
|
+
).hexdigest()
|
|
74
|
+
|
|
75
|
+
SEALED_CUTOFF_DELAYED_CADENCE_POLICY_ID = (
|
|
76
|
+
"sealed_cutoff_delayed_portfolio_recombination"
|
|
77
|
+
)
|
|
78
|
+
SEALED_CUTOFF_DELAYED_CADENCE_POLICY_VERSION = 1
|
|
79
|
+
SEALED_CUTOFF_DELAYED_CADENCE_DEFINITION_SHA256 = hashlib.sha256(
|
|
80
|
+
b"agent-evolve:sealed-cutoff-delayed-portfolio-recombination:v1;"
|
|
81
|
+
b"actual-generations-not-cycles;portfolio-first;recombination-second;"
|
|
82
|
+
b"reflection-input-is-exact-sealed-source-stage;"
|
|
83
|
+
b"one-complete-pair-delay;admit-after-next-recombination-seal;"
|
|
84
|
+
b"first-consumer-is-following-portfolio;future-content-not-visible-to-reflector;"
|
|
85
|
+
b"no-online-reflection-without-scheduled-future-portfolio-consumer"
|
|
86
|
+
).hexdigest()
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _canonical_json(value: object) -> bytes:
|
|
90
|
+
return json.dumps(
|
|
91
|
+
value,
|
|
92
|
+
ensure_ascii=True,
|
|
93
|
+
allow_nan=False,
|
|
94
|
+
separators=(",", ":"),
|
|
95
|
+
sort_keys=True,
|
|
96
|
+
).encode("ascii", errors="strict")
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _hash(domain: bytes, record: object) -> str:
|
|
100
|
+
return hashlib.sha256(domain + _canonical_json(record)).hexdigest()
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _validate_token(value: str, *, name: str) -> None:
|
|
104
|
+
if type(value) is not str or _TOKEN.fullmatch(value) is None:
|
|
105
|
+
raise ValueError(f"{name} must use the closed campaign-token grammar")
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _validate_frozen_object(value: object, *, name: str) -> FrozenJsonObject:
|
|
109
|
+
if type(value) is not FrozenJsonObject:
|
|
110
|
+
raise TypeError(f"{name} must be an exact FrozenJsonObject")
|
|
111
|
+
if freeze_json(value) is not value:
|
|
112
|
+
raise TypeError(f"{name} must already be frozen typed JSON")
|
|
113
|
+
return value
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class TerminalReflectionPolicy(str, Enum):
|
|
117
|
+
"""Whether online reflection may run without a later portfolio consumer."""
|
|
118
|
+
|
|
119
|
+
ALLOW_TERMINAL = "allow_terminal"
|
|
120
|
+
REQUIRE_FUTURE_PORTFOLIO_CONSUMER = "require_future_portfolio_consumer"
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@dataclass(frozen=True, slots=True)
|
|
124
|
+
class CampaignProtocol:
|
|
125
|
+
"""Provider- and workload-free behavioral protocol for one campaign.
|
|
126
|
+
|
|
127
|
+
``generation_count`` is the number of actual evolutionary generations,
|
|
128
|
+
not a number of portfolio/recombination cycles. Consequently a count of
|
|
129
|
+
three means portfolio, recombination, portfolio.
|
|
130
|
+
"""
|
|
131
|
+
|
|
132
|
+
protocol_id: str
|
|
133
|
+
protocol_version: int
|
|
134
|
+
definition_sha256: str
|
|
135
|
+
outer_seed: int
|
|
136
|
+
generation_count: int
|
|
137
|
+
required_seed_count: int
|
|
138
|
+
parents_per_portfolio_generation: int
|
|
139
|
+
portfolio_width: int
|
|
140
|
+
recombinations_per_parent: int
|
|
141
|
+
reflections_per_recombination_generation: int = 1
|
|
142
|
+
reflection_promotion_block_pairs: int = 3
|
|
143
|
+
terminal_reflection_policy: TerminalReflectionPolicy = (
|
|
144
|
+
TerminalReflectionPolicy.ALLOW_TERMINAL
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
def __post_init__(self) -> None:
|
|
148
|
+
_validate_token(self.protocol_id, name="protocol_id")
|
|
149
|
+
if type(self.protocol_version) is not int or self.protocol_version <= 0:
|
|
150
|
+
raise ValueError("protocol_version must be a positive exact integer")
|
|
151
|
+
require_sha256(self.definition_sha256, "definition_sha256")
|
|
152
|
+
if type(self.outer_seed) is not int or not -(1 << 127) <= self.outer_seed < (
|
|
153
|
+
1 << 127
|
|
154
|
+
):
|
|
155
|
+
raise ValueError("outer_seed must be an exact signed int128")
|
|
156
|
+
if type(self.generation_count) is not int or not (
|
|
157
|
+
MIN_CAMPAIGN_GENERATIONS
|
|
158
|
+
<= self.generation_count
|
|
159
|
+
<= MAX_CAMPAIGN_GENERATIONS
|
|
160
|
+
):
|
|
161
|
+
raise ValueError(
|
|
162
|
+
"generation_count must count 3 through 24 actual generations"
|
|
163
|
+
)
|
|
164
|
+
for name in (
|
|
165
|
+
"required_seed_count",
|
|
166
|
+
"parents_per_portfolio_generation",
|
|
167
|
+
"recombinations_per_parent",
|
|
168
|
+
):
|
|
169
|
+
value = getattr(self, name)
|
|
170
|
+
if type(value) is not int or value <= 0:
|
|
171
|
+
raise ValueError(f"{name} must be a positive exact integer")
|
|
172
|
+
if type(self.portfolio_width) is not int or self.portfolio_width < 2:
|
|
173
|
+
raise ValueError("portfolio_width must be an exact integer of at least two")
|
|
174
|
+
if (
|
|
175
|
+
type(self.reflections_per_recombination_generation) is not int
|
|
176
|
+
or self.reflections_per_recombination_generation < 0
|
|
177
|
+
):
|
|
178
|
+
raise ValueError(
|
|
179
|
+
"reflections_per_recombination_generation must be a "
|
|
180
|
+
"non-negative exact integer"
|
|
181
|
+
)
|
|
182
|
+
if (
|
|
183
|
+
type(self.reflection_promotion_block_pairs) is not int
|
|
184
|
+
or self.reflection_promotion_block_pairs <= 0
|
|
185
|
+
):
|
|
186
|
+
raise ValueError(
|
|
187
|
+
"reflection_promotion_block_pairs must be a positive exact integer"
|
|
188
|
+
)
|
|
189
|
+
if type(self.terminal_reflection_policy) is not TerminalReflectionPolicy:
|
|
190
|
+
raise TypeError(
|
|
191
|
+
"terminal_reflection_policy must be a TerminalReflectionPolicy"
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
def to_record(self) -> dict[str, object]:
|
|
195
|
+
self.__post_init__()
|
|
196
|
+
return {
|
|
197
|
+
"schema_version": 1,
|
|
198
|
+
"protocol_id": self.protocol_id,
|
|
199
|
+
"protocol_version": self.protocol_version,
|
|
200
|
+
"definition_sha256": self.definition_sha256,
|
|
201
|
+
"outer_seed": self.outer_seed,
|
|
202
|
+
"generation_count": self.generation_count,
|
|
203
|
+
"required_seed_count": self.required_seed_count,
|
|
204
|
+
"parents_per_portfolio_generation": (self.parents_per_portfolio_generation),
|
|
205
|
+
"portfolio_width": self.portfolio_width,
|
|
206
|
+
"recombinations_per_parent": self.recombinations_per_parent,
|
|
207
|
+
"reflections_per_recombination_generation": (
|
|
208
|
+
self.reflections_per_recombination_generation
|
|
209
|
+
),
|
|
210
|
+
"reflection_promotion_block_pairs": (self.reflection_promotion_block_pairs),
|
|
211
|
+
"terminal_reflection_policy": self.terminal_reflection_policy.value,
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
@property
|
|
215
|
+
def protocol_sha256(self) -> str:
|
|
216
|
+
return _hash(_PROTOCOL_DOMAIN, self.to_record())
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
class CampaignGenerationKind(str, Enum):
|
|
220
|
+
PORTFOLIO = "portfolio"
|
|
221
|
+
RECOMBINATION = "recombination"
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
@dataclass(frozen=True, slots=True)
|
|
225
|
+
class CampaignGenerationStep:
|
|
226
|
+
"""One exact generation in a campaign cadence."""
|
|
227
|
+
|
|
228
|
+
generation: int
|
|
229
|
+
kind: CampaignGenerationKind
|
|
230
|
+
source_portfolio_generation: int | None
|
|
231
|
+
parent_count: int
|
|
232
|
+
offspring_per_parent: int
|
|
233
|
+
planned_agent_calls: int
|
|
234
|
+
|
|
235
|
+
def __post_init__(self) -> None:
|
|
236
|
+
if type(self.generation) is not int or self.generation <= 0:
|
|
237
|
+
raise ValueError("generation must be a positive exact integer")
|
|
238
|
+
if type(self.kind) is not CampaignGenerationKind:
|
|
239
|
+
raise TypeError("kind must be a CampaignGenerationKind")
|
|
240
|
+
for name in ("parent_count", "offspring_per_parent"):
|
|
241
|
+
value = getattr(self, name)
|
|
242
|
+
if type(value) is not int or value <= 0:
|
|
243
|
+
raise ValueError(f"{name} must be a positive exact integer")
|
|
244
|
+
if type(self.planned_agent_calls) is not int or self.planned_agent_calls < 0:
|
|
245
|
+
raise ValueError("planned_agent_calls must be non-negative")
|
|
246
|
+
if self.kind is CampaignGenerationKind.PORTFOLIO:
|
|
247
|
+
if self.source_portfolio_generation is not None:
|
|
248
|
+
raise ValueError(
|
|
249
|
+
"a portfolio generation cannot name a source portfolio"
|
|
250
|
+
)
|
|
251
|
+
if self.planned_agent_calls != self.parent_count:
|
|
252
|
+
raise ValueError(
|
|
253
|
+
"a portfolio generation requires one agent call per parent"
|
|
254
|
+
)
|
|
255
|
+
else:
|
|
256
|
+
if (
|
|
257
|
+
type(self.source_portfolio_generation) is not int
|
|
258
|
+
or self.source_portfolio_generation <= 0
|
|
259
|
+
or self.source_portfolio_generation >= self.generation
|
|
260
|
+
):
|
|
261
|
+
raise ValueError(
|
|
262
|
+
"a recombination generation must name an earlier portfolio"
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
@property
|
|
266
|
+
def planned_candidate_evaluations(self) -> int:
|
|
267
|
+
return self.parent_count * self.offspring_per_parent
|
|
268
|
+
|
|
269
|
+
def to_record(self) -> dict[str, object]:
|
|
270
|
+
self.__post_init__()
|
|
271
|
+
return {
|
|
272
|
+
"generation": self.generation,
|
|
273
|
+
"kind": self.kind.value,
|
|
274
|
+
"source_portfolio_generation": self.source_portfolio_generation,
|
|
275
|
+
"parent_count": self.parent_count,
|
|
276
|
+
"offspring_per_parent": self.offspring_per_parent,
|
|
277
|
+
"planned_candidate_evaluations": self.planned_candidate_evaluations,
|
|
278
|
+
"planned_agent_calls": self.planned_agent_calls,
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
@dataclass(frozen=True, slots=True)
|
|
283
|
+
class CampaignGenerationPair:
|
|
284
|
+
"""One portfolio generation and its immediately paired recombination."""
|
|
285
|
+
|
|
286
|
+
portfolio_generation: int
|
|
287
|
+
recombination_generation: int
|
|
288
|
+
|
|
289
|
+
def __post_init__(self) -> None:
|
|
290
|
+
if type(self.portfolio_generation) is not int or self.portfolio_generation <= 0:
|
|
291
|
+
raise ValueError("portfolio_generation must be positive")
|
|
292
|
+
if self.recombination_generation != self.portfolio_generation + 1:
|
|
293
|
+
raise ValueError("recombination must immediately follow its portfolio")
|
|
294
|
+
|
|
295
|
+
def to_record(self) -> dict[str, int]:
|
|
296
|
+
self.__post_init__()
|
|
297
|
+
return {
|
|
298
|
+
"portfolio_generation": self.portfolio_generation,
|
|
299
|
+
"recombination_generation": self.recombination_generation,
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
class ReflectionLaunchMode(str, Enum):
|
|
304
|
+
"""When a reflection may enter the agent task queue."""
|
|
305
|
+
|
|
306
|
+
ASYNC_AFTER_STAGE_SEAL = "async_after_stage_seal"
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
class ReflectionVisibility(str, Enum):
|
|
310
|
+
"""Visibility law for evidence produced by a reflection wave."""
|
|
311
|
+
|
|
312
|
+
QUARANTINED_UNTIL_BLOCK_CLOSE = "quarantined_until_block_close"
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
class ReflectionFailureMode(str, Enum):
|
|
316
|
+
"""When a settled reflection failure changes campaign execution."""
|
|
317
|
+
|
|
318
|
+
FAIL_AT_NEXT_STAGE_BOUNDARY = "fail_at_next_stage_boundary"
|
|
319
|
+
COLLECT_ALL_AT_BARRIER_THEN_FAIL = "collect_all_at_barrier_then_fail"
|
|
320
|
+
BEST_EFFORT_DEGRADED = "best_effort_degraded"
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
@dataclass(frozen=True, slots=True)
|
|
324
|
+
class CampaignReflectionSupervisionPolicy:
|
|
325
|
+
"""Failure supervision independent from reflection-content visibility.
|
|
326
|
+
|
|
327
|
+
A visibility barrier controls when successful quarantined content may enter
|
|
328
|
+
later testing. It must not implicitly define when background-task failures
|
|
329
|
+
become observable or how sibling tasks are drained.
|
|
330
|
+
"""
|
|
331
|
+
|
|
332
|
+
mode: ReflectionFailureMode = ReflectionFailureMode.COLLECT_ALL_AT_BARRIER_THEN_FAIL
|
|
333
|
+
policy_id: str = field(
|
|
334
|
+
init=False,
|
|
335
|
+
default="campaign_reflection_supervision",
|
|
336
|
+
)
|
|
337
|
+
policy_version: int = field(init=False, default=1)
|
|
338
|
+
definition_sha256: str = field(
|
|
339
|
+
init=False,
|
|
340
|
+
default=hashlib.sha256(
|
|
341
|
+
b"agent-evolve:campaign-reflection-supervision:v1;"
|
|
342
|
+
b"visibility-independent=true;settlement-durable=true;"
|
|
343
|
+
b"safe-fail-checkpoint=after-stage-seal-before-next-stage;"
|
|
344
|
+
b"barrier-drain=all-launched;exception-short-circuit=false;"
|
|
345
|
+
b"failed-content-admission=false;partial-block-admission=false;"
|
|
346
|
+
b"abort-cancellation=typed-and-drained;"
|
|
347
|
+
b"modes=fail-at-next-stage-boundary,collect-all-at-barrier-then-fail,"
|
|
348
|
+
b"best-effort-degraded"
|
|
349
|
+
).hexdigest(),
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
def __post_init__(self) -> None:
|
|
353
|
+
if type(self.mode) is not ReflectionFailureMode:
|
|
354
|
+
raise TypeError("mode must be an exact ReflectionFailureMode")
|
|
355
|
+
_validate_token(self.policy_id, name="reflection_supervision.policy_id")
|
|
356
|
+
if type(self.policy_version) is not int or self.policy_version != 1:
|
|
357
|
+
raise ValueError("unsupported reflection supervision policy version")
|
|
358
|
+
require_sha256(
|
|
359
|
+
self.definition_sha256,
|
|
360
|
+
"reflection_supervision.definition_sha256",
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
@property
|
|
364
|
+
def configuration_sha256(self) -> str:
|
|
365
|
+
self.__post_init__()
|
|
366
|
+
return _hash(
|
|
367
|
+
_REFLECTION_SUPERVISION_CONFIGURATION_DOMAIN,
|
|
368
|
+
{"mode": self.mode.value},
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
def to_record(self) -> dict[str, object]:
|
|
372
|
+
self.__post_init__()
|
|
373
|
+
return {
|
|
374
|
+
"policy_id": self.policy_id,
|
|
375
|
+
"policy_version": self.policy_version,
|
|
376
|
+
"definition_sha256": self.definition_sha256,
|
|
377
|
+
"mode": self.mode.value,
|
|
378
|
+
"configuration_sha256": self.configuration_sha256,
|
|
379
|
+
"visibility_independent": True,
|
|
380
|
+
"safe_failure_checkpoint": ("after_stage_seal_before_next_archive_cutoff"),
|
|
381
|
+
"barrier_drains_all_launched_reflections": True,
|
|
382
|
+
"failed_or_partial_block_admitted": False,
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
@dataclass(frozen=True, slots=True)
|
|
387
|
+
class CampaignReflectionWave:
|
|
388
|
+
"""Nonblocking reflection work and its prospective visibility barrier."""
|
|
389
|
+
|
|
390
|
+
source_generation: int
|
|
391
|
+
call_count: int
|
|
392
|
+
launch_mode: ReflectionLaunchMode
|
|
393
|
+
visibility: ReflectionVisibility
|
|
394
|
+
promotion_barrier_generation: int | None
|
|
395
|
+
|
|
396
|
+
def __post_init__(self) -> None:
|
|
397
|
+
if type(self.source_generation) is not int or self.source_generation <= 0:
|
|
398
|
+
raise ValueError("reflection source_generation must be positive")
|
|
399
|
+
if type(self.call_count) is not int or self.call_count <= 0:
|
|
400
|
+
raise ValueError("reflection call_count must be positive")
|
|
401
|
+
if type(self.launch_mode) is not ReflectionLaunchMode:
|
|
402
|
+
raise TypeError("launch_mode must be a ReflectionLaunchMode")
|
|
403
|
+
if type(self.visibility) is not ReflectionVisibility:
|
|
404
|
+
raise TypeError("visibility must be a ReflectionVisibility")
|
|
405
|
+
barrier = self.promotion_barrier_generation
|
|
406
|
+
if barrier is not None and (
|
|
407
|
+
type(barrier) is not int or barrier < self.source_generation
|
|
408
|
+
):
|
|
409
|
+
raise ValueError("promotion barrier cannot precede reflection evidence")
|
|
410
|
+
|
|
411
|
+
def to_record(self) -> dict[str, object]:
|
|
412
|
+
self.__post_init__()
|
|
413
|
+
return {
|
|
414
|
+
"source_generation": self.source_generation,
|
|
415
|
+
"call_count": self.call_count,
|
|
416
|
+
"launch_mode": self.launch_mode.value,
|
|
417
|
+
"visibility": self.visibility.value,
|
|
418
|
+
"promotion_barrier_generation": self.promotion_barrier_generation,
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
@dataclass(frozen=True, slots=True)
|
|
423
|
+
class CampaignPromotionBarrier:
|
|
424
|
+
"""Stage seal after which quarantined reflections may enter testing.
|
|
425
|
+
|
|
426
|
+
The legacy cohort cadence closes a barrier at its last reflection source.
|
|
427
|
+
A delayed cadence may close it at a later sealed stage so provider work can
|
|
428
|
+
overlap intervening generations. In both cases the barrier is prospective:
|
|
429
|
+
admitted evidence can first appear in the *following* stage request.
|
|
430
|
+
"""
|
|
431
|
+
|
|
432
|
+
generation: int
|
|
433
|
+
reflection_source_generations: tuple[int, ...]
|
|
434
|
+
|
|
435
|
+
def __post_init__(self) -> None:
|
|
436
|
+
if type(self.generation) is not int or self.generation <= 0:
|
|
437
|
+
raise ValueError("promotion barrier generation must be positive")
|
|
438
|
+
if (
|
|
439
|
+
type(self.reflection_source_generations) is not tuple
|
|
440
|
+
or not self.reflection_source_generations
|
|
441
|
+
or any(
|
|
442
|
+
type(value) is not int or value <= 0
|
|
443
|
+
for value in self.reflection_source_generations
|
|
444
|
+
)
|
|
445
|
+
):
|
|
446
|
+
raise ValueError("promotion barrier requires exact source generations")
|
|
447
|
+
if self.reflection_source_generations != tuple(
|
|
448
|
+
sorted(set(self.reflection_source_generations))
|
|
449
|
+
):
|
|
450
|
+
raise ValueError("reflection source generations must be canonical")
|
|
451
|
+
if self.reflection_source_generations[-1] > self.generation:
|
|
452
|
+
raise ValueError("promotion barrier cannot precede its last source")
|
|
453
|
+
|
|
454
|
+
def to_record(self) -> dict[str, object]:
|
|
455
|
+
self.__post_init__()
|
|
456
|
+
return {
|
|
457
|
+
"generation": self.generation,
|
|
458
|
+
"reflection_source_generations": list(self.reflection_source_generations),
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
@dataclass(frozen=True, slots=True)
|
|
463
|
+
class CampaignSchedule:
|
|
464
|
+
"""Authenticated operator chronology derived from a campaign protocol."""
|
|
465
|
+
|
|
466
|
+
protocol_sha256: str
|
|
467
|
+
cadence_policy_id: str
|
|
468
|
+
cadence_policy_version: int
|
|
469
|
+
cadence_definition_sha256: str
|
|
470
|
+
steps: tuple[CampaignGenerationStep, ...]
|
|
471
|
+
recombination_pairs: tuple[CampaignGenerationPair, ...]
|
|
472
|
+
reflection_waves: tuple[CampaignReflectionWave, ...]
|
|
473
|
+
promotion_barriers: tuple[CampaignPromotionBarrier, ...]
|
|
474
|
+
|
|
475
|
+
def __post_init__(self) -> None:
|
|
476
|
+
require_sha256(self.protocol_sha256, "protocol_sha256")
|
|
477
|
+
_validate_token(self.cadence_policy_id, name="cadence_policy_id")
|
|
478
|
+
if (
|
|
479
|
+
type(self.cadence_policy_version) is not int
|
|
480
|
+
or self.cadence_policy_version <= 0
|
|
481
|
+
):
|
|
482
|
+
raise ValueError("cadence_policy_version must be positive")
|
|
483
|
+
require_sha256(
|
|
484
|
+
self.cadence_definition_sha256,
|
|
485
|
+
"cadence_definition_sha256",
|
|
486
|
+
)
|
|
487
|
+
if type(self.steps) is not tuple or not self.steps:
|
|
488
|
+
raise ValueError("steps must be a non-empty exact tuple")
|
|
489
|
+
if any(type(step) is not CampaignGenerationStep for step in self.steps):
|
|
490
|
+
raise TypeError("steps must contain exact CampaignGenerationStep values")
|
|
491
|
+
for step in self.steps:
|
|
492
|
+
CampaignGenerationStep.__post_init__(step)
|
|
493
|
+
if tuple(step.generation for step in self.steps) != tuple(
|
|
494
|
+
range(1, len(self.steps) + 1)
|
|
495
|
+
):
|
|
496
|
+
raise ValueError("schedule generations must be contiguous from one")
|
|
497
|
+
expected_kinds = tuple(
|
|
498
|
+
CampaignGenerationKind.PORTFOLIO
|
|
499
|
+
if index % 2 == 1
|
|
500
|
+
else CampaignGenerationKind.RECOMBINATION
|
|
501
|
+
for index in range(1, len(self.steps) + 1)
|
|
502
|
+
)
|
|
503
|
+
if tuple(step.kind for step in self.steps) != expected_kinds:
|
|
504
|
+
raise ValueError("schedule must alternate portfolio then recombination")
|
|
505
|
+
expected_pairs = tuple(
|
|
506
|
+
CampaignGenerationPair(index, index + 1)
|
|
507
|
+
for index in range(1, len(self.steps), 2)
|
|
508
|
+
)
|
|
509
|
+
if self.recombination_pairs != expected_pairs:
|
|
510
|
+
raise ValueError("recombination_pairs differ from the exact chronology")
|
|
511
|
+
for pair in self.recombination_pairs:
|
|
512
|
+
recombination = self.steps[pair.recombination_generation - 1]
|
|
513
|
+
if recombination.source_portfolio_generation != pair.portfolio_generation:
|
|
514
|
+
raise ValueError(
|
|
515
|
+
"recombination source differs from its paired portfolio"
|
|
516
|
+
)
|
|
517
|
+
if type(self.reflection_waves) is not tuple or any(
|
|
518
|
+
type(value) is not CampaignReflectionWave for value in self.reflection_waves
|
|
519
|
+
):
|
|
520
|
+
raise TypeError("reflection_waves must contain exact wave values")
|
|
521
|
+
for value in self.reflection_waves:
|
|
522
|
+
CampaignReflectionWave.__post_init__(value)
|
|
523
|
+
reflection_sources = tuple(
|
|
524
|
+
value.source_generation for value in self.reflection_waves
|
|
525
|
+
)
|
|
526
|
+
if reflection_sources != tuple(sorted(set(reflection_sources))):
|
|
527
|
+
raise ValueError("reflection waves must have unique canonical sources")
|
|
528
|
+
recombination_calls = {
|
|
529
|
+
step.generation: step.planned_agent_calls
|
|
530
|
+
for step in self.steps
|
|
531
|
+
if step.kind is CampaignGenerationKind.RECOMBINATION
|
|
532
|
+
and step.planned_agent_calls > 0
|
|
533
|
+
}
|
|
534
|
+
if reflection_sources != tuple(recombination_calls):
|
|
535
|
+
raise ValueError("reflection waves differ from recombination call plans")
|
|
536
|
+
if any(
|
|
537
|
+
value.call_count != recombination_calls[value.source_generation]
|
|
538
|
+
for value in self.reflection_waves
|
|
539
|
+
):
|
|
540
|
+
raise ValueError("reflection wave call count differs from its stage")
|
|
541
|
+
if type(self.promotion_barriers) is not tuple or any(
|
|
542
|
+
type(value) is not CampaignPromotionBarrier
|
|
543
|
+
for value in self.promotion_barriers
|
|
544
|
+
):
|
|
545
|
+
raise TypeError("promotion_barriers must contain exact barrier values")
|
|
546
|
+
for value in self.promotion_barriers:
|
|
547
|
+
CampaignPromotionBarrier.__post_init__(value)
|
|
548
|
+
if value.generation > len(self.steps):
|
|
549
|
+
raise ValueError("promotion barrier escapes the campaign horizon")
|
|
550
|
+
barrier_step = self.steps[value.generation - 1]
|
|
551
|
+
if barrier_step.kind is not CampaignGenerationKind.RECOMBINATION:
|
|
552
|
+
raise ValueError("promotion barrier must follow a recombination seal")
|
|
553
|
+
barrier_generations = tuple(
|
|
554
|
+
value.generation for value in self.promotion_barriers
|
|
555
|
+
)
|
|
556
|
+
if barrier_generations != tuple(sorted(set(barrier_generations))):
|
|
557
|
+
raise ValueError("promotion barriers must be unique and canonical")
|
|
558
|
+
waves_by_barrier: dict[int, list[int]] = {}
|
|
559
|
+
for wave in self.reflection_waves:
|
|
560
|
+
barrier = wave.promotion_barrier_generation
|
|
561
|
+
if barrier is not None:
|
|
562
|
+
waves_by_barrier.setdefault(barrier, []).append(wave.source_generation)
|
|
563
|
+
expected_barriers = tuple(
|
|
564
|
+
CampaignPromotionBarrier(generation, tuple(sources))
|
|
565
|
+
for generation, sources in sorted(waves_by_barrier.items())
|
|
566
|
+
)
|
|
567
|
+
if expected_barriers != self.promotion_barriers:
|
|
568
|
+
raise ValueError("promotion barriers differ from reflection visibility")
|
|
569
|
+
|
|
570
|
+
@property
|
|
571
|
+
def portfolio_generations(self) -> tuple[int, ...]:
|
|
572
|
+
return tuple(
|
|
573
|
+
step.generation
|
|
574
|
+
for step in self.steps
|
|
575
|
+
if step.kind is CampaignGenerationKind.PORTFOLIO
|
|
576
|
+
)
|
|
577
|
+
|
|
578
|
+
@property
|
|
579
|
+
def paired_recombination_generations(self) -> tuple[int, ...]:
|
|
580
|
+
return tuple(pair.recombination_generation for pair in self.recombination_pairs)
|
|
581
|
+
|
|
582
|
+
@property
|
|
583
|
+
def unpaired_terminal_portfolio_generation(self) -> int | None:
|
|
584
|
+
terminal = self.steps[-1]
|
|
585
|
+
return (
|
|
586
|
+
terminal.generation
|
|
587
|
+
if terminal.kind is CampaignGenerationKind.PORTFOLIO
|
|
588
|
+
else None
|
|
589
|
+
)
|
|
590
|
+
|
|
591
|
+
@property
|
|
592
|
+
def planned_candidate_evaluations(self) -> int:
|
|
593
|
+
return sum(step.planned_candidate_evaluations for step in self.steps)
|
|
594
|
+
|
|
595
|
+
@property
|
|
596
|
+
def planned_agent_calls(self) -> int:
|
|
597
|
+
return sum(step.planned_agent_calls for step in self.steps)
|
|
598
|
+
|
|
599
|
+
def to_record(self) -> dict[str, object]:
|
|
600
|
+
self.__post_init__()
|
|
601
|
+
return {
|
|
602
|
+
"schema_version": 1,
|
|
603
|
+
"protocol_sha256": self.protocol_sha256,
|
|
604
|
+
"cadence": {
|
|
605
|
+
"policy_id": self.cadence_policy_id,
|
|
606
|
+
"policy_version": self.cadence_policy_version,
|
|
607
|
+
"definition_sha256": self.cadence_definition_sha256,
|
|
608
|
+
},
|
|
609
|
+
"steps": [step.to_record() for step in self.steps],
|
|
610
|
+
"portfolio_generations": list(self.portfolio_generations),
|
|
611
|
+
"recombination_pairs": [
|
|
612
|
+
pair.to_record() for pair in self.recombination_pairs
|
|
613
|
+
],
|
|
614
|
+
"paired_recombination_generations": list(
|
|
615
|
+
self.paired_recombination_generations
|
|
616
|
+
),
|
|
617
|
+
"reflection_waves": [value.to_record() for value in self.reflection_waves],
|
|
618
|
+
"promotion_barriers": [
|
|
619
|
+
value.to_record() for value in self.promotion_barriers
|
|
620
|
+
],
|
|
621
|
+
"unpaired_terminal_portfolio_generation": (
|
|
622
|
+
self.unpaired_terminal_portfolio_generation
|
|
623
|
+
),
|
|
624
|
+
"planned_candidate_evaluations": self.planned_candidate_evaluations,
|
|
625
|
+
"planned_agent_calls": self.planned_agent_calls,
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
@property
|
|
629
|
+
def schedule_sha256(self) -> str:
|
|
630
|
+
return _hash(_SCHEDULE_DOMAIN, self.to_record())
|
|
631
|
+
|
|
632
|
+
|
|
633
|
+
@runtime_checkable
|
|
634
|
+
class CampaignCadence(Protocol):
|
|
635
|
+
policy_id: str
|
|
636
|
+
policy_version: int
|
|
637
|
+
definition_sha256: str
|
|
638
|
+
|
|
639
|
+
def build(self, protocol: CampaignProtocol) -> CampaignSchedule: ...
|
|
640
|
+
|
|
641
|
+
|
|
642
|
+
def _build_alternating_schedule(
|
|
643
|
+
protocol: CampaignProtocol,
|
|
644
|
+
*,
|
|
645
|
+
cadence_policy_id: str,
|
|
646
|
+
cadence_policy_version: int,
|
|
647
|
+
cadence_definition_sha256: str,
|
|
648
|
+
reflection_waves: tuple[CampaignReflectionWave, ...],
|
|
649
|
+
promotion_barriers: tuple[CampaignPromotionBarrier, ...],
|
|
650
|
+
) -> CampaignSchedule:
|
|
651
|
+
"""Materialize common P/R stage accounting around a reflection policy."""
|
|
652
|
+
|
|
653
|
+
reflection_generation_set = {
|
|
654
|
+
value.source_generation for value in reflection_waves
|
|
655
|
+
}
|
|
656
|
+
steps = tuple(
|
|
657
|
+
CampaignGenerationStep(
|
|
658
|
+
generation=generation,
|
|
659
|
+
kind=(
|
|
660
|
+
CampaignGenerationKind.PORTFOLIO
|
|
661
|
+
if generation % 2 == 1
|
|
662
|
+
else CampaignGenerationKind.RECOMBINATION
|
|
663
|
+
),
|
|
664
|
+
source_portfolio_generation=(
|
|
665
|
+
None if generation % 2 == 1 else generation - 1
|
|
666
|
+
),
|
|
667
|
+
parent_count=protocol.parents_per_portfolio_generation,
|
|
668
|
+
offspring_per_parent=(
|
|
669
|
+
protocol.portfolio_width
|
|
670
|
+
if generation % 2 == 1
|
|
671
|
+
else protocol.recombinations_per_parent
|
|
672
|
+
),
|
|
673
|
+
planned_agent_calls=(
|
|
674
|
+
protocol.parents_per_portfolio_generation
|
|
675
|
+
if generation % 2 == 1
|
|
676
|
+
else (
|
|
677
|
+
protocol.reflections_per_recombination_generation
|
|
678
|
+
if generation in reflection_generation_set
|
|
679
|
+
else 0
|
|
680
|
+
)
|
|
681
|
+
),
|
|
682
|
+
)
|
|
683
|
+
for generation in range(1, protocol.generation_count + 1)
|
|
684
|
+
)
|
|
685
|
+
pairs = tuple(
|
|
686
|
+
CampaignGenerationPair(generation, generation + 1)
|
|
687
|
+
for generation in range(1, protocol.generation_count, 2)
|
|
688
|
+
)
|
|
689
|
+
return CampaignSchedule(
|
|
690
|
+
protocol_sha256=protocol.protocol_sha256,
|
|
691
|
+
cadence_policy_id=cadence_policy_id,
|
|
692
|
+
cadence_policy_version=cadence_policy_version,
|
|
693
|
+
cadence_definition_sha256=cadence_definition_sha256,
|
|
694
|
+
steps=steps,
|
|
695
|
+
recombination_pairs=pairs,
|
|
696
|
+
reflection_waves=reflection_waves,
|
|
697
|
+
promotion_barriers=promotion_barriers,
|
|
698
|
+
)
|
|
699
|
+
|
|
700
|
+
|
|
701
|
+
@dataclass(frozen=True, slots=True)
|
|
702
|
+
class AlternatingPortfolioRecombinationCadence:
|
|
703
|
+
"""Portfolio-first cadence for three through twenty-four generations."""
|
|
704
|
+
|
|
705
|
+
policy_id: str = field(init=False, default=ALTERNATING_CADENCE_POLICY_ID)
|
|
706
|
+
policy_version: int = field(
|
|
707
|
+
init=False,
|
|
708
|
+
default=ALTERNATING_CADENCE_POLICY_VERSION,
|
|
709
|
+
)
|
|
710
|
+
definition_sha256: str = field(
|
|
711
|
+
init=False,
|
|
712
|
+
default=ALTERNATING_CADENCE_DEFINITION_SHA256,
|
|
713
|
+
)
|
|
714
|
+
|
|
715
|
+
def __post_init__(self) -> None:
|
|
716
|
+
if (
|
|
717
|
+
self.policy_id != ALTERNATING_CADENCE_POLICY_ID
|
|
718
|
+
or self.policy_version != ALTERNATING_CADENCE_POLICY_VERSION
|
|
719
|
+
or self.definition_sha256 != ALTERNATING_CADENCE_DEFINITION_SHA256
|
|
720
|
+
):
|
|
721
|
+
raise ValueError("alternating cadence policy identity changed")
|
|
722
|
+
|
|
723
|
+
def build(self, protocol: CampaignProtocol) -> CampaignSchedule:
|
|
724
|
+
if type(protocol) is not CampaignProtocol:
|
|
725
|
+
raise TypeError("protocol must be an exact CampaignProtocol")
|
|
726
|
+
CampaignProtocol.__post_init__(protocol)
|
|
727
|
+
paired_recombination_generations = tuple(
|
|
728
|
+
range(2, protocol.generation_count + 1, 2)
|
|
729
|
+
)
|
|
730
|
+
candidate_reflection_generations = tuple(
|
|
731
|
+
generation
|
|
732
|
+
for generation in paired_recombination_generations
|
|
733
|
+
if (
|
|
734
|
+
protocol.reflections_per_recombination_generation > 0
|
|
735
|
+
and (
|
|
736
|
+
protocol.terminal_reflection_policy
|
|
737
|
+
is TerminalReflectionPolicy.ALLOW_TERMINAL
|
|
738
|
+
or generation < protocol.generation_count
|
|
739
|
+
)
|
|
740
|
+
)
|
|
741
|
+
)
|
|
742
|
+
block_size = protocol.reflection_promotion_block_pairs
|
|
743
|
+
barrier_by_source: dict[int, int] = {}
|
|
744
|
+
promotion_barriers: list[CampaignPromotionBarrier] = []
|
|
745
|
+
if protocol.reflections_per_recombination_generation > 0:
|
|
746
|
+
for start in range(
|
|
747
|
+
0,
|
|
748
|
+
len(candidate_reflection_generations),
|
|
749
|
+
block_size,
|
|
750
|
+
):
|
|
751
|
+
block = candidate_reflection_generations[start : start + block_size]
|
|
752
|
+
if len(block) != block_size:
|
|
753
|
+
continue
|
|
754
|
+
barrier = block[-1]
|
|
755
|
+
promotion_barriers.append(
|
|
756
|
+
CampaignPromotionBarrier(
|
|
757
|
+
generation=barrier,
|
|
758
|
+
reflection_source_generations=block,
|
|
759
|
+
)
|
|
760
|
+
)
|
|
761
|
+
barrier_by_source.update({source: barrier for source in block})
|
|
762
|
+
reflection_generations = candidate_reflection_generations
|
|
763
|
+
if (
|
|
764
|
+
protocol.terminal_reflection_policy
|
|
765
|
+
is TerminalReflectionPolicy.REQUIRE_FUTURE_PORTFOLIO_CONSUMER
|
|
766
|
+
):
|
|
767
|
+
# Merely occurring before a later portfolio is insufficient. If a
|
|
768
|
+
# cohort is incomplete, no reflection in it can cross a promotion
|
|
769
|
+
# barrier, so none has an actual consumer under this policy.
|
|
770
|
+
reflection_generations = tuple(
|
|
771
|
+
generation
|
|
772
|
+
for generation in reflection_generations
|
|
773
|
+
if generation in barrier_by_source
|
|
774
|
+
and barrier_by_source[generation] + 1
|
|
775
|
+
<= protocol.generation_count
|
|
776
|
+
)
|
|
777
|
+
retained = set(reflection_generations)
|
|
778
|
+
promotion_barriers = [
|
|
779
|
+
barrier
|
|
780
|
+
for barrier in promotion_barriers
|
|
781
|
+
if set(barrier.reflection_source_generations) <= retained
|
|
782
|
+
]
|
|
783
|
+
reflection_waves = tuple(
|
|
784
|
+
CampaignReflectionWave(
|
|
785
|
+
source_generation=generation,
|
|
786
|
+
call_count=protocol.reflections_per_recombination_generation,
|
|
787
|
+
launch_mode=ReflectionLaunchMode.ASYNC_AFTER_STAGE_SEAL,
|
|
788
|
+
visibility=ReflectionVisibility.QUARANTINED_UNTIL_BLOCK_CLOSE,
|
|
789
|
+
promotion_barrier_generation=barrier_by_source.get(generation),
|
|
790
|
+
)
|
|
791
|
+
for generation in reflection_generations
|
|
792
|
+
)
|
|
793
|
+
return _build_alternating_schedule(
|
|
794
|
+
protocol,
|
|
795
|
+
cadence_policy_id=self.policy_id,
|
|
796
|
+
cadence_policy_version=self.policy_version,
|
|
797
|
+
cadence_definition_sha256=self.definition_sha256,
|
|
798
|
+
reflection_waves=reflection_waves,
|
|
799
|
+
promotion_barriers=tuple(promotion_barriers),
|
|
800
|
+
)
|
|
801
|
+
|
|
802
|
+
|
|
803
|
+
@dataclass(frozen=True, slots=True)
|
|
804
|
+
class SealedCutoffDelayedAdmissionCadence:
|
|
805
|
+
"""P/R cadence with one-pair-lagged, prospectively useful reflection.
|
|
806
|
+
|
|
807
|
+
A reflection sourced from sealed recombination generation ``g`` is allowed
|
|
808
|
+
to run while generations ``g+1`` and ``g+2`` execute. Its join barrier is
|
|
809
|
+
the seal of recombination generation ``g+2`` and its first possible
|
|
810
|
+
consumer is portfolio generation ``g+3``. Sources without that exact
|
|
811
|
+
future consumer are never launched.
|
|
812
|
+
|
|
813
|
+
This is a separate injected cadence rather than a flag on the legacy
|
|
814
|
+
cadence, preserving the existing default schedule and policy identity.
|
|
815
|
+
"""
|
|
816
|
+
|
|
817
|
+
policy_id: str = field(
|
|
818
|
+
init=False,
|
|
819
|
+
default=SEALED_CUTOFF_DELAYED_CADENCE_POLICY_ID,
|
|
820
|
+
)
|
|
821
|
+
policy_version: int = field(
|
|
822
|
+
init=False,
|
|
823
|
+
default=SEALED_CUTOFF_DELAYED_CADENCE_POLICY_VERSION,
|
|
824
|
+
)
|
|
825
|
+
definition_sha256: str = field(
|
|
826
|
+
init=False,
|
|
827
|
+
default=SEALED_CUTOFF_DELAYED_CADENCE_DEFINITION_SHA256,
|
|
828
|
+
)
|
|
829
|
+
|
|
830
|
+
def __post_init__(self) -> None:
|
|
831
|
+
if (
|
|
832
|
+
self.policy_id != SEALED_CUTOFF_DELAYED_CADENCE_POLICY_ID
|
|
833
|
+
or self.policy_version != SEALED_CUTOFF_DELAYED_CADENCE_POLICY_VERSION
|
|
834
|
+
or self.definition_sha256
|
|
835
|
+
!= SEALED_CUTOFF_DELAYED_CADENCE_DEFINITION_SHA256
|
|
836
|
+
):
|
|
837
|
+
raise ValueError("sealed-cutoff delayed cadence identity changed")
|
|
838
|
+
|
|
839
|
+
def build(self, protocol: CampaignProtocol) -> CampaignSchedule:
|
|
840
|
+
if type(protocol) is not CampaignProtocol:
|
|
841
|
+
raise TypeError("protocol must be an exact CampaignProtocol")
|
|
842
|
+
CampaignProtocol.__post_init__(protocol)
|
|
843
|
+
if protocol.terminal_reflection_policy is not (
|
|
844
|
+
TerminalReflectionPolicy.REQUIRE_FUTURE_PORTFOLIO_CONSUMER
|
|
845
|
+
):
|
|
846
|
+
raise ValueError(
|
|
847
|
+
"delayed admission requires the future-consumer terminal policy"
|
|
848
|
+
)
|
|
849
|
+
if protocol.reflection_promotion_block_pairs != 1:
|
|
850
|
+
raise ValueError(
|
|
851
|
+
"delayed admission requires one reflection per admission barrier"
|
|
852
|
+
)
|
|
853
|
+
|
|
854
|
+
sources = tuple(
|
|
855
|
+
generation
|
|
856
|
+
for generation in range(2, protocol.generation_count + 1, 2)
|
|
857
|
+
if (
|
|
858
|
+
protocol.reflections_per_recombination_generation > 0
|
|
859
|
+
and generation + 3 <= protocol.generation_count
|
|
860
|
+
)
|
|
861
|
+
)
|
|
862
|
+
barriers = tuple(
|
|
863
|
+
CampaignPromotionBarrier(
|
|
864
|
+
generation=source + 2,
|
|
865
|
+
reflection_source_generations=(source,),
|
|
866
|
+
)
|
|
867
|
+
for source in sources
|
|
868
|
+
)
|
|
869
|
+
waves = tuple(
|
|
870
|
+
CampaignReflectionWave(
|
|
871
|
+
source_generation=source,
|
|
872
|
+
call_count=protocol.reflections_per_recombination_generation,
|
|
873
|
+
launch_mode=ReflectionLaunchMode.ASYNC_AFTER_STAGE_SEAL,
|
|
874
|
+
visibility=ReflectionVisibility.QUARANTINED_UNTIL_BLOCK_CLOSE,
|
|
875
|
+
promotion_barrier_generation=source + 2,
|
|
876
|
+
)
|
|
877
|
+
for source in sources
|
|
878
|
+
)
|
|
879
|
+
return _build_alternating_schedule(
|
|
880
|
+
protocol,
|
|
881
|
+
cadence_policy_id=self.policy_id,
|
|
882
|
+
cadence_policy_version=self.policy_version,
|
|
883
|
+
cadence_definition_sha256=self.definition_sha256,
|
|
884
|
+
reflection_waves=waves,
|
|
885
|
+
promotion_barriers=barriers,
|
|
886
|
+
)
|
|
887
|
+
|
|
888
|
+
|
|
889
|
+
@dataclass(frozen=True, slots=True)
|
|
890
|
+
class CampaignConcurrency:
|
|
891
|
+
"""Independent evaluator and agent-queue concurrency caps."""
|
|
892
|
+
|
|
893
|
+
evaluator_concurrency: int
|
|
894
|
+
agent_concurrency: int
|
|
895
|
+
agent_queue_capacity: int
|
|
896
|
+
|
|
897
|
+
def __post_init__(self) -> None:
|
|
898
|
+
for name in ("evaluator_concurrency", "agent_concurrency"):
|
|
899
|
+
value = getattr(self, name)
|
|
900
|
+
if type(value) is not int or value <= 0:
|
|
901
|
+
raise ValueError(f"{name} must be a positive exact integer")
|
|
902
|
+
if (
|
|
903
|
+
type(self.agent_queue_capacity) is not int
|
|
904
|
+
or self.agent_queue_capacity < self.agent_concurrency
|
|
905
|
+
):
|
|
906
|
+
raise ValueError("agent_queue_capacity must be at least agent_concurrency")
|
|
907
|
+
|
|
908
|
+
def to_record(self) -> dict[str, int]:
|
|
909
|
+
self.__post_init__()
|
|
910
|
+
return {
|
|
911
|
+
"evaluator_concurrency": self.evaluator_concurrency,
|
|
912
|
+
"agent_concurrency": self.agent_concurrency,
|
|
913
|
+
"agent_queue_capacity": self.agent_queue_capacity,
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
@property
|
|
917
|
+
def concurrency_sha256(self) -> str:
|
|
918
|
+
return _hash(_CONCURRENCY_DOMAIN, self.to_record())
|
|
919
|
+
|
|
920
|
+
|
|
921
|
+
@dataclass(frozen=True, slots=True)
|
|
922
|
+
class BenchmarkSessionRequest:
|
|
923
|
+
"""Workload-neutral request passed to a benchmark session port."""
|
|
924
|
+
|
|
925
|
+
protocol_sha256: str
|
|
926
|
+
budget_sha256: str
|
|
927
|
+
outer_seed: int
|
|
928
|
+
requested_evaluator_concurrency: int
|
|
929
|
+
|
|
930
|
+
def __post_init__(self) -> None:
|
|
931
|
+
require_sha256(self.protocol_sha256, "protocol_sha256")
|
|
932
|
+
require_sha256(self.budget_sha256, "budget_sha256")
|
|
933
|
+
if type(self.outer_seed) is not int or not -(1 << 127) <= self.outer_seed < (
|
|
934
|
+
1 << 127
|
|
935
|
+
):
|
|
936
|
+
raise ValueError("outer_seed must be an exact signed int128")
|
|
937
|
+
if (
|
|
938
|
+
type(self.requested_evaluator_concurrency) is not int
|
|
939
|
+
or self.requested_evaluator_concurrency <= 0
|
|
940
|
+
):
|
|
941
|
+
raise ValueError("requested evaluator concurrency must be positive")
|
|
942
|
+
|
|
943
|
+
def to_record(self) -> dict[str, object]:
|
|
944
|
+
self.__post_init__()
|
|
945
|
+
return {
|
|
946
|
+
"protocol_sha256": self.protocol_sha256,
|
|
947
|
+
"budget_sha256": self.budget_sha256,
|
|
948
|
+
"outer_seed": self.outer_seed,
|
|
949
|
+
"requested_evaluator_concurrency": (self.requested_evaluator_concurrency),
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
@property
|
|
953
|
+
def request_sha256(self) -> str:
|
|
954
|
+
return _hash(_SESSION_REQUEST_DOMAIN, self.to_record())
|
|
955
|
+
|
|
956
|
+
|
|
957
|
+
@dataclass(frozen=True, slots=True)
|
|
958
|
+
class CampaignBenchmarkSession:
|
|
959
|
+
"""Benchmark facts and acquired-resource evidence from preflight."""
|
|
960
|
+
|
|
961
|
+
request_sha256: str
|
|
962
|
+
benchmark: FrozenJsonObject
|
|
963
|
+
evaluator_concurrency_cap: int
|
|
964
|
+
preflight_receipt: FrozenJsonObject
|
|
965
|
+
resource_lease: FrozenJsonObject
|
|
966
|
+
|
|
967
|
+
def __post_init__(self) -> None:
|
|
968
|
+
require_sha256(self.request_sha256, "request_sha256")
|
|
969
|
+
_validate_frozen_object(self.benchmark, name="benchmark")
|
|
970
|
+
_validate_frozen_object(self.preflight_receipt, name="preflight_receipt")
|
|
971
|
+
_validate_frozen_object(self.resource_lease, name="resource_lease")
|
|
972
|
+
if (
|
|
973
|
+
type(self.evaluator_concurrency_cap) is not int
|
|
974
|
+
or self.evaluator_concurrency_cap <= 0
|
|
975
|
+
):
|
|
976
|
+
raise ValueError("evaluator_concurrency_cap must be positive")
|
|
977
|
+
|
|
978
|
+
def to_record(self) -> dict[str, object]:
|
|
979
|
+
self.__post_init__()
|
|
980
|
+
return {
|
|
981
|
+
"request_sha256": self.request_sha256,
|
|
982
|
+
"benchmark": thaw_json(self.benchmark),
|
|
983
|
+
"benchmark_sha256": typed_json_sha256(self.benchmark),
|
|
984
|
+
"evaluator_concurrency_cap": self.evaluator_concurrency_cap,
|
|
985
|
+
"preflight_receipt": thaw_json(self.preflight_receipt),
|
|
986
|
+
"resource_lease": thaw_json(self.resource_lease),
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
@property
|
|
990
|
+
def session_sha256(self) -> str:
|
|
991
|
+
return _hash(_SESSION_DOMAIN, self.to_record())
|
|
992
|
+
|
|
993
|
+
|
|
994
|
+
@dataclass(frozen=True, slots=True)
|
|
995
|
+
class CampaignSeed:
|
|
996
|
+
"""One labelled, immutable benchmark seed configuration."""
|
|
997
|
+
|
|
998
|
+
seed_id: str
|
|
999
|
+
configuration: FrozenJsonObject
|
|
1000
|
+
|
|
1001
|
+
def __post_init__(self) -> None:
|
|
1002
|
+
_validate_token(self.seed_id, name="seed_id")
|
|
1003
|
+
_validate_frozen_object(self.configuration, name="configuration")
|
|
1004
|
+
|
|
1005
|
+
@property
|
|
1006
|
+
def configuration_sha256(self) -> str:
|
|
1007
|
+
return typed_json_sha256(self.configuration)
|
|
1008
|
+
|
|
1009
|
+
def to_record(self) -> dict[str, object]:
|
|
1010
|
+
self.__post_init__()
|
|
1011
|
+
record = {
|
|
1012
|
+
"seed_id": self.seed_id,
|
|
1013
|
+
"configuration_sha256": self.configuration_sha256,
|
|
1014
|
+
}
|
|
1015
|
+
return {**record, "seed_sha256": _hash(_SEED_DOMAIN, record)}
|
|
1016
|
+
|
|
1017
|
+
|
|
1018
|
+
@dataclass(frozen=True, slots=True)
|
|
1019
|
+
class CampaignSeedBatch:
|
|
1020
|
+
"""Exact seed batch bound to one opened benchmark session."""
|
|
1021
|
+
|
|
1022
|
+
session_sha256: str
|
|
1023
|
+
seeds: tuple[CampaignSeed, ...]
|
|
1024
|
+
|
|
1025
|
+
def __post_init__(self) -> None:
|
|
1026
|
+
require_sha256(self.session_sha256, "session_sha256")
|
|
1027
|
+
if type(self.seeds) is not tuple or not self.seeds:
|
|
1028
|
+
raise ValueError("seeds must be a non-empty exact tuple")
|
|
1029
|
+
if any(type(seed) is not CampaignSeed for seed in self.seeds):
|
|
1030
|
+
raise TypeError("seeds must contain exact CampaignSeed values")
|
|
1031
|
+
for seed in self.seeds:
|
|
1032
|
+
CampaignSeed.__post_init__(seed)
|
|
1033
|
+
if len({seed.seed_id for seed in self.seeds}) != len(self.seeds):
|
|
1034
|
+
raise ValueError("seed IDs must be unique")
|
|
1035
|
+
if len({seed.configuration_sha256 for seed in self.seeds}) != len(self.seeds):
|
|
1036
|
+
raise ValueError("seed configurations must be unique")
|
|
1037
|
+
|
|
1038
|
+
def to_record(self) -> dict[str, object]:
|
|
1039
|
+
self.__post_init__()
|
|
1040
|
+
return {
|
|
1041
|
+
"session_sha256": self.session_sha256,
|
|
1042
|
+
"seeds": [seed.to_record() for seed in self.seeds],
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
@property
|
|
1046
|
+
def batch_sha256(self) -> str:
|
|
1047
|
+
return _hash(_SEED_BATCH_DOMAIN, self.to_record())
|
|
1048
|
+
|
|
1049
|
+
|
|
1050
|
+
@dataclass(frozen=True, slots=True)
|
|
1051
|
+
class ParentVariationBinding:
|
|
1052
|
+
"""Catalog result sealed to one benchmark, parent, and novelty cutoff."""
|
|
1053
|
+
|
|
1054
|
+
benchmark_sha256: str
|
|
1055
|
+
parent_configuration_sha256: str
|
|
1056
|
+
known_phenotype_sha256s: tuple[str, ...]
|
|
1057
|
+
contract: FiniteVariationContract
|
|
1058
|
+
eligibility_receipt: FiniteVariationEligibilityReceipt | None = None
|
|
1059
|
+
|
|
1060
|
+
def __post_init__(self) -> None:
|
|
1061
|
+
require_sha256(self.benchmark_sha256, "benchmark_sha256")
|
|
1062
|
+
require_sha256(
|
|
1063
|
+
self.parent_configuration_sha256,
|
|
1064
|
+
"parent_configuration_sha256",
|
|
1065
|
+
)
|
|
1066
|
+
if type(self.known_phenotype_sha256s) is not tuple or any(
|
|
1067
|
+
type(value) is not str for value in self.known_phenotype_sha256s
|
|
1068
|
+
):
|
|
1069
|
+
raise TypeError("known_phenotype_sha256s must be an exact string tuple")
|
|
1070
|
+
for value in self.known_phenotype_sha256s:
|
|
1071
|
+
require_sha256(value, "known_phenotype_sha256")
|
|
1072
|
+
if self.known_phenotype_sha256s != tuple(
|
|
1073
|
+
sorted(set(self.known_phenotype_sha256s))
|
|
1074
|
+
):
|
|
1075
|
+
raise ValueError("known_phenotype_sha256s must be unique and canonical")
|
|
1076
|
+
if type(self.contract) is not FiniteVariationContract:
|
|
1077
|
+
raise TypeError("contract must be an exact FiniteVariationContract")
|
|
1078
|
+
validate_finite_variation_contract(self.contract)
|
|
1079
|
+
if (
|
|
1080
|
+
typed_json_sha256(self.contract.parent_configuration)
|
|
1081
|
+
!= self.parent_configuration_sha256
|
|
1082
|
+
):
|
|
1083
|
+
raise ValueError("finite contract is bound to a different parent")
|
|
1084
|
+
if self.eligibility_receipt is not None:
|
|
1085
|
+
if type(self.eligibility_receipt) is not FiniteVariationEligibilityReceipt:
|
|
1086
|
+
raise TypeError("eligibility_receipt must be exact or None")
|
|
1087
|
+
FiniteVariationEligibilityReceipt.__post_init__(self.eligibility_receipt)
|
|
1088
|
+
if (
|
|
1089
|
+
self.eligibility_receipt.eligible_contract_identity_sha256
|
|
1090
|
+
!= self.contract.identity_sha256
|
|
1091
|
+
):
|
|
1092
|
+
raise ValueError("finite contract differs from its eligibility receipt")
|
|
1093
|
+
if (
|
|
1094
|
+
self.eligibility_receipt.known_phenotype_sha256s
|
|
1095
|
+
!= self.known_phenotype_sha256s
|
|
1096
|
+
):
|
|
1097
|
+
raise ValueError("eligibility receipt differs from the novelty cutoff")
|
|
1098
|
+
|
|
1099
|
+
def to_record(self) -> dict[str, object]:
|
|
1100
|
+
self.__post_init__()
|
|
1101
|
+
return {
|
|
1102
|
+
"benchmark_sha256": self.benchmark_sha256,
|
|
1103
|
+
"parent_configuration_sha256": self.parent_configuration_sha256,
|
|
1104
|
+
"known_phenotype_sha256s": list(self.known_phenotype_sha256s),
|
|
1105
|
+
"finite_variation_contract_sha256": self.contract.identity_sha256,
|
|
1106
|
+
"eligibility_receipt": (
|
|
1107
|
+
None
|
|
1108
|
+
if self.eligibility_receipt is None
|
|
1109
|
+
else self.eligibility_receipt.to_record()
|
|
1110
|
+
),
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
|
|
1114
|
+
def _port_record(port: object, *, role: str) -> dict[str, object]:
|
|
1115
|
+
for name in ("port_id", "port_version", "definition_sha256"):
|
|
1116
|
+
if not hasattr(port, name):
|
|
1117
|
+
raise TypeError(f"{role} port is missing {name}")
|
|
1118
|
+
port_id = getattr(port, "port_id")
|
|
1119
|
+
port_version = getattr(port, "port_version")
|
|
1120
|
+
definition_sha256 = getattr(port, "definition_sha256")
|
|
1121
|
+
_validate_token(port_id, name=f"{role}.port_id")
|
|
1122
|
+
if type(port_version) is not int or port_version <= 0:
|
|
1123
|
+
raise ValueError(f"{role}.port_version must be positive")
|
|
1124
|
+
require_sha256(definition_sha256, f"{role}.definition_sha256")
|
|
1125
|
+
return {
|
|
1126
|
+
"role": role,
|
|
1127
|
+
"port_id": port_id,
|
|
1128
|
+
"port_version": port_version,
|
|
1129
|
+
"definition_sha256": definition_sha256,
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
|
|
1133
|
+
@runtime_checkable
|
|
1134
|
+
class CampaignBenchmarkPort(Protocol):
|
|
1135
|
+
port_id: str
|
|
1136
|
+
port_version: int
|
|
1137
|
+
definition_sha256: str
|
|
1138
|
+
|
|
1139
|
+
def open(self, request: BenchmarkSessionRequest) -> CampaignBenchmarkSession: ...
|
|
1140
|
+
|
|
1141
|
+
|
|
1142
|
+
@runtime_checkable
|
|
1143
|
+
class CampaignSeedPort(Protocol):
|
|
1144
|
+
port_id: str
|
|
1145
|
+
port_version: int
|
|
1146
|
+
definition_sha256: str
|
|
1147
|
+
|
|
1148
|
+
def load(self, session: CampaignBenchmarkSession) -> CampaignSeedBatch: ...
|
|
1149
|
+
|
|
1150
|
+
|
|
1151
|
+
@runtime_checkable
|
|
1152
|
+
class CampaignCatalogPort(Protocol):
|
|
1153
|
+
port_id: str
|
|
1154
|
+
port_version: int
|
|
1155
|
+
definition_sha256: str
|
|
1156
|
+
|
|
1157
|
+
def bind(
|
|
1158
|
+
self,
|
|
1159
|
+
benchmark: FrozenJsonObject,
|
|
1160
|
+
parent: FrozenJsonObject,
|
|
1161
|
+
known_phenotype_sha256s: tuple[str, ...],
|
|
1162
|
+
) -> ParentVariationBinding: ...
|
|
1163
|
+
|
|
1164
|
+
|
|
1165
|
+
@runtime_checkable
|
|
1166
|
+
class CampaignEvidencePort(Protocol):
|
|
1167
|
+
port_id: str
|
|
1168
|
+
port_version: int
|
|
1169
|
+
definition_sha256: str
|
|
1170
|
+
|
|
1171
|
+
def initialize_memory(
|
|
1172
|
+
self,
|
|
1173
|
+
session: CampaignBenchmarkSession,
|
|
1174
|
+
seeds: CampaignSeedBatch,
|
|
1175
|
+
) -> FrozenJsonObject: ...
|
|
1176
|
+
|
|
1177
|
+
def context(
|
|
1178
|
+
self,
|
|
1179
|
+
session: CampaignBenchmarkSession,
|
|
1180
|
+
parent: FrozenJsonObject,
|
|
1181
|
+
variation: ParentVariationBinding,
|
|
1182
|
+
memory: FrozenJsonObject,
|
|
1183
|
+
) -> FrozenJsonObject: ...
|
|
1184
|
+
|
|
1185
|
+
def cards(
|
|
1186
|
+
self,
|
|
1187
|
+
session: CampaignBenchmarkSession,
|
|
1188
|
+
parent: FrozenJsonObject,
|
|
1189
|
+
variation: ParentVariationBinding,
|
|
1190
|
+
memory: FrozenJsonObject,
|
|
1191
|
+
) -> tuple[FrozenJsonObject, ...]: ...
|
|
1192
|
+
|
|
1193
|
+
|
|
1194
|
+
@dataclass(frozen=True, slots=True)
|
|
1195
|
+
class CampaignWorkloadPorts:
|
|
1196
|
+
"""The complete benchmark-owned side of the inversion boundary."""
|
|
1197
|
+
|
|
1198
|
+
benchmark: CampaignBenchmarkPort
|
|
1199
|
+
seeds: CampaignSeedPort
|
|
1200
|
+
catalog: CampaignCatalogPort
|
|
1201
|
+
evidence: CampaignEvidencePort
|
|
1202
|
+
|
|
1203
|
+
def __post_init__(self) -> None:
|
|
1204
|
+
expected = (
|
|
1205
|
+
("benchmark", self.benchmark, CampaignBenchmarkPort),
|
|
1206
|
+
("seeds", self.seeds, CampaignSeedPort),
|
|
1207
|
+
("catalog", self.catalog, CampaignCatalogPort),
|
|
1208
|
+
("evidence", self.evidence, CampaignEvidencePort),
|
|
1209
|
+
)
|
|
1210
|
+
for role, value, contract in expected:
|
|
1211
|
+
if not isinstance(value, contract):
|
|
1212
|
+
raise TypeError(f"{role} must implement its campaign port")
|
|
1213
|
+
_port_record(value, role=role)
|
|
1214
|
+
|
|
1215
|
+
def to_record(self) -> dict[str, object]:
|
|
1216
|
+
self.__post_init__()
|
|
1217
|
+
return {
|
|
1218
|
+
"benchmark": _port_record(self.benchmark, role="benchmark"),
|
|
1219
|
+
"seeds": _port_record(self.seeds, role="seeds"),
|
|
1220
|
+
"catalog": _port_record(self.catalog, role="catalog"),
|
|
1221
|
+
"evidence": _port_record(self.evidence, role="evidence"),
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
@property
|
|
1225
|
+
def ports_sha256(self) -> str:
|
|
1226
|
+
return _hash(_WORKLOAD_PORTS_DOMAIN, self.to_record())
|
|
1227
|
+
|
|
1228
|
+
|
|
1229
|
+
@dataclass(frozen=True, slots=True)
|
|
1230
|
+
class CampaignPolicyBinding:
|
|
1231
|
+
"""An executable policy dependency paired with its public identity."""
|
|
1232
|
+
|
|
1233
|
+
implementation: object = field(repr=False, compare=False)
|
|
1234
|
+
policy_id: str
|
|
1235
|
+
policy_version: int
|
|
1236
|
+
definition_sha256: str
|
|
1237
|
+
|
|
1238
|
+
def __post_init__(self) -> None:
|
|
1239
|
+
if self.implementation is None:
|
|
1240
|
+
raise TypeError("policy implementation cannot be None")
|
|
1241
|
+
_validate_token(self.policy_id, name="policy_id")
|
|
1242
|
+
if type(self.policy_version) is not int or self.policy_version <= 0:
|
|
1243
|
+
raise ValueError("policy_version must be positive")
|
|
1244
|
+
require_sha256(self.definition_sha256, "definition_sha256")
|
|
1245
|
+
|
|
1246
|
+
def to_record(self) -> dict[str, object]:
|
|
1247
|
+
self.__post_init__()
|
|
1248
|
+
return {
|
|
1249
|
+
"policy_id": self.policy_id,
|
|
1250
|
+
"policy_version": self.policy_version,
|
|
1251
|
+
"definition_sha256": self.definition_sha256,
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
|
|
1255
|
+
@dataclass(frozen=True, slots=True)
|
|
1256
|
+
class ArchiveUtilitySnapshot:
|
|
1257
|
+
"""Frozen pre-generation utility/reference receipt for generic credit.
|
|
1258
|
+
|
|
1259
|
+
``snapshot_receipt`` is workload-owned data. It may encode a fixed
|
|
1260
|
+
hypervolume reference, normalization constants, or another archive utility
|
|
1261
|
+
definition, while this core sees only immutable typed JSON and identities.
|
|
1262
|
+
"""
|
|
1263
|
+
|
|
1264
|
+
utility_id: str
|
|
1265
|
+
utility_version: int
|
|
1266
|
+
definition_sha256: str
|
|
1267
|
+
generation: int
|
|
1268
|
+
benchmark_sha256: str
|
|
1269
|
+
archive_sha256: str
|
|
1270
|
+
snapshot_receipt: FrozenJsonObject
|
|
1271
|
+
scalar_utility_hex: str | None = None
|
|
1272
|
+
|
|
1273
|
+
def __post_init__(self) -> None:
|
|
1274
|
+
_validate_token(self.utility_id, name="utility_id")
|
|
1275
|
+
if type(self.utility_version) is not int or self.utility_version <= 0:
|
|
1276
|
+
raise ValueError("utility_version must be positive")
|
|
1277
|
+
require_sha256(self.definition_sha256, "definition_sha256")
|
|
1278
|
+
if type(self.generation) is not int or self.generation <= 0:
|
|
1279
|
+
raise ValueError("utility snapshot generation must be positive")
|
|
1280
|
+
require_sha256(self.benchmark_sha256, "benchmark_sha256")
|
|
1281
|
+
require_sha256(self.archive_sha256, "archive_sha256")
|
|
1282
|
+
_validate_frozen_object(self.snapshot_receipt, name="snapshot_receipt")
|
|
1283
|
+
if self.scalar_utility_hex is not None:
|
|
1284
|
+
if type(self.scalar_utility_hex) is not str:
|
|
1285
|
+
raise TypeError("scalar_utility_hex must be an exact string or None")
|
|
1286
|
+
try:
|
|
1287
|
+
scalar = float.fromhex(self.scalar_utility_hex)
|
|
1288
|
+
except ValueError as error:
|
|
1289
|
+
raise ValueError("scalar_utility_hex must be a float hex string") from error
|
|
1290
|
+
if not math.isfinite(scalar) or scalar < 0.0:
|
|
1291
|
+
raise ValueError(
|
|
1292
|
+
"scalar_utility_hex must encode a finite non-negative utility"
|
|
1293
|
+
)
|
|
1294
|
+
if scalar.hex() != self.scalar_utility_hex:
|
|
1295
|
+
raise ValueError("scalar_utility_hex must be canonical")
|
|
1296
|
+
|
|
1297
|
+
def _unsigned_record(self) -> dict[str, object]:
|
|
1298
|
+
self.__post_init__()
|
|
1299
|
+
record = {
|
|
1300
|
+
"utility_id": self.utility_id,
|
|
1301
|
+
"utility_version": self.utility_version,
|
|
1302
|
+
"definition_sha256": self.definition_sha256,
|
|
1303
|
+
"generation": self.generation,
|
|
1304
|
+
"benchmark_sha256": self.benchmark_sha256,
|
|
1305
|
+
"archive_sha256": self.archive_sha256,
|
|
1306
|
+
"snapshot_receipt": thaw_json(self.snapshot_receipt),
|
|
1307
|
+
}
|
|
1308
|
+
if self.scalar_utility_hex is not None:
|
|
1309
|
+
record["scalar_utility_hex"] = self.scalar_utility_hex
|
|
1310
|
+
return record
|
|
1311
|
+
|
|
1312
|
+
@property
|
|
1313
|
+
def snapshot_sha256(self) -> str:
|
|
1314
|
+
return _hash(_ARCHIVE_UTILITY_SNAPSHOT_DOMAIN, self._unsigned_record())
|
|
1315
|
+
|
|
1316
|
+
def to_record(self) -> dict[str, object]:
|
|
1317
|
+
return {**self._unsigned_record(), "snapshot_sha256": self.snapshot_sha256}
|
|
1318
|
+
|
|
1319
|
+
|
|
1320
|
+
@runtime_checkable
|
|
1321
|
+
class ArchiveUtilityPort(Protocol):
|
|
1322
|
+
"""Freeze one workload-defined archive utility before a generation."""
|
|
1323
|
+
|
|
1324
|
+
utility_id: str
|
|
1325
|
+
utility_version: int
|
|
1326
|
+
definition_sha256: str
|
|
1327
|
+
|
|
1328
|
+
def freeze(
|
|
1329
|
+
self,
|
|
1330
|
+
*,
|
|
1331
|
+
benchmark: FrozenJsonObject,
|
|
1332
|
+
generation: int,
|
|
1333
|
+
archive: FrozenJsonObject,
|
|
1334
|
+
) -> ArchiveUtilitySnapshot: ...
|
|
1335
|
+
|
|
1336
|
+
|
|
1337
|
+
def freeze_archive_utility(
|
|
1338
|
+
port: ArchiveUtilityPort,
|
|
1339
|
+
*,
|
|
1340
|
+
benchmark: FrozenJsonObject,
|
|
1341
|
+
generation: int,
|
|
1342
|
+
archive: FrozenJsonObject,
|
|
1343
|
+
) -> ArchiveUtilitySnapshot:
|
|
1344
|
+
"""Call and authenticate an injected pre-generation utility snapshot."""
|
|
1345
|
+
|
|
1346
|
+
if not isinstance(port, ArchiveUtilityPort):
|
|
1347
|
+
raise TypeError("port must implement ArchiveUtilityPort")
|
|
1348
|
+
_validate_token(port.utility_id, name="archive_utility.utility_id")
|
|
1349
|
+
if type(port.utility_version) is not int or port.utility_version <= 0:
|
|
1350
|
+
raise ValueError("archive utility version must be positive")
|
|
1351
|
+
require_sha256(
|
|
1352
|
+
port.definition_sha256,
|
|
1353
|
+
"archive_utility.definition_sha256",
|
|
1354
|
+
)
|
|
1355
|
+
_validate_frozen_object(benchmark, name="benchmark")
|
|
1356
|
+
_validate_frozen_object(archive, name="archive")
|
|
1357
|
+
if type(generation) is not int or generation <= 0:
|
|
1358
|
+
raise ValueError("generation must be positive")
|
|
1359
|
+
snapshot = port.freeze(
|
|
1360
|
+
benchmark=benchmark,
|
|
1361
|
+
generation=generation,
|
|
1362
|
+
archive=archive,
|
|
1363
|
+
)
|
|
1364
|
+
if type(snapshot) is not ArchiveUtilitySnapshot:
|
|
1365
|
+
raise TypeError("archive utility must return ArchiveUtilitySnapshot")
|
|
1366
|
+
ArchiveUtilitySnapshot.__post_init__(snapshot)
|
|
1367
|
+
if (
|
|
1368
|
+
snapshot.utility_id != port.utility_id
|
|
1369
|
+
or snapshot.utility_version != port.utility_version
|
|
1370
|
+
or snapshot.definition_sha256 != port.definition_sha256
|
|
1371
|
+
):
|
|
1372
|
+
raise ValueError("archive utility snapshot has a foreign definition")
|
|
1373
|
+
if snapshot.generation != generation:
|
|
1374
|
+
raise ValueError("archive utility snapshot has a foreign generation")
|
|
1375
|
+
if snapshot.benchmark_sha256 != typed_json_sha256(benchmark):
|
|
1376
|
+
raise ValueError("archive utility snapshot has a foreign benchmark")
|
|
1377
|
+
if snapshot.archive_sha256 != typed_json_sha256(archive):
|
|
1378
|
+
raise ValueError("archive utility snapshot has a foreign archive cutoff")
|
|
1379
|
+
return snapshot
|
|
1380
|
+
|
|
1381
|
+
|
|
1382
|
+
@dataclass(frozen=True, slots=True)
|
|
1383
|
+
class CampaignPolicies:
|
|
1384
|
+
"""Workload-independent behavioral policies injected into a campaign."""
|
|
1385
|
+
|
|
1386
|
+
cadence: CampaignCadence
|
|
1387
|
+
parent_selection: CampaignPolicyBinding
|
|
1388
|
+
memory_assignment: CampaignPolicyBinding
|
|
1389
|
+
portfolio_selection: CampaignPolicyBinding
|
|
1390
|
+
recombination: CampaignPolicyBinding
|
|
1391
|
+
reflection: CampaignPolicyBinding
|
|
1392
|
+
archive_utility: ArchiveUtilityPort
|
|
1393
|
+
reflection_supervision: CampaignReflectionSupervisionPolicy = field(
|
|
1394
|
+
default_factory=CampaignReflectionSupervisionPolicy
|
|
1395
|
+
)
|
|
1396
|
+
|
|
1397
|
+
def __post_init__(self) -> None:
|
|
1398
|
+
if not isinstance(self.cadence, CampaignCadence):
|
|
1399
|
+
raise TypeError("cadence must implement CampaignCadence")
|
|
1400
|
+
_port_record(
|
|
1401
|
+
_CadencePortView(self.cadence),
|
|
1402
|
+
role="cadence",
|
|
1403
|
+
)
|
|
1404
|
+
for name in (
|
|
1405
|
+
"parent_selection",
|
|
1406
|
+
"memory_assignment",
|
|
1407
|
+
"portfolio_selection",
|
|
1408
|
+
"recombination",
|
|
1409
|
+
"reflection",
|
|
1410
|
+
):
|
|
1411
|
+
value = getattr(self, name)
|
|
1412
|
+
if type(value) is not CampaignPolicyBinding:
|
|
1413
|
+
raise TypeError(f"{name} must be an exact CampaignPolicyBinding")
|
|
1414
|
+
CampaignPolicyBinding.__post_init__(value)
|
|
1415
|
+
if type(self.reflection_supervision) is not CampaignReflectionSupervisionPolicy:
|
|
1416
|
+
raise TypeError(
|
|
1417
|
+
"reflection_supervision must be an exact "
|
|
1418
|
+
"CampaignReflectionSupervisionPolicy"
|
|
1419
|
+
)
|
|
1420
|
+
CampaignReflectionSupervisionPolicy.__post_init__(self.reflection_supervision)
|
|
1421
|
+
if not isinstance(self.archive_utility, ArchiveUtilityPort):
|
|
1422
|
+
raise TypeError("archive_utility must implement ArchiveUtilityPort")
|
|
1423
|
+
_validate_token(
|
|
1424
|
+
self.archive_utility.utility_id,
|
|
1425
|
+
name="archive_utility.utility_id",
|
|
1426
|
+
)
|
|
1427
|
+
if (
|
|
1428
|
+
type(self.archive_utility.utility_version) is not int
|
|
1429
|
+
or self.archive_utility.utility_version <= 0
|
|
1430
|
+
):
|
|
1431
|
+
raise ValueError("archive utility version must be positive")
|
|
1432
|
+
require_sha256(
|
|
1433
|
+
self.archive_utility.definition_sha256,
|
|
1434
|
+
"archive_utility.definition_sha256",
|
|
1435
|
+
)
|
|
1436
|
+
|
|
1437
|
+
def to_record(self) -> dict[str, object]:
|
|
1438
|
+
self.__post_init__()
|
|
1439
|
+
return {
|
|
1440
|
+
"cadence": {
|
|
1441
|
+
"policy_id": self.cadence.policy_id,
|
|
1442
|
+
"policy_version": self.cadence.policy_version,
|
|
1443
|
+
"definition_sha256": self.cadence.definition_sha256,
|
|
1444
|
+
},
|
|
1445
|
+
"parent_selection": self.parent_selection.to_record(),
|
|
1446
|
+
"memory_assignment": self.memory_assignment.to_record(),
|
|
1447
|
+
"portfolio_selection": self.portfolio_selection.to_record(),
|
|
1448
|
+
"recombination": self.recombination.to_record(),
|
|
1449
|
+
"reflection": self.reflection.to_record(),
|
|
1450
|
+
"reflection_supervision": self.reflection_supervision.to_record(),
|
|
1451
|
+
"archive_utility": {
|
|
1452
|
+
"utility_id": self.archive_utility.utility_id,
|
|
1453
|
+
"utility_version": self.archive_utility.utility_version,
|
|
1454
|
+
"definition_sha256": self.archive_utility.definition_sha256,
|
|
1455
|
+
},
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
@property
|
|
1459
|
+
def policies_sha256(self) -> str:
|
|
1460
|
+
return _hash(_POLICIES_DOMAIN, self.to_record())
|
|
1461
|
+
|
|
1462
|
+
|
|
1463
|
+
@dataclass(frozen=True, slots=True)
|
|
1464
|
+
class _CadencePortView:
|
|
1465
|
+
cadence: CampaignCadence
|
|
1466
|
+
|
|
1467
|
+
@property
|
|
1468
|
+
def port_id(self) -> str:
|
|
1469
|
+
return self.cadence.policy_id
|
|
1470
|
+
|
|
1471
|
+
@property
|
|
1472
|
+
def port_version(self) -> int:
|
|
1473
|
+
return self.cadence.policy_version
|
|
1474
|
+
|
|
1475
|
+
@property
|
|
1476
|
+
def definition_sha256(self) -> str:
|
|
1477
|
+
return self.cadence.definition_sha256
|
|
1478
|
+
|
|
1479
|
+
|
|
1480
|
+
@dataclass(frozen=True, slots=True)
|
|
1481
|
+
class CampaignAgentRuntimeRequest:
|
|
1482
|
+
"""Identity-only runtime preflight; it intentionally carries no model config."""
|
|
1483
|
+
|
|
1484
|
+
protocol_sha256: str
|
|
1485
|
+
schedule_sha256: str
|
|
1486
|
+
session_sha256: str
|
|
1487
|
+
seed_batch_sha256: str
|
|
1488
|
+
workload_ports_sha256: str
|
|
1489
|
+
policies_sha256: str
|
|
1490
|
+
budget_sha256: str
|
|
1491
|
+
concurrency_sha256: str
|
|
1492
|
+
|
|
1493
|
+
def __post_init__(self) -> None:
|
|
1494
|
+
for name in (
|
|
1495
|
+
"protocol_sha256",
|
|
1496
|
+
"schedule_sha256",
|
|
1497
|
+
"session_sha256",
|
|
1498
|
+
"seed_batch_sha256",
|
|
1499
|
+
"workload_ports_sha256",
|
|
1500
|
+
"policies_sha256",
|
|
1501
|
+
"budget_sha256",
|
|
1502
|
+
"concurrency_sha256",
|
|
1503
|
+
):
|
|
1504
|
+
require_sha256(getattr(self, name), name)
|
|
1505
|
+
|
|
1506
|
+
def to_record(self) -> dict[str, str]:
|
|
1507
|
+
self.__post_init__()
|
|
1508
|
+
return {
|
|
1509
|
+
"protocol_sha256": self.protocol_sha256,
|
|
1510
|
+
"schedule_sha256": self.schedule_sha256,
|
|
1511
|
+
"session_sha256": self.session_sha256,
|
|
1512
|
+
"seed_batch_sha256": self.seed_batch_sha256,
|
|
1513
|
+
"workload_ports_sha256": self.workload_ports_sha256,
|
|
1514
|
+
"policies_sha256": self.policies_sha256,
|
|
1515
|
+
"budget_sha256": self.budget_sha256,
|
|
1516
|
+
"concurrency_sha256": self.concurrency_sha256,
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
@property
|
|
1520
|
+
def request_sha256(self) -> str:
|
|
1521
|
+
return _hash(_RUNTIME_REQUEST_DOMAIN, self.to_record())
|
|
1522
|
+
|
|
1523
|
+
|
|
1524
|
+
@dataclass(frozen=True, slots=True)
|
|
1525
|
+
class CampaignAgentRuntimeReceipt:
|
|
1526
|
+
"""Runtime-owned evidence that a prepared composition is executable."""
|
|
1527
|
+
|
|
1528
|
+
request_sha256: str
|
|
1529
|
+
runtime_id: str
|
|
1530
|
+
runtime_version: int
|
|
1531
|
+
definition_sha256: str
|
|
1532
|
+
accepted: bool
|
|
1533
|
+
evidence: FrozenJsonObject
|
|
1534
|
+
|
|
1535
|
+
def __post_init__(self) -> None:
|
|
1536
|
+
require_sha256(self.request_sha256, "request_sha256")
|
|
1537
|
+
_validate_token(self.runtime_id, name="runtime_id")
|
|
1538
|
+
if type(self.runtime_version) is not int or self.runtime_version <= 0:
|
|
1539
|
+
raise ValueError("runtime_version must be positive")
|
|
1540
|
+
require_sha256(self.definition_sha256, "definition_sha256")
|
|
1541
|
+
if type(self.accepted) is not bool:
|
|
1542
|
+
raise TypeError("accepted must be an exact bool")
|
|
1543
|
+
_validate_frozen_object(self.evidence, name="evidence")
|
|
1544
|
+
|
|
1545
|
+
def _unsigned_record(self) -> dict[str, object]:
|
|
1546
|
+
return {
|
|
1547
|
+
"request_sha256": self.request_sha256,
|
|
1548
|
+
"runtime_id": self.runtime_id,
|
|
1549
|
+
"runtime_version": self.runtime_version,
|
|
1550
|
+
"definition_sha256": self.definition_sha256,
|
|
1551
|
+
"accepted": self.accepted,
|
|
1552
|
+
"evidence": thaw_json(self.evidence),
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
def to_record(self) -> dict[str, object]:
|
|
1556
|
+
self.__post_init__()
|
|
1557
|
+
record = self._unsigned_record()
|
|
1558
|
+
return {**record, "receipt_sha256": _hash(_RUNTIME_RECEIPT_DOMAIN, record)}
|
|
1559
|
+
|
|
1560
|
+
@property
|
|
1561
|
+
def receipt_sha256(self) -> str:
|
|
1562
|
+
return str(self.to_record()["receipt_sha256"])
|
|
1563
|
+
|
|
1564
|
+
|
|
1565
|
+
@runtime_checkable
|
|
1566
|
+
class CampaignAgentRuntimePort(Protocol):
|
|
1567
|
+
"""Agent/runtime preflight outside the workload adapter boundary."""
|
|
1568
|
+
|
|
1569
|
+
def prepare(
|
|
1570
|
+
self,
|
|
1571
|
+
request: CampaignAgentRuntimeRequest,
|
|
1572
|
+
) -> CampaignAgentRuntimeReceipt: ...
|
|
1573
|
+
|
|
1574
|
+
|
|
1575
|
+
@runtime_checkable
|
|
1576
|
+
class CampaignJournalPort(Protocol):
|
|
1577
|
+
"""Append one immutable campaign-level record."""
|
|
1578
|
+
|
|
1579
|
+
def append(self, record: FrozenJsonObject) -> None: ...
|
|
1580
|
+
|
|
1581
|
+
|
|
1582
|
+
@dataclass(frozen=True, slots=True)
|
|
1583
|
+
class PreparedEvolutionCampaign:
|
|
1584
|
+
"""Complete, replay-identifiable result of campaign preparation."""
|
|
1585
|
+
|
|
1586
|
+
protocol: CampaignProtocol
|
|
1587
|
+
schedule: CampaignSchedule
|
|
1588
|
+
benchmark_session: CampaignBenchmarkSession
|
|
1589
|
+
seeds: CampaignSeedBatch
|
|
1590
|
+
workload_ports_sha256: str
|
|
1591
|
+
policies_sha256: str
|
|
1592
|
+
budget: OptimizerBudget
|
|
1593
|
+
concurrency: CampaignConcurrency
|
|
1594
|
+
runtime_receipt: CampaignAgentRuntimeReceipt
|
|
1595
|
+
|
|
1596
|
+
def __post_init__(self) -> None:
|
|
1597
|
+
if type(self.protocol) is not CampaignProtocol:
|
|
1598
|
+
raise TypeError("protocol must be exact")
|
|
1599
|
+
CampaignProtocol.__post_init__(self.protocol)
|
|
1600
|
+
if type(self.schedule) is not CampaignSchedule:
|
|
1601
|
+
raise TypeError("schedule must be exact")
|
|
1602
|
+
CampaignSchedule.__post_init__(self.schedule)
|
|
1603
|
+
if self.schedule.protocol_sha256 != self.protocol.protocol_sha256:
|
|
1604
|
+
raise ValueError("schedule is bound to a different protocol")
|
|
1605
|
+
if type(self.benchmark_session) is not CampaignBenchmarkSession:
|
|
1606
|
+
raise TypeError("benchmark_session must be exact")
|
|
1607
|
+
CampaignBenchmarkSession.__post_init__(self.benchmark_session)
|
|
1608
|
+
if type(self.seeds) is not CampaignSeedBatch:
|
|
1609
|
+
raise TypeError("seeds must be exact")
|
|
1610
|
+
CampaignSeedBatch.__post_init__(self.seeds)
|
|
1611
|
+
if self.seeds.session_sha256 != self.benchmark_session.session_sha256:
|
|
1612
|
+
raise ValueError("seed batch is bound to a different session")
|
|
1613
|
+
for name in ("workload_ports_sha256", "policies_sha256"):
|
|
1614
|
+
require_sha256(getattr(self, name), name)
|
|
1615
|
+
if type(self.budget) is not OptimizerBudget:
|
|
1616
|
+
raise TypeError("budget must be an exact OptimizerBudget")
|
|
1617
|
+
OptimizerBudget.__post_init__(self.budget)
|
|
1618
|
+
if type(self.concurrency) is not CampaignConcurrency:
|
|
1619
|
+
raise TypeError("concurrency must be exact")
|
|
1620
|
+
CampaignConcurrency.__post_init__(self.concurrency)
|
|
1621
|
+
if type(self.runtime_receipt) is not CampaignAgentRuntimeReceipt:
|
|
1622
|
+
raise TypeError("runtime_receipt must be exact")
|
|
1623
|
+
CampaignAgentRuntimeReceipt.__post_init__(self.runtime_receipt)
|
|
1624
|
+
expected_runtime_request = CampaignAgentRuntimeRequest(
|
|
1625
|
+
protocol_sha256=self.protocol.protocol_sha256,
|
|
1626
|
+
schedule_sha256=self.schedule.schedule_sha256,
|
|
1627
|
+
session_sha256=self.benchmark_session.session_sha256,
|
|
1628
|
+
seed_batch_sha256=self.seeds.batch_sha256,
|
|
1629
|
+
workload_ports_sha256=self.workload_ports_sha256,
|
|
1630
|
+
policies_sha256=self.policies_sha256,
|
|
1631
|
+
budget_sha256=self.budget.budget_hash,
|
|
1632
|
+
concurrency_sha256=self.concurrency.concurrency_sha256,
|
|
1633
|
+
)
|
|
1634
|
+
if (
|
|
1635
|
+
self.runtime_receipt.request_sha256
|
|
1636
|
+
!= expected_runtime_request.request_sha256
|
|
1637
|
+
):
|
|
1638
|
+
raise ValueError("runtime receipt is bound to a different preparation")
|
|
1639
|
+
if not self.runtime_receipt.accepted:
|
|
1640
|
+
raise ValueError("a prepared campaign requires runtime acceptance")
|
|
1641
|
+
|
|
1642
|
+
def _unsigned_record(self) -> dict[str, object]:
|
|
1643
|
+
return {
|
|
1644
|
+
"schema_version": 1,
|
|
1645
|
+
"protocol": self.protocol.to_record(),
|
|
1646
|
+
"protocol_sha256": self.protocol.protocol_sha256,
|
|
1647
|
+
"schedule": self.schedule.to_record(),
|
|
1648
|
+
"schedule_sha256": self.schedule.schedule_sha256,
|
|
1649
|
+
"benchmark_session": self.benchmark_session.to_record(),
|
|
1650
|
+
"benchmark_session_sha256": self.benchmark_session.session_sha256,
|
|
1651
|
+
"seeds": self.seeds.to_record(),
|
|
1652
|
+
"seed_batch_sha256": self.seeds.batch_sha256,
|
|
1653
|
+
"workload_ports_sha256": self.workload_ports_sha256,
|
|
1654
|
+
"policies_sha256": self.policies_sha256,
|
|
1655
|
+
"budget": self.budget.to_trace_record(),
|
|
1656
|
+
"budget_sha256": self.budget.budget_hash,
|
|
1657
|
+
"concurrency": self.concurrency.to_record(),
|
|
1658
|
+
"concurrency_sha256": self.concurrency.concurrency_sha256,
|
|
1659
|
+
"runtime_receipt": self.runtime_receipt.to_record(),
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
def to_record(self) -> dict[str, object]:
|
|
1663
|
+
self.__post_init__()
|
|
1664
|
+
record = self._unsigned_record()
|
|
1665
|
+
return {**record, "preparation_sha256": _hash(_PREPARATION_DOMAIN, record)}
|
|
1666
|
+
|
|
1667
|
+
@property
|
|
1668
|
+
def preparation_sha256(self) -> str:
|
|
1669
|
+
return str(self.to_record()["preparation_sha256"])
|
|
1670
|
+
|
|
1671
|
+
|
|
1672
|
+
@dataclass(frozen=True, slots=True)
|
|
1673
|
+
class EvolutionCampaign:
|
|
1674
|
+
"""Prepare one reusable campaign composition without executing live waves."""
|
|
1675
|
+
|
|
1676
|
+
protocol: CampaignProtocol
|
|
1677
|
+
workload: CampaignWorkloadPorts
|
|
1678
|
+
policies: CampaignPolicies
|
|
1679
|
+
runtime: CampaignAgentRuntimePort
|
|
1680
|
+
budget: OptimizerBudget
|
|
1681
|
+
concurrency: CampaignConcurrency
|
|
1682
|
+
journals: tuple[CampaignJournalPort, ...]
|
|
1683
|
+
|
|
1684
|
+
def __post_init__(self) -> None:
|
|
1685
|
+
if type(self.protocol) is not CampaignProtocol:
|
|
1686
|
+
raise TypeError("protocol must be an exact CampaignProtocol")
|
|
1687
|
+
CampaignProtocol.__post_init__(self.protocol)
|
|
1688
|
+
if type(self.workload) is not CampaignWorkloadPorts:
|
|
1689
|
+
raise TypeError("workload must be exact CampaignWorkloadPorts")
|
|
1690
|
+
CampaignWorkloadPorts.__post_init__(self.workload)
|
|
1691
|
+
if type(self.policies) is not CampaignPolicies:
|
|
1692
|
+
raise TypeError("policies must be exact CampaignPolicies")
|
|
1693
|
+
CampaignPolicies.__post_init__(self.policies)
|
|
1694
|
+
if not isinstance(self.runtime, CampaignAgentRuntimePort):
|
|
1695
|
+
raise TypeError("runtime must implement CampaignAgentRuntimePort")
|
|
1696
|
+
if type(self.budget) is not OptimizerBudget:
|
|
1697
|
+
raise TypeError("budget must be an exact OptimizerBudget")
|
|
1698
|
+
OptimizerBudget.__post_init__(self.budget)
|
|
1699
|
+
if type(self.concurrency) is not CampaignConcurrency:
|
|
1700
|
+
raise TypeError("concurrency must be exact CampaignConcurrency")
|
|
1701
|
+
CampaignConcurrency.__post_init__(self.concurrency)
|
|
1702
|
+
if type(self.journals) is not tuple or not self.journals:
|
|
1703
|
+
raise ValueError("journals must be a non-empty exact tuple")
|
|
1704
|
+
if any(
|
|
1705
|
+
not isinstance(journal, CampaignJournalPort) for journal in self.journals
|
|
1706
|
+
):
|
|
1707
|
+
raise TypeError("every journal must implement CampaignJournalPort")
|
|
1708
|
+
|
|
1709
|
+
def prepare(self) -> PreparedEvolutionCampaign:
|
|
1710
|
+
"""Preflight and seal a campaign; no evolutionary wave runs here."""
|
|
1711
|
+
|
|
1712
|
+
self.__post_init__()
|
|
1713
|
+
schedule = self.policies.cadence.build(self.protocol)
|
|
1714
|
+
if type(schedule) is not CampaignSchedule:
|
|
1715
|
+
raise TypeError("cadence must return an exact CampaignSchedule")
|
|
1716
|
+
CampaignSchedule.__post_init__(schedule)
|
|
1717
|
+
if schedule.protocol_sha256 != self.protocol.protocol_sha256:
|
|
1718
|
+
raise ValueError("cadence returned a schedule for a foreign protocol")
|
|
1719
|
+
|
|
1720
|
+
required_evaluations = (
|
|
1721
|
+
self.protocol.required_seed_count + schedule.planned_candidate_evaluations
|
|
1722
|
+
)
|
|
1723
|
+
if self.budget.max_generations < self.protocol.generation_count:
|
|
1724
|
+
raise ValueError("budget cannot cover the protocol generation count")
|
|
1725
|
+
if self.budget.max_unique_evaluations < required_evaluations:
|
|
1726
|
+
raise ValueError(
|
|
1727
|
+
"budget cannot cover the schedule's maximum candidate evaluations"
|
|
1728
|
+
)
|
|
1729
|
+
if self.budget.max_logical_llm_calls < schedule.planned_agent_calls:
|
|
1730
|
+
raise ValueError("budget cannot cover the schedule's planned agent calls")
|
|
1731
|
+
|
|
1732
|
+
session_request = BenchmarkSessionRequest(
|
|
1733
|
+
protocol_sha256=self.protocol.protocol_sha256,
|
|
1734
|
+
budget_sha256=self.budget.budget_hash,
|
|
1735
|
+
outer_seed=self.protocol.outer_seed,
|
|
1736
|
+
requested_evaluator_concurrency=(self.concurrency.evaluator_concurrency),
|
|
1737
|
+
)
|
|
1738
|
+
session = self.workload.benchmark.open(session_request)
|
|
1739
|
+
if type(session) is not CampaignBenchmarkSession:
|
|
1740
|
+
raise TypeError("benchmark port must return CampaignBenchmarkSession")
|
|
1741
|
+
CampaignBenchmarkSession.__post_init__(session)
|
|
1742
|
+
if session.request_sha256 != session_request.request_sha256:
|
|
1743
|
+
raise ValueError("benchmark session is bound to a foreign request")
|
|
1744
|
+
if self.concurrency.evaluator_concurrency > session.evaluator_concurrency_cap:
|
|
1745
|
+
raise ValueError("requested evaluator concurrency exceeds benchmark cap")
|
|
1746
|
+
|
|
1747
|
+
seeds = self.workload.seeds.load(session)
|
|
1748
|
+
if type(seeds) is not CampaignSeedBatch:
|
|
1749
|
+
raise TypeError("seed port must return CampaignSeedBatch")
|
|
1750
|
+
CampaignSeedBatch.__post_init__(seeds)
|
|
1751
|
+
if seeds.session_sha256 != session.session_sha256:
|
|
1752
|
+
raise ValueError("seed batch is bound to a foreign benchmark session")
|
|
1753
|
+
if len(seeds.seeds) != self.protocol.required_seed_count:
|
|
1754
|
+
raise ValueError("seed batch cardinality differs from the protocol")
|
|
1755
|
+
|
|
1756
|
+
runtime_request = CampaignAgentRuntimeRequest(
|
|
1757
|
+
protocol_sha256=self.protocol.protocol_sha256,
|
|
1758
|
+
schedule_sha256=schedule.schedule_sha256,
|
|
1759
|
+
session_sha256=session.session_sha256,
|
|
1760
|
+
seed_batch_sha256=seeds.batch_sha256,
|
|
1761
|
+
workload_ports_sha256=self.workload.ports_sha256,
|
|
1762
|
+
policies_sha256=self.policies.policies_sha256,
|
|
1763
|
+
budget_sha256=self.budget.budget_hash,
|
|
1764
|
+
concurrency_sha256=self.concurrency.concurrency_sha256,
|
|
1765
|
+
)
|
|
1766
|
+
runtime_receipt = self.runtime.prepare(runtime_request)
|
|
1767
|
+
if type(runtime_receipt) is not CampaignAgentRuntimeReceipt:
|
|
1768
|
+
raise TypeError("runtime must return CampaignAgentRuntimeReceipt")
|
|
1769
|
+
CampaignAgentRuntimeReceipt.__post_init__(runtime_receipt)
|
|
1770
|
+
if runtime_receipt.request_sha256 != runtime_request.request_sha256:
|
|
1771
|
+
raise ValueError("runtime receipt is bound to a foreign request")
|
|
1772
|
+
if not runtime_receipt.accepted:
|
|
1773
|
+
raise ValueError("agent runtime rejected campaign preparation")
|
|
1774
|
+
|
|
1775
|
+
prepared = PreparedEvolutionCampaign(
|
|
1776
|
+
protocol=self.protocol,
|
|
1777
|
+
schedule=schedule,
|
|
1778
|
+
benchmark_session=session,
|
|
1779
|
+
seeds=seeds,
|
|
1780
|
+
workload_ports_sha256=self.workload.ports_sha256,
|
|
1781
|
+
policies_sha256=self.policies.policies_sha256,
|
|
1782
|
+
budget=self.budget,
|
|
1783
|
+
concurrency=self.concurrency,
|
|
1784
|
+
runtime_receipt=runtime_receipt,
|
|
1785
|
+
)
|
|
1786
|
+
journal_record = freeze_json(prepared.to_record())
|
|
1787
|
+
if type(journal_record) is not FrozenJsonObject: # pragma: no cover
|
|
1788
|
+
raise AssertionError("campaign preparation did not freeze to an object")
|
|
1789
|
+
for journal in self.journals:
|
|
1790
|
+
journal.append(journal_record)
|
|
1791
|
+
return prepared
|
|
1792
|
+
|
|
1793
|
+
|
|
1794
|
+
__all__ = [
|
|
1795
|
+
"ALTERNATING_CADENCE_DEFINITION_SHA256",
|
|
1796
|
+
"ALTERNATING_CADENCE_POLICY_ID",
|
|
1797
|
+
"ALTERNATING_CADENCE_POLICY_VERSION",
|
|
1798
|
+
"SEALED_CUTOFF_DELAYED_CADENCE_DEFINITION_SHA256",
|
|
1799
|
+
"SEALED_CUTOFF_DELAYED_CADENCE_POLICY_ID",
|
|
1800
|
+
"SEALED_CUTOFF_DELAYED_CADENCE_POLICY_VERSION",
|
|
1801
|
+
"MAX_CAMPAIGN_GENERATIONS",
|
|
1802
|
+
"MIN_CAMPAIGN_GENERATIONS",
|
|
1803
|
+
"AlternatingPortfolioRecombinationCadence",
|
|
1804
|
+
"ArchiveUtilityPort",
|
|
1805
|
+
"ArchiveUtilitySnapshot",
|
|
1806
|
+
"BenchmarkSessionRequest",
|
|
1807
|
+
"CampaignAgentRuntimePort",
|
|
1808
|
+
"CampaignAgentRuntimeReceipt",
|
|
1809
|
+
"CampaignAgentRuntimeRequest",
|
|
1810
|
+
"CampaignBenchmarkPort",
|
|
1811
|
+
"CampaignBenchmarkSession",
|
|
1812
|
+
"CampaignCadence",
|
|
1813
|
+
"CampaignCatalogPort",
|
|
1814
|
+
"CampaignConcurrency",
|
|
1815
|
+
"CampaignEvidencePort",
|
|
1816
|
+
"CampaignGenerationKind",
|
|
1817
|
+
"CampaignGenerationPair",
|
|
1818
|
+
"CampaignGenerationStep",
|
|
1819
|
+
"CampaignJournalPort",
|
|
1820
|
+
"CampaignPolicies",
|
|
1821
|
+
"CampaignPolicyBinding",
|
|
1822
|
+
"CampaignPromotionBarrier",
|
|
1823
|
+
"CampaignProtocol",
|
|
1824
|
+
"CampaignReflectionWave",
|
|
1825
|
+
"CampaignReflectionSupervisionPolicy",
|
|
1826
|
+
"CampaignSchedule",
|
|
1827
|
+
"CampaignSeed",
|
|
1828
|
+
"CampaignSeedBatch",
|
|
1829
|
+
"CampaignSeedPort",
|
|
1830
|
+
"CampaignWorkloadPorts",
|
|
1831
|
+
"EvolutionCampaign",
|
|
1832
|
+
"ParentVariationBinding",
|
|
1833
|
+
"PreparedEvolutionCampaign",
|
|
1834
|
+
"ReflectionLaunchMode",
|
|
1835
|
+
"ReflectionFailureMode",
|
|
1836
|
+
"TerminalReflectionPolicy",
|
|
1837
|
+
"ReflectionVisibility",
|
|
1838
|
+
"SealedCutoffDelayedAdmissionCadence",
|
|
1839
|
+
"freeze_archive_utility",
|
|
1840
|
+
]
|