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,1228 @@
|
|
|
1
|
+
"""Generic propose -> broker -> evaluate orchestration for residual evolution.
|
|
2
|
+
|
|
3
|
+
Proposal experts own workload knowledge, candidate legality, and exact
|
|
4
|
+
materialization. This application service receives only already-materialized
|
|
5
|
+
actions, gives every expert access to the same authenticated prior cutoff, and
|
|
6
|
+
lets the workload-blind action broker choose the expensive evaluations.
|
|
7
|
+
|
|
8
|
+
The service deliberately stops before archive credit. A downstream utility
|
|
9
|
+
policy must turn the jointly evaluated slate into conserved immediate credit,
|
|
10
|
+
and a lineage resolver may later append finite-horizon return. Keeping those
|
|
11
|
+
steps outside proposal execution prevents an expert from grading itself.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import asyncio
|
|
17
|
+
import hashlib
|
|
18
|
+
import json
|
|
19
|
+
import re
|
|
20
|
+
from dataclasses import dataclass, field
|
|
21
|
+
from typing import Protocol, runtime_checkable
|
|
22
|
+
|
|
23
|
+
from agent_evolve.application.agentic_evolution import EvolutionCandidate
|
|
24
|
+
from agent_evolve.application.contextual_search_controller import SearchPhase
|
|
25
|
+
from agent_evolve.application.materialized_action_broker import (
|
|
26
|
+
MaterializedActionBrokerDecision,
|
|
27
|
+
MaterializedActionBrokerRequest,
|
|
28
|
+
MaterializedActionAllocationRequirement,
|
|
29
|
+
MaterializedActionDescriptor,
|
|
30
|
+
MaterializedSlateFeasibilityPort,
|
|
31
|
+
MaterializedSlateValuePort,
|
|
32
|
+
RegretBrokeredMaterializedActionPolicy,
|
|
33
|
+
)
|
|
34
|
+
from agent_evolve.domain.patch import require_sha256
|
|
35
|
+
from agent_evolve.domain.typed_json import (
|
|
36
|
+
FrozenJsonObject,
|
|
37
|
+
freeze_json,
|
|
38
|
+
thaw_json,
|
|
39
|
+
typed_json_sha256,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
RESIDUAL_PORTFOLIO_EVOLUTION_ID = "conserved_residual_portfolio_evolution"
|
|
44
|
+
RESIDUAL_PORTFOLIO_EVOLUTION_VERSION = 2
|
|
45
|
+
RESIDUAL_PORTFOLIO_EVOLUTION_DEFINITION_SHA256 = hashlib.sha256(
|
|
46
|
+
b"agent-evolve:conserved-residual-portfolio-evolution:v2;"
|
|
47
|
+
b"boundary=authenticated-prior-cutoff-and-opaque-proposal-context;"
|
|
48
|
+
b"experts=workload-owned-sealed-materialized-action-batches;"
|
|
49
|
+
b"allocation=optional-async-outcome-blind-policy-over-sealed-union;"
|
|
50
|
+
b"broker=workload-model-provider-blind-materialized-action-selection;"
|
|
51
|
+
b"reference=conservative-escrow;"
|
|
52
|
+
b"execution=selected-actions-only-grouped-by-owning-expert-concurrent;"
|
|
53
|
+
b"credit=downstream-conserved-archive-return-not-expert-self-score;"
|
|
54
|
+
b"lineage=downstream-append-only-resolved-return;"
|
|
55
|
+
b"core-workload-model-provider-branches=false"
|
|
56
|
+
).hexdigest()
|
|
57
|
+
DISJOINT_ACTION_EVALUATION_WAVES_V1 = "disjoint_action_subsets_v1"
|
|
58
|
+
|
|
59
|
+
_TOKEN = re.compile(r"^[a-z][a-z0-9_.:-]{0,255}$")
|
|
60
|
+
_REQUEST_DOMAIN = b"agent-evolve:residual-portfolio-request:v1\x00"
|
|
61
|
+
_PROPOSAL_DOMAIN = b"agent-evolve:residual-portfolio-proposal:v1\x00"
|
|
62
|
+
_EVALUATION_DOMAIN = b"agent-evolve:residual-portfolio-evaluation:v1\x00"
|
|
63
|
+
_RESULT_DOMAIN = b"agent-evolve:residual-portfolio-result:v1\x00"
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _canonical_json(value: object) -> bytes:
|
|
67
|
+
return json.dumps(
|
|
68
|
+
value,
|
|
69
|
+
allow_nan=False,
|
|
70
|
+
ensure_ascii=True,
|
|
71
|
+
separators=(",", ":"),
|
|
72
|
+
sort_keys=True,
|
|
73
|
+
).encode("ascii", errors="strict")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _hash(domain: bytes, value: object) -> str:
|
|
77
|
+
return hashlib.sha256(domain + _canonical_json(value)).hexdigest()
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _require_token(value: str, *, name: str) -> None:
|
|
81
|
+
if type(value) is not str or _TOKEN.fullmatch(value) is None:
|
|
82
|
+
raise ValueError(f"{name} must use the closed token grammar")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _expert_capacity(
|
|
86
|
+
values: tuple[tuple[str, int], ...],
|
|
87
|
+
expert_id: str,
|
|
88
|
+
) -> int:
|
|
89
|
+
try:
|
|
90
|
+
return dict(values)[expert_id]
|
|
91
|
+
except KeyError as error:
|
|
92
|
+
raise ValueError("proposal expert is absent from the request") from error
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _candidate_record(candidate: EvolutionCandidate) -> dict[str, object]:
|
|
96
|
+
if type(candidate) is not EvolutionCandidate:
|
|
97
|
+
raise TypeError("evaluations must contain exact EvolutionCandidate values")
|
|
98
|
+
EvolutionCandidate.__post_init__(candidate)
|
|
99
|
+
detailed = candidate.detailed_evaluation
|
|
100
|
+
return {
|
|
101
|
+
"candidate_id": candidate.candidate_id.value,
|
|
102
|
+
"configuration_sha256": candidate.occurrence.configuration_hash,
|
|
103
|
+
"generation": candidate.generation,
|
|
104
|
+
"valid": candidate.valid,
|
|
105
|
+
"operator_compliant": candidate.operator_compliant,
|
|
106
|
+
"evidence_compliant": candidate.evidence_compliant,
|
|
107
|
+
"objectives": [
|
|
108
|
+
{"metric_id": metric_id, "value_hex": value.hex()}
|
|
109
|
+
for metric_id, value in candidate.objectives
|
|
110
|
+
],
|
|
111
|
+
"detailed_evaluation_sha256": (
|
|
112
|
+
None if detailed is None else detailed.evidence_sha256
|
|
113
|
+
),
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@dataclass(frozen=True, slots=True)
|
|
118
|
+
class ResidualPortfolioDecisionRequest:
|
|
119
|
+
"""Authenticated common cutoff presented to every proposal expert.
|
|
120
|
+
|
|
121
|
+
``proposal_context`` is intentionally opaque to the core. A workload
|
|
122
|
+
adapter can place typed, replayable state in that object, while broker
|
|
123
|
+
behavior remains independent of its fields.
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
campaign_scope_sha256: str
|
|
127
|
+
prior_state_sha256: str
|
|
128
|
+
decision_index: int
|
|
129
|
+
phase: SearchPhase
|
|
130
|
+
remaining_decisions: int
|
|
131
|
+
remaining_evaluations: int
|
|
132
|
+
evaluation_slots: int
|
|
133
|
+
expert_proposal_slots: tuple[tuple[str, int], ...]
|
|
134
|
+
proposal_context: FrozenJsonObject
|
|
135
|
+
reference_escrow_slots: int = 1
|
|
136
|
+
request_sha256: str = field(init=False)
|
|
137
|
+
|
|
138
|
+
def __post_init__(self) -> None:
|
|
139
|
+
require_sha256(self.campaign_scope_sha256, "campaign_scope_sha256")
|
|
140
|
+
require_sha256(self.prior_state_sha256, "prior_state_sha256")
|
|
141
|
+
if type(self.decision_index) is not int or self.decision_index <= 0:
|
|
142
|
+
raise ValueError("decision_index must be a positive exact integer")
|
|
143
|
+
if type(self.phase) is not SearchPhase:
|
|
144
|
+
raise TypeError("phase must be an exact SearchPhase")
|
|
145
|
+
for name in ("remaining_decisions", "remaining_evaluations"):
|
|
146
|
+
value = getattr(self, name)
|
|
147
|
+
if type(value) is not int or value <= 0:
|
|
148
|
+
raise ValueError(f"{name} must be a positive exact integer")
|
|
149
|
+
if (
|
|
150
|
+
type(self.evaluation_slots) is not int
|
|
151
|
+
or self.evaluation_slots <= 0
|
|
152
|
+
or self.evaluation_slots > self.remaining_evaluations
|
|
153
|
+
):
|
|
154
|
+
raise ValueError("evaluation_slots must fit remaining evaluations")
|
|
155
|
+
if (
|
|
156
|
+
type(self.expert_proposal_slots) is not tuple
|
|
157
|
+
or not self.expert_proposal_slots
|
|
158
|
+
):
|
|
159
|
+
raise ValueError("expert_proposal_slots must be a non-empty exact tuple")
|
|
160
|
+
for expert_id, slots in self.expert_proposal_slots:
|
|
161
|
+
_require_token(expert_id, name="expert_id")
|
|
162
|
+
if type(slots) is not int or slots <= 0:
|
|
163
|
+
raise ValueError("expert proposal slots must be positive")
|
|
164
|
+
expert_ids = tuple(value[0] for value in self.expert_proposal_slots)
|
|
165
|
+
if expert_ids != tuple(sorted(set(expert_ids))):
|
|
166
|
+
raise ValueError("expert proposal capacities must be unique and canonical")
|
|
167
|
+
if sum(value[1] for value in self.expert_proposal_slots) < (
|
|
168
|
+
self.evaluation_slots
|
|
169
|
+
):
|
|
170
|
+
raise ValueError("proposal capacity cannot cover evaluation capacity")
|
|
171
|
+
if (
|
|
172
|
+
type(self.reference_escrow_slots) is not int
|
|
173
|
+
or not 0 <= self.reference_escrow_slots <= self.evaluation_slots
|
|
174
|
+
):
|
|
175
|
+
raise ValueError("reference escrow must fit evaluation capacity")
|
|
176
|
+
if (
|
|
177
|
+
type(self.proposal_context) is not FrozenJsonObject
|
|
178
|
+
or freeze_json(self.proposal_context) is not self.proposal_context
|
|
179
|
+
):
|
|
180
|
+
raise TypeError("proposal_context must be an exact frozen JSON object")
|
|
181
|
+
object.__setattr__(
|
|
182
|
+
self,
|
|
183
|
+
"request_sha256",
|
|
184
|
+
_hash(_REQUEST_DOMAIN, self._unsigned_record()),
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
def proposal_slots_for(self, expert_id: str) -> int:
|
|
188
|
+
_require_token(expert_id, name="expert_id")
|
|
189
|
+
return _expert_capacity(self.expert_proposal_slots, expert_id)
|
|
190
|
+
|
|
191
|
+
def _unsigned_record(self) -> dict[str, object]:
|
|
192
|
+
return {
|
|
193
|
+
"schema_version": 1,
|
|
194
|
+
"campaign_scope_sha256": self.campaign_scope_sha256,
|
|
195
|
+
"prior_state_sha256": self.prior_state_sha256,
|
|
196
|
+
"decision_index": self.decision_index,
|
|
197
|
+
"phase": self.phase.value,
|
|
198
|
+
"remaining_decisions": self.remaining_decisions,
|
|
199
|
+
"remaining_evaluations": self.remaining_evaluations,
|
|
200
|
+
"evaluation_slots": self.evaluation_slots,
|
|
201
|
+
"expert_proposal_slots": [
|
|
202
|
+
{"expert_id": expert_id, "proposal_slots": slots}
|
|
203
|
+
for expert_id, slots in self.expert_proposal_slots
|
|
204
|
+
],
|
|
205
|
+
"proposal_context_sha256": typed_json_sha256(self.proposal_context),
|
|
206
|
+
"reference_escrow_slots": self.reference_escrow_slots,
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
def to_record(self, *, include_proposal_context: bool = False) -> dict[str, object]:
|
|
210
|
+
self.__post_init__()
|
|
211
|
+
record = {**self._unsigned_record(), "request_sha256": self.request_sha256}
|
|
212
|
+
if include_proposal_context:
|
|
213
|
+
record["proposal_context"] = thaw_json(self.proposal_context)
|
|
214
|
+
return record
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
@dataclass(frozen=True, slots=True)
|
|
218
|
+
class MaterializedActionProposalBatch:
|
|
219
|
+
"""One expert's sealed, unevaluated action batch."""
|
|
220
|
+
|
|
221
|
+
request_sha256: str
|
|
222
|
+
expert_id: str
|
|
223
|
+
expert_version: int
|
|
224
|
+
expert_definition_sha256: str
|
|
225
|
+
actions: tuple[MaterializedActionDescriptor, ...]
|
|
226
|
+
evidence: FrozenJsonObject
|
|
227
|
+
proposal_sha256: str = field(init=False)
|
|
228
|
+
|
|
229
|
+
def __post_init__(self) -> None:
|
|
230
|
+
require_sha256(self.request_sha256, "request_sha256")
|
|
231
|
+
_require_token(self.expert_id, name="expert_id")
|
|
232
|
+
if type(self.expert_version) is not int or self.expert_version <= 0:
|
|
233
|
+
raise ValueError("expert_version must be positive")
|
|
234
|
+
require_sha256(
|
|
235
|
+
self.expert_definition_sha256,
|
|
236
|
+
"expert_definition_sha256",
|
|
237
|
+
)
|
|
238
|
+
if type(self.actions) is not tuple or not self.actions:
|
|
239
|
+
raise ValueError("proposal batch must contain at least one action")
|
|
240
|
+
for action in self.actions:
|
|
241
|
+
if type(action) is not MaterializedActionDescriptor:
|
|
242
|
+
raise TypeError("proposal actions must be exact descriptors")
|
|
243
|
+
action.__post_init__()
|
|
244
|
+
if action.expert_id != self.expert_id:
|
|
245
|
+
raise ValueError("proposal action names another expert")
|
|
246
|
+
if tuple(value.native_rank for value in self.actions) != tuple(
|
|
247
|
+
range(1, len(self.actions) + 1)
|
|
248
|
+
):
|
|
249
|
+
raise ValueError("expert actions must retain contiguous native ranks")
|
|
250
|
+
for values, name in (
|
|
251
|
+
(
|
|
252
|
+
tuple(value.action_sha256 for value in self.actions),
|
|
253
|
+
"action identities",
|
|
254
|
+
),
|
|
255
|
+
(
|
|
256
|
+
tuple(value.target_candidate_id for value in self.actions),
|
|
257
|
+
"target candidate IDs",
|
|
258
|
+
),
|
|
259
|
+
):
|
|
260
|
+
if len(set(values)) != len(values):
|
|
261
|
+
raise ValueError(f"proposal batch repeats {name}")
|
|
262
|
+
contexts = {
|
|
263
|
+
(
|
|
264
|
+
value.context.campaign_scope_sha256,
|
|
265
|
+
value.context.decision_index,
|
|
266
|
+
value.context.phase,
|
|
267
|
+
)
|
|
268
|
+
for value in self.actions
|
|
269
|
+
}
|
|
270
|
+
if len(contexts) != 1:
|
|
271
|
+
raise ValueError("one proposal batch cannot mix decision cutoffs")
|
|
272
|
+
if (
|
|
273
|
+
type(self.evidence) is not FrozenJsonObject
|
|
274
|
+
or freeze_json(self.evidence) is not self.evidence
|
|
275
|
+
):
|
|
276
|
+
raise TypeError("proposal evidence must be an exact frozen object")
|
|
277
|
+
object.__setattr__(
|
|
278
|
+
self,
|
|
279
|
+
"proposal_sha256",
|
|
280
|
+
_hash(_PROPOSAL_DOMAIN, self._unsigned_record()),
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
def require_request(self, request: ResidualPortfolioDecisionRequest) -> None:
|
|
284
|
+
if type(request) is not ResidualPortfolioDecisionRequest:
|
|
285
|
+
raise TypeError("request must be exact")
|
|
286
|
+
request.__post_init__()
|
|
287
|
+
if self.request_sha256 != request.request_sha256:
|
|
288
|
+
raise ValueError("proposal targets another residual decision")
|
|
289
|
+
if len(self.actions) > request.proposal_slots_for(self.expert_id):
|
|
290
|
+
raise ValueError("expert exceeded its proposal capacity")
|
|
291
|
+
for action in self.actions:
|
|
292
|
+
context = action.context
|
|
293
|
+
if (
|
|
294
|
+
context.campaign_scope_sha256 != request.campaign_scope_sha256
|
|
295
|
+
or context.decision_index != request.decision_index
|
|
296
|
+
or context.phase is not request.phase
|
|
297
|
+
or context.remaining_decisions != request.remaining_decisions
|
|
298
|
+
or context.remaining_evaluations != request.remaining_evaluations
|
|
299
|
+
):
|
|
300
|
+
raise ValueError("proposal action context differs from the request")
|
|
301
|
+
|
|
302
|
+
def _unsigned_record(self) -> dict[str, object]:
|
|
303
|
+
return {
|
|
304
|
+
"schema_version": 1,
|
|
305
|
+
"request_sha256": self.request_sha256,
|
|
306
|
+
"expert": {
|
|
307
|
+
"expert_id": self.expert_id,
|
|
308
|
+
"expert_version": self.expert_version,
|
|
309
|
+
"definition_sha256": self.expert_definition_sha256,
|
|
310
|
+
},
|
|
311
|
+
"actions": [value.to_record() for value in self.actions],
|
|
312
|
+
"evidence_sha256": typed_json_sha256(self.evidence),
|
|
313
|
+
"evaluation_performed": False,
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
def to_record(
|
|
317
|
+
self,
|
|
318
|
+
*,
|
|
319
|
+
include_configurations: bool = False,
|
|
320
|
+
include_evidence: bool = False,
|
|
321
|
+
) -> dict[str, object]:
|
|
322
|
+
self.__post_init__()
|
|
323
|
+
record = {
|
|
324
|
+
**self._unsigned_record(),
|
|
325
|
+
"actions": [
|
|
326
|
+
value.to_record(include_configuration=include_configurations)
|
|
327
|
+
for value in self.actions
|
|
328
|
+
],
|
|
329
|
+
"proposal_sha256": self.proposal_sha256,
|
|
330
|
+
}
|
|
331
|
+
if include_evidence:
|
|
332
|
+
record["evidence"] = thaw_json(self.evidence)
|
|
333
|
+
return record
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
@dataclass(frozen=True, slots=True)
|
|
337
|
+
class DisjointActionEvaluationWave:
|
|
338
|
+
"""One fail-closed reservation of previously unevaluated actions.
|
|
339
|
+
|
|
340
|
+
Sequential allocation can evaluate a pilot and a continuation from the
|
|
341
|
+
same sealed proposal. The action—not the proposal batch—is therefore the
|
|
342
|
+
exactly-once boundary. A reservation is recorded before invoking the
|
|
343
|
+
authoritative evaluator so an interrupted wave cannot be retried
|
|
344
|
+
ambiguously through the same live expert instance.
|
|
345
|
+
"""
|
|
346
|
+
|
|
347
|
+
proposal_sha256: str
|
|
348
|
+
wave_index: int
|
|
349
|
+
previously_attempted_action_count: int
|
|
350
|
+
selected_action_sha256s: tuple[str, ...]
|
|
351
|
+
|
|
352
|
+
def __post_init__(self) -> None:
|
|
353
|
+
require_sha256(self.proposal_sha256, "proposal_sha256")
|
|
354
|
+
if type(self.wave_index) is not int or self.wave_index <= 0:
|
|
355
|
+
raise ValueError("wave_index must be a positive exact integer")
|
|
356
|
+
if (
|
|
357
|
+
type(self.previously_attempted_action_count) is not int
|
|
358
|
+
or self.previously_attempted_action_count < 0
|
|
359
|
+
):
|
|
360
|
+
raise ValueError(
|
|
361
|
+
"previously_attempted_action_count must be non-negative"
|
|
362
|
+
)
|
|
363
|
+
if (
|
|
364
|
+
type(self.selected_action_sha256s) is not tuple
|
|
365
|
+
or not self.selected_action_sha256s
|
|
366
|
+
or self.selected_action_sha256s
|
|
367
|
+
!= tuple(sorted(set(self.selected_action_sha256s)))
|
|
368
|
+
):
|
|
369
|
+
raise ValueError(
|
|
370
|
+
"selected_action_sha256s must be non-empty and canonical"
|
|
371
|
+
)
|
|
372
|
+
for value in self.selected_action_sha256s:
|
|
373
|
+
require_sha256(value, "selected_action_sha256")
|
|
374
|
+
|
|
375
|
+
def to_record(self) -> dict[str, object]:
|
|
376
|
+
self.__post_init__()
|
|
377
|
+
return {
|
|
378
|
+
"semantics": DISJOINT_ACTION_EVALUATION_WAVES_V1,
|
|
379
|
+
"wave_index": self.wave_index,
|
|
380
|
+
"previously_attempted_action_count": (
|
|
381
|
+
self.previously_attempted_action_count
|
|
382
|
+
),
|
|
383
|
+
"selected_action_sha256s": list(
|
|
384
|
+
self.selected_action_sha256s
|
|
385
|
+
),
|
|
386
|
+
"selected_subset_frozen_before_wave": True,
|
|
387
|
+
"selected_actions_disjoint_from_previous_waves": True,
|
|
388
|
+
"repeated_action_evaluation_forbidden": True,
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
@dataclass(slots=True)
|
|
393
|
+
class DisjointActionEvaluationLedger:
|
|
394
|
+
"""Reserve disjoint evaluation waves over any sealed proposal batch."""
|
|
395
|
+
|
|
396
|
+
_attempted_by_proposal: dict[str, set[str]] = field(
|
|
397
|
+
init=False,
|
|
398
|
+
default_factory=dict,
|
|
399
|
+
)
|
|
400
|
+
_wave_count_by_proposal: dict[str, int] = field(
|
|
401
|
+
init=False,
|
|
402
|
+
default_factory=dict,
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
def reserve(
|
|
406
|
+
self,
|
|
407
|
+
proposal: MaterializedActionProposalBatch,
|
|
408
|
+
selected_action_sha256s: tuple[str, ...],
|
|
409
|
+
) -> DisjointActionEvaluationWave:
|
|
410
|
+
if type(proposal) is not MaterializedActionProposalBatch:
|
|
411
|
+
raise TypeError("proposal must be exact")
|
|
412
|
+
proposal.__post_init__()
|
|
413
|
+
if (
|
|
414
|
+
type(selected_action_sha256s) is not tuple
|
|
415
|
+
or not selected_action_sha256s
|
|
416
|
+
or selected_action_sha256s
|
|
417
|
+
!= tuple(sorted(set(selected_action_sha256s)))
|
|
418
|
+
):
|
|
419
|
+
raise ValueError(
|
|
420
|
+
"selected action hashes must be non-empty and canonical"
|
|
421
|
+
)
|
|
422
|
+
available = {
|
|
423
|
+
value.action_sha256 for value in proposal.actions
|
|
424
|
+
}
|
|
425
|
+
selected = set(selected_action_sha256s)
|
|
426
|
+
if not selected.issubset(available):
|
|
427
|
+
raise ValueError("selected action is outside the sealed proposal")
|
|
428
|
+
attempted = self._attempted_by_proposal.setdefault(
|
|
429
|
+
proposal.proposal_sha256,
|
|
430
|
+
set(),
|
|
431
|
+
)
|
|
432
|
+
overlap = tuple(sorted(selected.intersection(attempted)))
|
|
433
|
+
if overlap:
|
|
434
|
+
raise ValueError(
|
|
435
|
+
"an action cannot be evaluated in more than one wave: "
|
|
436
|
+
+ ",".join(overlap)
|
|
437
|
+
)
|
|
438
|
+
wave_index = (
|
|
439
|
+
self._wave_count_by_proposal.get(
|
|
440
|
+
proposal.proposal_sha256,
|
|
441
|
+
0,
|
|
442
|
+
)
|
|
443
|
+
+ 1
|
|
444
|
+
)
|
|
445
|
+
reservation = DisjointActionEvaluationWave(
|
|
446
|
+
proposal_sha256=proposal.proposal_sha256,
|
|
447
|
+
wave_index=wave_index,
|
|
448
|
+
previously_attempted_action_count=len(attempted),
|
|
449
|
+
selected_action_sha256s=selected_action_sha256s,
|
|
450
|
+
)
|
|
451
|
+
# Reserve before any evaluator await. Failure is deliberately
|
|
452
|
+
# fail-closed: a fresh run may replay from durable evidence, while this
|
|
453
|
+
# live expert cannot accidentally double-spend evaluator budget.
|
|
454
|
+
attempted.update(selected)
|
|
455
|
+
self._wave_count_by_proposal[proposal.proposal_sha256] = wave_index
|
|
456
|
+
return reservation
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
@dataclass(frozen=True, slots=True)
|
|
460
|
+
class MaterializedActionEvaluation:
|
|
461
|
+
"""Exact join between one selected action and its real candidate."""
|
|
462
|
+
|
|
463
|
+
action: MaterializedActionDescriptor
|
|
464
|
+
candidate: EvolutionCandidate
|
|
465
|
+
evaluator_receipt_sha256: str
|
|
466
|
+
evaluation_sha256: str = field(init=False)
|
|
467
|
+
|
|
468
|
+
def __post_init__(self) -> None:
|
|
469
|
+
if type(self.action) is not MaterializedActionDescriptor:
|
|
470
|
+
raise TypeError("action must be an exact descriptor")
|
|
471
|
+
self.action.__post_init__()
|
|
472
|
+
if type(self.candidate) is not EvolutionCandidate:
|
|
473
|
+
raise TypeError("candidate must be an exact EvolutionCandidate")
|
|
474
|
+
EvolutionCandidate.__post_init__(self.candidate)
|
|
475
|
+
require_sha256(
|
|
476
|
+
self.evaluator_receipt_sha256,
|
|
477
|
+
"evaluator_receipt_sha256",
|
|
478
|
+
)
|
|
479
|
+
if self.candidate.candidate_id != self.action.target_candidate_id:
|
|
480
|
+
raise ValueError("evaluated candidate ID differs from its action")
|
|
481
|
+
if (
|
|
482
|
+
self.candidate.occurrence.configuration_hash
|
|
483
|
+
!= self.action.configuration_sha256
|
|
484
|
+
):
|
|
485
|
+
raise ValueError("evaluated configuration differs from its action")
|
|
486
|
+
object.__setattr__(
|
|
487
|
+
self,
|
|
488
|
+
"evaluation_sha256",
|
|
489
|
+
_hash(_EVALUATION_DOMAIN, self._unsigned_record()),
|
|
490
|
+
)
|
|
491
|
+
|
|
492
|
+
def _unsigned_record(self) -> dict[str, object]:
|
|
493
|
+
return {
|
|
494
|
+
"schema_version": 1,
|
|
495
|
+
"action_sha256": self.action.action_sha256,
|
|
496
|
+
"evaluator_receipt_sha256": self.evaluator_receipt_sha256,
|
|
497
|
+
"candidate": _candidate_record(self.candidate),
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
def to_record(self) -> dict[str, object]:
|
|
501
|
+
self.__post_init__()
|
|
502
|
+
return {
|
|
503
|
+
**self._unsigned_record(),
|
|
504
|
+
"evaluation_sha256": self.evaluation_sha256,
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
@dataclass(frozen=True, slots=True)
|
|
509
|
+
class MaterializedActionEvaluationBatch:
|
|
510
|
+
"""One expert's exact response for its broker-selected subset."""
|
|
511
|
+
|
|
512
|
+
proposal_sha256: str
|
|
513
|
+
expert_id: str
|
|
514
|
+
expert_version: int
|
|
515
|
+
expert_definition_sha256: str
|
|
516
|
+
selected_action_sha256s: tuple[str, ...]
|
|
517
|
+
evaluations: tuple[MaterializedActionEvaluation, ...]
|
|
518
|
+
evidence: FrozenJsonObject
|
|
519
|
+
batch_sha256: str = field(init=False)
|
|
520
|
+
|
|
521
|
+
def __post_init__(self) -> None:
|
|
522
|
+
require_sha256(self.proposal_sha256, "proposal_sha256")
|
|
523
|
+
_require_token(self.expert_id, name="expert_id")
|
|
524
|
+
if type(self.expert_version) is not int or self.expert_version <= 0:
|
|
525
|
+
raise ValueError("expert_version must be positive")
|
|
526
|
+
require_sha256(
|
|
527
|
+
self.expert_definition_sha256,
|
|
528
|
+
"expert_definition_sha256",
|
|
529
|
+
)
|
|
530
|
+
if (
|
|
531
|
+
type(self.selected_action_sha256s) is not tuple
|
|
532
|
+
or not self.selected_action_sha256s
|
|
533
|
+
):
|
|
534
|
+
raise ValueError("selected action hashes must be non-empty")
|
|
535
|
+
for value in self.selected_action_sha256s:
|
|
536
|
+
require_sha256(value, "selected_action_sha256")
|
|
537
|
+
if self.selected_action_sha256s != tuple(
|
|
538
|
+
sorted(set(self.selected_action_sha256s))
|
|
539
|
+
):
|
|
540
|
+
raise ValueError("selected action hashes must be unique and canonical")
|
|
541
|
+
if (
|
|
542
|
+
type(self.evaluations) is not tuple
|
|
543
|
+
or len(self.evaluations) != len(self.selected_action_sha256s)
|
|
544
|
+
):
|
|
545
|
+
raise ValueError("evaluations must exactly cover selected actions")
|
|
546
|
+
for value in self.evaluations:
|
|
547
|
+
if type(value) is not MaterializedActionEvaluation:
|
|
548
|
+
raise TypeError("evaluations must contain exact values")
|
|
549
|
+
value.__post_init__()
|
|
550
|
+
if value.action.expert_id != self.expert_id:
|
|
551
|
+
raise ValueError("evaluation action names another expert")
|
|
552
|
+
if tuple(value.action.action_sha256 for value in self.evaluations) != (
|
|
553
|
+
self.selected_action_sha256s
|
|
554
|
+
):
|
|
555
|
+
raise ValueError("evaluation order differs from selected action order")
|
|
556
|
+
if (
|
|
557
|
+
type(self.evidence) is not FrozenJsonObject
|
|
558
|
+
or freeze_json(self.evidence) is not self.evidence
|
|
559
|
+
):
|
|
560
|
+
raise TypeError("evaluation evidence must be an exact frozen object")
|
|
561
|
+
object.__setattr__(
|
|
562
|
+
self,
|
|
563
|
+
"batch_sha256",
|
|
564
|
+
_hash(_EVALUATION_DOMAIN, self._unsigned_record()),
|
|
565
|
+
)
|
|
566
|
+
|
|
567
|
+
def require_proposal(self, proposal: MaterializedActionProposalBatch) -> None:
|
|
568
|
+
if type(proposal) is not MaterializedActionProposalBatch:
|
|
569
|
+
raise TypeError("proposal must be exact")
|
|
570
|
+
proposal.__post_init__()
|
|
571
|
+
if (
|
|
572
|
+
self.proposal_sha256 != proposal.proposal_sha256
|
|
573
|
+
or (
|
|
574
|
+
self.expert_id,
|
|
575
|
+
self.expert_version,
|
|
576
|
+
self.expert_definition_sha256,
|
|
577
|
+
)
|
|
578
|
+
!= (
|
|
579
|
+
proposal.expert_id,
|
|
580
|
+
proposal.expert_version,
|
|
581
|
+
proposal.expert_definition_sha256,
|
|
582
|
+
)
|
|
583
|
+
):
|
|
584
|
+
raise ValueError("evaluation batch differs from its proposal identity")
|
|
585
|
+
action_by_sha256 = {
|
|
586
|
+
value.action_sha256: value for value in proposal.actions
|
|
587
|
+
}
|
|
588
|
+
try:
|
|
589
|
+
expected = tuple(
|
|
590
|
+
action_by_sha256[value] for value in self.selected_action_sha256s
|
|
591
|
+
)
|
|
592
|
+
except KeyError as error:
|
|
593
|
+
raise ValueError(
|
|
594
|
+
"evaluation selected outside the sealed proposal"
|
|
595
|
+
) from error
|
|
596
|
+
if tuple(value.action for value in self.evaluations) != expected:
|
|
597
|
+
raise ValueError("evaluation descriptors differ from sealed actions")
|
|
598
|
+
|
|
599
|
+
def _unsigned_record(self) -> dict[str, object]:
|
|
600
|
+
return {
|
|
601
|
+
"schema_version": 1,
|
|
602
|
+
"proposal_sha256": self.proposal_sha256,
|
|
603
|
+
"expert": {
|
|
604
|
+
"expert_id": self.expert_id,
|
|
605
|
+
"expert_version": self.expert_version,
|
|
606
|
+
"definition_sha256": self.expert_definition_sha256,
|
|
607
|
+
},
|
|
608
|
+
"selected_action_sha256s": list(self.selected_action_sha256s),
|
|
609
|
+
"evaluations": [value.to_record() for value in self.evaluations],
|
|
610
|
+
"evidence_sha256": typed_json_sha256(self.evidence),
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
def to_record(self, *, include_evidence: bool = False) -> dict[str, object]:
|
|
614
|
+
self.__post_init__()
|
|
615
|
+
record = {**self._unsigned_record(), "batch_sha256": self.batch_sha256}
|
|
616
|
+
if include_evidence:
|
|
617
|
+
record["evidence"] = thaw_json(self.evidence)
|
|
618
|
+
return record
|
|
619
|
+
|
|
620
|
+
|
|
621
|
+
@runtime_checkable
|
|
622
|
+
class MaterializedActionProposalExpertPort(Protocol):
|
|
623
|
+
"""Workload-owned proposal and evaluation boundary.
|
|
624
|
+
|
|
625
|
+
``evaluate`` may be called more than once for one proposal only when the
|
|
626
|
+
requested action subsets are disjoint. Implementations used by a
|
|
627
|
+
sequential runtime expose
|
|
628
|
+
``evaluation_wave_semantics=DISJOINT_ACTION_EVALUATION_WAVES_V1`` and
|
|
629
|
+
fail closed on any repeated action.
|
|
630
|
+
"""
|
|
631
|
+
|
|
632
|
+
expert_id: str
|
|
633
|
+
expert_version: int
|
|
634
|
+
definition_sha256: str
|
|
635
|
+
|
|
636
|
+
async def propose(
|
|
637
|
+
self,
|
|
638
|
+
request: ResidualPortfolioDecisionRequest,
|
|
639
|
+
) -> MaterializedActionProposalBatch: ...
|
|
640
|
+
|
|
641
|
+
async def evaluate(
|
|
642
|
+
self,
|
|
643
|
+
proposal: MaterializedActionProposalBatch,
|
|
644
|
+
selected_action_sha256s: tuple[str, ...],
|
|
645
|
+
) -> MaterializedActionEvaluationBatch: ...
|
|
646
|
+
|
|
647
|
+
|
|
648
|
+
@runtime_checkable
|
|
649
|
+
class MaterializedActionCounterfactualEvaluationPort(Protocol):
|
|
650
|
+
"""Optional workload capability for excluded real-evaluation assays.
|
|
651
|
+
|
|
652
|
+
This method is deliberately separate from authoritative ``evaluate``.
|
|
653
|
+
Implementations must execute the same physical evaluator while avoiding
|
|
654
|
+
authoritative budget registration, archive admission, and any state that
|
|
655
|
+
can influence later action selection. A runtime requesting a paired
|
|
656
|
+
counterfactual fails closed when the owning expert does not expose this
|
|
657
|
+
capability.
|
|
658
|
+
"""
|
|
659
|
+
|
|
660
|
+
async def evaluate_counterfactual(
|
|
661
|
+
self,
|
|
662
|
+
proposal: MaterializedActionProposalBatch,
|
|
663
|
+
selected_action_sha256s: tuple[str, ...],
|
|
664
|
+
) -> MaterializedActionEvaluationBatch: ...
|
|
665
|
+
|
|
666
|
+
|
|
667
|
+
@runtime_checkable
|
|
668
|
+
class MaterializedActionAllocationPolicyPort(Protocol):
|
|
669
|
+
"""Select authenticated required actions from the sealed proposal union.
|
|
670
|
+
|
|
671
|
+
The policy is asynchronous so an LLM, learned ranker, or committee can
|
|
672
|
+
inspect the complete population without blocking the event loop. It sees
|
|
673
|
+
the prior-cutoff request and unevaluated proposal batches only; real
|
|
674
|
+
candidate evaluations do not exist at this boundary.
|
|
675
|
+
"""
|
|
676
|
+
|
|
677
|
+
policy_id: str
|
|
678
|
+
policy_version: int
|
|
679
|
+
definition_sha256: str
|
|
680
|
+
|
|
681
|
+
async def require(
|
|
682
|
+
self,
|
|
683
|
+
request: ResidualPortfolioDecisionRequest,
|
|
684
|
+
proposals: tuple[MaterializedActionProposalBatch, ...],
|
|
685
|
+
) -> MaterializedActionAllocationRequirement: ...
|
|
686
|
+
|
|
687
|
+
|
|
688
|
+
def _expert_identity(
|
|
689
|
+
expert: MaterializedActionProposalExpertPort,
|
|
690
|
+
) -> tuple[str, int, str]:
|
|
691
|
+
if not isinstance(expert, MaterializedActionProposalExpertPort):
|
|
692
|
+
raise TypeError("proposal expert must implement its runtime port")
|
|
693
|
+
identity = (
|
|
694
|
+
getattr(expert, "expert_id", None),
|
|
695
|
+
getattr(expert, "expert_version", None),
|
|
696
|
+
getattr(expert, "definition_sha256", None),
|
|
697
|
+
)
|
|
698
|
+
_require_token(identity[0], name="expert_id")
|
|
699
|
+
if type(identity[1]) is not int or identity[1] <= 0:
|
|
700
|
+
raise ValueError("proposal expert version must be positive")
|
|
701
|
+
require_sha256(identity[2], "proposal expert definition_sha256")
|
|
702
|
+
return identity # type: ignore[return-value]
|
|
703
|
+
|
|
704
|
+
|
|
705
|
+
def _allocation_policy_identity(
|
|
706
|
+
policy: MaterializedActionAllocationPolicyPort,
|
|
707
|
+
) -> tuple[str, int, str]:
|
|
708
|
+
if not isinstance(policy, MaterializedActionAllocationPolicyPort):
|
|
709
|
+
raise TypeError(
|
|
710
|
+
"allocation policy must implement "
|
|
711
|
+
"MaterializedActionAllocationPolicyPort"
|
|
712
|
+
)
|
|
713
|
+
identity = (
|
|
714
|
+
getattr(policy, "policy_id", None),
|
|
715
|
+
getattr(policy, "policy_version", None),
|
|
716
|
+
getattr(policy, "definition_sha256", None),
|
|
717
|
+
)
|
|
718
|
+
_require_token(identity[0], name="allocation policy_id")
|
|
719
|
+
if type(identity[1]) is not int or identity[1] <= 0:
|
|
720
|
+
raise ValueError("allocation policy version must be positive")
|
|
721
|
+
require_sha256(identity[2], "allocation policy definition_sha256")
|
|
722
|
+
return identity # type: ignore[return-value]
|
|
723
|
+
|
|
724
|
+
|
|
725
|
+
async def propose_materialized_action_batches(
|
|
726
|
+
*,
|
|
727
|
+
experts: tuple[MaterializedActionProposalExpertPort, ...],
|
|
728
|
+
request: ResidualPortfolioDecisionRequest,
|
|
729
|
+
) -> tuple[MaterializedActionProposalBatch, ...]:
|
|
730
|
+
"""Run one sealed proposal wave and validate its complete expert join."""
|
|
731
|
+
|
|
732
|
+
if type(request) is not ResidualPortfolioDecisionRequest:
|
|
733
|
+
raise TypeError("request must be an exact residual portfolio request")
|
|
734
|
+
request.__post_init__()
|
|
735
|
+
if type(experts) is not tuple or not experts:
|
|
736
|
+
raise ValueError("experts must be a non-empty exact tuple")
|
|
737
|
+
identities = tuple(_expert_identity(value) for value in experts)
|
|
738
|
+
expert_ids = tuple(value[0] for value in identities)
|
|
739
|
+
if expert_ids != tuple(sorted(set(expert_ids))):
|
|
740
|
+
raise ValueError("experts must use canonical unique IDs")
|
|
741
|
+
requested_expert_ids = tuple(
|
|
742
|
+
value[0] for value in request.expert_proposal_slots
|
|
743
|
+
)
|
|
744
|
+
if requested_expert_ids != expert_ids:
|
|
745
|
+
raise ValueError("request expert capacities differ from runtime experts")
|
|
746
|
+
raw = await asyncio.gather(
|
|
747
|
+
*(expert.propose(request) for expert in experts)
|
|
748
|
+
)
|
|
749
|
+
proposals = tuple(raw)
|
|
750
|
+
for identity, proposal in zip(identities, proposals, strict=True):
|
|
751
|
+
if type(proposal) is not MaterializedActionProposalBatch:
|
|
752
|
+
raise TypeError("proposal expert returned a foreign batch")
|
|
753
|
+
proposal.__post_init__()
|
|
754
|
+
proposal.require_request(request)
|
|
755
|
+
if (
|
|
756
|
+
proposal.expert_id,
|
|
757
|
+
proposal.expert_version,
|
|
758
|
+
proposal.expert_definition_sha256,
|
|
759
|
+
) != identity:
|
|
760
|
+
raise ValueError("proposal batch has a foreign expert identity")
|
|
761
|
+
actions = tuple(
|
|
762
|
+
action for proposal in proposals for action in proposal.actions
|
|
763
|
+
)
|
|
764
|
+
if len(actions) < request.evaluation_slots:
|
|
765
|
+
raise ValueError("realized proposal union cannot fill evaluation capacity")
|
|
766
|
+
if len({value.action_sha256 for value in actions}) != len(actions):
|
|
767
|
+
raise ValueError("proposal union repeats an action identity")
|
|
768
|
+
if len({value.target_candidate_id for value in actions}) != len(actions):
|
|
769
|
+
raise ValueError("proposal union repeats a target candidate ID")
|
|
770
|
+
return proposals
|
|
771
|
+
|
|
772
|
+
|
|
773
|
+
async def _evaluate_materialized_action_subset(
|
|
774
|
+
*,
|
|
775
|
+
experts: tuple[MaterializedActionProposalExpertPort, ...],
|
|
776
|
+
proposals: tuple[MaterializedActionProposalBatch, ...],
|
|
777
|
+
selected_action_sha256s: tuple[str, ...],
|
|
778
|
+
counterfactual: bool,
|
|
779
|
+
) -> tuple[MaterializedActionEvaluationBatch, ...]:
|
|
780
|
+
"""Evaluate and authenticate one exact subset of a sealed universe."""
|
|
781
|
+
|
|
782
|
+
if (
|
|
783
|
+
type(selected_action_sha256s) is not tuple
|
|
784
|
+
or not selected_action_sha256s
|
|
785
|
+
or selected_action_sha256s
|
|
786
|
+
!= tuple(sorted(set(selected_action_sha256s)))
|
|
787
|
+
):
|
|
788
|
+
raise ValueError(
|
|
789
|
+
"selected_action_sha256s must be non-empty and canonical"
|
|
790
|
+
)
|
|
791
|
+
identities = tuple(_expert_identity(value) for value in experts)
|
|
792
|
+
expert_ids = tuple(value[0] for value in identities)
|
|
793
|
+
if expert_ids != tuple(sorted(set(expert_ids))):
|
|
794
|
+
raise ValueError("experts must use canonical unique IDs")
|
|
795
|
+
if (
|
|
796
|
+
type(proposals) is not tuple
|
|
797
|
+
or tuple(value.expert_id for value in proposals) != expert_ids
|
|
798
|
+
):
|
|
799
|
+
raise ValueError("proposals must exactly cover runtime experts")
|
|
800
|
+
proposal_by_expert = {value.expert_id: value for value in proposals}
|
|
801
|
+
action_by_sha256 = {
|
|
802
|
+
action.action_sha256: action
|
|
803
|
+
for proposal in proposals
|
|
804
|
+
for action in proposal.actions
|
|
805
|
+
}
|
|
806
|
+
if not set(selected_action_sha256s).issubset(action_by_sha256):
|
|
807
|
+
raise ValueError("selected action is outside the proposal universe")
|
|
808
|
+
selected_by_expert = {
|
|
809
|
+
expert_id: tuple(
|
|
810
|
+
value
|
|
811
|
+
for value in selected_action_sha256s
|
|
812
|
+
if action_by_sha256[value].expert_id == expert_id
|
|
813
|
+
)
|
|
814
|
+
for expert_id in expert_ids
|
|
815
|
+
}
|
|
816
|
+
experts_by_id = {
|
|
817
|
+
identity[0]: expert
|
|
818
|
+
for identity, expert in zip(identities, experts, strict=True)
|
|
819
|
+
}
|
|
820
|
+
active = tuple(
|
|
821
|
+
value for value in expert_ids if selected_by_expert[value]
|
|
822
|
+
)
|
|
823
|
+
|
|
824
|
+
async def evaluate_one(
|
|
825
|
+
expert_id: str,
|
|
826
|
+
) -> MaterializedActionEvaluationBatch:
|
|
827
|
+
expert = experts_by_id[expert_id]
|
|
828
|
+
proposal = proposal_by_expert[expert_id]
|
|
829
|
+
selected = selected_by_expert[expert_id]
|
|
830
|
+
if not counterfactual:
|
|
831
|
+
return await expert.evaluate(proposal, selected)
|
|
832
|
+
if not isinstance(
|
|
833
|
+
expert,
|
|
834
|
+
MaterializedActionCounterfactualEvaluationPort,
|
|
835
|
+
):
|
|
836
|
+
raise TypeError(
|
|
837
|
+
"paired counterfactual evaluation requires the owning "
|
|
838
|
+
"expert to implement "
|
|
839
|
+
"MaterializedActionCounterfactualEvaluationPort"
|
|
840
|
+
)
|
|
841
|
+
return await expert.evaluate_counterfactual(proposal, selected)
|
|
842
|
+
|
|
843
|
+
raw = await asyncio.gather(
|
|
844
|
+
*(evaluate_one(expert_id) for expert_id in active)
|
|
845
|
+
)
|
|
846
|
+
batches = tuple(raw)
|
|
847
|
+
for expert_id, batch in zip(active, batches, strict=True):
|
|
848
|
+
if type(batch) is not MaterializedActionEvaluationBatch:
|
|
849
|
+
raise TypeError("proposal expert returned a foreign evaluation batch")
|
|
850
|
+
batch.__post_init__()
|
|
851
|
+
batch.require_proposal(proposal_by_expert[expert_id])
|
|
852
|
+
if batch.selected_action_sha256s != selected_by_expert[expert_id]:
|
|
853
|
+
raise ValueError("expert evaluated a different selected subset")
|
|
854
|
+
identity = _expert_identity(experts_by_id[expert_id])
|
|
855
|
+
if (
|
|
856
|
+
batch.expert_id,
|
|
857
|
+
batch.expert_version,
|
|
858
|
+
batch.expert_definition_sha256,
|
|
859
|
+
) != identity:
|
|
860
|
+
raise ValueError("evaluation batch has a foreign expert identity")
|
|
861
|
+
evaluated = tuple(
|
|
862
|
+
sorted(
|
|
863
|
+
action_sha256
|
|
864
|
+
for batch in batches
|
|
865
|
+
for action_sha256 in batch.selected_action_sha256s
|
|
866
|
+
)
|
|
867
|
+
)
|
|
868
|
+
if evaluated != selected_action_sha256s:
|
|
869
|
+
raise ValueError("evaluation batches do not close the selected subset")
|
|
870
|
+
return batches
|
|
871
|
+
|
|
872
|
+
|
|
873
|
+
async def evaluate_materialized_action_subset(
|
|
874
|
+
*,
|
|
875
|
+
experts: tuple[MaterializedActionProposalExpertPort, ...],
|
|
876
|
+
proposals: tuple[MaterializedActionProposalBatch, ...],
|
|
877
|
+
selected_action_sha256s: tuple[str, ...],
|
|
878
|
+
) -> tuple[MaterializedActionEvaluationBatch, ...]:
|
|
879
|
+
"""Evaluate an authoritative subset of one sealed proposal universe."""
|
|
880
|
+
|
|
881
|
+
return await _evaluate_materialized_action_subset(
|
|
882
|
+
experts=experts,
|
|
883
|
+
proposals=proposals,
|
|
884
|
+
selected_action_sha256s=selected_action_sha256s,
|
|
885
|
+
counterfactual=False,
|
|
886
|
+
)
|
|
887
|
+
|
|
888
|
+
|
|
889
|
+
async def evaluate_materialized_action_counterfactual_subset(
|
|
890
|
+
*,
|
|
891
|
+
experts: tuple[MaterializedActionProposalExpertPort, ...],
|
|
892
|
+
proposals: tuple[MaterializedActionProposalBatch, ...],
|
|
893
|
+
selected_action_sha256s: tuple[str, ...],
|
|
894
|
+
) -> tuple[MaterializedActionEvaluationBatch, ...]:
|
|
895
|
+
"""Evaluate an excluded real arm without authoritative side effects."""
|
|
896
|
+
|
|
897
|
+
return await _evaluate_materialized_action_subset(
|
|
898
|
+
experts=experts,
|
|
899
|
+
proposals=proposals,
|
|
900
|
+
selected_action_sha256s=selected_action_sha256s,
|
|
901
|
+
counterfactual=True,
|
|
902
|
+
)
|
|
903
|
+
|
|
904
|
+
|
|
905
|
+
@dataclass(frozen=True, slots=True)
|
|
906
|
+
class ResidualPortfolioEvolutionResult:
|
|
907
|
+
"""Authenticated execution evidence before downstream archive credit."""
|
|
908
|
+
|
|
909
|
+
request: ResidualPortfolioDecisionRequest
|
|
910
|
+
proposals: tuple[MaterializedActionProposalBatch, ...]
|
|
911
|
+
broker_decision: MaterializedActionBrokerDecision
|
|
912
|
+
evaluation_batches: tuple[MaterializedActionEvaluationBatch, ...]
|
|
913
|
+
slate_value_definition_sha256: str
|
|
914
|
+
slate_feasibility_definition_sha256: str
|
|
915
|
+
method_definition_sha256: str = (
|
|
916
|
+
RESIDUAL_PORTFOLIO_EVOLUTION_DEFINITION_SHA256
|
|
917
|
+
)
|
|
918
|
+
result_sha256: str = field(init=False)
|
|
919
|
+
|
|
920
|
+
def __post_init__(self) -> None:
|
|
921
|
+
if type(self.request) is not ResidualPortfolioDecisionRequest:
|
|
922
|
+
raise TypeError("request must be exact")
|
|
923
|
+
self.request.__post_init__()
|
|
924
|
+
if type(self.proposals) is not tuple or not self.proposals:
|
|
925
|
+
raise ValueError("result must contain proposal batches")
|
|
926
|
+
for value in self.proposals:
|
|
927
|
+
if type(value) is not MaterializedActionProposalBatch:
|
|
928
|
+
raise TypeError("proposals must contain exact batches")
|
|
929
|
+
value.__post_init__()
|
|
930
|
+
value.require_request(self.request)
|
|
931
|
+
proposal_ids = tuple(value.expert_id for value in self.proposals)
|
|
932
|
+
if proposal_ids != tuple(sorted(set(proposal_ids))):
|
|
933
|
+
raise ValueError("proposal batches must be expert-canonical")
|
|
934
|
+
if type(self.broker_decision) is not MaterializedActionBrokerDecision:
|
|
935
|
+
raise TypeError("broker_decision must be exact")
|
|
936
|
+
self.broker_decision.__post_init__()
|
|
937
|
+
if len(self.broker_decision.selected_actions) != self.request.evaluation_slots:
|
|
938
|
+
raise ValueError("broker decision does not fill evaluation capacity")
|
|
939
|
+
action_by_sha256 = {
|
|
940
|
+
action.action_sha256: action
|
|
941
|
+
for proposal in self.proposals
|
|
942
|
+
for action in proposal.actions
|
|
943
|
+
}
|
|
944
|
+
if len(action_by_sha256) != sum(
|
|
945
|
+
len(value.actions) for value in self.proposals
|
|
946
|
+
):
|
|
947
|
+
raise ValueError("proposal union repeats an action identity")
|
|
948
|
+
for action in self.broker_decision.selected_actions:
|
|
949
|
+
if action_by_sha256.get(action.action_sha256) != action:
|
|
950
|
+
raise ValueError("broker selected outside the proposal union")
|
|
951
|
+
if type(self.evaluation_batches) is not tuple:
|
|
952
|
+
raise TypeError("evaluation_batches must be an exact tuple")
|
|
953
|
+
proposal_by_expert = {value.expert_id: value for value in self.proposals}
|
|
954
|
+
for value in self.evaluation_batches:
|
|
955
|
+
if type(value) is not MaterializedActionEvaluationBatch:
|
|
956
|
+
raise TypeError("evaluation_batches must contain exact values")
|
|
957
|
+
value.__post_init__()
|
|
958
|
+
try:
|
|
959
|
+
proposal = proposal_by_expert[value.expert_id]
|
|
960
|
+
except KeyError as error:
|
|
961
|
+
raise ValueError(
|
|
962
|
+
"evaluation names an absent proposal expert"
|
|
963
|
+
) from error
|
|
964
|
+
value.require_proposal(proposal)
|
|
965
|
+
evaluation_expert_ids = tuple(
|
|
966
|
+
value.expert_id for value in self.evaluation_batches
|
|
967
|
+
)
|
|
968
|
+
if evaluation_expert_ids != tuple(sorted(set(evaluation_expert_ids))):
|
|
969
|
+
raise ValueError("evaluation batches must be expert-canonical")
|
|
970
|
+
selected = tuple(
|
|
971
|
+
value.action_sha256 for value in self.broker_decision.selected_actions
|
|
972
|
+
)
|
|
973
|
+
evaluated = tuple(
|
|
974
|
+
sorted(
|
|
975
|
+
action_sha256
|
|
976
|
+
for batch in self.evaluation_batches
|
|
977
|
+
for action_sha256 in batch.selected_action_sha256s
|
|
978
|
+
)
|
|
979
|
+
)
|
|
980
|
+
if tuple(sorted(selected)) != evaluated:
|
|
981
|
+
raise ValueError("real evaluations do not exactly cover the broker slate")
|
|
982
|
+
for name in (
|
|
983
|
+
"slate_value_definition_sha256",
|
|
984
|
+
"slate_feasibility_definition_sha256",
|
|
985
|
+
"method_definition_sha256",
|
|
986
|
+
):
|
|
987
|
+
require_sha256(getattr(self, name), name)
|
|
988
|
+
object.__setattr__(
|
|
989
|
+
self,
|
|
990
|
+
"result_sha256",
|
|
991
|
+
_hash(_RESULT_DOMAIN, self._unsigned_record()),
|
|
992
|
+
)
|
|
993
|
+
|
|
994
|
+
@property
|
|
995
|
+
def evaluations(self) -> tuple[MaterializedActionEvaluation, ...]:
|
|
996
|
+
by_action = {
|
|
997
|
+
value.action.action_sha256: value
|
|
998
|
+
for batch in self.evaluation_batches
|
|
999
|
+
for value in batch.evaluations
|
|
1000
|
+
}
|
|
1001
|
+
return tuple(
|
|
1002
|
+
by_action[value.action_sha256]
|
|
1003
|
+
for value in self.broker_decision.selected_actions
|
|
1004
|
+
)
|
|
1005
|
+
|
|
1006
|
+
@property
|
|
1007
|
+
def candidates(self) -> tuple[EvolutionCandidate, ...]:
|
|
1008
|
+
return tuple(value.candidate for value in self.evaluations)
|
|
1009
|
+
|
|
1010
|
+
def _unsigned_record(self) -> dict[str, object]:
|
|
1011
|
+
return {
|
|
1012
|
+
"schema_version": 1,
|
|
1013
|
+
"method": {
|
|
1014
|
+
"method_id": RESIDUAL_PORTFOLIO_EVOLUTION_ID,
|
|
1015
|
+
"method_version": RESIDUAL_PORTFOLIO_EVOLUTION_VERSION,
|
|
1016
|
+
"definition_sha256": self.method_definition_sha256,
|
|
1017
|
+
},
|
|
1018
|
+
"request_sha256": self.request.request_sha256,
|
|
1019
|
+
"proposal_sha256s": [value.proposal_sha256 for value in self.proposals],
|
|
1020
|
+
"broker_decision_sha256": self.broker_decision.decision_sha256,
|
|
1021
|
+
"evaluation_batch_sha256s": [
|
|
1022
|
+
value.batch_sha256 for value in self.evaluation_batches
|
|
1023
|
+
],
|
|
1024
|
+
"selected_action_sha256s": [
|
|
1025
|
+
value.action_sha256
|
|
1026
|
+
for value in self.broker_decision.selected_actions
|
|
1027
|
+
],
|
|
1028
|
+
"candidate_records": [
|
|
1029
|
+
_candidate_record(value) for value in self.candidates
|
|
1030
|
+
],
|
|
1031
|
+
"slate_value_definition_sha256": (
|
|
1032
|
+
self.slate_value_definition_sha256
|
|
1033
|
+
),
|
|
1034
|
+
"slate_feasibility_definition_sha256": (
|
|
1035
|
+
self.slate_feasibility_definition_sha256
|
|
1036
|
+
),
|
|
1037
|
+
"archive_credit_included": False,
|
|
1038
|
+
"lineage_return_resolution_included": False,
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
def to_record(
|
|
1042
|
+
self,
|
|
1043
|
+
*,
|
|
1044
|
+
include_allocation_evidence: bool = False,
|
|
1045
|
+
) -> dict[str, object]:
|
|
1046
|
+
self.__post_init__()
|
|
1047
|
+
return {
|
|
1048
|
+
**self._unsigned_record(),
|
|
1049
|
+
"broker_decision": self.broker_decision.to_record(
|
|
1050
|
+
include_allocation_evidence=include_allocation_evidence,
|
|
1051
|
+
),
|
|
1052
|
+
"proposals": [value.to_record() for value in self.proposals],
|
|
1053
|
+
"evaluation_batches": [
|
|
1054
|
+
value.to_record() for value in self.evaluation_batches
|
|
1055
|
+
],
|
|
1056
|
+
"result_sha256": self.result_sha256,
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
|
|
1060
|
+
@dataclass(frozen=True, slots=True)
|
|
1061
|
+
class ResidualPortfolioEvolution:
|
|
1062
|
+
"""Execute one workload-blind residual expert market."""
|
|
1063
|
+
|
|
1064
|
+
experts: tuple[MaterializedActionProposalExpertPort, ...]
|
|
1065
|
+
broker: RegretBrokeredMaterializedActionPolicy
|
|
1066
|
+
slate_value: MaterializedSlateValuePort
|
|
1067
|
+
slate_feasibility: MaterializedSlateFeasibilityPort
|
|
1068
|
+
allocation_policy: MaterializedActionAllocationPolicyPort | None = field(
|
|
1069
|
+
default=None,
|
|
1070
|
+
repr=False,
|
|
1071
|
+
compare=False,
|
|
1072
|
+
)
|
|
1073
|
+
|
|
1074
|
+
def __post_init__(self) -> None:
|
|
1075
|
+
if type(self.experts) is not tuple or not self.experts:
|
|
1076
|
+
raise ValueError("experts must be a non-empty exact tuple")
|
|
1077
|
+
identities = tuple(_expert_identity(value) for value in self.experts)
|
|
1078
|
+
expert_ids = tuple(value[0] for value in identities)
|
|
1079
|
+
if expert_ids != tuple(sorted(set(expert_ids))):
|
|
1080
|
+
raise ValueError("experts must use canonical unique IDs")
|
|
1081
|
+
if type(self.broker) is not RegretBrokeredMaterializedActionPolicy:
|
|
1082
|
+
raise TypeError("broker must be an exact regret-broker policy")
|
|
1083
|
+
self.broker.__post_init__()
|
|
1084
|
+
if not isinstance(self.slate_value, MaterializedSlateValuePort):
|
|
1085
|
+
raise TypeError("slate_value must implement its port")
|
|
1086
|
+
if not isinstance(self.slate_feasibility, MaterializedSlateFeasibilityPort):
|
|
1087
|
+
raise TypeError("slate_feasibility must implement its port")
|
|
1088
|
+
require_sha256(
|
|
1089
|
+
self.slate_value.definition_sha256,
|
|
1090
|
+
"slate value definition_sha256",
|
|
1091
|
+
)
|
|
1092
|
+
require_sha256(
|
|
1093
|
+
self.slate_feasibility.definition_sha256,
|
|
1094
|
+
"slate feasibility definition_sha256",
|
|
1095
|
+
)
|
|
1096
|
+
if self.allocation_policy is not None:
|
|
1097
|
+
_allocation_policy_identity(self.allocation_policy)
|
|
1098
|
+
|
|
1099
|
+
async def run(
|
|
1100
|
+
self,
|
|
1101
|
+
request: ResidualPortfolioDecisionRequest,
|
|
1102
|
+
) -> ResidualPortfolioEvolutionResult:
|
|
1103
|
+
if type(request) is not ResidualPortfolioDecisionRequest:
|
|
1104
|
+
raise TypeError("request must be an exact residual portfolio request")
|
|
1105
|
+
request.__post_init__()
|
|
1106
|
+
proposals = await propose_materialized_action_batches(
|
|
1107
|
+
experts=self.experts,
|
|
1108
|
+
request=request,
|
|
1109
|
+
)
|
|
1110
|
+
|
|
1111
|
+
actions = tuple(
|
|
1112
|
+
action for proposal in proposals for action in proposal.actions
|
|
1113
|
+
)
|
|
1114
|
+
|
|
1115
|
+
allocation_requirement = None
|
|
1116
|
+
if self.allocation_policy is not None:
|
|
1117
|
+
allocation_requirement = await self.allocation_policy.require(
|
|
1118
|
+
request,
|
|
1119
|
+
proposals,
|
|
1120
|
+
)
|
|
1121
|
+
if (
|
|
1122
|
+
type(allocation_requirement)
|
|
1123
|
+
is not MaterializedActionAllocationRequirement
|
|
1124
|
+
):
|
|
1125
|
+
raise TypeError(
|
|
1126
|
+
"allocation policy returned a foreign requirement"
|
|
1127
|
+
)
|
|
1128
|
+
allocation_requirement.__post_init__()
|
|
1129
|
+
policy_identity = _allocation_policy_identity(
|
|
1130
|
+
self.allocation_policy
|
|
1131
|
+
)
|
|
1132
|
+
if (
|
|
1133
|
+
allocation_requirement.policy_id,
|
|
1134
|
+
allocation_requirement.policy_version,
|
|
1135
|
+
allocation_requirement.policy_definition_sha256,
|
|
1136
|
+
) != policy_identity:
|
|
1137
|
+
raise ValueError(
|
|
1138
|
+
"allocation requirement differs from its policy"
|
|
1139
|
+
)
|
|
1140
|
+
if (
|
|
1141
|
+
allocation_requirement.residual_request_sha256
|
|
1142
|
+
!= request.request_sha256
|
|
1143
|
+
):
|
|
1144
|
+
raise ValueError(
|
|
1145
|
+
"allocation requirement targets another residual request"
|
|
1146
|
+
)
|
|
1147
|
+
proposal_sha256s = tuple(
|
|
1148
|
+
sorted(value.proposal_sha256 for value in proposals)
|
|
1149
|
+
)
|
|
1150
|
+
if (
|
|
1151
|
+
allocation_requirement.proposal_sha256s
|
|
1152
|
+
!= proposal_sha256s
|
|
1153
|
+
):
|
|
1154
|
+
raise ValueError(
|
|
1155
|
+
"allocation requirement targets another proposal universe"
|
|
1156
|
+
)
|
|
1157
|
+
action_by_sha256 = {
|
|
1158
|
+
value.action_sha256: value for value in actions
|
|
1159
|
+
}
|
|
1160
|
+
required_action_sha256s = (
|
|
1161
|
+
allocation_requirement.required_action_sha256s
|
|
1162
|
+
)
|
|
1163
|
+
if not set(required_action_sha256s).issubset(action_by_sha256):
|
|
1164
|
+
raise ValueError(
|
|
1165
|
+
"allocation policy required an action outside the "
|
|
1166
|
+
"sealed proposal union"
|
|
1167
|
+
)
|
|
1168
|
+
if len(required_action_sha256s) > request.evaluation_slots:
|
|
1169
|
+
raise ValueError(
|
|
1170
|
+
"allocation requirement exceeds evaluation capacity"
|
|
1171
|
+
)
|
|
1172
|
+
required_phenotypes = {
|
|
1173
|
+
action_by_sha256[value].phenotype_identity_sha256
|
|
1174
|
+
for value in required_action_sha256s
|
|
1175
|
+
}
|
|
1176
|
+
if len(required_phenotypes) != len(required_action_sha256s):
|
|
1177
|
+
raise ValueError(
|
|
1178
|
+
"allocation requirement repeats a materialized phenotype"
|
|
1179
|
+
)
|
|
1180
|
+
|
|
1181
|
+
broker_decision = self.broker.select(
|
|
1182
|
+
MaterializedActionBrokerRequest(
|
|
1183
|
+
actions=actions,
|
|
1184
|
+
evaluation_slots=request.evaluation_slots,
|
|
1185
|
+
slate_value=self.slate_value,
|
|
1186
|
+
slate_feasibility=self.slate_feasibility,
|
|
1187
|
+
reference_escrow_slots=request.reference_escrow_slots,
|
|
1188
|
+
allocation_requirement=allocation_requirement,
|
|
1189
|
+
)
|
|
1190
|
+
)
|
|
1191
|
+
evaluation_batches = await evaluate_materialized_action_subset(
|
|
1192
|
+
experts=self.experts,
|
|
1193
|
+
proposals=proposals,
|
|
1194
|
+
selected_action_sha256s=tuple(
|
|
1195
|
+
value.action_sha256
|
|
1196
|
+
for value in broker_decision.selected_actions
|
|
1197
|
+
),
|
|
1198
|
+
)
|
|
1199
|
+
|
|
1200
|
+
return ResidualPortfolioEvolutionResult(
|
|
1201
|
+
request=request,
|
|
1202
|
+
proposals=proposals,
|
|
1203
|
+
broker_decision=broker_decision,
|
|
1204
|
+
evaluation_batches=evaluation_batches,
|
|
1205
|
+
slate_value_definition_sha256=self.slate_value.definition_sha256,
|
|
1206
|
+
slate_feasibility_definition_sha256=(
|
|
1207
|
+
self.slate_feasibility.definition_sha256
|
|
1208
|
+
),
|
|
1209
|
+
)
|
|
1210
|
+
|
|
1211
|
+
|
|
1212
|
+
__all__ = [
|
|
1213
|
+
"RESIDUAL_PORTFOLIO_EVOLUTION_DEFINITION_SHA256",
|
|
1214
|
+
"RESIDUAL_PORTFOLIO_EVOLUTION_ID",
|
|
1215
|
+
"RESIDUAL_PORTFOLIO_EVOLUTION_VERSION",
|
|
1216
|
+
"MaterializedActionEvaluation",
|
|
1217
|
+
"MaterializedActionEvaluationBatch",
|
|
1218
|
+
"MaterializedActionAllocationPolicyPort",
|
|
1219
|
+
"MaterializedActionCounterfactualEvaluationPort",
|
|
1220
|
+
"MaterializedActionProposalBatch",
|
|
1221
|
+
"MaterializedActionProposalExpertPort",
|
|
1222
|
+
"ResidualPortfolioDecisionRequest",
|
|
1223
|
+
"ResidualPortfolioEvolution",
|
|
1224
|
+
"ResidualPortfolioEvolutionResult",
|
|
1225
|
+
"evaluate_materialized_action_counterfactual_subset",
|
|
1226
|
+
"evaluate_materialized_action_subset",
|
|
1227
|
+
"propose_materialized_action_batches",
|
|
1228
|
+
]
|