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,1613 @@
|
|
|
1
|
+
"""Card-blind empirical feedback from evaluated portfolio actions.
|
|
2
|
+
|
|
3
|
+
The selector's complete card/action attribution remains in its internal audit.
|
|
4
|
+
This module exposes only treatment-neutral action facts to later generations:
|
|
5
|
+
sealed option identity, categorical forecasts, meaningful observed directions,
|
|
6
|
+
correctness, parent relation, and reward. Candidate-attributable infeasibility
|
|
7
|
+
is retained as a no-yield action but is never converted into metric calibration
|
|
8
|
+
or a parent relation. Consequently a currently unassigned card cannot leak back
|
|
9
|
+
through outcome history.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import hashlib
|
|
15
|
+
import json
|
|
16
|
+
import math
|
|
17
|
+
import re
|
|
18
|
+
from dataclasses import dataclass, field
|
|
19
|
+
from enum import Enum
|
|
20
|
+
from typing import Protocol, Sequence
|
|
21
|
+
|
|
22
|
+
from agent_evolve.application.agentic_evolution import EvolutionCandidate
|
|
23
|
+
from agent_evolve.application.decision_metric_projection import (
|
|
24
|
+
project_candidate_decision_metrics,
|
|
25
|
+
)
|
|
26
|
+
from agent_evolve.application.contextual_campaign_outcomes import (
|
|
27
|
+
ContextualPortfolioOutcomeBatch,
|
|
28
|
+
observe_contextual_portfolio_outcomes,
|
|
29
|
+
)
|
|
30
|
+
from agent_evolve.application.contextual_search_controller import (
|
|
31
|
+
ContextualSearchLedger,
|
|
32
|
+
)
|
|
33
|
+
from agent_evolve.application.outcome_relation import OutcomeRelation
|
|
34
|
+
from agent_evolve.application.evolution_campaign import (
|
|
35
|
+
ArchiveUtilitySnapshot,
|
|
36
|
+
CampaignGenerationKind,
|
|
37
|
+
)
|
|
38
|
+
from agent_evolve.application.campaign_execution import CampaignStageRequest
|
|
39
|
+
from agent_evolve.application.portfolio_evolution import (
|
|
40
|
+
PortfolioCandidateFailureEvidence,
|
|
41
|
+
PortfolioMemberDisposition,
|
|
42
|
+
PortfolioVariationWaveRequest,
|
|
43
|
+
PortfolioVariationWaveResult,
|
|
44
|
+
)
|
|
45
|
+
from agent_evolve.domain.patch import require_sha256
|
|
46
|
+
from agent_evolve.domain.typed_json import (
|
|
47
|
+
FrozenJsonObject,
|
|
48
|
+
freeze_json,
|
|
49
|
+
thaw_json,
|
|
50
|
+
typed_json_sha256,
|
|
51
|
+
)
|
|
52
|
+
from agent_evolve.policies.selection.forecast_calibration import (
|
|
53
|
+
BetaCorrectnessPrior,
|
|
54
|
+
ForecastCalibrationObservation,
|
|
55
|
+
ForecastCalibrationScope,
|
|
56
|
+
ForecastCalibrationSnapshot,
|
|
57
|
+
ForecastPredictionReceipt,
|
|
58
|
+
MeaningfulDirectionRequest,
|
|
59
|
+
MeaningfulMetricDirectionAdjudicator,
|
|
60
|
+
build_calibration_snapshot,
|
|
61
|
+
observe_forecast,
|
|
62
|
+
)
|
|
63
|
+
from agent_evolve.ports.decision_metric_projection import DecisionMetricProjection
|
|
64
|
+
from agent_evolve.ports.agentic_generator import MetricEffectDirection
|
|
65
|
+
from agent_evolve.ports.contextual_search_allocation import (
|
|
66
|
+
ContextualPortfolioAllocationRealization,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
_TOKEN = re.compile(r"^[a-z][a-z0-9_.:-]{0,255}$")
|
|
71
|
+
_CANONICAL_JSON_PATH = re.compile(
|
|
72
|
+
r"^\$\.[^.\[\]\s]+(?:\.[^.\[\]\s]+|\[(?:0|[1-9][0-9]*)\])*$"
|
|
73
|
+
)
|
|
74
|
+
_CANDIDATE_OUTCOME_DOMAIN = b"agent-evolve:portfolio-parent-outcome:v1\x00"
|
|
75
|
+
_ACTION_DOMAIN = b"agent-evolve:portfolio-action-feedback:v1\x00"
|
|
76
|
+
_RECEIPT_DOMAIN = b"agent-evolve:portfolio-outcome-feedback:v1\x00"
|
|
77
|
+
_METRIC_TRANSITION_DOMAIN = b"agent-evolve:decision-metric-transition:v1\x00"
|
|
78
|
+
_CONTEXTUAL_QUERY_DOMAIN = b"agent-evolve:contextual-outcome-query:v1\x00"
|
|
79
|
+
_CONTEXTUAL_HISTORY_DOMAIN = b"agent-evolve:contextual-outcome-history:v1\x00"
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _canonical_json(value: object) -> bytes:
|
|
83
|
+
return json.dumps(
|
|
84
|
+
value,
|
|
85
|
+
allow_nan=False,
|
|
86
|
+
ensure_ascii=True,
|
|
87
|
+
separators=(",", ":"),
|
|
88
|
+
sort_keys=True,
|
|
89
|
+
).encode("ascii", errors="strict")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _hash(domain: bytes, value: object) -> str:
|
|
93
|
+
return hashlib.sha256(domain + _canonical_json(value)).hexdigest()
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _candidate_outcome_sha256(candidate: EvolutionCandidate) -> str:
|
|
97
|
+
EvolutionCandidate.__post_init__(candidate)
|
|
98
|
+
return _hash(
|
|
99
|
+
_CANDIDATE_OUTCOME_DOMAIN,
|
|
100
|
+
{
|
|
101
|
+
"candidate_id": candidate.candidate_id.value,
|
|
102
|
+
"configuration_sha256": candidate.occurrence.configuration_hash,
|
|
103
|
+
"objectives": [
|
|
104
|
+
[metric_id, value.hex()] for metric_id, value in candidate.objectives
|
|
105
|
+
],
|
|
106
|
+
"valid": candidate.valid,
|
|
107
|
+
"operator_compliant": candidate.operator_compliant,
|
|
108
|
+
"evidence_compliant": candidate.evidence_compliant,
|
|
109
|
+
"detailed_evaluation_sha256": (
|
|
110
|
+
None
|
|
111
|
+
if candidate.detailed_evaluation is None
|
|
112
|
+
else candidate.detailed_evaluation.evidence_sha256
|
|
113
|
+
),
|
|
114
|
+
},
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _canonical_tokens(values: tuple[str, ...], *, name: str) -> tuple[str, ...]:
|
|
119
|
+
if type(values) is not tuple or any(
|
|
120
|
+
type(value) is not str or _TOKEN.fullmatch(value) is None for value in values
|
|
121
|
+
):
|
|
122
|
+
raise TypeError(f"{name} must be an exact tuple of closed tokens")
|
|
123
|
+
if values != tuple(sorted(set(values))):
|
|
124
|
+
raise ValueError(f"{name} must be unique and canonically sorted")
|
|
125
|
+
return values
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _canonical_paths(values: tuple[str, ...], *, name: str) -> tuple[str, ...]:
|
|
129
|
+
if type(values) is not tuple or any(
|
|
130
|
+
type(value) is not str or _CANONICAL_JSON_PATH.fullmatch(value) is None
|
|
131
|
+
for value in values
|
|
132
|
+
):
|
|
133
|
+
raise TypeError(f"{name} must be an exact candidate-path tuple")
|
|
134
|
+
if values != tuple(sorted(set(values))):
|
|
135
|
+
raise ValueError(f"{name} must be unique and canonically sorted")
|
|
136
|
+
return values
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _paths_overlap(first: str, second: str) -> bool:
|
|
140
|
+
return (
|
|
141
|
+
first == second
|
|
142
|
+
or first.startswith(second + ".")
|
|
143
|
+
or first.startswith(second + "[")
|
|
144
|
+
or second.startswith(first + ".")
|
|
145
|
+
or second.startswith(first + "[")
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
@dataclass(frozen=True, slots=True)
|
|
150
|
+
class DecisionMetricTransition:
|
|
151
|
+
"""One engine-authored parent/child metric transition.
|
|
152
|
+
|
|
153
|
+
Direction-only history is insufficient for contextual transfer: the same
|
|
154
|
+
target can be an increase for one parent and a decrease for another. This
|
|
155
|
+
receipt preserves the exact numeric baseline and child value while keeping
|
|
156
|
+
the benchmark-owned meaningful-direction adjudication authoritative.
|
|
157
|
+
"""
|
|
158
|
+
|
|
159
|
+
metric_id: str
|
|
160
|
+
parent_value: float
|
|
161
|
+
child_value: float
|
|
162
|
+
actual_direction: MetricEffectDirection
|
|
163
|
+
adjudication_receipt_sha256: str
|
|
164
|
+
|
|
165
|
+
def __post_init__(self) -> None:
|
|
166
|
+
if type(self.metric_id) is not str or _TOKEN.fullmatch(self.metric_id) is None:
|
|
167
|
+
raise ValueError("metric_id must use the closed token grammar")
|
|
168
|
+
for name in ("parent_value", "child_value"):
|
|
169
|
+
value = getattr(self, name)
|
|
170
|
+
if type(value) is not float or not math.isfinite(value):
|
|
171
|
+
raise TypeError(f"{name} must be a finite canonical float")
|
|
172
|
+
if (
|
|
173
|
+
type(self.actual_direction) is not MetricEffectDirection
|
|
174
|
+
or self.actual_direction is MetricEffectDirection.UNKNOWN
|
|
175
|
+
):
|
|
176
|
+
raise ValueError("actual_direction must be a known metric direction")
|
|
177
|
+
require_sha256(
|
|
178
|
+
self.adjudication_receipt_sha256,
|
|
179
|
+
"adjudication_receipt_sha256",
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
def _unsigned_record(self) -> dict[str, object]:
|
|
183
|
+
self.__post_init__()
|
|
184
|
+
return {
|
|
185
|
+
"schema_version": 1,
|
|
186
|
+
"metric_id": self.metric_id,
|
|
187
|
+
"parent_value_hex": self.parent_value.hex(),
|
|
188
|
+
"child_value_hex": self.child_value.hex(),
|
|
189
|
+
"delta_hex": (self.child_value - self.parent_value).hex(),
|
|
190
|
+
"actual_direction": self.actual_direction.value,
|
|
191
|
+
"adjudication_receipt_sha256": self.adjudication_receipt_sha256,
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
@property
|
|
195
|
+
def transition_sha256(self) -> str:
|
|
196
|
+
return _hash(_METRIC_TRANSITION_DOMAIN, self._unsigned_record())
|
|
197
|
+
|
|
198
|
+
def to_record(self) -> dict[str, object]:
|
|
199
|
+
return {
|
|
200
|
+
**self._unsigned_record(),
|
|
201
|
+
"transition_sha256": self.transition_sha256,
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
class OutcomeTransferScope(str, Enum):
|
|
206
|
+
"""How far an historical action is transferred to the current parent."""
|
|
207
|
+
|
|
208
|
+
SAME_PARENT = "same_parent"
|
|
209
|
+
SAME_LINEAGE = "same_lineage"
|
|
210
|
+
CROSS_LINEAGE_ANALOGY = "cross_lineage_analogy"
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
@dataclass(frozen=True, slots=True)
|
|
214
|
+
class ContextualOutcomeQuery:
|
|
215
|
+
"""Deterministic prior-only retrieval with cross-lineage transfer opt-in."""
|
|
216
|
+
|
|
217
|
+
current_parent_candidate_id: str
|
|
218
|
+
current_parent_configuration_sha256: str
|
|
219
|
+
cutoff_wave_index_exclusive: int
|
|
220
|
+
lineage_candidate_ids: tuple[str, ...] = ()
|
|
221
|
+
families: tuple[str, ...] = ()
|
|
222
|
+
changed_paths: tuple[str, ...] = ()
|
|
223
|
+
max_actions: int = 24
|
|
224
|
+
include_cross_lineage_analogies: bool = False
|
|
225
|
+
|
|
226
|
+
def __post_init__(self) -> None:
|
|
227
|
+
if (
|
|
228
|
+
type(self.current_parent_candidate_id) is not str
|
|
229
|
+
or _TOKEN.fullmatch(self.current_parent_candidate_id) is None
|
|
230
|
+
):
|
|
231
|
+
raise ValueError(
|
|
232
|
+
"current_parent_candidate_id must use the closed token grammar"
|
|
233
|
+
)
|
|
234
|
+
require_sha256(
|
|
235
|
+
self.current_parent_configuration_sha256,
|
|
236
|
+
"current_parent_configuration_sha256",
|
|
237
|
+
)
|
|
238
|
+
if (
|
|
239
|
+
type(self.cutoff_wave_index_exclusive) is not int
|
|
240
|
+
or self.cutoff_wave_index_exclusive <= 0
|
|
241
|
+
):
|
|
242
|
+
raise ValueError("cutoff_wave_index_exclusive must be positive")
|
|
243
|
+
_canonical_tokens(self.lineage_candidate_ids, name="lineage_candidate_ids")
|
|
244
|
+
_canonical_tokens(self.families, name="families")
|
|
245
|
+
_canonical_paths(self.changed_paths, name="changed_paths")
|
|
246
|
+
if type(self.max_actions) is not int or self.max_actions <= 0:
|
|
247
|
+
raise ValueError("max_actions must be positive")
|
|
248
|
+
if type(self.include_cross_lineage_analogies) is not bool:
|
|
249
|
+
raise TypeError("include_cross_lineage_analogies must be an exact bool")
|
|
250
|
+
|
|
251
|
+
def _unsigned_record(self) -> dict[str, object]:
|
|
252
|
+
self.__post_init__()
|
|
253
|
+
return {
|
|
254
|
+
"schema_version": 1,
|
|
255
|
+
"current_parent_candidate_id": self.current_parent_candidate_id,
|
|
256
|
+
"current_parent_configuration_sha256": (
|
|
257
|
+
self.current_parent_configuration_sha256
|
|
258
|
+
),
|
|
259
|
+
"cutoff_wave_index_exclusive": self.cutoff_wave_index_exclusive,
|
|
260
|
+
"lineage_candidate_ids": list(self.lineage_candidate_ids),
|
|
261
|
+
"families": list(self.families),
|
|
262
|
+
"changed_paths": list(self.changed_paths),
|
|
263
|
+
"max_actions": self.max_actions,
|
|
264
|
+
"include_cross_lineage_analogies": (self.include_cross_lineage_analogies),
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
@property
|
|
268
|
+
def query_sha256(self) -> str:
|
|
269
|
+
return _hash(_CONTEXTUAL_QUERY_DOMAIN, self._unsigned_record())
|
|
270
|
+
|
|
271
|
+
def to_record(self) -> dict[str, object]:
|
|
272
|
+
return {**self._unsigned_record(), "query_sha256": self.query_sha256}
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
@dataclass(frozen=True, slots=True)
|
|
276
|
+
class PortfolioActionOutcomeFeedback:
|
|
277
|
+
"""One terminal selected action with no memory-card or rationale fields.
|
|
278
|
+
|
|
279
|
+
A scored action carries calibrated metric transitions and one parent
|
|
280
|
+
relation. Candidate-attributable infeasibility instead remains as a
|
|
281
|
+
no-yield action: it is useful search history, but cannot be laundered into
|
|
282
|
+
forecast calibration, a metric transition, or a parent comparison.
|
|
283
|
+
"""
|
|
284
|
+
|
|
285
|
+
wave_index: int
|
|
286
|
+
request_sha256: str
|
|
287
|
+
ranked_decision_sha256: str
|
|
288
|
+
proposal_sha256: str
|
|
289
|
+
parent_candidate_id: str
|
|
290
|
+
parent_candidate_identity_sha256: str
|
|
291
|
+
parent_outcome_sha256: str
|
|
292
|
+
candidate_id: str
|
|
293
|
+
candidate_outcome_sha256: str
|
|
294
|
+
option_id: str
|
|
295
|
+
option_identity_sha256: str
|
|
296
|
+
family: str
|
|
297
|
+
changed_paths: tuple[str, ...]
|
|
298
|
+
observations: tuple[ForecastCalibrationObservation, ...]
|
|
299
|
+
parent_relation: OutcomeRelation | None
|
|
300
|
+
reward: float
|
|
301
|
+
dominates_parent: bool
|
|
302
|
+
better_than_parent: bool
|
|
303
|
+
metric_transitions: tuple[DecisionMetricTransition, ...] = ()
|
|
304
|
+
disposition: PortfolioMemberDisposition = PortfolioMemberDisposition.SCORED
|
|
305
|
+
candidate_failure: PortfolioCandidateFailureEvidence | None = None
|
|
306
|
+
|
|
307
|
+
def __post_init__(self) -> None:
|
|
308
|
+
if type(self.wave_index) is not int or self.wave_index <= 0:
|
|
309
|
+
raise ValueError("wave_index must be a positive exact integer")
|
|
310
|
+
for name in (
|
|
311
|
+
"request_sha256",
|
|
312
|
+
"ranked_decision_sha256",
|
|
313
|
+
"proposal_sha256",
|
|
314
|
+
"parent_candidate_identity_sha256",
|
|
315
|
+
"parent_outcome_sha256",
|
|
316
|
+
"candidate_outcome_sha256",
|
|
317
|
+
"option_identity_sha256",
|
|
318
|
+
):
|
|
319
|
+
require_sha256(getattr(self, name), name)
|
|
320
|
+
for name in ("parent_candidate_id", "candidate_id", "option_id", "family"):
|
|
321
|
+
value = getattr(self, name)
|
|
322
|
+
if type(value) is not str or _TOKEN.fullmatch(value) is None:
|
|
323
|
+
raise ValueError(f"{name} must use the closed token grammar")
|
|
324
|
+
if self.parent_candidate_id == self.candidate_id:
|
|
325
|
+
raise ValueError("action feedback child cannot equal its parent")
|
|
326
|
+
if type(self.changed_paths) is not tuple or any(
|
|
327
|
+
type(value) is not str or not value.startswith("$.")
|
|
328
|
+
for value in self.changed_paths
|
|
329
|
+
):
|
|
330
|
+
raise TypeError("changed_paths must be an exact candidate-path tuple")
|
|
331
|
+
if not self.changed_paths or self.changed_paths != tuple(
|
|
332
|
+
sorted(set(self.changed_paths))
|
|
333
|
+
):
|
|
334
|
+
raise ValueError("changed_paths must be non-empty, unique, and canonical")
|
|
335
|
+
if type(self.observations) is not tuple or any(
|
|
336
|
+
type(value) is not ForecastCalibrationObservation
|
|
337
|
+
for value in self.observations
|
|
338
|
+
):
|
|
339
|
+
raise TypeError("observations must contain exact calibration observations")
|
|
340
|
+
for value in self.observations:
|
|
341
|
+
value.revalidate()
|
|
342
|
+
metric_ids = tuple(value.prediction.metric_id for value in self.observations)
|
|
343
|
+
if metric_ids != tuple(sorted(set(metric_ids))):
|
|
344
|
+
raise ValueError("observations must use unique canonical metric order")
|
|
345
|
+
for value in self.observations:
|
|
346
|
+
prediction = value.prediction
|
|
347
|
+
if (
|
|
348
|
+
prediction.wave_index != self.wave_index
|
|
349
|
+
or prediction.selector_decision_sha256 != self.proposal_sha256
|
|
350
|
+
or prediction.parent_candidate_identity_sha256
|
|
351
|
+
!= self.parent_candidate_identity_sha256
|
|
352
|
+
or prediction.option_id != self.option_id
|
|
353
|
+
or prediction.option_identity_sha256 != self.option_identity_sha256
|
|
354
|
+
or prediction.family != self.family
|
|
355
|
+
or value.adjudication.parent_outcome_sha256
|
|
356
|
+
!= self.parent_outcome_sha256
|
|
357
|
+
or value.adjudication.child_outcome_sha256
|
|
358
|
+
!= self.candidate_outcome_sha256
|
|
359
|
+
):
|
|
360
|
+
raise ValueError("forecast observation belongs to a foreign action")
|
|
361
|
+
if type(self.reward) is not float or not math.isfinite(self.reward):
|
|
362
|
+
raise TypeError("reward must be a finite canonical float")
|
|
363
|
+
if (
|
|
364
|
+
type(self.dominates_parent) is not bool
|
|
365
|
+
or type(self.better_than_parent) is not bool
|
|
366
|
+
):
|
|
367
|
+
raise TypeError("parent-comparison projections must be exact bools")
|
|
368
|
+
if type(self.metric_transitions) is not tuple or any(
|
|
369
|
+
type(value) is not DecisionMetricTransition
|
|
370
|
+
for value in self.metric_transitions
|
|
371
|
+
):
|
|
372
|
+
raise TypeError(
|
|
373
|
+
"metric_transitions must contain exact DecisionMetricTransition values"
|
|
374
|
+
)
|
|
375
|
+
for value in self.metric_transitions:
|
|
376
|
+
DecisionMetricTransition.__post_init__(value)
|
|
377
|
+
transition_metric_ids = tuple(
|
|
378
|
+
value.metric_id for value in self.metric_transitions
|
|
379
|
+
)
|
|
380
|
+
if transition_metric_ids and transition_metric_ids != metric_ids:
|
|
381
|
+
raise ValueError(
|
|
382
|
+
"metric transitions must exactly cover observation metric order"
|
|
383
|
+
)
|
|
384
|
+
if self.metric_transitions:
|
|
385
|
+
for transition, observation in zip(
|
|
386
|
+
self.metric_transitions,
|
|
387
|
+
self.observations,
|
|
388
|
+
strict=True,
|
|
389
|
+
):
|
|
390
|
+
numeric_request = MeaningfulDirectionRequest(
|
|
391
|
+
benchmark_sha256=(observation.prediction.scope.benchmark_sha256),
|
|
392
|
+
session_sha256=observation.prediction.scope.session_sha256,
|
|
393
|
+
wave_index=self.wave_index,
|
|
394
|
+
parent_candidate_identity_sha256=(
|
|
395
|
+
self.parent_candidate_identity_sha256
|
|
396
|
+
),
|
|
397
|
+
option_id=self.option_id,
|
|
398
|
+
option_identity_sha256=self.option_identity_sha256,
|
|
399
|
+
metric_id=transition.metric_id,
|
|
400
|
+
parent_outcome_sha256=self.parent_outcome_sha256,
|
|
401
|
+
child_outcome_sha256=self.candidate_outcome_sha256,
|
|
402
|
+
parent_metric_value=transition.parent_value,
|
|
403
|
+
child_metric_value=transition.child_value,
|
|
404
|
+
)
|
|
405
|
+
try:
|
|
406
|
+
observation.adjudication.require_request(numeric_request)
|
|
407
|
+
except ValueError as error:
|
|
408
|
+
raise ValueError(
|
|
409
|
+
"metric transition numeric values differ from its "
|
|
410
|
+
"adjudication request"
|
|
411
|
+
) from error
|
|
412
|
+
if (
|
|
413
|
+
transition.actual_direction
|
|
414
|
+
is not observation.adjudication.actual_direction
|
|
415
|
+
or transition.adjudication_receipt_sha256
|
|
416
|
+
!= observation.adjudication.receipt_sha256
|
|
417
|
+
):
|
|
418
|
+
raise ValueError(
|
|
419
|
+
"metric transition differs from its adjudication receipt"
|
|
420
|
+
)
|
|
421
|
+
if type(self.disposition) is not PortfolioMemberDisposition:
|
|
422
|
+
raise TypeError("disposition must be exact PortfolioMemberDisposition")
|
|
423
|
+
failure = self.candidate_failure
|
|
424
|
+
if self.disposition is PortfolioMemberDisposition.SCORED:
|
|
425
|
+
if not self.observations:
|
|
426
|
+
raise ValueError("scored action requires calibration observations")
|
|
427
|
+
if type(self.parent_relation) is not OutcomeRelation:
|
|
428
|
+
raise TypeError("scored action requires one exact parent relation")
|
|
429
|
+
if failure is not None:
|
|
430
|
+
raise ValueError("scored action cannot carry candidate failure evidence")
|
|
431
|
+
else:
|
|
432
|
+
if self.observations or self.metric_transitions:
|
|
433
|
+
raise ValueError(
|
|
434
|
+
"candidate-infeasible action cannot publish metric calibration"
|
|
435
|
+
)
|
|
436
|
+
if self.parent_relation is not None:
|
|
437
|
+
raise ValueError(
|
|
438
|
+
"candidate-infeasible action cannot publish a parent relation"
|
|
439
|
+
)
|
|
440
|
+
if self.dominates_parent or self.better_than_parent:
|
|
441
|
+
raise ValueError(
|
|
442
|
+
"candidate-infeasible action cannot publish improvement flags"
|
|
443
|
+
)
|
|
444
|
+
if type(failure) is not PortfolioCandidateFailureEvidence:
|
|
445
|
+
raise TypeError(
|
|
446
|
+
"candidate-infeasible action requires exact failure evidence"
|
|
447
|
+
)
|
|
448
|
+
PortfolioCandidateFailureEvidence.__post_init__(failure)
|
|
449
|
+
|
|
450
|
+
def _unsigned_record(self) -> dict[str, object]:
|
|
451
|
+
self.__post_init__()
|
|
452
|
+
record: dict[str, object] = {
|
|
453
|
+
"schema_version": 2 if self.metric_transitions else 1,
|
|
454
|
+
"wave_index": self.wave_index,
|
|
455
|
+
"request_sha256": self.request_sha256,
|
|
456
|
+
"ranked_decision_sha256": self.ranked_decision_sha256,
|
|
457
|
+
"proposal_sha256": self.proposal_sha256,
|
|
458
|
+
"parent_candidate_id": self.parent_candidate_id,
|
|
459
|
+
"parent_candidate_identity_sha256": (self.parent_candidate_identity_sha256),
|
|
460
|
+
"parent_outcome_sha256": self.parent_outcome_sha256,
|
|
461
|
+
"candidate_id": self.candidate_id,
|
|
462
|
+
"candidate_outcome_sha256": self.candidate_outcome_sha256,
|
|
463
|
+
"option_id": self.option_id,
|
|
464
|
+
"option_identity_sha256": self.option_identity_sha256,
|
|
465
|
+
"family": self.family,
|
|
466
|
+
"changed_paths": list(self.changed_paths),
|
|
467
|
+
"observations": [value.to_record() for value in self.observations],
|
|
468
|
+
"parent_relation": (
|
|
469
|
+
None if self.parent_relation is None else self.parent_relation.value
|
|
470
|
+
),
|
|
471
|
+
"reward_hex": self.reward.hex(),
|
|
472
|
+
"dominates_parent": self.dominates_parent,
|
|
473
|
+
"better_than_parent": self.better_than_parent,
|
|
474
|
+
}
|
|
475
|
+
if self.metric_transitions:
|
|
476
|
+
record["metric_transitions"] = [
|
|
477
|
+
value.to_record() for value in self.metric_transitions
|
|
478
|
+
]
|
|
479
|
+
if self.disposition is PortfolioMemberDisposition.CANDIDATE_INFEASIBLE:
|
|
480
|
+
failure = self.candidate_failure
|
|
481
|
+
assert type(failure) is PortfolioCandidateFailureEvidence
|
|
482
|
+
record.update(
|
|
483
|
+
{
|
|
484
|
+
"schema_version": 3,
|
|
485
|
+
"disposition": self.disposition.value,
|
|
486
|
+
"yield_status": "candidate_infeasible_no_yield",
|
|
487
|
+
"candidate_failure": failure.to_record(),
|
|
488
|
+
"calibration_excluded": True,
|
|
489
|
+
"parent_relation_excluded": True,
|
|
490
|
+
}
|
|
491
|
+
)
|
|
492
|
+
return record
|
|
493
|
+
|
|
494
|
+
@property
|
|
495
|
+
def feedback_sha256(self) -> str:
|
|
496
|
+
return _hash(_ACTION_DOMAIN, self._unsigned_record())
|
|
497
|
+
|
|
498
|
+
def to_record(self) -> dict[str, object]:
|
|
499
|
+
return {**self._unsigned_record(), "feedback_sha256": self.feedback_sha256}
|
|
500
|
+
|
|
501
|
+
def to_prompt_record(self) -> dict[str, object]:
|
|
502
|
+
"""Render only treatment-neutral empirical facts for future selectors."""
|
|
503
|
+
|
|
504
|
+
self.__post_init__()
|
|
505
|
+
transitions = {value.metric_id: value for value in self.metric_transitions}
|
|
506
|
+
if self.disposition is PortfolioMemberDisposition.CANDIDATE_INFEASIBLE:
|
|
507
|
+
failure = self.candidate_failure
|
|
508
|
+
assert type(failure) is PortfolioCandidateFailureEvidence
|
|
509
|
+
return {
|
|
510
|
+
"wave_index": self.wave_index,
|
|
511
|
+
"option_id": self.option_id,
|
|
512
|
+
"option_identity_sha256": self.option_identity_sha256,
|
|
513
|
+
"family": self.family,
|
|
514
|
+
"changed_paths": list(self.changed_paths),
|
|
515
|
+
"outcome_status": "candidate_infeasible_no_yield",
|
|
516
|
+
"failure_code": failure.failure_code.value,
|
|
517
|
+
"failure_evidence_sha256": failure.evidence_sha256,
|
|
518
|
+
"metric_feedback": [],
|
|
519
|
+
"parent_relation": None,
|
|
520
|
+
"reward_hex": self.reward.hex(),
|
|
521
|
+
"dominates_parent": False,
|
|
522
|
+
"better_than_parent": False,
|
|
523
|
+
}
|
|
524
|
+
return {
|
|
525
|
+
"wave_index": self.wave_index,
|
|
526
|
+
"option_id": self.option_id,
|
|
527
|
+
"option_identity_sha256": self.option_identity_sha256,
|
|
528
|
+
"family": self.family,
|
|
529
|
+
"changed_paths": list(self.changed_paths),
|
|
530
|
+
"metric_feedback": [
|
|
531
|
+
{
|
|
532
|
+
"metric_id": value.prediction.metric_id,
|
|
533
|
+
"predicted_direction": value.prediction.asserted_direction.value,
|
|
534
|
+
"confidence": value.prediction.confidence.value,
|
|
535
|
+
"observed_direction": value.adjudication.actual_direction.value,
|
|
536
|
+
"correctness": value.correctness,
|
|
537
|
+
**(
|
|
538
|
+
{}
|
|
539
|
+
if value.prediction.metric_id not in transitions
|
|
540
|
+
else {
|
|
541
|
+
"parent_value_hex": transitions[
|
|
542
|
+
value.prediction.metric_id
|
|
543
|
+
].parent_value.hex(),
|
|
544
|
+
"child_value_hex": transitions[
|
|
545
|
+
value.prediction.metric_id
|
|
546
|
+
].child_value.hex(),
|
|
547
|
+
"delta_hex": (
|
|
548
|
+
transitions[value.prediction.metric_id].child_value
|
|
549
|
+
- transitions[value.prediction.metric_id].parent_value
|
|
550
|
+
).hex(),
|
|
551
|
+
}
|
|
552
|
+
),
|
|
553
|
+
}
|
|
554
|
+
for value in self.observations
|
|
555
|
+
],
|
|
556
|
+
"parent_relation": self.parent_relation.value,
|
|
557
|
+
"reward_hex": self.reward.hex(),
|
|
558
|
+
"dominates_parent": self.dominates_parent,
|
|
559
|
+
"better_than_parent": self.better_than_parent,
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
def _classify_outcome_transfer_scope(
|
|
564
|
+
action: PortfolioActionOutcomeFeedback,
|
|
565
|
+
query: ContextualOutcomeQuery,
|
|
566
|
+
) -> OutcomeTransferScope:
|
|
567
|
+
"""Derive transfer distance solely from authenticated action/query facts."""
|
|
568
|
+
|
|
569
|
+
if action.parent_candidate_id == query.current_parent_candidate_id:
|
|
570
|
+
return OutcomeTransferScope.SAME_PARENT
|
|
571
|
+
if action.candidate_id == query.current_parent_candidate_id:
|
|
572
|
+
return OutcomeTransferScope.SAME_LINEAGE
|
|
573
|
+
if (
|
|
574
|
+
action.parent_candidate_identity_sha256
|
|
575
|
+
== query.current_parent_configuration_sha256
|
|
576
|
+
):
|
|
577
|
+
return OutcomeTransferScope.SAME_PARENT
|
|
578
|
+
lineage = set(query.lineage_candidate_ids)
|
|
579
|
+
if action.parent_candidate_id in lineage or action.candidate_id in lineage:
|
|
580
|
+
return OutcomeTransferScope.SAME_LINEAGE
|
|
581
|
+
return OutcomeTransferScope.CROSS_LINEAGE_ANALOGY
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
@dataclass(frozen=True, slots=True)
|
|
585
|
+
class PortfolioOutcomeFeedbackReceipt:
|
|
586
|
+
"""Complete all-members join for one sealed evaluated portfolio."""
|
|
587
|
+
|
|
588
|
+
wave_index: int
|
|
589
|
+
request_sha256: str
|
|
590
|
+
ranked_decision_sha256: str
|
|
591
|
+
scope: ForecastCalibrationScope
|
|
592
|
+
actions: tuple[PortfolioActionOutcomeFeedback, ...]
|
|
593
|
+
receipt_sha256: str = field(init=False, default="")
|
|
594
|
+
|
|
595
|
+
def __post_init__(self) -> None:
|
|
596
|
+
if type(self.wave_index) is not int or self.wave_index <= 0:
|
|
597
|
+
raise ValueError("wave_index must be a positive exact integer")
|
|
598
|
+
require_sha256(self.request_sha256, "request_sha256")
|
|
599
|
+
require_sha256(self.ranked_decision_sha256, "ranked_decision_sha256")
|
|
600
|
+
if type(self.scope) is not ForecastCalibrationScope:
|
|
601
|
+
raise TypeError("scope must be exact ForecastCalibrationScope")
|
|
602
|
+
self.scope.revalidate()
|
|
603
|
+
if (
|
|
604
|
+
type(self.actions) is not tuple
|
|
605
|
+
or not self.actions
|
|
606
|
+
or any(
|
|
607
|
+
type(value) is not PortfolioActionOutcomeFeedback
|
|
608
|
+
for value in self.actions
|
|
609
|
+
)
|
|
610
|
+
):
|
|
611
|
+
raise ValueError("actions must contain exact feedback values")
|
|
612
|
+
for value in self.actions:
|
|
613
|
+
value.__post_init__()
|
|
614
|
+
if len({value.option_id for value in self.actions}) != len(self.actions):
|
|
615
|
+
raise ValueError("feedback receipt cannot repeat an action")
|
|
616
|
+
for value in self.actions:
|
|
617
|
+
if (
|
|
618
|
+
value.wave_index != self.wave_index
|
|
619
|
+
or value.request_sha256 != self.request_sha256
|
|
620
|
+
or value.ranked_decision_sha256 != self.ranked_decision_sha256
|
|
621
|
+
or any(
|
|
622
|
+
observation.prediction.scope != self.scope
|
|
623
|
+
for observation in value.observations
|
|
624
|
+
)
|
|
625
|
+
):
|
|
626
|
+
raise ValueError("feedback action belongs to a foreign wave")
|
|
627
|
+
computed = _hash(_RECEIPT_DOMAIN, self._unsigned_record())
|
|
628
|
+
if self.receipt_sha256 not in ("", computed):
|
|
629
|
+
raise ValueError("receipt_sha256 does not authenticate feedback")
|
|
630
|
+
object.__setattr__(self, "receipt_sha256", computed)
|
|
631
|
+
|
|
632
|
+
def _unsigned_record(self) -> dict[str, object]:
|
|
633
|
+
record: dict[str, object] = {
|
|
634
|
+
"schema_version": 1,
|
|
635
|
+
"wave_index": self.wave_index,
|
|
636
|
+
"request_sha256": self.request_sha256,
|
|
637
|
+
"ranked_decision_sha256": self.ranked_decision_sha256,
|
|
638
|
+
"scope_sha256": self.scope.scope_sha256,
|
|
639
|
+
"actions": [value.to_record() for value in self.actions],
|
|
640
|
+
"treatment_visibility": "card_blind_action_outcomes_only",
|
|
641
|
+
}
|
|
642
|
+
infeasible_count = sum(
|
|
643
|
+
value.disposition is PortfolioMemberDisposition.CANDIDATE_INFEASIBLE
|
|
644
|
+
for value in self.actions
|
|
645
|
+
)
|
|
646
|
+
if infeasible_count:
|
|
647
|
+
record.update(
|
|
648
|
+
{
|
|
649
|
+
"schema_version": 2,
|
|
650
|
+
"ranked_itt_action_count": len(self.actions),
|
|
651
|
+
"scored_action_count": len(self.actions) - infeasible_count,
|
|
652
|
+
"candidate_infeasible_action_count": infeasible_count,
|
|
653
|
+
"candidate_infeasible_semantics": (
|
|
654
|
+
"retained_as_no_yield_action_without_metric_calibration_or_"
|
|
655
|
+
"parent_relation"
|
|
656
|
+
),
|
|
657
|
+
}
|
|
658
|
+
)
|
|
659
|
+
return record
|
|
660
|
+
|
|
661
|
+
def to_record(self) -> dict[str, object]:
|
|
662
|
+
self.__post_init__()
|
|
663
|
+
return {**self._unsigned_record(), "receipt_sha256": self.receipt_sha256}
|
|
664
|
+
|
|
665
|
+
|
|
666
|
+
@dataclass(frozen=True, slots=True)
|
|
667
|
+
class ContextualOutcomeHistoryReceipt:
|
|
668
|
+
"""Authenticated, relevance-ranked empirical history for one parent.
|
|
669
|
+
|
|
670
|
+
The receipt deliberately labels transfer distance. Cross-lineage facts are
|
|
671
|
+
analogies, never silently presented as though the same action had already
|
|
672
|
+
been evaluated on the current parent.
|
|
673
|
+
"""
|
|
674
|
+
|
|
675
|
+
query: ContextualOutcomeQuery
|
|
676
|
+
actions: tuple[PortfolioActionOutcomeFeedback, ...]
|
|
677
|
+
transfer_scopes: tuple[OutcomeTransferScope, ...]
|
|
678
|
+
receipt_sha256: str = field(init=False, default="")
|
|
679
|
+
|
|
680
|
+
def __post_init__(self) -> None:
|
|
681
|
+
if type(self.query) is not ContextualOutcomeQuery:
|
|
682
|
+
raise TypeError("query must be an exact ContextualOutcomeQuery")
|
|
683
|
+
ContextualOutcomeQuery.__post_init__(self.query)
|
|
684
|
+
if type(self.actions) is not tuple or any(
|
|
685
|
+
type(value) is not PortfolioActionOutcomeFeedback for value in self.actions
|
|
686
|
+
):
|
|
687
|
+
raise TypeError("actions must contain exact feedback values")
|
|
688
|
+
if type(self.transfer_scopes) is not tuple or any(
|
|
689
|
+
type(value) is not OutcomeTransferScope for value in self.transfer_scopes
|
|
690
|
+
):
|
|
691
|
+
raise TypeError("transfer_scopes must contain exact scope values")
|
|
692
|
+
if len(self.actions) != len(self.transfer_scopes):
|
|
693
|
+
raise ValueError("actions and transfer_scopes must have equal length")
|
|
694
|
+
if len(self.actions) > self.query.max_actions:
|
|
695
|
+
raise ValueError("contextual history exceeds the requested bound")
|
|
696
|
+
if len({value.feedback_sha256 for value in self.actions}) != len(self.actions):
|
|
697
|
+
raise ValueError("contextual history cannot repeat feedback actions")
|
|
698
|
+
if any(
|
|
699
|
+
value.wave_index >= self.query.cutoff_wave_index_exclusive
|
|
700
|
+
for value in self.actions
|
|
701
|
+
):
|
|
702
|
+
raise ValueError("contextual history contains same/future-wave evidence")
|
|
703
|
+
if (
|
|
704
|
+
not self.query.include_cross_lineage_analogies
|
|
705
|
+
and OutcomeTransferScope.CROSS_LINEAGE_ANALOGY in self.transfer_scopes
|
|
706
|
+
):
|
|
707
|
+
raise ValueError("contextual history contains forbidden cross-lineage data")
|
|
708
|
+
expected_scopes = tuple(
|
|
709
|
+
_classify_outcome_transfer_scope(action, self.query)
|
|
710
|
+
for action in self.actions
|
|
711
|
+
)
|
|
712
|
+
if self.transfer_scopes != expected_scopes:
|
|
713
|
+
raise ValueError(
|
|
714
|
+
"contextual history transfer scopes differ from query lineage"
|
|
715
|
+
)
|
|
716
|
+
computed = _hash(_CONTEXTUAL_HISTORY_DOMAIN, self._unsigned_record())
|
|
717
|
+
if self.receipt_sha256 not in ("", computed):
|
|
718
|
+
raise ValueError("receipt_sha256 does not authenticate contextual history")
|
|
719
|
+
object.__setattr__(self, "receipt_sha256", computed)
|
|
720
|
+
|
|
721
|
+
def _unsigned_record(self) -> dict[str, object]:
|
|
722
|
+
return {
|
|
723
|
+
"schema_version": 1,
|
|
724
|
+
"query_sha256": self.query.query_sha256,
|
|
725
|
+
"actions": [
|
|
726
|
+
{
|
|
727
|
+
"feedback_sha256": action.feedback_sha256,
|
|
728
|
+
"transfer_scope": scope.value,
|
|
729
|
+
}
|
|
730
|
+
for action, scope in zip(
|
|
731
|
+
self.actions,
|
|
732
|
+
self.transfer_scopes,
|
|
733
|
+
strict=True,
|
|
734
|
+
)
|
|
735
|
+
],
|
|
736
|
+
"epistemic_status": "observational_predictive_history_not_causal_credit",
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
def to_record(self) -> dict[str, object]:
|
|
740
|
+
self.__post_init__()
|
|
741
|
+
return {
|
|
742
|
+
**self._unsigned_record(),
|
|
743
|
+
"query": self.query.to_record(),
|
|
744
|
+
"receipt_sha256": self.receipt_sha256,
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
def to_prompt_record(self) -> FrozenJsonObject:
|
|
748
|
+
self.__post_init__()
|
|
749
|
+
frozen = freeze_json(
|
|
750
|
+
{
|
|
751
|
+
"schema_version": 1,
|
|
752
|
+
"receipt_sha256": self.receipt_sha256,
|
|
753
|
+
"query_sha256": self.query.query_sha256,
|
|
754
|
+
"cutoff_wave_index_exclusive": (self.query.cutoff_wave_index_exclusive),
|
|
755
|
+
"current_parent_candidate_id": (self.query.current_parent_candidate_id),
|
|
756
|
+
"current_parent_configuration_sha256": (
|
|
757
|
+
self.query.current_parent_configuration_sha256
|
|
758
|
+
),
|
|
759
|
+
"actions": [
|
|
760
|
+
{
|
|
761
|
+
"transfer_scope": scope.value,
|
|
762
|
+
"source_parent_candidate_id": action.parent_candidate_id,
|
|
763
|
+
"source_parent_configuration_sha256": (
|
|
764
|
+
action.parent_candidate_identity_sha256
|
|
765
|
+
),
|
|
766
|
+
"source_child_candidate_id": action.candidate_id,
|
|
767
|
+
"source_child_outcome_sha256": (
|
|
768
|
+
action.candidate_outcome_sha256
|
|
769
|
+
),
|
|
770
|
+
"applicability": (
|
|
771
|
+
"direct_same_parent_evidence"
|
|
772
|
+
if scope is OutcomeTransferScope.SAME_PARENT
|
|
773
|
+
else (
|
|
774
|
+
"same_lineage_transfer_evidence"
|
|
775
|
+
if scope is OutcomeTransferScope.SAME_LINEAGE
|
|
776
|
+
else "cross_lineage_analogy_requires_revalidation"
|
|
777
|
+
)
|
|
778
|
+
),
|
|
779
|
+
**action.to_prompt_record(),
|
|
780
|
+
}
|
|
781
|
+
for action, scope in zip(
|
|
782
|
+
self.actions,
|
|
783
|
+
self.transfer_scopes,
|
|
784
|
+
strict=True,
|
|
785
|
+
)
|
|
786
|
+
],
|
|
787
|
+
"epistemic_status": (
|
|
788
|
+
"observational_predictive_history_not_causal_credit"
|
|
789
|
+
),
|
|
790
|
+
"card_and_rationale_fields_excluded": True,
|
|
791
|
+
}
|
|
792
|
+
)
|
|
793
|
+
if type(frozen) is not FrozenJsonObject:
|
|
794
|
+
raise AssertionError("contextual outcome history did not freeze")
|
|
795
|
+
return frozen
|
|
796
|
+
|
|
797
|
+
|
|
798
|
+
def observe_selected_portfolio_forecasts(
|
|
799
|
+
*,
|
|
800
|
+
wave_index: int,
|
|
801
|
+
parent: EvolutionCandidate,
|
|
802
|
+
result: PortfolioVariationWaveResult,
|
|
803
|
+
selected_predictions: tuple[ForecastPredictionReceipt, ...],
|
|
804
|
+
adjudicator: MeaningfulMetricDirectionAdjudicator,
|
|
805
|
+
decision_metric_projection: DecisionMetricProjection | None = None,
|
|
806
|
+
) -> PortfolioOutcomeFeedbackReceipt:
|
|
807
|
+
"""Join only evaluated K4 forecasts to exact outcomes and adjudication.
|
|
808
|
+
|
|
809
|
+
The optional benchmark projection exposes semantic objective plus sealed
|
|
810
|
+
violation/constraint values. ``None`` intentionally retains the legacy
|
|
811
|
+
objective-map path and therefore its byte-identical feedback receipts.
|
|
812
|
+
"""
|
|
813
|
+
|
|
814
|
+
if type(parent) is not EvolutionCandidate:
|
|
815
|
+
raise TypeError("parent must be exact EvolutionCandidate")
|
|
816
|
+
EvolutionCandidate.__post_init__(parent)
|
|
817
|
+
if type(result) is not PortfolioVariationWaveResult:
|
|
818
|
+
raise TypeError("result must be exact PortfolioVariationWaveResult")
|
|
819
|
+
PortfolioVariationWaveResult.__post_init__(result)
|
|
820
|
+
decision = result.selection_decision
|
|
821
|
+
if decision is None:
|
|
822
|
+
raise ValueError("forecast feedback requires the typed selection decision")
|
|
823
|
+
if type(selected_predictions) is not tuple or any(
|
|
824
|
+
type(value) is not ForecastPredictionReceipt for value in selected_predictions
|
|
825
|
+
):
|
|
826
|
+
raise TypeError("selected_predictions must be an exact prediction tuple")
|
|
827
|
+
for value in selected_predictions:
|
|
828
|
+
value.revalidate()
|
|
829
|
+
if decision_metric_projection is not None:
|
|
830
|
+
if type(decision_metric_projection) is not DecisionMetricProjection:
|
|
831
|
+
raise TypeError("decision_metric_projection must be exact or None")
|
|
832
|
+
decision_metric_projection.__post_init__()
|
|
833
|
+
if not selected_predictions:
|
|
834
|
+
raise ValueError("forecast feedback requires selected predictions")
|
|
835
|
+
scopes = {value.scope for value in selected_predictions}
|
|
836
|
+
proposals = {value.selector_decision_sha256 for value in selected_predictions}
|
|
837
|
+
if len(scopes) != 1 or len(proposals) != 1:
|
|
838
|
+
raise ValueError("selected predictions mix calibration scopes or proposals")
|
|
839
|
+
scope = next(iter(scopes))
|
|
840
|
+
proposal_sha256 = next(iter(proposals))
|
|
841
|
+
parent_identity = parent.occurrence.configuration_hash
|
|
842
|
+
if any(
|
|
843
|
+
value.wave_index != wave_index
|
|
844
|
+
or value.parent_candidate_identity_sha256 != parent_identity
|
|
845
|
+
for value in selected_predictions
|
|
846
|
+
):
|
|
847
|
+
raise ValueError("selected predictions belong to a foreign parent or wave")
|
|
848
|
+
by_option_metric = {
|
|
849
|
+
(value.option_id, value.metric_id): value for value in selected_predictions
|
|
850
|
+
}
|
|
851
|
+
if len(by_option_metric) != len(selected_predictions):
|
|
852
|
+
raise ValueError("selected predictions repeat an option/metric cell")
|
|
853
|
+
parent_outcome_sha256 = _candidate_outcome_sha256(parent)
|
|
854
|
+
parent_metrics = (
|
|
855
|
+
parent.objective_map
|
|
856
|
+
if decision_metric_projection is None
|
|
857
|
+
else project_candidate_decision_metrics(
|
|
858
|
+
parent,
|
|
859
|
+
decision_metric_projection,
|
|
860
|
+
).metric_map
|
|
861
|
+
)
|
|
862
|
+
actions: list[PortfolioActionOutcomeFeedback] = []
|
|
863
|
+
for selected, member, outcome in zip(
|
|
864
|
+
decision.members,
|
|
865
|
+
result.receipt.members,
|
|
866
|
+
result.outcomes,
|
|
867
|
+
strict=True,
|
|
868
|
+
):
|
|
869
|
+
materialization = member.materialization
|
|
870
|
+
child = outcome.candidate
|
|
871
|
+
if child is None: # Closed by result validation.
|
|
872
|
+
raise AssertionError("validated portfolio result lost its child")
|
|
873
|
+
if (
|
|
874
|
+
selected.option_id != materialization.option_id
|
|
875
|
+
or selected.option_identity_sha256 != materialization.option_identity_sha256
|
|
876
|
+
):
|
|
877
|
+
raise ValueError("selection and materialization action identities differ")
|
|
878
|
+
predictions = tuple(
|
|
879
|
+
sorted(
|
|
880
|
+
(
|
|
881
|
+
value
|
|
882
|
+
for (option_id, _), value in by_option_metric.items()
|
|
883
|
+
if option_id == selected.option_id
|
|
884
|
+
),
|
|
885
|
+
key=lambda value: value.metric_id,
|
|
886
|
+
)
|
|
887
|
+
)
|
|
888
|
+
if tuple(value.metric_id for value in predictions) != tuple(
|
|
889
|
+
sorted(parent_metrics)
|
|
890
|
+
):
|
|
891
|
+
raise ValueError("selected forecasts differ from parent decision metrics")
|
|
892
|
+
if member.disposition is PortfolioMemberDisposition.CANDIDATE_INFEASIBLE:
|
|
893
|
+
failure = member.candidate_failure
|
|
894
|
+
if type(failure) is not PortfolioCandidateFailureEvidence:
|
|
895
|
+
raise AssertionError(
|
|
896
|
+
"validated infeasible member lost candidate failure evidence"
|
|
897
|
+
)
|
|
898
|
+
actions.append(
|
|
899
|
+
PortfolioActionOutcomeFeedback(
|
|
900
|
+
wave_index=wave_index,
|
|
901
|
+
request_sha256=decision.request_sha256,
|
|
902
|
+
ranked_decision_sha256=decision.decision_sha256,
|
|
903
|
+
proposal_sha256=proposal_sha256,
|
|
904
|
+
parent_candidate_id=parent.candidate_id.value,
|
|
905
|
+
parent_candidate_identity_sha256=parent_identity,
|
|
906
|
+
parent_outcome_sha256=parent_outcome_sha256,
|
|
907
|
+
candidate_id=child.candidate_id.value,
|
|
908
|
+
candidate_outcome_sha256=member.outcome_sha256,
|
|
909
|
+
option_id=selected.option_id,
|
|
910
|
+
option_identity_sha256=selected.option_identity_sha256,
|
|
911
|
+
family=selected.family,
|
|
912
|
+
changed_paths=materialization.changed_paths,
|
|
913
|
+
observations=(),
|
|
914
|
+
parent_relation=None,
|
|
915
|
+
reward=member.reward,
|
|
916
|
+
dominates_parent=False,
|
|
917
|
+
better_than_parent=False,
|
|
918
|
+
disposition=member.disposition,
|
|
919
|
+
candidate_failure=failure,
|
|
920
|
+
)
|
|
921
|
+
)
|
|
922
|
+
continue
|
|
923
|
+
child_metrics = (
|
|
924
|
+
child.objective_map
|
|
925
|
+
if decision_metric_projection is None
|
|
926
|
+
else project_candidate_decision_metrics(
|
|
927
|
+
child,
|
|
928
|
+
decision_metric_projection,
|
|
929
|
+
).metric_map
|
|
930
|
+
)
|
|
931
|
+
if set(child_metrics) != set(parent_metrics):
|
|
932
|
+
raise ValueError(
|
|
933
|
+
"selected forecasts differ from evaluated decision metrics"
|
|
934
|
+
)
|
|
935
|
+
observations: list[ForecastCalibrationObservation] = []
|
|
936
|
+
for prediction in predictions:
|
|
937
|
+
metric_id = prediction.metric_id
|
|
938
|
+
request = MeaningfulDirectionRequest(
|
|
939
|
+
benchmark_sha256=scope.benchmark_sha256,
|
|
940
|
+
session_sha256=scope.session_sha256,
|
|
941
|
+
wave_index=wave_index,
|
|
942
|
+
parent_candidate_identity_sha256=parent_identity,
|
|
943
|
+
option_id=selected.option_id,
|
|
944
|
+
option_identity_sha256=selected.option_identity_sha256,
|
|
945
|
+
metric_id=metric_id,
|
|
946
|
+
parent_outcome_sha256=parent_outcome_sha256,
|
|
947
|
+
child_outcome_sha256=member.outcome_sha256,
|
|
948
|
+
parent_metric_value=parent_metrics[metric_id],
|
|
949
|
+
child_metric_value=child_metrics[metric_id],
|
|
950
|
+
)
|
|
951
|
+
observations.append(observe_forecast(prediction, request, adjudicator))
|
|
952
|
+
actions.append(
|
|
953
|
+
PortfolioActionOutcomeFeedback(
|
|
954
|
+
wave_index=wave_index,
|
|
955
|
+
request_sha256=decision.request_sha256,
|
|
956
|
+
ranked_decision_sha256=decision.decision_sha256,
|
|
957
|
+
proposal_sha256=proposal_sha256,
|
|
958
|
+
parent_candidate_id=parent.candidate_id.value,
|
|
959
|
+
parent_candidate_identity_sha256=parent_identity,
|
|
960
|
+
parent_outcome_sha256=parent_outcome_sha256,
|
|
961
|
+
candidate_id=child.candidate_id.value,
|
|
962
|
+
candidate_outcome_sha256=member.outcome_sha256,
|
|
963
|
+
option_id=selected.option_id,
|
|
964
|
+
option_identity_sha256=selected.option_identity_sha256,
|
|
965
|
+
family=selected.family,
|
|
966
|
+
changed_paths=materialization.changed_paths,
|
|
967
|
+
observations=tuple(observations),
|
|
968
|
+
parent_relation=member.parent_relations[0],
|
|
969
|
+
reward=member.reward,
|
|
970
|
+
dominates_parent=member.dominates_any_parent,
|
|
971
|
+
better_than_parent=member.better_than_any_parent,
|
|
972
|
+
metric_transitions=tuple(
|
|
973
|
+
DecisionMetricTransition(
|
|
974
|
+
metric_id=observation.prediction.metric_id,
|
|
975
|
+
parent_value=float(
|
|
976
|
+
parent_metrics[observation.prediction.metric_id]
|
|
977
|
+
),
|
|
978
|
+
child_value=float(
|
|
979
|
+
child_metrics[observation.prediction.metric_id]
|
|
980
|
+
),
|
|
981
|
+
actual_direction=observation.adjudication.actual_direction,
|
|
982
|
+
adjudication_receipt_sha256=(
|
|
983
|
+
observation.adjudication.receipt_sha256
|
|
984
|
+
),
|
|
985
|
+
)
|
|
986
|
+
for observation in observations
|
|
987
|
+
),
|
|
988
|
+
disposition=member.disposition,
|
|
989
|
+
)
|
|
990
|
+
)
|
|
991
|
+
calibrated_cells = {
|
|
992
|
+
(action.option_id, observation.prediction.metric_id)
|
|
993
|
+
for action in actions
|
|
994
|
+
for observation in action.observations
|
|
995
|
+
}
|
|
996
|
+
excluded_cells = {
|
|
997
|
+
(action.option_id, prediction.metric_id)
|
|
998
|
+
for action in actions
|
|
999
|
+
if action.disposition is PortfolioMemberDisposition.CANDIDATE_INFEASIBLE
|
|
1000
|
+
for (option_id, _), prediction in by_option_metric.items()
|
|
1001
|
+
if option_id == action.option_id
|
|
1002
|
+
}
|
|
1003
|
+
if set(by_option_metric) != calibrated_cells.union(excluded_cells):
|
|
1004
|
+
raise ValueError("unevaluated or missing K8 forecasts entered K4 feedback")
|
|
1005
|
+
return PortfolioOutcomeFeedbackReceipt(
|
|
1006
|
+
wave_index=wave_index,
|
|
1007
|
+
request_sha256=decision.request_sha256,
|
|
1008
|
+
ranked_decision_sha256=decision.decision_sha256,
|
|
1009
|
+
scope=scope,
|
|
1010
|
+
actions=tuple(actions),
|
|
1011
|
+
)
|
|
1012
|
+
|
|
1013
|
+
|
|
1014
|
+
@dataclass(slots=True)
|
|
1015
|
+
class PortfolioOutcomeFeedbackLedger:
|
|
1016
|
+
"""Append-only in-memory ledger with prior-wave snapshot projections."""
|
|
1017
|
+
|
|
1018
|
+
receipts: list[PortfolioOutcomeFeedbackReceipt] = field(default_factory=list)
|
|
1019
|
+
|
|
1020
|
+
def append(self, receipt: PortfolioOutcomeFeedbackReceipt) -> None:
|
|
1021
|
+
if type(receipt) is not PortfolioOutcomeFeedbackReceipt:
|
|
1022
|
+
raise TypeError("receipt must be exact PortfolioOutcomeFeedbackReceipt")
|
|
1023
|
+
receipt.__post_init__()
|
|
1024
|
+
if any(
|
|
1025
|
+
value.receipt_sha256 == receipt.receipt_sha256 for value in self.receipts
|
|
1026
|
+
):
|
|
1027
|
+
raise ValueError("feedback ledger already contains this receipt")
|
|
1028
|
+
if any(
|
|
1029
|
+
value.request_sha256 == receipt.request_sha256 for value in self.receipts
|
|
1030
|
+
):
|
|
1031
|
+
raise ValueError("feedback ledger already contains this selector request")
|
|
1032
|
+
self.receipts.append(receipt)
|
|
1033
|
+
|
|
1034
|
+
@property
|
|
1035
|
+
def observations(self) -> tuple[ForecastCalibrationObservation, ...]:
|
|
1036
|
+
return tuple(
|
|
1037
|
+
observation
|
|
1038
|
+
for receipt in self.receipts
|
|
1039
|
+
for action in receipt.actions
|
|
1040
|
+
for observation in action.observations
|
|
1041
|
+
)
|
|
1042
|
+
|
|
1043
|
+
def calibration_snapshot(
|
|
1044
|
+
self,
|
|
1045
|
+
*,
|
|
1046
|
+
scope: ForecastCalibrationScope,
|
|
1047
|
+
cutoff_wave_index_exclusive: int,
|
|
1048
|
+
prior: BetaCorrectnessPrior = BetaCorrectnessPrior(),
|
|
1049
|
+
family_min_support: int = 4,
|
|
1050
|
+
) -> ForecastCalibrationSnapshot:
|
|
1051
|
+
return build_calibration_snapshot(
|
|
1052
|
+
self.observations,
|
|
1053
|
+
scope=scope,
|
|
1054
|
+
cutoff_wave_index_exclusive=cutoff_wave_index_exclusive,
|
|
1055
|
+
prior=prior,
|
|
1056
|
+
family_min_support=family_min_support,
|
|
1057
|
+
)
|
|
1058
|
+
|
|
1059
|
+
def prompt_history(
|
|
1060
|
+
self,
|
|
1061
|
+
*,
|
|
1062
|
+
cutoff_wave_index_exclusive: int,
|
|
1063
|
+
max_actions: int | None = None,
|
|
1064
|
+
) -> FrozenJsonObject:
|
|
1065
|
+
if (
|
|
1066
|
+
type(cutoff_wave_index_exclusive) is not int
|
|
1067
|
+
or cutoff_wave_index_exclusive <= 0
|
|
1068
|
+
):
|
|
1069
|
+
raise ValueError("cutoff_wave_index_exclusive must be positive")
|
|
1070
|
+
if max_actions is not None and (
|
|
1071
|
+
type(max_actions) is not int or max_actions <= 0
|
|
1072
|
+
):
|
|
1073
|
+
raise ValueError("max_actions must be positive or None")
|
|
1074
|
+
actions = [
|
|
1075
|
+
action
|
|
1076
|
+
for receipt in self.receipts
|
|
1077
|
+
if receipt.wave_index < cutoff_wave_index_exclusive
|
|
1078
|
+
for action in receipt.actions
|
|
1079
|
+
]
|
|
1080
|
+
actions.sort(
|
|
1081
|
+
key=lambda value: (
|
|
1082
|
+
value.wave_index,
|
|
1083
|
+
value.request_sha256,
|
|
1084
|
+
value.option_id,
|
|
1085
|
+
)
|
|
1086
|
+
)
|
|
1087
|
+
if max_actions is not None:
|
|
1088
|
+
actions = actions[-max_actions:]
|
|
1089
|
+
frozen = freeze_json(
|
|
1090
|
+
{
|
|
1091
|
+
"schema_version": 1,
|
|
1092
|
+
"cutoff_wave_index_exclusive": cutoff_wave_index_exclusive,
|
|
1093
|
+
"actions": [value.to_prompt_record() for value in actions],
|
|
1094
|
+
"treatment_visibility": "action_outcomes_only",
|
|
1095
|
+
}
|
|
1096
|
+
)
|
|
1097
|
+
if type(frozen) is not FrozenJsonObject:
|
|
1098
|
+
raise AssertionError("feedback history did not freeze as an object")
|
|
1099
|
+
return frozen
|
|
1100
|
+
|
|
1101
|
+
def contextual_history(
|
|
1102
|
+
self,
|
|
1103
|
+
query: ContextualOutcomeQuery,
|
|
1104
|
+
) -> ContextualOutcomeHistoryReceipt:
|
|
1105
|
+
"""Retrieve prior-only evidence with explicit parent-transfer distance.
|
|
1106
|
+
|
|
1107
|
+
Filtering is intentionally structural and deterministic in v1. It is
|
|
1108
|
+
safer than a learned similarity score while the latter lacks held-out
|
|
1109
|
+
calibration, and it makes the exact evidence set replayable.
|
|
1110
|
+
"""
|
|
1111
|
+
|
|
1112
|
+
if type(query) is not ContextualOutcomeQuery:
|
|
1113
|
+
raise TypeError("query must be an exact ContextualOutcomeQuery")
|
|
1114
|
+
ContextualOutcomeQuery.__post_init__(query)
|
|
1115
|
+
ranked: list[
|
|
1116
|
+
tuple[
|
|
1117
|
+
tuple[int, int, str, str, str],
|
|
1118
|
+
PortfolioActionOutcomeFeedback,
|
|
1119
|
+
OutcomeTransferScope,
|
|
1120
|
+
]
|
|
1121
|
+
] = []
|
|
1122
|
+
for receipt in self.receipts:
|
|
1123
|
+
if receipt.wave_index >= query.cutoff_wave_index_exclusive:
|
|
1124
|
+
continue
|
|
1125
|
+
for action in receipt.actions:
|
|
1126
|
+
if query.families and action.family not in query.families:
|
|
1127
|
+
continue
|
|
1128
|
+
if query.changed_paths and not any(
|
|
1129
|
+
_paths_overlap(observed, requested)
|
|
1130
|
+
for observed in action.changed_paths
|
|
1131
|
+
for requested in query.changed_paths
|
|
1132
|
+
):
|
|
1133
|
+
continue
|
|
1134
|
+
scope = _classify_outcome_transfer_scope(action, query)
|
|
1135
|
+
if scope is OutcomeTransferScope.SAME_PARENT:
|
|
1136
|
+
scope_rank = 0
|
|
1137
|
+
elif scope is OutcomeTransferScope.SAME_LINEAGE:
|
|
1138
|
+
scope_rank = 1
|
|
1139
|
+
else:
|
|
1140
|
+
scope_rank = 2
|
|
1141
|
+
if not query.include_cross_lineage_analogies:
|
|
1142
|
+
continue
|
|
1143
|
+
ranked.append(
|
|
1144
|
+
(
|
|
1145
|
+
(
|
|
1146
|
+
scope_rank,
|
|
1147
|
+
-action.wave_index,
|
|
1148
|
+
action.request_sha256,
|
|
1149
|
+
action.option_id,
|
|
1150
|
+
action.feedback_sha256,
|
|
1151
|
+
),
|
|
1152
|
+
action,
|
|
1153
|
+
scope,
|
|
1154
|
+
)
|
|
1155
|
+
)
|
|
1156
|
+
ranked.sort(key=lambda value: value[0])
|
|
1157
|
+
chosen = ranked[: query.max_actions]
|
|
1158
|
+
return ContextualOutcomeHistoryReceipt(
|
|
1159
|
+
query=query,
|
|
1160
|
+
actions=tuple(value[1] for value in chosen),
|
|
1161
|
+
transfer_scopes=tuple(value[2] for value in chosen),
|
|
1162
|
+
)
|
|
1163
|
+
|
|
1164
|
+
|
|
1165
|
+
class SelectedForecastProvider(Protocol):
|
|
1166
|
+
"""Recover the typed selected K4 forecasts from a sealed selector result."""
|
|
1167
|
+
|
|
1168
|
+
def __call__(
|
|
1169
|
+
self,
|
|
1170
|
+
wave: PortfolioVariationWaveRequest,
|
|
1171
|
+
result: PortfolioVariationWaveResult,
|
|
1172
|
+
) -> tuple[ForecastPredictionReceipt, ...]: ...
|
|
1173
|
+
|
|
1174
|
+
|
|
1175
|
+
class SelectedSearchSourceProvider(Protocol):
|
|
1176
|
+
"""Recover framework source labels aligned to one evaluated portfolio."""
|
|
1177
|
+
|
|
1178
|
+
def __call__(
|
|
1179
|
+
self,
|
|
1180
|
+
wave: PortfolioVariationWaveRequest,
|
|
1181
|
+
result: PortfolioVariationWaveResult,
|
|
1182
|
+
) -> tuple[str, ...]: ...
|
|
1183
|
+
|
|
1184
|
+
|
|
1185
|
+
class SelectedAllocationRealizationProvider(Protocol):
|
|
1186
|
+
"""Recover objective-blind requested-to-realized allocation evidence."""
|
|
1187
|
+
|
|
1188
|
+
def __call__(
|
|
1189
|
+
self,
|
|
1190
|
+
wave: PortfolioVariationWaveRequest,
|
|
1191
|
+
result: PortfolioVariationWaveResult,
|
|
1192
|
+
) -> ContextualPortfolioAllocationRealization | None: ...
|
|
1193
|
+
|
|
1194
|
+
|
|
1195
|
+
class ContextualMarginalUtilityProjector(Protocol):
|
|
1196
|
+
"""Project a pre-generation utility snapshot onto evaluated candidates."""
|
|
1197
|
+
|
|
1198
|
+
def project(
|
|
1199
|
+
self,
|
|
1200
|
+
*,
|
|
1201
|
+
snapshot: ArchiveUtilitySnapshot,
|
|
1202
|
+
results: tuple[PortfolioVariationWaveResult, ...],
|
|
1203
|
+
) -> tuple[tuple[float, ...], ...]: ...
|
|
1204
|
+
|
|
1205
|
+
|
|
1206
|
+
class DirectionAdjudicatorProvider(Protocol):
|
|
1207
|
+
"""Return the benchmark/session-bound adjudicator for one completed wave."""
|
|
1208
|
+
|
|
1209
|
+
def __call__(
|
|
1210
|
+
self,
|
|
1211
|
+
wave: PortfolioVariationWaveRequest,
|
|
1212
|
+
result: PortfolioVariationWaveResult,
|
|
1213
|
+
) -> MeaningfulMetricDirectionAdjudicator: ...
|
|
1214
|
+
|
|
1215
|
+
|
|
1216
|
+
@dataclass(slots=True)
|
|
1217
|
+
class CalibratedCampaignOutcomeUpdater:
|
|
1218
|
+
"""Zero-call campaign bridge from completed waves to prior-only feedback."""
|
|
1219
|
+
|
|
1220
|
+
ledger: PortfolioOutcomeFeedbackLedger
|
|
1221
|
+
selected_forecasts: SelectedForecastProvider
|
|
1222
|
+
adjudicator_for: DirectionAdjudicatorProvider
|
|
1223
|
+
decision_metric_projection: DecisionMetricProjection | None = None
|
|
1224
|
+
contextual_ledger: ContextualSearchLedger | None = None
|
|
1225
|
+
selected_search_sources: SelectedSearchSourceProvider | None = None
|
|
1226
|
+
contextual_marginal_utility: ContextualMarginalUtilityProjector | None = None
|
|
1227
|
+
contextual_campaign_scope_sha256: str | None = None
|
|
1228
|
+
selected_allocation_realization: (
|
|
1229
|
+
SelectedAllocationRealizationProvider | None
|
|
1230
|
+
) = None
|
|
1231
|
+
_prepared: dict[
|
|
1232
|
+
str,
|
|
1233
|
+
tuple[
|
|
1234
|
+
tuple[str, ...],
|
|
1235
|
+
tuple[PortfolioOutcomeFeedbackReceipt, ...],
|
|
1236
|
+
tuple[tuple[str, ...], tuple[str, ...], tuple[str, ...]] | None,
|
|
1237
|
+
ContextualPortfolioOutcomeBatch | None,
|
|
1238
|
+
tuple[ContextualPortfolioAllocationRealization, ...],
|
|
1239
|
+
],
|
|
1240
|
+
] = field(init=False, default_factory=dict)
|
|
1241
|
+
|
|
1242
|
+
def __post_init__(self) -> None:
|
|
1243
|
+
if type(self.ledger) is not PortfolioOutcomeFeedbackLedger:
|
|
1244
|
+
raise TypeError("ledger must be exact PortfolioOutcomeFeedbackLedger")
|
|
1245
|
+
if not callable(self.selected_forecasts):
|
|
1246
|
+
raise TypeError("selected_forecasts must be callable")
|
|
1247
|
+
if not callable(self.adjudicator_for):
|
|
1248
|
+
raise TypeError("adjudicator_for must be callable")
|
|
1249
|
+
if self.decision_metric_projection is not None:
|
|
1250
|
+
if type(self.decision_metric_projection) is not DecisionMetricProjection:
|
|
1251
|
+
raise TypeError("decision_metric_projection must be exact or None")
|
|
1252
|
+
self.decision_metric_projection.__post_init__()
|
|
1253
|
+
contextual = (
|
|
1254
|
+
self.contextual_ledger,
|
|
1255
|
+
self.selected_search_sources,
|
|
1256
|
+
self.contextual_marginal_utility,
|
|
1257
|
+
self.contextual_campaign_scope_sha256,
|
|
1258
|
+
)
|
|
1259
|
+
if any(value is not None for value in contextual) and not all(
|
|
1260
|
+
value is not None for value in contextual
|
|
1261
|
+
):
|
|
1262
|
+
raise ValueError("contextual outcome dependencies are all-or-none")
|
|
1263
|
+
if self.contextual_ledger is not None:
|
|
1264
|
+
if type(self.contextual_ledger) is not ContextualSearchLedger:
|
|
1265
|
+
raise TypeError("contextual_ledger must be exact or None")
|
|
1266
|
+
if not callable(self.selected_search_sources):
|
|
1267
|
+
raise TypeError("selected_search_sources must be callable")
|
|
1268
|
+
if not callable(
|
|
1269
|
+
getattr(self.contextual_marginal_utility, "project", None)
|
|
1270
|
+
):
|
|
1271
|
+
raise TypeError("contextual marginal utility must implement its port")
|
|
1272
|
+
assert self.contextual_campaign_scope_sha256 is not None
|
|
1273
|
+
require_sha256(
|
|
1274
|
+
self.contextual_campaign_scope_sha256,
|
|
1275
|
+
"contextual_campaign_scope_sha256",
|
|
1276
|
+
)
|
|
1277
|
+
if self.selected_allocation_realization is not None:
|
|
1278
|
+
if self.contextual_ledger is None:
|
|
1279
|
+
raise ValueError(
|
|
1280
|
+
"allocation realization feedback requires contextual outcomes"
|
|
1281
|
+
)
|
|
1282
|
+
if not callable(self.selected_allocation_realization):
|
|
1283
|
+
raise TypeError("selected_allocation_realization must be callable")
|
|
1284
|
+
|
|
1285
|
+
async def prepare_update(
|
|
1286
|
+
self,
|
|
1287
|
+
request: CampaignStageRequest,
|
|
1288
|
+
waves: tuple[PortfolioVariationWaveRequest, ...],
|
|
1289
|
+
results: tuple[PortfolioVariationWaveResult, ...],
|
|
1290
|
+
prior_memory: FrozenJsonObject,
|
|
1291
|
+
):
|
|
1292
|
+
from agent_evolve.application.portfolio_campaign_runtime import (
|
|
1293
|
+
CampaignPortfolioOutcomePreparation,
|
|
1294
|
+
)
|
|
1295
|
+
|
|
1296
|
+
self.__post_init__()
|
|
1297
|
+
if type(request) is not CampaignStageRequest:
|
|
1298
|
+
raise TypeError("request must be exact CampaignStageRequest")
|
|
1299
|
+
CampaignStageRequest.__post_init__(request)
|
|
1300
|
+
if request.step.kind is not CampaignGenerationKind.PORTFOLIO:
|
|
1301
|
+
raise ValueError("outcome feedback requires a portfolio stage")
|
|
1302
|
+
if (
|
|
1303
|
+
type(waves) is not tuple
|
|
1304
|
+
or type(results) is not tuple
|
|
1305
|
+
or len(waves) != len(results)
|
|
1306
|
+
or not waves
|
|
1307
|
+
):
|
|
1308
|
+
raise ValueError("waves and results must be equal non-empty tuples")
|
|
1309
|
+
if type(prior_memory) is not FrozenJsonObject:
|
|
1310
|
+
raise TypeError("prior_memory must be an exact frozen object")
|
|
1311
|
+
pending: list[PortfolioOutcomeFeedbackReceipt] = []
|
|
1312
|
+
for wave, result in zip(waves, results, strict=True):
|
|
1313
|
+
if type(wave) is not PortfolioVariationWaveRequest:
|
|
1314
|
+
raise TypeError("waves must contain exact portfolio requests")
|
|
1315
|
+
if type(result) is not PortfolioVariationWaveResult:
|
|
1316
|
+
raise TypeError("results must contain exact portfolio results")
|
|
1317
|
+
predictions = self.selected_forecasts(wave, result)
|
|
1318
|
+
adjudicator = self.adjudicator_for(wave, result)
|
|
1319
|
+
pending.append(
|
|
1320
|
+
observe_selected_portfolio_forecasts(
|
|
1321
|
+
wave_index=request.step.generation,
|
|
1322
|
+
parent=wave.parent,
|
|
1323
|
+
result=result,
|
|
1324
|
+
selected_predictions=predictions,
|
|
1325
|
+
adjudicator=adjudicator,
|
|
1326
|
+
decision_metric_projection=self.decision_metric_projection,
|
|
1327
|
+
)
|
|
1328
|
+
)
|
|
1329
|
+
# Validate the whole stage without publishing any new ledger entry.
|
|
1330
|
+
combined = (*self.ledger.receipts, *pending)
|
|
1331
|
+
validate_feedback_ledger(combined)
|
|
1332
|
+
preview_ledger = PortfolioOutcomeFeedbackLedger()
|
|
1333
|
+
for receipt in combined:
|
|
1334
|
+
preview_ledger.append(receipt)
|
|
1335
|
+
memory = thaw_json(prior_memory)
|
|
1336
|
+
feedback = {
|
|
1337
|
+
"schema_version": 1,
|
|
1338
|
+
"latest_wave_index": request.step.generation,
|
|
1339
|
+
"stage_receipt_sha256s": [value.receipt_sha256 for value in pending],
|
|
1340
|
+
"ledger_receipt_sha256s": [
|
|
1341
|
+
value.receipt_sha256 for value in preview_ledger.receipts
|
|
1342
|
+
],
|
|
1343
|
+
"observation_count": len(preview_ledger.observations),
|
|
1344
|
+
"prompt_history": thaw_json(
|
|
1345
|
+
preview_ledger.prompt_history(
|
|
1346
|
+
cutoff_wave_index_exclusive=request.step.generation + 1,
|
|
1347
|
+
)
|
|
1348
|
+
),
|
|
1349
|
+
"provider_calls": 0,
|
|
1350
|
+
}
|
|
1351
|
+
memory["portfolio_outcome_feedback"] = feedback
|
|
1352
|
+
contextual_prior_state: (
|
|
1353
|
+
tuple[tuple[str, ...], tuple[str, ...], tuple[str, ...]] | None
|
|
1354
|
+
) = None
|
|
1355
|
+
contextual_batch: ContextualPortfolioOutcomeBatch | None = None
|
|
1356
|
+
contextual_realizations: tuple[
|
|
1357
|
+
ContextualPortfolioAllocationRealization, ...
|
|
1358
|
+
] = ()
|
|
1359
|
+
contextual_evidence: dict[str, object] | None = None
|
|
1360
|
+
if self.contextual_ledger is not None:
|
|
1361
|
+
assert self.selected_search_sources is not None
|
|
1362
|
+
assert self.contextual_marginal_utility is not None
|
|
1363
|
+
assert self.contextual_campaign_scope_sha256 is not None
|
|
1364
|
+
selected_sources = tuple(
|
|
1365
|
+
self.selected_search_sources(wave, result)
|
|
1366
|
+
for wave, result in zip(waves, results, strict=True)
|
|
1367
|
+
)
|
|
1368
|
+
marginal_utilities = self.contextual_marginal_utility.project(
|
|
1369
|
+
snapshot=request.archive_utility,
|
|
1370
|
+
results=results,
|
|
1371
|
+
)
|
|
1372
|
+
# The authenticated campaign cadence alternates portfolio and
|
|
1373
|
+
# recombination generations, so controller waves are 1, 2, ...
|
|
1374
|
+
# for campaign generations 1, 3, ... .
|
|
1375
|
+
controller_wave_index = (request.step.generation + 1) // 2
|
|
1376
|
+
contextual_batch = observe_contextual_portfolio_outcomes(
|
|
1377
|
+
campaign_scope_sha256=self.contextual_campaign_scope_sha256,
|
|
1378
|
+
wave_index=controller_wave_index,
|
|
1379
|
+
waves=waves,
|
|
1380
|
+
results=results,
|
|
1381
|
+
selected_source_ids=selected_sources,
|
|
1382
|
+
marginal_utilities=marginal_utilities,
|
|
1383
|
+
)
|
|
1384
|
+
contextual_prior_state = (
|
|
1385
|
+
tuple(
|
|
1386
|
+
value.observation_sha256
|
|
1387
|
+
for value in self.contextual_ledger.observations
|
|
1388
|
+
),
|
|
1389
|
+
tuple(
|
|
1390
|
+
value.credit_sha256
|
|
1391
|
+
for value in self.contextual_ledger.delayed_credits
|
|
1392
|
+
),
|
|
1393
|
+
tuple(
|
|
1394
|
+
value.realization_sha256
|
|
1395
|
+
for value in self.contextual_ledger.allocation_realizations
|
|
1396
|
+
),
|
|
1397
|
+
)
|
|
1398
|
+
preview_contextual = ContextualSearchLedger(
|
|
1399
|
+
observations=list(self.contextual_ledger.observations),
|
|
1400
|
+
delayed_credits=list(self.contextual_ledger.delayed_credits),
|
|
1401
|
+
allocation_realizations=list(
|
|
1402
|
+
self.contextual_ledger.allocation_realizations
|
|
1403
|
+
),
|
|
1404
|
+
)
|
|
1405
|
+
preview_contextual.append_batch(contextual_batch.observations)
|
|
1406
|
+
if self.selected_allocation_realization is not None:
|
|
1407
|
+
recovered = tuple(
|
|
1408
|
+
self.selected_allocation_realization(wave, result)
|
|
1409
|
+
for wave, result in zip(waves, results, strict=True)
|
|
1410
|
+
)
|
|
1411
|
+
if any(value is None for value in recovered):
|
|
1412
|
+
raise ValueError(
|
|
1413
|
+
"contextual run omitted an allocation realization"
|
|
1414
|
+
)
|
|
1415
|
+
contextual_realizations = tuple(
|
|
1416
|
+
sorted(
|
|
1417
|
+
(
|
|
1418
|
+
value
|
|
1419
|
+
for value in recovered
|
|
1420
|
+
if type(value)
|
|
1421
|
+
is ContextualPortfolioAllocationRealization
|
|
1422
|
+
),
|
|
1423
|
+
key=lambda value: (
|
|
1424
|
+
value.controller_wave_index,
|
|
1425
|
+
value.slice_id,
|
|
1426
|
+
value.realization_sha256,
|
|
1427
|
+
),
|
|
1428
|
+
)
|
|
1429
|
+
)
|
|
1430
|
+
if len(contextual_realizations) != len(recovered):
|
|
1431
|
+
raise TypeError(
|
|
1432
|
+
"allocation realization provider returned a foreign value"
|
|
1433
|
+
)
|
|
1434
|
+
preview_contextual.append_allocation_realization_batch(
|
|
1435
|
+
contextual_realizations
|
|
1436
|
+
)
|
|
1437
|
+
contextual_evidence = {
|
|
1438
|
+
"batch": contextual_batch.to_record(),
|
|
1439
|
+
"ledger_observation_count_after": len(
|
|
1440
|
+
preview_contextual.observations
|
|
1441
|
+
),
|
|
1442
|
+
"ledger_delayed_credit_count_after": len(
|
|
1443
|
+
preview_contextual.delayed_credits
|
|
1444
|
+
),
|
|
1445
|
+
"allocation_realization_sha256s": [
|
|
1446
|
+
value.realization_sha256
|
|
1447
|
+
for value in contextual_realizations
|
|
1448
|
+
],
|
|
1449
|
+
"ledger_allocation_realization_count_after": len(
|
|
1450
|
+
preview_contextual.allocation_realizations
|
|
1451
|
+
),
|
|
1452
|
+
"provider_calls": 0,
|
|
1453
|
+
}
|
|
1454
|
+
memory["contextual_search_controller"] = {
|
|
1455
|
+
"schema_version": 1,
|
|
1456
|
+
"latest_wave_index": controller_wave_index,
|
|
1457
|
+
"latest_batch_sha256": contextual_batch.batch_sha256,
|
|
1458
|
+
"latest_observation_sha256s": [
|
|
1459
|
+
value.observation_sha256
|
|
1460
|
+
for value in contextual_batch.observations
|
|
1461
|
+
],
|
|
1462
|
+
"ledger_observation_count": len(
|
|
1463
|
+
preview_contextual.observations
|
|
1464
|
+
),
|
|
1465
|
+
"ledger_delayed_credit_count": len(
|
|
1466
|
+
preview_contextual.delayed_credits
|
|
1467
|
+
),
|
|
1468
|
+
"ledger_allocation_realization_count": len(
|
|
1469
|
+
preview_contextual.allocation_realizations
|
|
1470
|
+
),
|
|
1471
|
+
"provider_calls": 0,
|
|
1472
|
+
}
|
|
1473
|
+
frozen = freeze_json(memory)
|
|
1474
|
+
if type(frozen) is not FrozenJsonObject:
|
|
1475
|
+
raise AssertionError("updated campaign memory did not freeze as an object")
|
|
1476
|
+
preparation = CampaignPortfolioOutcomePreparation(
|
|
1477
|
+
request_sha256=request.request_sha256,
|
|
1478
|
+
generation=request.step.generation,
|
|
1479
|
+
wave_request_sha256s=tuple(
|
|
1480
|
+
wave.selection_request.request_sha256 for wave in waves
|
|
1481
|
+
),
|
|
1482
|
+
result_receipt_sha256s=tuple(
|
|
1483
|
+
result.receipt.receipt_sha256 for result in results
|
|
1484
|
+
),
|
|
1485
|
+
prior_memory_sha256=typed_json_sha256(prior_memory),
|
|
1486
|
+
updated_memory=frozen,
|
|
1487
|
+
evidence=freeze_json(
|
|
1488
|
+
{
|
|
1489
|
+
"pending_feedback_receipt_sha256s": [
|
|
1490
|
+
value.receipt_sha256 for value in pending
|
|
1491
|
+
],
|
|
1492
|
+
"ledger_receipt_count_after": len(preview_ledger.receipts),
|
|
1493
|
+
"contextual_outcomes": contextual_evidence,
|
|
1494
|
+
"provider_calls": 0,
|
|
1495
|
+
}
|
|
1496
|
+
),
|
|
1497
|
+
)
|
|
1498
|
+
if type(preparation.evidence) is not FrozenJsonObject:
|
|
1499
|
+
raise AssertionError("outcome preparation evidence was not an object")
|
|
1500
|
+
if preparation.preparation_sha256 in self._prepared:
|
|
1501
|
+
raise ValueError("outcome update preparation already exists")
|
|
1502
|
+
self._prepared[preparation.preparation_sha256] = (
|
|
1503
|
+
tuple(value.receipt_sha256 for value in self.ledger.receipts),
|
|
1504
|
+
tuple(pending),
|
|
1505
|
+
contextual_prior_state,
|
|
1506
|
+
contextual_batch,
|
|
1507
|
+
contextual_realizations,
|
|
1508
|
+
)
|
|
1509
|
+
return preparation
|
|
1510
|
+
|
|
1511
|
+
def commit_update(self, preparation) -> None:
|
|
1512
|
+
from agent_evolve.application.portfolio_campaign_runtime import (
|
|
1513
|
+
CampaignPortfolioOutcomePreparation,
|
|
1514
|
+
)
|
|
1515
|
+
|
|
1516
|
+
if type(preparation) is not CampaignPortfolioOutcomePreparation:
|
|
1517
|
+
raise TypeError("preparation must be exact")
|
|
1518
|
+
prepared = self._prepared.pop(preparation.preparation_sha256, None)
|
|
1519
|
+
if prepared is None:
|
|
1520
|
+
raise ValueError("outcome update preparation is unavailable")
|
|
1521
|
+
(
|
|
1522
|
+
prior_receipts,
|
|
1523
|
+
pending,
|
|
1524
|
+
contextual_prior_state,
|
|
1525
|
+
contextual_batch,
|
|
1526
|
+
contextual_realizations,
|
|
1527
|
+
) = prepared
|
|
1528
|
+
if tuple(value.receipt_sha256 for value in self.ledger.receipts) != (
|
|
1529
|
+
prior_receipts
|
|
1530
|
+
):
|
|
1531
|
+
raise RuntimeError("outcome ledger changed after preparation")
|
|
1532
|
+
if contextual_prior_state is not None:
|
|
1533
|
+
if self.contextual_ledger is None or contextual_batch is None:
|
|
1534
|
+
raise RuntimeError("contextual outcome dependencies disappeared")
|
|
1535
|
+
current_contextual_state = (
|
|
1536
|
+
tuple(
|
|
1537
|
+
value.observation_sha256
|
|
1538
|
+
for value in self.contextual_ledger.observations
|
|
1539
|
+
),
|
|
1540
|
+
tuple(
|
|
1541
|
+
value.credit_sha256
|
|
1542
|
+
for value in self.contextual_ledger.delayed_credits
|
|
1543
|
+
),
|
|
1544
|
+
tuple(
|
|
1545
|
+
value.realization_sha256
|
|
1546
|
+
for value in self.contextual_ledger.allocation_realizations
|
|
1547
|
+
),
|
|
1548
|
+
)
|
|
1549
|
+
if current_contextual_state != contextual_prior_state:
|
|
1550
|
+
raise RuntimeError("contextual search ledger changed after preparation")
|
|
1551
|
+
self.ledger.receipts.extend(pending)
|
|
1552
|
+
if contextual_batch is not None:
|
|
1553
|
+
assert self.contextual_ledger is not None
|
|
1554
|
+
self.contextual_ledger.observations.extend(
|
|
1555
|
+
contextual_batch.observations
|
|
1556
|
+
)
|
|
1557
|
+
self.contextual_ledger.allocation_realizations.extend(
|
|
1558
|
+
contextual_realizations
|
|
1559
|
+
)
|
|
1560
|
+
|
|
1561
|
+
def abort_update(self, preparation) -> None:
|
|
1562
|
+
from agent_evolve.application.portfolio_campaign_runtime import (
|
|
1563
|
+
CampaignPortfolioOutcomePreparation,
|
|
1564
|
+
)
|
|
1565
|
+
|
|
1566
|
+
if type(preparation) is not CampaignPortfolioOutcomePreparation:
|
|
1567
|
+
raise TypeError("preparation must be exact")
|
|
1568
|
+
self._prepared.pop(preparation.preparation_sha256, None)
|
|
1569
|
+
|
|
1570
|
+
async def update(
|
|
1571
|
+
self,
|
|
1572
|
+
request: CampaignStageRequest,
|
|
1573
|
+
waves: tuple[PortfolioVariationWaveRequest, ...],
|
|
1574
|
+
results: tuple[PortfolioVariationWaveResult, ...],
|
|
1575
|
+
prior_memory: FrozenJsonObject,
|
|
1576
|
+
) -> FrozenJsonObject:
|
|
1577
|
+
"""Backward-compatible immediate publication wrapper."""
|
|
1578
|
+
|
|
1579
|
+
preparation = await self.prepare_update(
|
|
1580
|
+
request,
|
|
1581
|
+
waves,
|
|
1582
|
+
results,
|
|
1583
|
+
prior_memory,
|
|
1584
|
+
)
|
|
1585
|
+
self.commit_update(preparation)
|
|
1586
|
+
return preparation.updated_memory
|
|
1587
|
+
|
|
1588
|
+
|
|
1589
|
+
def validate_feedback_ledger(
|
|
1590
|
+
values: Sequence[PortfolioOutcomeFeedbackReceipt],
|
|
1591
|
+
) -> None:
|
|
1592
|
+
ledger = PortfolioOutcomeFeedbackLedger()
|
|
1593
|
+
for value in values:
|
|
1594
|
+
ledger.append(value)
|
|
1595
|
+
|
|
1596
|
+
|
|
1597
|
+
__all__ = [
|
|
1598
|
+
"CalibratedCampaignOutcomeUpdater",
|
|
1599
|
+
"ContextualOutcomeHistoryReceipt",
|
|
1600
|
+
"ContextualOutcomeQuery",
|
|
1601
|
+
"DecisionMetricTransition",
|
|
1602
|
+
"DirectionAdjudicatorProvider",
|
|
1603
|
+
"ContextualMarginalUtilityProjector",
|
|
1604
|
+
"OutcomeTransferScope",
|
|
1605
|
+
"PortfolioActionOutcomeFeedback",
|
|
1606
|
+
"PortfolioOutcomeFeedbackLedger",
|
|
1607
|
+
"PortfolioOutcomeFeedbackReceipt",
|
|
1608
|
+
"SelectedForecastProvider",
|
|
1609
|
+
"SelectedAllocationRealizationProvider",
|
|
1610
|
+
"SelectedSearchSourceProvider",
|
|
1611
|
+
"observe_selected_portfolio_forecasts",
|
|
1612
|
+
"validate_feedback_ledger",
|
|
1613
|
+
]
|